what the side purpose net mvc exclude custom attribute asp c# asp.net-mvc model-binding custom-model-binder

c# - the - Carpeta modelo personalizada para una propiedad



model binding asp net (4)

Tengo la siguiente acción del controlador:

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

Donde MyModel ve así:

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

Así que DefaultModelBinder debería enlazar esto sin problemas. Lo único es que quiero usar un cuaderno especial / personalizado para enlazar PropertyB y también quiero reutilizar este cuaderno. Así que pensé que la solución sería colocar un atributo ModelBinder antes de PropertyB que, por supuesto, no funciona (el atributo ModelBinder no está permitido en las propiedades). Veo dos soluciones:

  1. Para usar parámetros de acción en cada propiedad individual en lugar de todo el modelo (que no preferiría, ya que el modelo tiene muchas propiedades) de esta manera:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)

  2. Para crear un nuevo tipo digamos MyCustomType: List<int> y registre el cuaderno de modelos para este tipo (esta es una opción)

  3. Tal vez para crear una carpeta para MyModel, BindProperty y si la propiedad es "PropertyB" vincule la propiedad con mi carpeta personalizada. es posible?

hay alguna otra solucion?


anular BindProperty y si la propiedad es "PropertyB" vincular la propiedad con mi carpeta personalizada

Esa es una buena solución, aunque en lugar de verificar "es PropertyB", es mejor que busque sus propios atributos personalizados que definan carpetas a nivel de propiedad, como

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

Puedes ver un ejemplo de anulación de BindProperty here .


Han pasado 6 años desde que se hizo esta pregunta, preferiría aprovechar este espacio para resumir la actualización, en lugar de ofrecer una solución completamente nueva. En el momento de escribir este artículo, MVC 5 ha existido durante bastante tiempo y ASP.NET Core acaba de salir.

Seguí el enfoque examinado en la publicación escrita por Vijaya Anand (por cierto, gracias a Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes . Y una cosa que vale la pena mencionar es que, la lógica de enlace de datos se coloca en la clase de atributo personalizado, que es el método BindProperty de la clase StringArrayPropertyBindAttribute en el ejemplo de Vijaya Anand.

Sin embargo, en todos los otros artículos sobre este tema que he leído (incluida la solución de @ jonathanconway), la clase de atributo personalizado es solo un escalón que lleva al marco a descubrir la carpeta de modelos personalizados correcta para aplicar; y la lógica de enlace se coloca en ese modelo de carpeta personalizada, que generalmente es un objeto IModelBinder.

El primer enfoque es más simple para mí. Puede haber algunas deficiencias en el primer enfoque, que aún no conozco, porque estoy bastante nuevo en el marco MVC en este momento.

Además, descubrí que la clase ExtendedModelBinder en el ejemplo de Vijaya Anand no es necesaria en MVC 5. Parece que la clase DefaultModelBinder que viene con MVC 5 es lo suficientemente inteligente como para cooperar con los atributos de enlace de modelos personalizados.


La respuesta de @Jonathanconway es genial, pero me gustaría agregar un pequeño detalle.

Probablemente sea mejor anular el método BindProperty en lugar de BindProperty para que el mecanismo de validación de DefaultBinder oportunidad de funcionar.

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); }


Solo me gusta su tercera solución, solo la convertiría en una solución genérica para todos los ModelBinders, al colocarla en un cuaderno personalizado que hereda de DefaultModelBinder y está configurado para ser el modelo predeterminado para su aplicación MVC.

Luego haría que este nuevo DefaultModelBinder vincule automáticamente cualquier propiedad que esté decorada con un atributo PropertyBinder , utilizando el tipo proporcionado en el parámetro.

Tengo la idea de este excelente artículo: http://aboutcode.net/2011/03/12/mvc-property-binder.html .

También te mostraré mi opinión sobre la solución:

Mi 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(); } } }

Mi interfaz IPropertyBinder :

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

Mi PropertyBinderAttribute :

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

Un ejemplo de una carpeta de propiedades:

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); } } }

Ejemplo de la carpeta de propiedad anterior que se utiliza:

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