سؤال

لدي إجراء وحدة تحكم التالية:

[HttpPost]
public ViewResult DoSomething(MyModel model)
{
    // do something
    return View();
}

أين MyModel يشبه هذا:

public class MyModel
{
    public string PropertyA {get; set;}
    public IList<int> PropertyB {get; set;}
}

لذلك يجب أن يربط DefaultModelBinder هذا دون مشكلة. الشيء الوحيد هو أنني أريد استخدام موثق خاص/مخصص للربط PropertyB وأريد أيضًا إعادة استخدام هذا الموثق. لذلك اعتقدت أن الحل هو وضع سمة نموذجية قبل PropertyB التي لا تعمل بالطبع (لا يُسمح بسمات النموذجية على الممتلكات). أرى حلين:

  1. لاستخدام معلمات الإجراء على كل خاصية واحدة بدلاً من النموذج بأكمله (الذي لن أفضله لأن النموذج يحتوي على الكثير من الخصائص) مثل هذا:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
    
  2. لإنشاء نوع جديد ، دعنا نقول MyCustomType: List<int> وتسجيل موثق النموذج لهذا النوع (هذا خيار)

  3. ربما لإنشاء موثق ل mymodel ، تجاوز BindProperty وإذا كان العقار "PropertyB" ربط الخاصية مع الموثق المخصص الخاص بي. هل هذا ممكن؟

هل يوجد حل آخر؟

هل كانت مفيدة؟

المحلول

تجاوز BindProperty وإذا كان الخاصية "PropertyB" قم بربط الخاصية مع الموثق المخصص الخاص بي

هذا حل جيد ، على الرغم من أنه بدلاً من التحقق "هو PropertyB" ، يمكنك التحقق بشكل أفضل من سماتك المخصصة التي تحدد المجلدات على مستوى الخصائص ، مثل

[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}

يمكنك رؤية مثال على تجاوز BindProperty هنا.

نصائح أخرى

أنا في الواقع يعجبني الحل الثالث ، فقط ، سأجعله حلاً عامًا لجميع مواد الطرازات ، عن طريق وضعه في موثق مخصص يرث من DefaultModelBinder ويتم تكوينه ليكون موثق النموذج الافتراضي لتطبيق MVC الخاص بك.

ثم ستجعل هذا جديد DefaultModelBinder ربط أي خاصية تلقائيًا مزين بـ PropertyBinder سمة ، باستخدام النوع المقدم في المعلمة.

حصلت على الفكرة من هذا المقال الممتاز: http://aboutcode.net/2011/03/12/mvc-property-binder.html.

سأريكم أيضًا في الحل:

لي DefaultModelBinder:

namespace MyApp.Web.Mvc
{
    public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder
    {
        protected override void BindProperty(
            ControllerContext controllerContext, 
            ModelBindingContext bindingContext, 
            PropertyDescriptor propertyDescriptor)
        {
            var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
            if (propertyBinderAttribute != null)
            {
                var binder = CreateBinder(propertyBinderAttribute);
                var value = binder.BindModel(controllerContext, bindingContext, propertyDescriptor);
                propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            else // revert to the default behavior.
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }

        IPropertyBinder CreateBinder(PropertyBinderAttribute propertyBinderAttribute)
        {
            return (IPropertyBinder)DependencyResolver.Current.GetService(propertyBinderAttribute.BinderType);
        }

        PropertyBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
        {
            return propertyDescriptor.Attributes
              .OfType<PropertyBinderAttribute>()
              .FirstOrDefault();
        }
    }
}

لي IPropertyBinder واجهه المستخدم:

namespace MyApp.Web.Mvc
{
    interface IPropertyBinder
    {
        object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor);
    }
}

لي PropertyBinderAttribute:

namespace MyApp.Web.Mvc
{
    public class PropertyBinderAttribute : Attribute
    {
        public PropertyBinderAttribute(Type binderType)
        {
            BinderType = binderType;
        }

        public Type BinderType { get; private set; }
    }
}

مثال على موثق الخاصية:

namespace MyApp.Web.Mvc.PropertyBinders
{
    public class TimeSpanBinder : IPropertyBinder
    {
        readonly HttpContextBase _httpContext;

        public TimeSpanBinder(HttpContextBase httpContext)
        {
            _httpContext = httpContext;
        }

        public object BindModel(
            ControllerContext controllerContext,
            ModelBindingContext bindingContext,
            MemberDescriptor memberDescriptor)
        {
            var timeString = _httpContext.Request.Form[memberDescriptor.Name].ToLower();
            var timeParts = timeString.Replace("am", "").Replace("pm", "").Trim().Split(':');
            return
                new TimeSpan(
                    int.Parse(timeParts[0]) + (timeString.Contains("pm") ? 12 : 0),
                    int.Parse(timeParts[1]),
                    0);
        }
    }
}

مثال على موثق الخاصية أعلاه المستخدمة:

namespace MyApp.Web.Models
{
    public class MyModel
    {
        [PropertyBinder(typeof(TimeSpanBinder))]
        public TimeSpan InspectionDate { get; set; }
    }
}

@إجابة Jonathanconway رائعة ، لكنني أود إضافة تفاصيل بسيطة.

ربما من الأفضل تجاوز GetPropertyValue الطريقة بدلا من BindProperty من أجل إعطاء آلية التحقق من صحة DefaultBinder فرصة للعمل.

protected override object GetPropertyValue(
    ControllerContext controllerContext,
    ModelBindingContext bindingContext,
    PropertyDescriptor propertyDescriptor,
    IModelBinder propertyBinder)
{
    PropertyBinderAttribute propertyBinderAttribute =
        TryFindPropertyBinderAttribute(propertyDescriptor);
    if (propertyBinderAttribute != null)
    {
        propertyBinder = CreateBinder(propertyBinderAttribute);
    }

    return base.GetPropertyValue(
        controllerContext,
        bindingContext,
        propertyDescriptor,
        propertyBinder);
}

لقد مرت 6 سنوات منذ طرح هذا السؤال ، أفضل أن آخذ هذا المساحة لتلخيص التحديث ، بدلاً من توفير حل جديد. في وقت كتابة هذا التقرير ، كانت MVC 5 موجودة لفترة طويلة ، وقد خرجت ASP.NET Core للتو.

تابعت النهج الذي تم فحصه في المنشور الذي كتبه فيجايا أناند (راجع للشغل ، بفضل فيجايا): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes. وشيء واحد جدير بالذكر أنه يتم وضع منطق ربط البيانات في فئة السمات المخصصة ، وهي طريقة BindProperty لفئة StringArrayProperTyBindattribute في مثال Vijaya Anand.

ومع ذلك ، في جميع المقالات الأخرى حول هذا الموضوع الذي قرأته (بما في ذلك حل @Jonathanconway) ، فإن فئة السمات المخصصة ليست سوى حجر خطوة تقود الإطار لمعرفة رابط النموذج المخصص الصحيح ؛ ويتم وضع منطق الربط في هذا الموثق النموذج المخصص ، والذي عادة ما يكون كائن Imodelbinder.

النهج الأول هو أبسط بالنسبة لي. قد تكون هناك بعض أوجه القصور في النهج الأول ، والتي لم أكن أعرفها بعد ، على الرغم من أنني جديد في إطار عمل MVC في الوقت الحالي.

بالإضافة إلى ذلك ، وجدت أن فئة ExtendedModelBinder في مثال Vijaya Anand غير ضروري في MVC 5. يبدو أن فئة DefaultModelBinder التي تأتي مع MVC 5 ذكية بما يكفي للتعاون مع سمات ربط الطراز المخصصة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top