Domanda

Ho la seguente azione di controllo:

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

Dove sguardi MyModel come questo:

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

Quindi DefaultModelBinder dovrebbe legare questo senza un problema. L'unica cosa è che voglio usare speciale legante / personalizzato per PropertyB rilegatura e voglio anche riutilizzare questo legante. Così ho pensato che la soluzione sarebbe quella di mettere un attributo ModelBinder prima della PropertyB che ovviamente non funziona (attributo ModelBinder non è consentito su una proprietà). Vedo due soluzioni:

  1. Per usare parametri di azione su ogni singola proprietà, invece di tutto il modello (che non preferirei come il modello ha un sacco di proprietà) in questo modo:

    public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
    
  2. Per creare un nuovo tipo permette di dire MyCustomType: List<int> e registrare il modello legante per questo tipo (questa è un'opzione)

  3. Forse per creare un legante per MyModel, override BindProperty e se la proprietà è "PropertyB" associare la proprietà con il mio raccoglitore personalizzato. È possibile?

C'è qualche altra soluzione?

È stato utile?

Soluzione

  

ignorare BindProperty e se la   proprietà è "PropertyB" legare il   Proprietà con il mio raccoglitore personalizzato

Questa è una buona soluzione, anche se invece di controllare "è PropertyB" è meglio controllare per i propri attributi personalizzati che definiscono leganti a livello di proprietà, come

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

È possibile vedere un esempio di BindProperty di override qui .

Altri suggerimenti

Io in realtà come la vostra terza soluzione, solo, mi renderebbe una soluzione generica per tutti i ModelBinders, mettendolo in un costume legante che eredita da DefaultModelBinder ed è configurato per essere il legante modello predefinito per l'applicazione MVC.

Poi si renderebbe questa nuova DefaultModelBinder automaticamente associare qualsiasi proprietà che è decorato con un attributo PropertyBinder, utilizzando il tipo fornito nel parametro.

Ho avuto l'idea da questo eccellente articolo: http://aboutcode.net/2011/ 12/03 / MVC-proprietà-binder.html .

Sarò anche mostrarvi il mio prendere sulla soluzione:

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

La mia interfaccia IPropertyBinder:

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

La mia PropertyBinderAttribute:

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

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

Un esempio di un legante struttura:

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

Esempio della proprietà sopra legante utilizzato:

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

@ di jonathanconway risposta è grande, ma vorrei aggiungere un piccolo dettaglio.

E 'probabilmente meglio l'override del metodo GetPropertyValue invece di BindProperty al fine di dare il meccanismo di validazione del DefaultBinder una possibilità di lavoro.

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

E 'stato 6 anni da quando questa domanda è stato chiesto, avrei preferito prendere questo spazio per riassumere l'aggiornamento, invece di fornire una nuova soluzione. Al momento della scrittura, MVC 5 è stato intorno per un bel po ', e ASP.NET core è appena uscito.

Ho seguito l'approccio esaminato nel post scritto da Vijaya Anand (a proposito, grazie a Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes . E una cosa da notare è che, i dati logica legame è collocato nella classe di attributo personalizzato, che è il metodo BindProperty della classe StringArrayPropertyBindAttribute nell'esempio di Vijaya Anand.

Tuttavia, in tutti gli altri articoli su questo argomento che ho letto (comprensivo di @ di jonathanconway soluzione), classe di attributo personalizzato è solo una pietra passo che conduce il quadro per trovare la corretta legante modello personalizzato da applicare; e la logica legame è inserito in tale modello personalizzato legante, che di solito è un oggetto IModelBinder.

Il primo approccio è più semplice per me. Ci possono essere alcune lacune dell'approccio prima, che non ho ancora noto, però, coz io sono abbastanza nuovo a framework MVC in questo momento.

Inoltre, ho trovato che la classe ExtendedModelBinder nell'esempio di Vijaya Anand è inutile nella MVC 5. Sembra che la classe DefaultModelBinder che viene fornito con MVC 5 è abbastanza intelligente per cooperare con gli attributi modello personalizzato vincolanti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top