ModelBinder سفارشی در ASP.NET MVC
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: سه دقیقه

زمانی که درخواستی به سمت یک Action پارامتر دار ارسال میشود، قسمت  ActionInvoker قبل از فراخوانی اکشن مربوطه، به دنبال Model Binder مناسبی برای داده‌های پارامترها می‌گردد و در صورت یافت نشدن، از ModelBinder پیش فرض ASP.NET MVC استفاده می‌کند.
اما وظیفه‌ی ModelBinder چیست ؟
ModelBinder  داده‌های ارسال شده از مرورگر را که توسط درخواست‌های HTTP (کوئری استرینگ‌ها و یا داده‌های همراه با فرم‌ها ) ارسال شده است، تبدیل به داده‌های قابل فهم برای پارامترها میکند.
به عبارتی ModelBinder وظیفه تبدیل داده‌های ارسال شده از سمت مرورگر به اشیاء NET. را دارد.
فرض کنید ما مدلی به شکل زیر داریم :
public class CustomerInfo
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}
فیلد آخر برای ذخیره‌ی تاریخ تولد مشتری استفاده میشود. که View مربوط به آن به شکل زیر خواهد بود :

همانطور که می‌بینید تایپ کردن تاریخ به این صورت (1/1/2009  12:00:00 AM) ، هم زیاد جالب نیست و هم کمی مشکل است. به همین دلیل برخی سایت‌ها  از سه قسمت جدا برای گرفتن روز ، ماه و سال استفاده می‌کنند و در نهایت آنها را با یکدیگر ترکیب میکنند.
در این مثال ما نیز می‌خواهیم تاریخ را به صورت زیر دریافت و پس از تبدیل آن به تاریخ میلادی، آن را به کاربر نمایش دهیم :

اما هنگام ارسال فرم  به صورت بالا ، ModelBinder  توانایی تبدیل این سه ورودی (روز ، ماه و سال)  به فیلد BirthDate موجود در کلاس CustomerInfo را ندارد. به همین خاطر ما باید یک ModelBinder متناسب با نیاز خود را طراحی کنیم.
برای ایجاد یک ModelBinder  سفارشی نیاز است که از کلاس IModelBinder ارثبری و متد BindModel آن را پیاده سازی کنیم.
ساختار این اینترفیس به شکل زیر است :
public interface IModelBinder
{ 
    object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext); 
}
متد BindModel حاوی 2 پارامتر است :
ControllerContext : حاوی اطلاعاتی در مورد درخواست http جاری 
ModelBindingContext : این کلاس حاوی یک property به نام Model  است که حاوی ارجاعی به مدلی که همکنون قصد پردازشش آن را دارد.
با توجه به موارد بالا کلاس ما به شکل زیر خواهد بود :
using System;
using System.Web;
using System.Web.Mvc;
using ModelBinderExample.Models;
using Persia;

namespace ModelBinderExample.CustomModelBinder
{
    // Article written for www.dotnettips.info
    public class CustomerInfoModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            HttpRequestBase request = controllerContext.HttpContext.Request;

            string firstName = request.Form.Get("FirstName");
            string lastName  = request.Form.Get("LastName");

            DateTime birthDate = this.GetMiladiDate(request);

            return new CustomerInfo()
            {
                FirstName = firstName,
                LastName  = lastName,
                BirthDate = birthDate
            };
        }

        private DateTime GetMiladiDate(HttpRequestBase request)
        {
            int day   = int.Parse(request.Form.Get("Day"));
            int month = int.Parse(request.Form.Get("Month"));
            int years = int.Parse(request.Form.Get("Years"));

            //Convert shamsi to miladi
            return Persia.Calendar.ConvertToGregorian(years, month, day, DateType.Gerigorian);
        }
    }
}
در کد بالا ایتدا موارد ارسال شده را دریافت میکنیم و توسط متد ()GetMiladiDate تاریخ دریافتی از کاربر که به صورت روز، ماه و سال میباشد را  تبدیل به میلادی میکنیم و سپس در قالب یک شی customerInfo آنها را برگشت می‌دهیم.
نکته : جهت تبدیل تاریخ شمسی به میلادی از کتابخانه‌ی Persia کمک گرفته شده است که در فایل پیوستی قرار داده شده.
کار ایجاد یک ModelBinder سفارشی تمام شده و حال نیاز است کلاس را در فایل Global.asax  در قسمت ()Application_start ثبت کنیم به شکل زیر :
protected void Application_Start()
{
     AreaRegistration.RegisterAllAreas();

     WebApiConfig.Register(GlobalConfiguration.Configuration);
     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
     RouteConfig.RegisterRoutes(RouteTable.Routes);
     //Register New ModelBinder
     ModelBinders.Binders.Add(typeof(CustomerInfo), new CustomerInfoModelBinder());
}
و برای استفاده از این ModelBinder ، ما باید به کنترلر اطلاع دهیم که میخواهیم از چه نوع Binding استفاده کنیم به همین دلیل از attribute زیر برای انجام این کار استفاده می‌کنیم: 
[HttpPost]
public ActionResult Create([ModelBinder(typeof (CustomerInfoModelBinder))] CustomerInfo customerInfo)
{
      if (ModelState.IsValid)
      {
           ViewBag.FirstName = customerInfo.FirstName;
           ViewBag.LastName = customerInfo.LastName;
           ViewBag.BirthDate = customerInfo.BirthDate;
      }
      return View();
}

پروژه پیوستی : ModelBinder-Example.zip
  • #
    ‫۱۱ سال و ۳ ماه قبل، پنجشنبه ۲۰ تیر ۱۳۹۲، ساعت ۲۱:۴۴
    ضمن تشکر از مقاله خوبتان
    در قسمت آخر فرمودید ، باید به کنترلر اطلاع دهیم که میخواهیم از چه نوع Binding استفاده کنیم  ولی اگر اشتباه نکنم این مورد اجباری نیست یعنی اکشن ما می‌تواند به شکل زیر هم باشد
    public ActionResult Create(CustomerInfo customerInfo)
    • #
      ‫۱۱ سال و ۳ ماه قبل، جمعه ۲۱ تیر ۱۳۹۲، ساعت ۰۰:۴۷
      ممنون
      بله. در صورتی که ModelBinder رو در ()Application_start ریجیستر کنیم نیازی به ذکر صریح در اکشن نیست.
      در غیر این صورت نیاز است.
      ModelBinders.Binders.Add(typeof(CustomerInfo), new CustomerInfoModelBinder());
      در صورتی که ModelBinder را ریجیستر نکنیم و مستقیما نوع Binding را در اکشن مشخص کنیم ،این امکان را خواهیم داشت که برای یک Model چندین ModelBinder مختلف ایجاد و استفاده کنیم.
      • #
        ‫۱۱ سال و ۲ ماه قبل، سه‌شنبه ۸ مرداد ۱۳۹۲، ساعت ۱۳:۲۷
        خب، برای خاطر خوانندگانی که دچار اشتباه میشوند بهتر نمیدونید که مقاله رو ویرایش کنید و این مطلب رو اصلاح کنید؟
        • #
          ‫۱۱ سال و ۲ ماه قبل، سه‌شنبه ۸ مرداد ۱۳۹۲، ساعت ۱۴:۰۸
          اشتباه نیست، فقط کمی verbose است.
    • #
      ‫۹ سال و ۲ ماه قبل، پنجشنبه ۲۵ تیر ۱۳۹۴، ساعت ۱۸:۲۱
      با سلام وقتی پرژه را دانلود میکنیم دو تا رفرنس را خطا میدهد 
      ظاهرا dll Persia  در پرژه وجود ندارد .
  • #
    ‫۱۱ سال و ۲ ماه قبل، سه‌شنبه ۸ مرداد ۱۳۹۲، ساعت ۱۳:۳۳
    من میخواهم که مدل بایندر را برای یک پراپرتی از مدلم تنظیم کنم. یعنی پراپرتی از جنس byte[] داریم که میخواهم توسط FileModelBinder بایند شود. FileModelBinder فایلهای آپلود شده به سرور را میخواند و در پراپرتی میریزد. من نمیخواهم همه‌ی پراپرتی‌های از جنس byte[] را به این بایندر واگذار کنم فقط میخواهم در مدلهای معینی در پراپرتی‌های مورد نظرم این بایندر اعمال شود. همچنین من نمیخواهم که از بایندر در پارامترهای ورودی action method بر روی یک پارامتر byte[] استفاده کنم.
    آیا اطلاق یک بایندر به یک پراپرتی از یک مدل برای یک تایپ مشخص مانند byte[] قابل انجام هست؟
    • #
      ‫۱۱ سال و ۲ ماه قبل، سه‌شنبه ۸ مرداد ۱۳۹۲، ساعت ۱۴:۰۴
      بله. یک کلاس مشتق شده از System.Web.Mvc.DefaultModelBinder ایجاد کنید. در آن متد BindProperty را override کرده و سپس اگر خاصیتی، مثلا خاصیت X بود (بر اساس مثلا یک ویژگی خاص که به آن انتساب داده شده)، آنگاه از Model binder سفارشی استفاده گردد.
      یک نمونه پیاده سازی کامل آن:
      MVC Property Binder 
  • #
    ‫۹ سال و ۹ ماه قبل، دوشنبه ۱ دی ۱۳۹۳، ساعت ۱۴:۱۷
    سلام؛ با تشکر از مقاله شما.
    میخواستم بپرسم override کردن BindModel یا BindProperty برای زمانییه که ما به تمام دیتا هامون دسترسی داریم حالا شکل برگرداندنمون فرق میکنه؟
    اگر اینطوره سوالم اینه که برای حالتی که مدل ما به شکل زیر هست چگونه Items را Bind کنم چون از هر روشی میرم null هست!
    public class Model 
    {
      public Model()
      {
         Items = new List<ItemModel>();
      }
     public Guid Id { get; set; }
     public Guid ProductId { get; set; }
      public List<ItemModel> Items { get; set; }
    }
    
    public class ItemModel
    {
            Public Guid Id
            public string  Title{ get; set; }
            public int Value { get; set; }
    }
    و من در view مدل زیر را احتیاج دارم.
    @model  List<Model>
    در اکشن  HttpPost مربوط به این مدل ItemsProperty Is Null.
  • #
    ‫۷ سال و ۳ ماه قبل، جمعه ۱۲ خرداد ۱۳۹۶، ساعت ۱۸:۲۶
    سلام؛ بنده یک کلاس به نام PersainCalender ایجاد کرده‌ام که از اینترفیس IModelBinder ارث بری می‌کند. در زمانیکه بخوام اطلاعات فرم رو با Ajax به سرور بفرستم عمل بایند انجام می‌شود؛ ولی فیلد تاریخ که به صورت شمسی از ورودی فرستادم به میلادی تبدیل نمی‌شود. ولی در غیر Ajax این طور نیست و تبدیل شمسی به میلادی انجام می‌شود. فقط در زمان ارسال اطلاعات با Ajax این اتفاق می‌افتد. به نظرتون مشکل از کجاست؟
    • #
      ‫۷ سال و ۳ ماه قبل، جمعه ۱۲ خرداد ۱۳۹۶، ساعت ۱۸:۴۵
      - کد ارسال Ajax ایی شما به صورتی چه هست؟
      - از همین Model Binder مطلب فوق استفاده می‌کنید؟ (چون این model binder از request.Form استفاده کرده که Ajax ایی نیست)