使用MVC3强类型的视图模式时可能继承? [英] Model inheritance possible when using strongly-typed view in MVC3?

查看:125
本文介绍了使用MVC3强类型的视图模式时可能继承?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下安装在我的模型:

 命名空间QuickTest.Models
{
    公共类Person
    {
        [需要]
        [显示(NAME =全名)]
        公共字符串全名{获得;组; }        [显示(NAME =地址1号线)]
        公共虚拟字符串的地址1 {搞定;组; }
    }
    公共类发件人:人
    {
        [需要]
        公众覆盖字符串地址1 {搞定;组; }
    }
    公共类接收机:人
    {
    }
}

在我看来:

  @model QuickTest.Models.Person
@ {
    ViewBag.Title =编辑;
}
<脚本的src =@ Url.Content(〜/脚本/ jquery.validate.min.js)TYPE =文/ JavaScript的>< / SCRIPT>
<脚本的src =@ Url.Content(〜/脚本/ jquery.validate.unobtrusive.min.js)TYPE =文/ JavaScript的>< / SCRIPT>@using(Html.BeginForm()){
    <&字段集GT;
        <传奇>与人LT; /传说>
        < D​​IV CLASS =编辑标记>
            @ Html.LabelFor(型号=> model.FullName)
        < / DIV>
        < D​​IV CLASS =主编场>
            @ Html.EditorFor(型号=> model.FullName)
            @ Html.ValidationMessageFor(型号=> model.FullName)
        < / DIV>
        < D​​IV CLASS =编辑标记>
            @ Html.LabelFor(型号=> model.Address1)
        < / DIV>
        < D​​IV CLASS =主编场>
            @ Html.EditorFor(型号=> model.Address1)
            @ Html.ValidationMessageFor(型号=> model.Address1)
        < / DIV>        < D​​IV CLASS =错误>
            @ Html.ValidationSummary(真)
        < / DIV>
        &所述p为H.;
            <输入类型=提交值=保存/>
        &所述; / P>
    < /字段集>
}

启用客户端验证。不过,如果我送类型发件人的目的来看,客户端验证未检测到地址1场是必需的。有没有使客户端验证工作,在这种情况下任何方式?

PS:
我发现,如果我使用以下方法来在视图中显示的地址1场客户端验证工作:

 < D​​IV CLASS =主编场>
    @ Html.Editor(地址1,Model.Address1)
    @ Html.ValidationMessageFor(型号=> model.Address1)
< / DIV>


解决方案

您可以自定义验证器和来自您的具体类的元数据,但解决的办法有几个可移动部件,包括两个自定义元数据提供者。

首先,创建一个自定义的属性来装点基类的每个属性。这是必要的,因为一个标记为我们的自定义提供,还需要进一步分析时,以指示。这是属性:

  [AttributeUsage(AttributeTargets.All,继承= TRUE,的AllowMultiple = TRUE)]
公共类BaseTypeAttribute:属性{}

接下来,创建一个自定义的 ModelMetadataProvider 继承 DataAnnotationsModelMetadataProvider

 公共类MyModelMetadataProvider:DataAnnotationsModelMetadataProvider
{
    保护覆盖ModelMetadata CreateMetadata(
        IEnumerable的<属性与GT;属性,
        键入containerType,
        FUNC<对象> modelAccessor,
        键入modelType,
        字符串propertyName的)
    {
        var属性= attributes.FirstOrDefault(A => a.GetType()等于(typeof运算(BaseTypeAttribute)))作为BaseTypeAttribute;
        如果(属性= NULL&放大器;!&安培;!modelAccessor ​​= NULL)
        {
            VAR的目标= modelAccessor.Target;
            VAR containerField = target.GetType()getfield命令(容器)。
            如果(containerField == NULL)
            {
                VAR VDI = target.GetType()getfield命令(VDI)的GetValue(目标)作为ViewDataInfo。;
                变种concreteType = vdi.Container.GetType();
                返回base.CreateMetadata(属性,concreteType,modelAccessor,modelType,propertyName的);
            }
            其他
            {
                VAR容器= containerField.GetValue(目标);
                变种concreteType = container.GetType();
                VAR propertyField = target.GetType()getfield命令(财产)。
                如果(propertyField == NULL)
                {
                    concreteType = base.GetMetadataForProperties(集装箱,containerType)
                        .FirstOrDefault(P => p.PropertyName ==ConcreteType)型号为System.Type的。
                    如果(concreteType!= NULL)
                        返回base.GetMetadataForProperties(集装箱,concreteType)
                            .FirstOrDefault(PR => pr.PropertyName == propertyName的);
                }
                返回base.CreateMetadata(属性,concreteType,modelAccessor,modelType,propertyName的);
            }
        }
        返回base.CreateMetadata(属性,containerType,modelAccessor,modelType,propertyName的);
    }
}

然后,创建一个自定义的 ModelValidatorProvider 继承 DataAnnotationsModelValidatorProvider

 公共类MyModelMetadataValidatorProvider:DataAnnotationsModelValidatorProvider
{
    保护覆盖的IEnumerable< ModelValidator> GetValidators(ModelMetadata元,ControllerContext背景下,IEnumerable的<属性与GT;属性)
    {
        清单< ModelValidator>瓦尔斯= base.GetValidators(元数据,内容,属性).ToList();        VAR baseTypeAttribute = attributes.FirstOrDefault(A => a.GetType()等于(typeof运算(BaseTypeAttribute))。)
            作为BaseTypeAttribute;        如果(baseTypeAttribute!= NULL)
        {
            //得到我们的父模型
            VAR parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
                metadata.ContainerType);            //获取具体类型
            VAR concreteType = parentMetaData.FirstOrDefault(P => p.PropertyName ==ConcreteType)模式。
            如果(concreteType!= NULL)
            {
                VAR concreteMetadata = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
                    Type.GetType(concreteType.ToString()));                VAR concretePropertyMetadata = concreteMetadata.FirstOrDefault(P => p.PropertyName == metadata.PropertyName);                瓦尔斯= base.GetValidators(concretePropertyMetadata,背景,属性).ToList();
            }
        }
        返回vals.AsEnumerable();
    }
}

后,在Global.asax.cs中登记在的Application_Start 这两个自定义提供:

  ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(新MvcApplication8.Controllers.MyModelMetadataValidatorProvider());
ModelMetadataProviders.Current =新Mv​​cApplication8.Controllers.MyModelMetadataProvider();

现在,改变你的模型像这样:

 公共类Person
{
    公共类型ConcreteType {搞定;组; }    [需要]
    [显示(NAME =全名)]
    [BASETYPE]
    公共字符串全名{获得;组; }    [显示(NAME =地址1号线)]
    [BASETYPE]
    公共虚拟字符串的地址1 {搞定;组; }
}公共类发件人:人
{
    公共发件人()
    {
        this.ConcreteType = typeof运算(发件人);
    }    [需要]
    [显示(NAME =地址一号线)]
    公众覆盖字符串地址1 {搞定;组; }
}公共类接收机:人
{
}

请注意,该基类有一个新的属性, ConcreteType 。这将用来指示哪个继承类实例化此基类。每当继承类具有覆盖元数据在基类中的元数据,继承类的构造函数应该设置的基类ConcreteType属性。

现在,即使您的视图使用的基类,具体到任何具体的继承类的属性将出现在您的视图,并会影响模型的验证。

另外,你应该能够打开查看成Person类型模板,使用基类或从继承使用该模板任何实例。

I have the following setup in my model:

namespace QuickTest.Models
{
    public class Person
    {
        [Required]
        [Display(Name = "Full name")]
        public string FullName { get; set; }

        [Display(Name = "Address Line 1")]
        public virtual string Address1 { get; set; }
    }
    public class Sender : Person
    {
        [Required]
        public override string Address1 { get; set; }
    }
    public class Receiver : Person
    {
    }
}

and in my view:

@model QuickTest.Models.Person
@{
    ViewBag.Title = "Edit";
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    <fieldset>
        <legend>Person</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.FullName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FullName)
            @Html.ValidationMessageFor(model => model.FullName)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Address1)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address1)
            @Html.ValidationMessageFor(model => model.Address1)
        </div>

        <div class="errors">
            @Html.ValidationSummary(true)
        </div>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

Client-side validation is enabled. However, if I send an object of type Sender to the View, client-side validation does not detect that the Address1 field is required. Is there any way of making the client validation work in this scenario?

PS: I discovered that client validation works if I use the following to display the Address1 field in the view:

<div class="editor-field">
    @Html.Editor("Address1", Model.Address1)
    @Html.ValidationMessageFor(model => model.Address1)
</div>

解决方案

You can customize the validators and the metadata to come from your concrete class, but the solution has several moving parts, including two custom metadata providers.

First, create a custom Attribute to decorate each property of the base class. This is necessary as a flag for our custom providers, to indicate when further analysis is needed. This is the attribute:

[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class BaseTypeAttribute : Attribute { }

Next, create a custom ModelMetadataProvider inheriting from DataAnnotationsModelMetadataProvider:

public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes,
        Type containerType,
        Func<object> modelAccessor,
        Type modelType,
        string propertyName)
    {
        var attribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) as BaseTypeAttribute;
        if (attribute != null && modelAccessor != null)
        {
            var target = modelAccessor.Target;
            var containerField = target.GetType().GetField("container");
            if (containerField == null)
            {
                var vdi = target.GetType().GetField("vdi").GetValue(target) as ViewDataInfo;
                var concreteType = vdi.Container.GetType();
                return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName);
            }
            else
            {
                var container = containerField.GetValue(target);
                var concreteType = container.GetType();
                var propertyField = target.GetType().GetField("property");
                if (propertyField == null)
                {
                    concreteType = base.GetMetadataForProperties(container, containerType)
                        .FirstOrDefault(p => p.PropertyName == "ConcreteType").Model as System.Type;
                    if (concreteType != null)
                        return base.GetMetadataForProperties(container, concreteType)
                            .FirstOrDefault(pr => pr.PropertyName == propertyName);
                }
                return base.CreateMetadata(attributes, concreteType, modelAccessor, modelType, propertyName);
            }
        }
        return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
    }
}

Then, create a custom ModelValidatorProvider inheriting from DataAnnotationsModelValidatorProvider:

public class MyModelMetadataValidatorProvider : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
        List<ModelValidator> vals = base.GetValidators(metadata, context, attributes).ToList();

        var baseTypeAttribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) 
            as BaseTypeAttribute;

        if (baseTypeAttribute != null)
        {
            // get our parent model
            var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
                metadata.ContainerType);

            // get the concrete type
            var concreteType = parentMetaData.FirstOrDefault(p => p.PropertyName == "ConcreteType").Model;
            if (concreteType != null)
            {
                var concreteMetadata = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,
                    Type.GetType(concreteType.ToString()));

                var concretePropertyMetadata = concreteMetadata.FirstOrDefault(p => p.PropertyName == metadata.PropertyName);

                vals = base.GetValidators(concretePropertyMetadata, context, attributes).ToList();
            }
        }
        return vals.AsEnumerable();
    }
}

After that, register both custom providers in Application_Start in Global.asax.cs:

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MvcApplication8.Controllers.MyModelMetadataValidatorProvider());
ModelMetadataProviders.Current = new MvcApplication8.Controllers.MyModelMetadataProvider();

Now, change your models like so:

public class Person
{
    public Type ConcreteType { get; set; }

    [Required]
    [Display(Name = "Full name")]
    [BaseType]
    public string FullName { get; set; }

    [Display(Name = "Address Line 1")]
    [BaseType]
    public virtual string Address1 { get; set; }
}

public class Sender : Person
{
    public Sender()
    {
        this.ConcreteType = typeof(Sender);
    }

    [Required]
    [Display(Name = "Address Line One")]
    public override string Address1 { get; set; }
}

public class Receiver : Person
{
}

Note that the base class has a new property, ConcreteType. This will be used to indicate which inheriting class has instantiated this base class. Whenever an inheriting class has metadata which overrides the metadata in the base class, the inheriting class' constructor should set the base class ConcreteType property.

Now, even though your view uses the base class, the attributes specific to any concrete inheriting class will appear in your view, and will affect the validation of the model.

In addition, you should be able to turn the View into a template for the Person type, and use the template for any instance using the base class or inheriting from it.

这篇关于使用MVC3强类型的视图模式时可能继承?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆