自定义模型Binder继承自DefaultModelBinder

时间:2013-05-25 07:09:37

标签: c# asp.net-mvc-4 model-binding

我正在尝试为MVC 4构建一个继承自DefaultModelBinder的自定义模型绑定器。我希望它拦截任何绑定级别的任何接口,并尝试从名为AssemblyQualifiedName的隐藏字段加载所需类型。

这是我到目前为止(简化):

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

示例Create.cshtml文件(简化):

@model Models.ScheduledJob

@* Begin Form *@
@Html.Hidden("AssemblyQualifiedName", Model.Job.GetType().AssemblyQualifiedName)

@Html.Partial("_JobParameters")
@* End Form *@

以上部分_JobParameters.cshtml查看Model.Job的属性并构建编辑控件,类似于@Html.EditorFor(),但带有一些额外的标记。 ScheduledJob.Job属性的类型为IJob(接口)。

ScheduledJobsController.cs示例(简化):

[HttpPost]
public ActionResult Create(ScheduledJob scheduledJob)
{
    //scheduledJob.Job here is not null, but has only default values
}

当我保存表单时,它会正确解释对象类型并获取一个新实例,但是对象的属性没有设置为适当的值。

我还需要做些什么来告诉默认绑定器接管指定类型的属性绑定?

2 个答案:

答案 0 :(得分:23)

This article告诉我,我的模型绑定器过于复杂。以下代码有效:

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

答案 1 :(得分:1)

使用MVC 4可以轻松覆盖消息,如果这是您在自定义模型绑定器中可能需要的全部内容:

    protected void Application_Start(object sender, EventArgs e)
    {
        //set mvc default messages, or language specifc
        ClientDataTypeModelValidatorProvider.ResourceClassKey = "ValidationMessages";
        DefaultModelBinder.ResourceClassKey = "ValidationMessages";
    }

然后使用以下条目创建名为ValidationMessages的资源文件:

NAME: FieldMustBeDate 
VALUE: The field {0} must be a date. 
NAME: FieldMustBeNumeric 
VALUE: The field {0} must be a number

我们这样做是因为合规性失败了。我们的安全扫描不喜欢javascript注入将返回并出现在验证消息中并执行。通过使用此实现,我们将覆盖返回用户提供的值的默认消息。