J'essaie de créer un classeur de modèle personnalisé pour MVC 4 qui héritera de DefaultModelBinder
. Je voudrais qu’il intercepte toutes les interfaces au niveau any et tente de charger le type souhaité à partir d’un champ caché appelé AssemblyQualifiedName
.
Voici ce que j'ai jusqu'à présent (simplifié):
public class MyWebApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ModelBinders.Binders.DefaultBinder = new InterfaceModelBinder();
}
}
public class InterfaceModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.IsInterface
&& controllerContext.RequestContext.HttpContext.Request.Form.AllKeys.Contains("AssemblyQualifiedName"))
{
ModelBindingContext context = new ModelBindingContext(bindingContext);
var item = Activator.CreateInstance(
Type.GetType(controllerContext.RequestContext.HttpContext.Request.Form["AssemblyQualifiedName"]));
Func<object> modelAccessor = () => item;
context.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(),
bindingContext.ModelMetadata.ContainerType, modelAccessor, item.GetType(), bindingContext.ModelName);
return base.BindModel(controllerContext, context);
}
return base.BindModel(controllerContext, bindingContext);
}
}
Exemple de fichier Create.cshtml (simplifié):
@model Models.ScheduledJob
@* Begin Form *@
@Html.Hidden("AssemblyQualifiedName", Model.Job.GetType().AssemblyQualifiedName)
@Html.Partial("_JobParameters")
@* End Form *@
Le _JobParameters.cshtml
partiel ci-dessus examine les propriétés du Model.Job
et construit les contrôles de saisie, similaires à @Html.EditorFor()
, mais avec quelques balises supplémentaires. La propriété ScheduledJob.Job
est de type IJob
(interface).
Exemple ScheduledJobsController.cs (simplifié):
[HttpPost]
public ActionResult Create(ScheduledJob scheduledJob)
{
//scheduledJob.Job here is not null, but has only default values
}
Lorsque j'enregistre le formulaire, il interprète correctement le type d'objet et obtient une nouvelle instance, mais les propriétés de l'objet ne sont pas définies sur leurs valeurs appropriées.
Que dois-je faire d'autre pour indiquer au classeur par défaut de prendre en charge la liaison de propriété du type spécifié?
Cet article m'a montré que je compliquais excessivement le classeur. Le code suivant fonctionne:
public class InterfaceModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.IsInterface)
{
Type desiredType = Type.GetType(
EncryptionService.Decrypt(
(string)bindingContext.ValueProvider.GetValue("AssemblyQualifiedName").ConvertTo(typeof(string))));
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, desiredType);
}
return base.BindModel(controllerContext, bindingContext);
}
}
Avec MVC 4, il est facile de remplacer les messages, si c'est tout ce dont vous pourriez avoir besoin dans un classeur de modèle personnalisé:
protected void Application_Start(object sender, EventArgs e)
{
//set mvc default messages, or language specifc
ClientDataTypeModelValidatorProvider.ResourceClassKey = "ValidationMessages";
DefaultModelBinder.ResourceClassKey = "ValidationMessages";
}
Créez ensuite un fichier de ressources nommé ValidationMessages
avec des entrées comme celle-ci:
NAME: FieldMustBeDate
VALUE: The field {0} must be a date.
NAME: FieldMustBeNumeric
VALUE: The field {0} must be a number
.
Nous l'avons fait pour un manquement à la conformité. Notre analyse de sécurité n’aimait pas le fait qu’une injection javascript
revienne et apparaisse dans les messages de validation et s’exécute. En utilisant cette implémentation, nous remplaçons les messages par défaut qui renvoient la valeur fournie par l'utilisateur.