I'm attempting to build a custom model binder for MVC 4 that will inherit from DefaultModelBinder
. I'd like it to intercept any interfaces at any binding level and attempt to load the desired type from a hidden field called AssemblyQualifiedName
.
Here's what I have so far (simplified):
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);
}
}
Example Create.cshtml file (simplified):
@model Models.ScheduledJob
@* Begin Form *@
@Html.Hidden("AssemblyQualifiedName", Model.Job.GetType().AssemblyQualifiedName)
@Html.Partial("_JobParameters")
@* End Form *@
The above partial _JobParameters.cshtml
looks at the Model.Job
's properties and builds the edit controls, similar to @Html.EditorFor()
, but with some extra markup. The ScheduledJob.Job
property is of type IJob
(interface).
Example ScheduledJobsController.cs (simplified):
[HttpPost]
public ActionResult Create(ScheduledJob scheduledJob)
{
//scheduledJob.Job here is not null, but has only default values
}
When I save the form, it interprets the object type correctly and gets a new instance, but the properties of the object are not being set to their appropriate values.
What else do I need to do to this to tell the default binder to take over the property binding of the specified type?
This article showed me that I was over-complicating the model binder. The following code works:
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);
}
}
With MVC 4 it is easy to override the messages, if that is all you might need in a custom model binder:
protected void Application_Start(object sender, EventArgs e)
{
//set mvc default messages, or language specifc
ClientDataTypeModelValidatorProvider.ResourceClassKey = "ValidationMessages";
DefaultModelBinder.ResourceClassKey = "ValidationMessages";
}
Then create resource file named ValidationMessages
with entries like this:
NAME: FieldMustBeDate
VALUE: The field {0} must be a date.
NAME: FieldMustBeNumeric
VALUE: The field {0} must be a number
.
We did this for a compliance failure. Our security scan did not like that a javascript
injection would come back and appear in the Validation Messages and execute. By using this implementation we are overriding the default messages which return the user provided value.
来源:https://stackoverflow.com/questions/16747320/custom-model-binder-inheriting-from-defaultmodelbinder