Carpeta de modelo personalizado para una propiedad

Tengo la siguiente acción de 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 PropertyB {get; set;} } 

Entonces DefaultModelBinder debería unir esto sin ningún problema. Lo único es que quiero utilizar una carpeta especial / personalizada para enlazar PropertyB y también quiero reutilizar esta carpeta. Así que pensé que esa solución sería poner un atributo de ModelBinder antes del PropertyB que, por supuesto, no funciona (el atributo de 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) como este:

     public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB) 
  2. Para crear un nuevo tipo, digamos MyCustomType: List y registro de la carpeta del modelo para este tipo (esta es una opción)

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

hay alguna otra solucion?

anula BindProperty y si la propiedad es “PropertyB” une la propiedad con mi carpeta personalizada

Esa es una buena solución, aunque en lugar de verificar “is PropertyB”, es mejor que compruebe sus propios atributos personalizados que definen los enlaces de propiedad, como

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

Puede ver un ejemplo de anulación de BindProperty aquí .

De hecho, me gusta su tercera solución, la convertiría en una solución genérica para todos los ModelBinders, colocándola en una carpeta personalizada que hereda de DefaultModelBinder y está configurada para ser la carpeta de modelo predeterminada 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.

Obtuve 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() .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 propiedades anterior que se utiliza:

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

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

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

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 digna de mención 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 una piedra de avance que lleva al marco a encontrar el encuadernador de modelo personalizado correcto para aplicar; y la lógica de enlace se coloca en ese enlazador de modelo personalizado, que suele ser 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 soy bastante nuevo en el marco de MVC en este momento.

Además, encontré que la clase ExtendedModelBinder en el ejemplo de Vijaya Anand es innecesaria 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 modelo personalizado.