در ASP.NET MVC، ما مدلهایمان را با استفاده از Entity Framework، ADO.NET یا هر تکنیک دیگری از دسترسی به داده ها میتوانیم بیسازیم. سپس در اکشن هایی که در کنترلهایمان داریم میتوانیم از این مدل ها به عنوان ورودی هایمان استفاده نماییم. اگر ما از EntityFramework استفاده کنیم سپس Application، آبجکتهایی را که ما می توانیم به عنوان موجودیت در برنامه استفاده نماییم را در اختیارمان قرار می دهد. سپس از این آبجکتها در اکشن متدها در MVC می توانیم به عنوان ورودی استفاده نماییم. اینجا Model binder مسئول map کردن المنتهای درون view به خصوصیات مدلها می باشد.
قسمت اول: Model Binder درASP.NET MVC
MVC از انواع زیر برای bind کردن مدل استفاده می نماید:
IModelBinder interface – این اینترفیس متدهایی که برای یک Model Binder مورد نیاز می باشد را تعریف می کند. مثل متد BindModel . این متد مسئول Bind کردن یک مدل به تعدای value با استفاده از ControllerContext و BindingContext می باشد.
IModelBinderProvider interface – این اینترفیس شامل متدهایی برای پیاده سازی داینامیک model binding برای کلاسهایی که اینترفیس IModelBinder را پیاده سازی می کنند می باشد. این اینترفیس برای مدیریت Binder سفارشی از داده های ارسال شده توسط کاربر در View است.
کلاس DefaultModelBinder
- این کلاس برای map کردن درخواست مرورگر به آبجکتی از داده ها می باشد. این کلاس یک درخواستی مشخص از IModelBinder است.
- این کلاس به صورت پیش فرض برای map کردن داده ها ارسال شده توسط المنت های موجود در view به خصوصیات درون مدل که توسط کنترلر می تواند در پردازش های بعدی استفاده شود به کار گرفته می شود.
نمودار Model Binder
قسمت دوم: پیاده سازی Model Binder های سفارشی
ما قصد داریم این مفهوم را طی یک مثال به شما نشان دهیم.
مرحله 1: یک پروژه از نوع ASP.NET Web application با نام MVC_CustomModelBinder در ویژوال استودیو ایجاد نمایید:
سپس در پنجره بعدی نوع پروژه را Empty انتخاب نمایید:
مرحله 2: یک دیتابیس جدید از Sql Server با نام ApplicationDB.mdf اضافه نمایید. در فولدر models، یک ADO.NET Entity Data Model جدید با نام ApplicationEntities بسازید در ویزارد، Code-First from database را مانند شکل زیر انتخاب نمایید:
ApplicationDB.mdf را انتخاب نمایید و finish را بزنید. این کلاس ApplicationEntities را درون فایل کلاس ApplicationEntities.cs ایجاد می نماید. این کلاس از DbContext مشتق شده است. ما از این کلاس برای عملیات دیتاببیس استفاده می کنیم.
مرحله 3: در فولدر models، یک کلاس جدید با نام Employee.cs ایجاد نمایید و خصوصیات زیر را درون آن قرار دهید:
using System.ComponentModel.DataAnnotations; namespace MVC_CustomModelBinder.Models { public class Employee { [Key] public int EmpNo { get ; set ; } public string EmpName { get ; set ; } public decimal Salary { get ; set ; } } } |
این کلاس یک موجودیت است که فیلد EmpNo در آن به عنوان کلید اولیه می باشد.
از آنجایی که ما قصد داریم این کلاس را به عنوان یک جدول درون دیتابیس داشته باشیم کد زیر را در کلاس ApplicationEntities قرار دهید:
1 |
public DbSet<Employee> Employees { get; set; } |
مرحله 4: یک کنترلر با نام Employee در فولدر Controllers ایجاد نمایید و کدهای زیر را درون آن قرار دهید:
using System.Linq; using System.Web.Mvc; using MVC_CustomModelBinder.Models; using System.Xml.Serialization; namespace MVC_CustomModelBinder.Controllers { public class EmployeeController : Controller { ApplicationEntities ctx; public EmployeeController() { ctx = new ApplicationEntities(); } // GET: Employee public ActionResult Index() { var Emps = ctx.Employees.ToList(); return View(Emps); } public ActionResult Create() { var empPostedData = new XmlSerializer( typeof (Employee)); var Emp = (Employee)empPostedData.Deserialize(HttpContext.Request.InputStream); ctx.Employees.Add(Emp); return View( "Index" ) ; } } } |
کلاس بالا از ApplicationEntities برای برقراری ارتباط با دیتابیس استفاده میکند. اکشن Create اینجا کار اصلی را برای ما انجام میدهد. اینجا از کلاس XmlSerializer برای تعریف نوع داده های XML که توسط request که از طرف کاربر post می شود استفاده می شود. سپس با استفاده از متد Deserialize، request مورد نظر خوانده شده و در آبجکت Employee ذخیره می گردد که در خط بعدی برای عملیات دیتابیس استفاده می شود. جهت تست داخل متد یک Break Point قرار می دهیم. جهت تست داده ها ما از ابزار fiddler استفاده می نماییم.(از ایزار postman نیز جهت تست می توانید استفاده نمایید)
مرحله 5: در نتیجه fiddler را باز نموده و از url زیر در composer با داده های xml نشان داده شده در تصویر زیر متد create را اجرا نمایید:
حال پروژه را اجرا نمایید.
دکمه Execute را کلیک نمایید نتیجه Debug به صورت شکل زیر نمایش داده می شود:
همونطور که میبینید داده های مورد نظر ارسال شده اند و ما از آن می توانیم در عمیلات خود استفاده نماییم. اما از آنجایی که بهتر است ما در هر اکشن متد با استفاده از داده های ارسال شده که باید به صورت خودکار با آبجکت مورد نظر map شده باشد یک مکانیسم داشته باشیم اینجا جایی است که ModelBinder وارد عمل می شود. در این موردی که ما مثال زدیم نیاز است که یک زیرساخت را با استفاده از داده های xml ارسال شده که به آبجکت Employee بایند می شود پیاده سازی نماییم. ما می توانیم این کار را با Custom Model Binder انجام دهیم.
مرحله 6: در پروژه یک فولدر با نام CustomModelBinders ایجاد نمایید. در این فولدر یک کلاس با کدهای زیر ایجاد نمایید:
using System; using System.Web; using System.Web.Mvc; using System.Xml.Serialization; namespace MVC_CustomModelBinder.CustomModelBinders { public class XMLToObjectModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { try { //1. var model = bindingContext.ModelType; //2. var data = new XmlSerializer(model); //3. var receivedStream = controllerContext.HttpContext.Request.InputStream; //4. return data.Deserialize(receivedStream); } catch (Exception ex) { bindingContext.ModelState.AddModelError( "Error" , "Received Model cannot be serialized" ); return null ; } } } public class XMLToObjectModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(Type modelType) { //5. var receivedContentType = HttpContext.Current.Request.ContentType.ToLower(); if (receivedContentType != "text/xml" ) { return null ; } return new XMLToObjectModelBinder(); } } } |
XMLToObjectModelBinder شامل مشخصات زیر می باشد:
- اول از همه این کد، نوع مدل را تحت مدل Context مورد نظر می خواند.
- نمونه ای از XmlSerializer جهت کار با داده های ارسال شده تعریف شده است.
- داده های دریافت شده از request ارسال شده خوانده می شود.
- داده های دریافت شده deserialize می شود.
کلاس XMLToObjectModelBindingProvider رابط IModelBinderProvider و متد GetBinder را پیاده سازی می نماید. این متد نوع محتوا را از request ورودی می خواند. اگر نوع محتوای دریافت شده از نوع text/xml نباشد سپس null برگردانده می شود در غیر اینصورت XMLToObjectModelBinder برگردانده خواهد شد.
مرحله 7: در نتیجه کلاس model provider بالا را در application اضافه می نماییم که در نتیجه application آن را در فرایند Bind کردن مدل بارگزاری خواهد کرد. برای این کار فایل Global.asax را باز نموده و خط مشخص شده را در متد Application_Start() به صورت زیر د رآن اضافه نمایید:
protected void Application_Start() { ModelBinderProviders.BinderProviders.Insert(0, new XMLToObjectModelBinderProvider()); AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); } |
ModelBinderProviders بایندرهای سفارشی که در مرحله قبل ساختیم را در application اضافه خواهد کرد.
مرحله 8: کد اکشن متد Create در EmployeeController را به صورت زیر تغییر دهید:
public ActionResult Create(Employee Emp) { ctx.Employees.Add(Emp); ctx.SaveChanges(); return View( "Index" ) ; } |
حال یک breakpoint درون کد قرار می دهیم و پروژه را با ابزار Fiddler اجرا می کنیم. همان داده هایی که در مرحله 5 وارد نموده بودیم را مجددا اینجا وارد می نماییم. داده های ارسال شده در شکل زیر نمایش داده شده اند:
این تصویر نشان می دهد که داده ها به داخل آبجکت Employee با موفقیت deserialize شده است.
همانطور که مشاهده نمودید ما در این مثال ModelBinder سفارشی خودمان را پیاده سازی نمودیم و قبل از عملیات ثبت در دیتابیس در ModelBinder سفارشی خود تعیین نمودیم که اگر نوع داده های ارسال شده به غیر از text/xml بود null برگدانده شود و عملیات Bind به آبجکت Employee صورت نگیرد.
ایجاد ModelBinder سفارشی برای فیلد تاریخ
به عنوان مثالی دیگر ما قصد داریم 3 فیلد درون فرم برای گرفتن روز، ماه و سال در نظر بگیریم. قصد داریم زمانی که کاربر داده ها را وارد نمود و ارسال کرد داده های وارد شده در این 3 فیلد با یکدیگر الحاق شده و در قالب یک فیلد تاریخ برگردانده شود.
برای این کار View زیر را در نظر بگیرید:
1 2 3 4 5 6 |
<form id="Home" action="" method="POST"> Day <input id="Day" name="Day" value="" type="text" /> Month <input id="Month" name="Month" value="" type="text" /> Year <input id="Year" name="Year" value="" type="text" /> <input id="Submit" type="Submit" value="Submit" /> </form> |
در فولدر Models، یک کلاس با نام HomePageModels ایجاد نموده و فیلدهای زیر را درون آن قرار می دهیم:
1 2 3 4 5 |
public class HomePageModels { public string Title { get; set; } public string Date { get; set; } } |
سپس یک کلاس ModelBinder سفارشی با نام HomeCustomBinder در فولدر CustomModelBinders ایجاد می نماییم و کدهای زیر را درون آن قرار می دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class HomeCustomBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { HttpRequestBase request = controllerContext.HttpContext.Request; string title = request.Form.Get("Title"); string day = request.Form.Get("Day"); string month = request.Form.Get("Month"); string year = request.Form.Get("Year"); return new HomePageModels { Title = title, Date = day +"/"+ month +"/"+ year }; } } |
همانطور که در کد بالا مشاهده می نمایید ما request مورد نظر را دریافت نموده و مقدارهای وارد شده در فیلدهای ورودی را استخراج می نماییم. سپس ما می توانیم این فیلدها را به هر صورتی که تمایل داشتیم دستکاری نماییم. همانطور که مشاهده می نمایید من آنها را درون یک property به نام Date قرار داده ام.
توجه: ما اگر تمایل نداریم که برای هر مدل و فیلد در application کلاس binding سفارشی پیاده سازی نماییم در این صورت می توانیم از کلاس DefaultModelBinder یک کلاس مشتق شده بسازیم و متد BindModel درون آن را همانند کدهای زیر override نماییم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class HomeCustomDataBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext.ModelType == typeof(HomePageModels)) { HttpRequestBase request = controllerContext.HttpContext.Request; string title = request.Form.Get("Title"); string day = request.Form.Get("Day"); string month = request.Form.Get("Month"); string year = request.Form.Get("Year"); return new HomePageModels { Title = title, Date = day + "/" + month + "/" + year }; //// call the default model binder this new binding context //return base.BindModel(controllerContext, newBindingContext); } else { return base.BindModel(controllerContext, bindingContext); } } } |
حال که ما ModelBinder سفارشی خودمان را ایجاد نمودیم باید آن را توسط کد زیر در فایل Global.asax در متد Application_Start اضافه نماییم:
1 |
ModelBinders.Binders.Add(typeof(HomePageModels), new HomeCustomBinder()); |
در آخر نیاز است که ما اکشن متد مورد نظر را محدود به استفاده از این ModelBinder نماییم که برای این منظور از این Attribute استفاده می نماییم:
1 |
[ModelBinder(typeof(HomeCustomBinder))] |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[HttpPost] public ActionResult Index([ModelBinder(typeof(HomeCustomBinder))] HomePageModels home) { if (ModelState.IsValid) { ViewBag.Title = home.Title; ViewBag.Date = home.Date; } return View(); } |
برای دانلود سورس این پروژه میتوانید اینجا کلیک نمایید.
نتیجه گیری
در نتیجه شما می توانید با استفاده از ModelBinder ، مکانیسمی را برای خود طراحی کنید که قبل از map شدن request ارسالی به Data Model، ان را به سلیقه خود دستکاری و یا مدیریت نمایید.