在 MVC3 中使用强类型视图时是否可以继承模型? [英] Model inheritance possible when using strongly-typed view in MVC3?

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

问题描述

我的模型中有以下设置:

命名空间 QuickTest.Models{公开课人{[必需的][Display(Name = "全名")]公共字符串全名 { 获取;放;}[显示(名称 =地址行 1")]公共虚拟字符串 Address1 { get;放;}}公共类发件人:人{[必需的]公共覆盖字符串 Address1 { get;放;}}公共类接收者:人{}}

在我看来:

@model QuickTest.Models.Person@{ViewBag.Title = "编辑";}<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()) {<字段集><legend>人物</legend><div class="editor-label">@Html.LabelFor(model => model.FullName)

<div class="editor-field">@Html.EditorFor(model => model.FullName)@Html.ValidationMessageFor(model => model.FullName)

<div class="editor-label">@Html.LabelFor(model => model.Address1)

<div class="editor-field">@Html.EditorFor(model => model.Address1)@Html.ValidationMessageFor(model =>model.Address1)

<div class="errors">@Html.ValidationSummary(true)

<p><input type="submit" value="保存"/></p></fieldset>}

客户端验证已启用.但是,如果我将 Sender 类型的对象发送到视图,客户端验证不会检测到 Address1 字段是必需的.在这种情况下,有什么方法可以使客户端验证工作吗?

附注:我发现如果我使用以下内容在视图中显示 Address1 字段,则客户端验证有效:

@Html.Editor("Address1", Model.Address1)@Html.ValidationMessageFor(model =>model.Address1)

解决方案

您可以自定义验证器和来自具体类的元数据,但该解决方案有几个活动部分,包括两个自定义元数据提供程序.

首先,创建一个自定义的Attribute来装饰基类的每个属性.这对于我们的自定义提供程序来说是必要的,以指示何时需要进一步分析.这是属性:

[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]公共类 BaseTypeAttribute : 属性 { }

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

公共类 MyModelMetadataProvider : DataAnnotationsModelMetadataProvider{受保护的覆盖 ModelMetadata CreateMetadata(IEnumerable<属性>属性,类型容器类型,Func<对象>模型访问器,类型模型类型,字符串属性名称){var attribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute))) as BaseTypeAttribute;如果(属性 != 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;varcreteType = vdi.Container.GetType();返回 base.CreateMetadata(attributes、concreteType、modelAccessor、modelType、propertyName);}别的{var container = containerField.GetValue(target);varcreteType = container.GetType();var propertyField = target.GetType().GetField("property");if (propertyField == null){具体类型 = base.GetMetadataForProperties(container, containerType).FirstOrDefault(p => p.PropertyName == "ConcreteType").Model as System.Type;如果(具体类型!= null)返回 base.GetMetadataForProperties(容器,具体类型).FirstOrDefault(pr => pr.PropertyName == propertyName);}返回 base.CreateMetadata(attributes、concreteType、modelAccessor、modelType、propertyName);}}返回 base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);}}

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

公共类 MyModelMetadataValidatorProvider : DataAnnotationsModelValidatorProvider{受保护的覆盖 IEnumerableGetValidators(ModelMetadata 元数据、ControllerContext 上下文、IEnumerable 属性){列表vals = base.GetValidators(metadata, context, attributes).ToList();var baseTypeAttribute = attributes.FirstOrDefault(a => a.GetType().Equals(typeof(BaseTypeAttribute)))作为 BaseTypeAttribute;if (baseTypeAttribute != null){//获取我们的父模型var parentMetaData = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,元数据.容器类型);//获取具体类型varcreteType = parentMetaData.FirstOrDefault(p => p.PropertyName == "ConcreteType").Model;如果(具体类型!= null){varcreteMetadata = ModelMetadataProviders.Current.GetMetadataForProperties(context.Controller.ViewData.Model,Type.GetType(concreteType.ToString()));varcretePropertyMetadata =creteMetadata.FirstOrDefault(p => p.PropertyName == metadata.PropertyName);vals = base.GetValidators(concretePropertyMetadata, context, attributes).ToList();}}返回 vals.AsEnumerable();}}

之后,在 Global.asax.cs 的 Application_Start 中注册两个自定义提供程序:

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

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

公共类人{公共类型 ConcreteType { 获取;放;}[必需的][Display(Name = "全名")][基本类型]公共字符串全名 { 获取;放;}[显示(名称 =地址行 1")][基本类型]公共虚拟字符串 Address1 { get;放;}}公共类发件人:人{公共发件人(){this.ConcreteType = typeof(Sender);}[必需的][显示(名称=地址第一行")]公共覆盖字符串 Address1 { get;放;}}公共类接收者:人{}

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

现在,即使您的视图使用基类,特定于任何具体继承类的属性也会出现在您的视图中,并且会影响模型的验证.

此外,您应该能够将 View 转换为 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天全站免登陆