liant modèle personnalisé pour une propriété
-
25-09-2019 - |
Question
Je l'action du contrôleur suivant:
[HttpPost]
public ViewResult DoSomething(MyModel model)
{
// do something
return View();
}
Où MyModel
ressemble à ceci:
public class MyModel
{
public string PropertyA {get; set;}
public IList<int> PropertyB {get; set;}
}
DefaultModelBinder devrait lier cela sans problème. La seule chose est que je veux utiliser un liant spécial / custom pour la liaison PropertyB
et je veux aussi réutiliser ce liant. Donc, je pensais que la solution serait de mettre un attribut ModelBinder avant la PropertyB qui bien sûr ne fonctionne pas (attribut ModelBinder est pas autorisée sur une des propriétés). Je vois deux solutions:
-
Pour utiliser les paramètres d'action sur chaque propriété au lieu du modèle entier (que je ne préférerais pas que le modèle a beaucoup de propriétés) comme ceci:
public ViewResult DoSomething(string propertyA, [ModelBinder(typeof(MyModelBinder))] propertyB)
-
Pour créer un nouveau type permet de dire
MyCustomType: List<int>
et registre liant modèle pour ce type (ce qui est une option) -
Peut-être pour créer un liant pour MyModel, passer outre
BindProperty
et si la propriété est"PropertyB"
lier la propriété avec mon classeur personnalisé. Est-ce possible?
Y at-il une autre solution?
La solution
override bindProperty et si le la propriété est « PropertyB » lier la propriété avec mon classeur personnalisé
C'est une bonne solution, mais au lieu de vérifier « est PropertyB » vous feriez mieux de vérifier vos propres attributs personnalisés qui définissent des liants niveau de la propriété, comme
[PropertyBinder(typeof(PropertyBBinder))]
public IList<int> PropertyB {get; set;}
Vous pouvez voir un exemple de bindProperty override ici .
Autres conseils
En fait, je comme votre troisième solution, que je ferais une solution générique pour tous les ModelBinders, en le mettant dans un classeur personnalisé qui hérite de DefaultModelBinder
et est configuré pour être le modèle de liaison par défaut pour votre application MVC.
Ensuite, vous faire de cette nouvelle DefaultModelBinder
lier automatiquement une propriété qui est décorée avec un attribut PropertyBinder
, en utilisant le type fourni dans le paramètre.
J'ai eu l'idée de cet excellent article: http://aboutcode.net/2011/ 03/12 / mvc-propriété-binder.html .
Je vais aussi vous montrer mon avis sur la solution:
Mon 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();
}
}
}
Mon interface IPropertyBinder
:
namespace MyApp.Web.Mvc
{
interface IPropertyBinder
{
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, MemberDescriptor memberDescriptor);
}
}
Mon PropertyBinderAttribute
:
namespace MyApp.Web.Mvc
{
public class PropertyBinderAttribute : Attribute
{
public PropertyBinderAttribute(Type binderType)
{
BinderType = binderType;
}
public Type BinderType { get; private set; }
}
}
Un exemple d'un liant de bien:
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);
}
}
}
Exemple de liant de la propriété ci-dessus étant utilisés:
namespace MyApp.Web.Models
{
public class MyModel
{
[PropertyBinder(typeof(TimeSpanBinder))]
public TimeSpan InspectionDate { get; set; }
}
}
@ jonathanconway est grande, mais je voudrais ajouter un détail mineur.
Il est sans doute préférable de remplacer la méthode GetPropertyValue
au lieu de BindProperty
afin de donner le mécanisme de validation de la DefaultBinder
une chance de travailler.
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);
}
Il a été 6 ans que cette question a été posée, je préfère prendre cet espace pour résumer la mise à jour, au lieu de fournir une toute nouvelle solution. Au moment de l'écriture, MVC 5 a été autour depuis un certain temps, et ASP.NET de base vient de sortir.
J'ai suivi l'approche examinée dans le post écrit par Anand Vijaya (BTW, grâce à Vijaya): http://www.prideparrot.com/blog/archive/2012/6/customizing_property_binding_through_attributes . Et une chose à noter est que, les données logiques de liaison est placé dans la classe attribut personnalisé, qui est la méthode bindProperty de la classe StringArrayPropertyBindAttribute dans l'exemple de Vijaya Anand.
Cependant, dans tous les autres articles sur ce sujet que j'ai lu (y compris la solution de @ jonathanconway), attribut personnalisé classe est seulement une pierre étape qui mène le cadre à trouver le liant modèle correct personnalisé à appliquer; et la logique de liaison est placé dans le classeur de modèle personnalisé, qui est généralement un objet IModelBinder.
La 1ère approche est plus simple pour moi. Il peut y avoir des lacunes de la 1ère approche, que je ne l'ai pas encore connu, mais, coz je suis assez nouveau pour framework MVC pour le moment.
De plus, je trouve que la classe ExtendedModelBinder dans l'exemple de Vijaya Anand est inutile dans MVC 5. Il semble que la classe DefaultModelBinder qui est livré avec MVC 5 est assez intelligent pour coopérer avec des attributs de liaison modèle personnalisé.