Pregunta

I tienen la siguiente acción del controlador:

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

Cuando se ve MyModel como esta:

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

Así DefaultModelBinder debe enlazar este sin ningún problema. La única cosa es que quiero utilizar ligante / encargo especial para PropertyB vinculante y también quiero volver a usar esta carpeta. Así que pensé que la solución sería poner un atributo ModelBinder antes de la PropertyB que por supuesto no funciona (atributo ModelBinder no está permitido en el inmueble). Veo dos soluciones:

  1. Para utilizar parámetros de acción en cada propiedad en lugar de todo el modelo (que no preferiría que el modelo tiene una gran cantidad de propiedades) de esta manera:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
    
  2. Para crear un nuevo tipo permite MyCustomType: List<int> digamos y registrar ligante modelo de este tipo (esto es una opción)

  3. Tal vez para crear un aglutinante para MyModel, BindProperty anulación y si la propiedad es la propiedad "PropertyB" se unen con mi carpeta de encargo. ¿Es esto posible?

¿Hay alguna otra solución?

¿Fue útil?

Solución

  

anular BindProperty y si el   propiedad es "PropertyB" atar el   propiedad con mi carpeta de encargo

Esto es una buena solución, aunque en vez de comprobar "es PropertyB" es mejor que ver para sus propios atributos personalizados que definen carpetas de nivel de propiedad, como

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

Se puede ver un ejemplo de BindProperty anulación aquí .

Otros consejos

En realidad me gusta su tercera solución, solamente, yo haría una solución genérica para todos los ModelBinders, poniéndolo en una costumbre ligante que hereda de DefaultModelBinder y está configurado para ser el aglutinante de modelo por defecto para su aplicación MVC.

A continuación, haría que esta nueva DefaultModelBinder automáticamente se unen cualquier propiedad que está decorado con un atributo PropertyBinder, utilizando el tipo suministrado en el parámetro.

Me tomó la idea de este excelente artículo: http://aboutcode.net/2011/ 03/12 / MVC-propiedad-binder.html .

También voy a 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 un aglutinante de propiedad:

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 propiedad por encima de aglutinante que se utiliza:

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

@ de jonathanconway es grande, pero me gustaría añadir un detalle menor.

Es probablemente mejor reemplazar el método GetPropertyValue en lugar de BindProperty con el fin de dar al mecanismo de validación de la DefaultBinder una oportunidad de trabajo.

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

Ha sido 6 años desde que se hizo esta pregunta, yo preferiría tener este espacio para resumir la actualización, en lugar de proporcionar una nueva solución. En el momento de la escritura, MVC 5 ha sido de alrededor durante bastante tiempo, y ASP.NET Core acaba de salir.

Me siguió el enfoque examinado en el post escrito por Vijaya Anand (por cierto, gracias a Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes . Y una cosa a destacar es que, los datos de la lógica de enlace se coloca en la clase de atributo personalizado, que es el método de la clase BindProperty StringArrayPropertyBindAttribute en el ejemplo de Vijaya Anand.

Sin embargo, en todos los demás artículos sobre este tema que he leído (incluyendo la solución de @ jonathanconway), la clase de atributo personalizado es sólo una piedra de paso que conduce el marco para averiguar el modelo personalizado correcto ligante de aplicar; y la lógica de unión se coloca en ese modelo de encargo aglutinante, que suele ser un objeto IModelBinder.

La primera solución es más sencilla para mí. Puede haber algunas deficiencias de la primera aproximación, que no he conocido todavía, sin embargo, coz Soy bastante nuevo en el marco de MVC en el momento.

Además, he encontrado que la clase ExtendedModelBinder en el ejemplo de Vijaya Anand es innecesario 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top