public class LockFilter : ActionFilterAttribute { static ConcurrentDictionary<StringBuilder, int> _properties; static LockFilter() { _properties = new ConcurrentDictionary<StringBuilder, int>(); } public int Duration { get; set; } public string VaryByParam { get; set; } public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var actionArguments = context.ActionArguments.Values.Single(); var properties = VaryByParam.Split(",").ToList(); StringBuilder key = new StringBuilder(); foreach (var actionArgument in actionArguments.GetType().GetProperties()) { if (!properties.Any(t => t.Trim().ToLower() == actionArgument.Name.ToLower())) continue; var value = actionArguments.GetType().GetProperty(actionArgument.Name).GetValue(actionArguments, null).ToString(); key.Append(value); } _properties.AddOrUpdate(key, 1, (x, y) => y + 1); // rest of code } }
Reflection در ES6
var person = { name: 'Sirwan', family: 'Afifi', doWork: function () { //.... } }; for (var prop in person) { console.log(prop); }
for (var prop in person) { console.log(person[prop]); }
var person = { name: 'Sirwan', family: 'Afifi', doWork: function () { //.... } }; for (let prop of Reflect.enumerate(person)) { console.log(`the value for ${prop} is ${person[prop]}`); }
Reflect.get(target, name, [receiver]) Reflect.set(target, name, value, [receiver]) Reflect.has(target, name) Reflect.apply(target, receiver, args) Reflect.construct(target, args) Reflect.getOwnPropertyDescriptor(target, name) Reflect.defineProperty(target, name, desc) Reflect.getPrototypeOf(target) Reflect.setPrototypeOf(target, newProto) Reflect.deleteProperty(target, name) Reflect.enumerate(target) Reflect.preventExtensions(target) Reflect.isExtensible(target) Reflect.ownKeys(target)
var yay = Reflect.defineProperty(target, 'foo', { value: 'bar' }) if (yay) { // yay! } else { // oops. }
try { Object.defineProperty(target, 'foo', { value: 'bar' }) // yay! } catch (e) { // oops. }
var target = { foo: 'bar', baz: 'wat' } delete target.foo console.log(target) // <- { baz: 'wat' }
var target = { foo: 'bar', baz: 'wat' } let isDeleted = Reflect.deleteProperty(target, 'foo') console.log(isDeleted ); // true
- این شیء فاقد متد [[Construct]] میباشد. یعنی نمیتواند همراه با کلمهی کلیدی new مورد استفاده قرار گیرد.
- همچنین نمیتوان آن را همانند یک تابع فراخوانی کرد.
- تمام متدهای آن به صورت استاتیک تعریف شدهاند (همانند شیء Math).
تبدیل html به pdf با کیفیت بالا
using (var converter = new Converter()) { converter.ConvertToPdf(new ConvertUri(SourcePath), "output.pdf", new PageSettings(PaperFormat.A4) { DisplayHeaderFooter = true, HeaderTemplate = header, FooterTemplate = footer, PrintBackground = true, }); converter.ConvertToImage(new ConvertUri(tempSourcePath), "output.png", new PageSettings(PaperFormat.A6)); }
var header = """ <div class="text center" style="color: lightgray;border-bottom: solid lightgray 0.1px; width: 100%; font-family: 'Samim'; font-size:7px;"> <span class="title"></span> </div> """; var footer = """ <div class="text center" style="color: lightgray; font-family: 'Samim'; font-size:7px;"> <span class="pageNumber"></span>/<span class="totalPages"></span> </div> """;
var HTML = "<HTML code>"; using (Converter converter = new Converter()) using (MemoryStream stream = new MemoryStream()) { // This is necessary when running on Docker converter.AddChromeArgument("--no-sandbox"); // Create PDF out of HTML string converter.ConvertToPdf(html, stream, new ChromeHtmlToPdfLib.Settings.PageSettings()); // Return file to user return File(stream.ToArray(), MediaTypeNames.Application.Pdf, "Report.pdf"); }
OpenCVSharp #8
به تصویر زیر دقت کنید:
فرض کنید در اینجا قصد دارید تعداد توپهای قرمز را شمارش کنید. از دیدگاه یک انسان، شاید سه توپ قرمز قابل مشاهده باشد. اما از دیدگاه یک برنامه، توپ وسطی به دو توپ تفسیر خواهد شد و همچنین نویزهای قرمزی که بین توپها در صفحه وجود دارند نیز شمارش میشوند. بنابراین بهتر است پیش از پردازش این تصویر، ریخت شناسی آنرا بهبود بخشید. برای مثال توپ وسطی را یکی کرد،حفرههای توپهای دیگر را پوشاند و یا نویزهای قرمز را حذف نمود. به علاوه خطوط آبی رنگی را که با یکدیگر تماس یافتهاند نیز میخواهیم اندکی از هم جدا کنیم.
متدهایی که مورفولوژی تصاویر را تغییر میدهند
در OpenCV سه متد یا فیلتر مهم، کار تغییر مورفولوژی تصاویر را انجام میدهند:
1) Cv2.Erode
تحلیل/فرسایش یا erosion سبب میشود تا نواحی تیرهی تصویر «رشد» کنند.
در اینجا فیلتر Erode کار یکی کردن اجزای جدای توپهای رنگی را انجام دادهاست.
2) Cv2.Dilate
اتساع یا dilation سبب خواهد شد تا نواحی روشن تصویر «رشد» کنند.
بکارگیری فیلتر Dilate سبب شدهاست تا نویزهای تصویر محو شوند و اشیاء به هم پیوسته از هم جدا گردند.
3) Cv2.MorphologyEx
کار این متد انجام اعمال پیشرفتهی مورفولوژی بر روی تصاویر است و در اینجا ترکیبی از erosion و dilation، با هم انجام میشوند.
اگر پارامتر سوم آن به MorphologyOperation.Open تنظیم شود، ابتدا erosion و سپس dilation انجام خواهد شد:
و اگر این پارامتر به MorphologyOperation.Close مقدار دهی شود، ابتدا dilation و سپس erosion انجام میشود:
در تمام این حالات، پارامتر آخر که Structuring Element نام دارد، یکی از مقادیر اشیاء مستطیل، به علاوه و بیضی را میتواند داشته باشد. این اشیاء و اندازهی آنها، مشخص کنندهی میزان تحلیل و یا اتساع نهایی هستند.
استفاده از متدهای مورفولوژی در عمل
در اینجا مثالی را از نحوهی بکارگیری متدهای اتساع و فرسایش، ملاحظه میکنید:
using (var src = new Mat(@"..\..\Images\cvmorph.Png", LoadMode.AnyDepth | LoadMode.AnyColor)) using (var dst = new Mat()) { src.CopyTo(dst); var elementShape = StructuringElementShape.Rect; var maxIterations = 10; var erodeDilateWindow = new Window("Erode/Dilate", image: dst); var erodeDilateTrackbar = erodeDilateWindow.CreateTrackbar( name: "Iterations", value: 0, max: maxIterations * 2 + 1, callback: pos => { var n = pos - maxIterations; var an = n > 0 ? n : -n; var element = Cv2.GetStructuringElement( elementShape, new Size(an * 2 + 1, an * 2 + 1), new Point(an, an)); if (n < 0) { Cv2.Erode(src, dst, element); } else { Cv2.Dilate(src, dst, element); } Cv2.PutText(dst, (n < 0) ? string.Format("Erode[{0}]", elementShape) : string.Format("Dilate[{0}]", elementShape), new Point(10, 15), FontFace.HersheyPlain, 1, Scalar.Black); erodeDilateWindow.Image = dst; }); Cv2.WaitKey(); erodeDilateWindow.Dispose(); }
کار متد PutText، نوشتن یک متن ساده بر روی پنجرهی native مربوط به OpenCV است.
همچنین در ادامه کدهای بکارگیری متد MorphologyEx را که کار ترکیب اتساع و فرسایش را با هم انجام میدهد، ذکر شدهاست و نکات بکارگیری آن همانند مثال اول بحث است:
using (var src = new Mat(@"..\..\Images\cvmorph.Png", LoadMode.AnyDepth | LoadMode.AnyColor)) using (var dst = new Mat()) { src.CopyTo(dst); var elementShape = StructuringElementShape.Rect; var maxIterations = 10; var openCloseWindow = new Window("Open/Close", image: dst); var openCloseTrackbar = openCloseWindow.CreateTrackbar( name: "Iterations", value: 0, max: maxIterations * 2 + 1, callback: pos => { var n = pos - maxIterations; var an = n > 0 ? n : -n; var element = Cv2.GetStructuringElement( elementShape, new Size(an * 2 + 1, an * 2 + 1), new Point(an, an)); if (n < 0) { Cv2.MorphologyEx(src, dst, MorphologyOperation.Open, element); } else { Cv2.MorphologyEx(src, dst, MorphologyOperation.Close, element); } Cv2.PutText(dst, (n < 0) ? string.Format("Open/Erosion followed by Dilation[{0}]", elementShape) : string.Format("Close/Dilation followed by Erosion[{0}]", elementShape), new Point(10, 15), FontFace.HersheyPlain, 1, Scalar.Black); openCloseWindow.Image = dst; }); Cv2.WaitKey(); openCloseWindow.Dispose(); }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
let names = ["Jack", "Jecci", "Ram", "Tom"]; let upperCaseNames = []; for(let i=0, totalNames = names.length; i< totalNames ; i= i +1) { upperCaseNames = names[i].toUpperCase(); }
let names = ["Jack", "Jecci", "Ram", "Tom"]; let upperCaseNames = names.map(name => name.toUpperCase());
function print(name) { console.log(name); } let names = ["Jack", "Jecci", "Ram", "Tom"]; for(let i=0, totalNames = names.length; i< totalNames ; i= i +1) { print(names[i]) }
function print(name) { console.log(name); } let names = ["Jack", "Jecci", "Ram", "Tom"]; names.forEach(name=> print(name)); // اگر صرفا چاپ در کنسول مد نظر هست let names = ["Jack", "Jecci", "Ram", "Tom"]; names.forEach(name=> console.log(name));
function isOdd(n) { return n %2; } let numbers = [1,2,3,4,5]; let odd = []; for(let i=0, total = numbers.length; i< total ; i= i +1) { let number = numbers[i]; if( isOdd(number) ) { odd.push(number); } }
let numbers = [1,2,3,4,5, 6, 7] let odd = numbers.filter(n => n%2);
let numbers = [1,2,3,4,5] let result = 0; for(let i=0, total = numbers.length; i< total ; i= i +1) { result = result + numbers[i]; }
let numbers = [1,2,3,4,5,6,7]; function sum(accumulator, currentValue){ return accumulator + currentValue; } let initialVal = 0; let result = numbers.reduce(sum, initialVal); // یا بصورت زیر کد را خلاصه نمود let numbers = [1,2,3,4,5,6,7, 10]; let result = numbers.reduce((acc, val)=> acc+val, 0);
let names = ["ram",, "rahul", "raj", "rahul"]; for(let i=0, totalNames = names.length; i< totalNames ; i= i +1) { if(names[i] === "rahul") { console.log(" found rahul"); break; } }
let names = ["ram",, "rahul", "raj", "rahul"]; let isRahulPresent = names.some(name => name==="rahul"); if(isRahulPresent) { console.log("found rahul"); }
let numbers = [1,2,3,4,5, 0]; for(let i=0, total = numbers.length; i< total ; i= i +1) { if(numbers[i] <= 0) { console.log("0 present in array"); break; } }
let numbers = [1,2,3,4,5,0]; let isZeroFree = numbers.every(e => e > 0); if(!isZeroFree) { console.log("0 present in array"); }
- خوانایی بهتر
- فهم راحت
- خطایابی آسانتر
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebAppSingleSubmit._Default" %> <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> <script type="text/javascript"> $(document).ready(function () { $("input[type=submit]").click(function (e) { if ((typeof (Page_ClientValidate) == 'function') && (Page_ClientValidate() == false)) { return false; } if (!confirm("آیا مطمئن هستید؟")) { return false; } this.disabled = true; this.value = 'در حال پردازش اطلاعات ...'; __doPostBack($(this).attr('name'), ''); }); }); </script> </asp:Content> <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <p> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1" ErrorMessage="RequiredFieldValidator"></asp:RequiredFieldValidator> <br /> <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /> </p> </asp:Content>
Garbage Collection
فرض کنید متغیری را ایجاد کرده و به آن مقدار دادهاید:
string message = "Hello World!";
آیا تابحال به این موضوع فکر کردهاید که طول عمر متغیر message تا چه زمانی است و چه زمانی باید از بین برود؟
چه زمانی باید توسط کامپایلر ( یا بهتر بگوییم ، Runtime ) طول عمر این متغیر به پایان برسد و از حافظه حذف شود؟
- Unmanaged: در این دسته از زبان ها، وظیفهی ایجاد اشیاء، تشخیص زمان درست برای از بین بردن و از بین بردن آنها، برعهدهی شماست. زبانهای C و ++C جز این نوع زبانها میباشند.
- Managed: در این دسته از زبانها، ایجاد اشیاء مانند قبل بر عهدهی شماست، اما وظیفه تشخیص و از بین بردن آنها در زمان درست را Runtime برعهده میگیرد.
در این نوع زبانها ما دغدغهی حذف متغیری را که در چندین خط بالاتر، آن را ایجاد کرده و به آن مقدار داده، به چندین متد آن را پاس داده و انواع تغییرات را روی آن انجام دادهایم، نداریم. زبانهای #C و Java جزو این نوع زبانها میباشند.
ایده کلی ایجاد زبانهای Managed بر این است که شما درگیر مباحث مربوط به Memory Management نشوید و تمرکز اصلیتان روی Business باشد.
اکثر پروژهها به اندازه کافی پیچیدگیهای Business ای دارند و ترکیب کردن این نوع پیچیدگیها با پیچیدگیهای Low Level و Technical ای همچون مباحث Memory Management، در اکثر اوقات باعث میشود که نگهداری از پروژه کاری بسیار دشوار شده و به تدریج مانند بسیاری از پروژههای دیگر، نام Legacy را با خود به یدک بکشد.
این بدان معنا نیست که پروژههایی که با زبان هایی همچون C و ++C نوشته میشوند، از همان روز اول Legacy بوده و قابل نگهداری نیستند، بلکه بدین معناست که نگهداری کد چنین زبانهایی نسبت به زبانهای Managed، به مراتب مشکلتر است و همچنین قابل ذکر است که قابلیت نگهداری یک پروژه به مباحث بسیار زیاد دیگری نیز بستگی دارد.
کد این برنامه در زبان C :
#include <stdio.h> #include <stdlib.h> void printReport(int* data) { printf("Report: %d", *data); } int main(void) { int *myNumber; myNumber = (int*)malloc(sizeof(int)); if (myNumber == 0) { printf("ERROR: Out of memory"); return 1; } *myNumber = 25; printReport(myNumber); free(myNumber); myNumber = NULL; return 0; }
"هدف" و Business اصلی این برنامه، چاپ و یک گزارش ساده بود، اما مسائل بسیار بیشتری در این مثال دخیل شده اند:
چند مرحله از این مراحل ذکر شده، واقعا نیاز Business ای برنامه ما بود؟ این مثال، بسیار ساده و غیر واقعی بود؛ اما تصور کنید با این روش، یک برنامه بزرگ با Business Ruleها و پیچیدگیهای خودش، چه حجمی از کد و پیچیدگی را خواهد داشت.
کد این برنامه در زبان #C :
using System; public class Program { public static void Main() { int myNumber = 25; PrintReport(myNumber); } private static void PrintReport(int number) { Console.WriteLine($"Report: {number}"); } }
همانطور که میبینید در اینجا تمرکزمان روی هدف اصلی و Business است و درگیر پیچیدگی مباحث جانبی نظیر Manual Memory Management نشدهایم و Runtime زبان #C یعنی CoreCLR وظیفه Memory Management را در پشت صحنه برعهده گرفته است.
تفاوت بین زبانهای Managed و Unmanaged را میتوانیم به رانندگی با ماشین دندهای و اتومات تشبیه کنیم.
اکثر اوقات هدف اصلی رانندگی، رفتن از یک مبدا به یک مقصد است. با استفاده از یک ماشین دندهای، علاوه بر هدف اصلی یعنی رسیدن به یک مقصد، ذهن ما درگیر تعویض دنده در سرعت مناسب در طول مسیر میشود و اینکار را ممکن است بیش از صدها یا هزاران بار انجام دهیم. در این روش طبیعتا ما کنترل بیشتری داریم و در بعضی مواقع بسیار بهتر به نسبت یک سیستم خودکار عمل میکنیم، اما از هدف اصلی خود یعنی رفتن از نقطه A به B دور شدهایم.
در زبانهای Managed و Unmanaged هم دقیقا چنین تفاوت هایی وجود دارد.
در زبانهای Unmanaged، شما کنترل کاملی را روی طول عمر اشیا و مدیریت حافظه دارید و همه چیز برعهده شماست؛ اما علاوه بر هدف اصلیتان، درگیر مباحث جانبی دیگری نیز شدهاید. اکثر اوقات قدرت زبانهای Unmanaged را در Game Engineها و Real-Time Processing Systemها میتوانید ببینید که در آنها مدیریت حافظه بصورت دستی انجام میشود و برنامه نویسهای آن سیستم، تعیین کننده اصلی این هستند که طول عمر اشیاء تا چه زمانی باشد و چه زمانی از بین بروند که باعث اختلال یا کندی یک سیستم حتی برای چند میلی ثانیه نشوند.
در زبانهای Managed، اکثر اوقات حتی نیازی نیست که شما درگیر مباحث جانبی مدیریت حافظه شوید و تمام کار را Runtime شما بصورت خودکار انجام میدهد. اما گاهی اوقات لازم است که قسمتهای حساس برنامه ( اصطلاحا Hot-Pathها ) خود را پیدا کنید، از این قسمتها Benchmark گرفته و مطمئن شوید که با حجم تعداد بالای درخواست و بار، به خوبی عمل میکند و همچنین قسمتی از برنامه، نشستی حافظه ( اصطلاحا Memory Leak ) ندارد. همانطور که گفتیم، گاهی اوقات یک انسان ( سیستم دستی ) بهتر از یک سیستم خودکار میتواند تصمیم بگیرد که در یک لحظه چه اتفاقی داخل برنامه رخ دهد.
در این سری مقالات قصد داریم وارد مبحث Memory Management در #C شده، با Garbage Collector آشنا و دیدی کلی از نحوهی انجام کار آن داشته باشیم.
طی مقاله چک لیست تولید برنامه Asp.net mvc و بررسی امنیتی ایجکس هنگام استفاده در مورد چک لیست امنیتی
سایت سرفصلهای مهم عنوان و بررسی شده است که یکی از موارد، مقاوم ساختن وب اپلیکشن در برابر حملات CSRF میباشد. اینگونه حملات بر پایه این استراتژی شکل میگیرند که با
ارسال درخواستی به نیابت از سمت سیستم/مرورگر کاربر تایید هویت شده، سایت مقصد را مجبور
به انجام عملی کند. برای مثال اگر شما در سایت a.com یک
کاربر تایید شده باشید و هم اکنون در سایت فوق نیز لاگین باشید، مهاجم با ارسال یک برنامه/صفحه یا موارد مشابه و در قالب src یک
عکس یا با ترغیب شما با کلیک بر روی یک لینک با href آلوده یا موارد
مشابه، از سمت مرورگر شما درخواستی را به سمت سایت a.com ارسال میکند .
این درخواست ممکن است شامل حذف اطلاعات، تغییر مشخصات، پرداخت هزینه یا موارد مشابه باشد. جهت مقابله با این حمله، یکی از موارد مهم، استفاده همیشگی از Html.AntiForgeryToken() در تمامی فرمهای ورود اطلاعات است. همچنین استفاده همیشگی از متد Post و بررسی تایید مبدا درخواستهای ایجکسی، بررسی http referrer ، محدود کردن طول عمر کوکی، استفاده از کپچهای قوی مانند کپچای گوگل میتواند تا حد زیادی وب اپلیکیشن را در مورد اینگونه حملات، مصون کند.
در این بین یکی از موارد دیگر، اضافه کردن AntiForgeryToken به درخواستهای ایجکسی سایت میباشد. جهت حصول این منظور، راههای مختلفی موجود است. یکی از راه حلها استفاده از یک هلپر جهت تولید توکن مورد نظر است.
ساختار
هلپر مورد نظر به شرح زیر است :
public static class AntiForgeryToken { public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper) { var antiForgeryInputTag = helper.AntiForgeryToken().ToString(); // Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="some value" /> var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", ""); var tokenValue = removedStart.Replace(@""" />", ""); if (antiForgeryInputTag == removedStart || removedStart == tokenValue) throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect."); return new MvcHtmlString($@"{"__RequestVerificationToken"}:""{tokenValue}"""); } }
در مرحله بعد طبق الگوی زیر، درخواست ایجکسی به همراه توکن تولید شده و به کنترلر ارسال خواهد شد:
function AddToCart(pid) { $.ajax({ url: '@Url.Action("AddToBasket","Shop")', data: { 'pid': pid,@Html.AntiForgeryTokenForAjaxPost() }, type: 'post', success:function(e) { //do something } }); }
در مرحله آخر، باید کنترلر مورد نظر شامل ویژگیهای [HttpPost] [ValidateAntiForgeryToken] باشد تا صحت توکن تولیدی را بررسی کند و در صورت نامعتبر بودن، از اجرای دستورات جلوگیری گردد.
public class UserModel { public int Id { get; set; } [Required(ErrorMessage = "(*)")] [Display(Name = "نام")] [StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "نام باید حداقل 3 و حداکثر 10 حرف باشد")] public string FirstName { get; set; } [Required(ErrorMessage = "(*)")] [Display(Name = "نام خانوادگی")] [StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "نام خانوادگی باید حداقل 3 و حداکثر 10 حرف باشد")] public string LastName { get; set; } }
public class UserViewModel { public string FirstName { get; set; } public string LastName { get; set; } }
در ادامه قصد داریم راه حلی را به کمک جایگزین سازی Providerهای توکار ASP.NET MVC با نمونهی سازگار با AutoMapper، ارائه دهیم، به نحوی که دیگر نیازی نباشد تا این ویژگیها را در ViewModelها تکرار کرد.
قسمتهایی از ASP.NET MVC که باید جهت انتقال خودکار ویژگیها تعویض شوند
ASP.NET MVC به صورت توکار دارای یک ModelMetadataProviders.Current است که از آن جهت دریافت ویژگیهای هر خاصیت استفاده میکند. میتوان این تامین کنندهی ویژگیها را به نحو ذیل سفارشی سازی نمود.
در اینجا IConfigurationProvider همان Mapper.Engine.ConfigurationProvider مربوط به AutoMapper است. از آن جهت استخراج اطلاعات نگاشتهای AutoMapper استفاده میکنیم. برای مثال کدام خاصیت Model به کدام خاصیت ViewModel نگاشت شدهاست. اینکارها توسط متد الحاقی GetMappedAttributes انجام میشوند که در ادامهی مطلب معرفی خواهد شد.
public class MappedMetadataProvider : DataAnnotationsModelMetadataProvider { private readonly IConfigurationProvider _mapper; public MappedMetadataProvider(IConfigurationProvider mapper) { _mapper = mapper; } protected override ModelMetadata CreateMetadata( IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var mappedAttributes = containerType == null ? attributes : _mapper.GetMappedAttributes(containerType, propertyName, attributes.ToList()); return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName); } }
شبیه به همین کار را باید برای ModelValidatorProviders.Providers نیز انجام داد. در اینجا یکی از تامین کنندههای ModelValidator، از نوع DataAnnotationsModelValidatorProvider است که حتما نیاز است این مورد را نیز به نحو ذیل سفارشی سازی نمود. در غیراینصورت error messages موجود در ویژگیهای تعریف شده، به صورت خودکار منتقل نخواهند شد.
public class MappedValidatorProvider : DataAnnotationsModelValidatorProvider { private readonly IConfigurationProvider _mapper; public MappedValidatorProvider(IConfigurationProvider mapper) { _mapper = mapper; } protected override IEnumerable<ModelValidator> GetValidators( ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) { var mappedAttributes = metadata.ContainerType == null ? attributes : _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes.ToList()); return base.GetValidators(metadata, context, mappedAttributes); } }
و در اینجا پیاده سازی متد GetMappedAttributes را ملاحظه میکنید.
ASP.NET MVC هر زمانیکه قرار است توسط متدهای توکار خود مانند Html.TextBoxFor, Html.ValidationMessageFor، اطلاعات خاصیتها را تبدیل به المانهای HTML کند، از تامین کنندههای فوق جهت دریافت اطلاعات ویژگیهای مرتبط با هر خاصیت استفاده میکند. در اینجا فرصت داریم تا ویژگیهای مدل را از تنظیمات AutoMapper دریافت کرده و سپس بجای ویژگیهای خاصیت معادل ViewModel درخواست شده، بازگشت دهیم. به این ترتیب ASP.NET MVC تصور خواهد کرد که ViewModel ما نیز دقیقا دارای همان ویژگیهای Model است.
public static class AutoMapperExtensions { public static IEnumerable<Attribute> GetMappedAttributes( this IConfigurationProvider mapper, Type viewModelType, string viewModelPropertyName, IList<Attribute> existingAttributes) { if (viewModelType != null) { foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.DestinationType == viewModelType)) { var propertyMaps = typeMap.GetPropertyMaps() .Where(propertyMap => !propertyMap.IsIgnored() && propertyMap.SourceMember != null) .Where(propertyMap => propertyMap.DestinationProperty.Name == viewModelPropertyName); foreach (var propertyMap in propertyMaps) { foreach (Attribute attribute in propertyMap.SourceMember.GetCustomAttributes(true)) { if (existingAttributes.All(i => i.GetType() != attribute.GetType())) { yield return attribute; } } } } } if (existingAttributes == null) { yield break; } foreach (var attribute in existingAttributes) { yield return attribute; } } }
ثبت تامین کنندههای سفارشی سازی شده توسط AutoMapper
پس از تهیهی تامین کنندههای انتقال ویژگیها، اکنون نیاز است آنها را به ASP.NET MVC معرفی کنیم:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); Mappings.RegisterMappings(); ModelMetadataProviders.Current = new MappedMetadataProvider(Mapper.Engine.ConfigurationProvider); var modelValidatorProvider = ModelValidatorProviders.Providers .Single(provider => provider is DataAnnotationsModelValidatorProvider); ModelValidatorProviders.Providers.Remove(modelValidatorProvider); ModelValidatorProviders.Providers.Add(new MappedValidatorProvider(Mapper.Engine.ConfigurationProvider)); }
در قسمت کار با ModelValidatorProviders.Providers، ابتدا صرفا همان تامین کنندهی از نوع DataAnnotationsModelValidatorProvider پیش فرض، یافت شده و حذف میشود. سپس تامین کنندهی سفارشی سازی شدهی خود را معرفی میکنیم تا جایگزین آن شود.
مثالی جهت آزمایش انتقال خودکار ویژگیهای مدل به ViewModel
کنترلر مثال برنامه به شرح زیر است. در اینجا از متد Mapper.Map جهت تبدیل خودکار مدل کاربر به ViewModel آن استفاده شدهاست:
public class HomeController : Controller { public ActionResult Index() { var model = new UserModel { FirstName = "و", Id = 1, LastName = "ن" }; var viewModel = Mapper.Map<UserViewModel>(model); return View(viewModel); } [HttpPost] public ActionResult Index(UserViewModel data) { return View(data); } }
@model Sample12.ViewModels.UserViewModel @using (Html.BeginForm("Index", "Home", FormMethod.Post, htmlAttributes: new { @class = "form-horizontal", role = "form" })) { <div class="row"> <div class="form-group"> @Html.LabelFor(d => d.FirstName, htmlAttributes: new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(d => d.FirstName) @Html.ValidationMessageFor(d => d.FirstName) </div> </div> <div class="form-group"> @Html.LabelFor(d => d.LastName, htmlAttributes: new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(d => d.LastName) @Html.ValidationMessageFor(d => d.LastName) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="ارسال" class="btn btn-default" /> </div> </div> </div> }
در این شکل هر چند نوع مدل View مورد استفاده از ViewModel ایی تامین شدهاست که دارای هیچ ویژگی و Data Annotations/Attributes نیست، اما برچسب هر فیلد از ویژگی Display دریافت شدهاست. همچنین اعتبارسنجی سمت کاربر فعال بوده و برچسبهای آنها نیز به درستی دریافت شدهاند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
public enum Grade { Failing = 5, BelowAverage = 10, Average = BelowAverage + 5, // = 15 VeryGood = 18, Excellent = 20 }
با در نظر گرفتن مثال قبل، یک Custom Attribute به نوع داده شمارشی اضافه میکنیم. برای این منظور بصورت زیر عمل میکنیم.
class Description : Attribute { public string Text; public Description(string text) { Text = text; } }
public enum Grade { [Description("Mardood")] Failing = 5, [Description("Ajab Shansi")] BelowAverage = 10, [Description("Bad Nabood")] Average = BelowAverage + 5, [Description("Khoob Bood")] VeryGood = 18, [Description("Gol Kashti")] Excellent = 20 }
public static class ExtensionMethodCls { public static string GetDescription(this Enum enu) { Type type = enu.GetType(); MemberInfo[] memInfo = type.GetMember(enu.ToString()); if (memInfo != null && memInfo.Length > 0) { object[] attrs = memInfo[0].GetCustomAttributes(typeof(Description), false); if (attrs != null && attrs.Length > 0) return ((Description)attrs[0]).Text; } return enu.ToString(); } }
Console.WriteLine(grade.GetDescription()); // Print Bad Nabood
using System; using System.Reflection; namespace CSharpEnum { class Description : Attribute { public string Text; public Description(string text) { Text = text; } } public enum Grade { [Description("Mardood")] Failing = 5, [Description("Ajab Shansi")] BelowAverage = 10, [Description("Bad Nabood")] Average = BelowAverage + 5, [Description("Khoob Bood")] VeryGood = 18, [Description("Gol Kashti")] Excellent = 20 } public static class ExtensionMethodCls { public static string GetDescription(this Enum enu) { Type type = enu.GetType(); MemberInfo[] memInfo = type.GetMember(enu.ToString()); if (memInfo != null && memInfo.Length > 0) { object[] attrs = memInfo[0].GetCustomAttributes(typeof(Description), false); if (attrs != null && attrs.Length > 0) return ((Description)attrs[0]).Text; } return enu.ToString(); } } class Program { static void Main(string[] args) { const Grade grade = Grade.Average; Console.WriteLine("Underlying type: {0}", Enum.GetUnderlyingType(grade.GetType())); Console.WriteLine("Type Code : {0}", grade.GetTypeCode()); Console.WriteLine("Value : {0}", (int)grade); Console.WriteLine("--------------------------------------"); Console.WriteLine(grade.ToString()); // name of the constant Console.WriteLine(grade.ToString("G")); // name of the constant Console.WriteLine(grade.ToString("F")); // name of the constant Console.WriteLine(grade.ToString("x")); // value is hex Console.WriteLine(grade.ToString("D")); // value in decimal Console.WriteLine("--------------------------------------"); Console.WriteLine(grade.GetDescription()); // Print Bad Nabood Console.ReadKey(); } } }