mvc3从接口反射具体类的数据注释

时间:2012-10-26 20:02:19

标签: c# asp.net-mvc-3 data-annotations unboxing asp.net-mvc-views

我试图从各种类型的视图中抽象出我的视图模型。整个事情编译没有问题,但我遇到了“反映”(正式称为拆箱)数据注释的问题。

我有一个界面:

public interface IPerson
{
    string FirstName { get;set;}
    string LastName {get;set;}
}

我有两个实现接口的类:

public class Employee : IPerson
{
    [Required]
    [Display(Description = "Employee First Name", Name = "Employee First Name")]
    public string FirstName {get;set;}

    [Required]
    [Display(Description = "Employee Last Name", Name = "Employee Last Name")]
    public string LastName {get;set;}

    public int NumberOfYearsWithCompany {get;set;}
}

public class Client : IPerson
{
    [Required]
    [Display(Description = "Your first Name", Name = "Your first Name")]
    public string FirstName {get;set;}

    [Display(Description = "Your last Name", Name = "Your last Name")]
    public string LastName {get;set;}

    [Display(Description = "Company Name", Name = "What company do you work for?")]
    public string CompanyName {get;set;}
}

人物编辑视图:views / Person / Edit as:

@model IPerson

<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => model.FirstName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FirstName)
        @Html.ValidationMessageFor(model => model.FirstName)
    </div>
</div>
<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => model.LastName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(model => model.LastName)
        @Html.ValidationMessageFor(model => model.LastName)
    </div>
</div>

员工编辑视图:views / Employee / Edit:

@model Employee

Html.RenderAction("Edit", "Person", new { person = Model });

<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => model.CompanyName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(model => model.CompanyName)
        @Html.ValidationMessageFor(model => model.CompanyName)
    </div>
</div>    

PersonController是:

public ActionResult Edit(IPerson person)
{
    return PartialView(person);
}

所有内容都会编译并呈现正常。但是,数据注释正在丢失。

所以,Employee / Edit就像:

  

FirstName [textfield]

     

LastName [textfield]

     

你为哪家公司工作? [textfield]公司名称是必填字段

有没有为具体类拆箱那些数据注释?

旁注

我尝试将IPerson显式地转换为Employee:

@model IPerson
@{
    var employee = (Employee)Model;
}
<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => employee.FirstName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => employee.FirstName)
        @Html.ValidationMessageFor(model => employee.FirstName)
    </div>
</div>
<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => employee.LastName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(model => employee.LastName)
        @Html.ValidationMessageFor(model => employee.LastName)
    </div>
</div>

这样做需要第一个名字,但没有从标签中获取显示属性。

更新 在讨论了这是否取消装箱之后,我还没有找到一个从(更基本的)具体类中获取数据注释的简单解决方案。在视图(或帮助器)中使用反射来获取具体类的数据注释,这实际上会破坏简单性的目标。

我们有一些基本相同的视图,但是必需的字段和显示名称略有不同。如果我可以将视图模型传递到接口视图并且它将找出所需的字段和显示属性,那将是非常方便的。如果有人想出办法做到这一点,我将不胜感激。

3 个答案:

答案 0 :(得分:2)

我遇到了TextBoxFor助手无法生成正确的标记验证的问题。

我能够解决问题的方法是使用TextBox助手而不是TextBoxFor助手。

这是为我工作的部分片段

    @model Interfaces.Models.EntryPage.ICustomerRegisterVM

    <p>
        @Html.ValidationMessageFor(model => model.Department)
        @Html.TextBox(Html.NameFor(model => model.Department).ToString(), Model.Department)
    </p>

如您所见,我使用Html.NameFor帮助器从表达式生成正确的名称,然后传入属性。使用这种方法,MVC能够为具体类生成正确的不显眼的验证标记,该标记实现了作为视图模型引用的接口。

我没有为LabelFor或其他助手尝试过这种方法。但我希望结果是一样的。

请注意Html.NameFor帮助程序在MVC5中可用

答案 1 :(得分:1)

您指定的模型(IPerson)在您调用PersonController.Edit操作时没有数据属性。默认元数据提供程序将仅选取在指定类型(在本例中为IPerson)或继承的类型上显式定义的数据属性。您可以共享元数据类或接口,也可以将数据注释属性复制到接口。

但是,我想您可能想重新设计一下它的工作原理(例如,调用RenderAction将另一个视图包含到当前视图中是一种代码味道。)

我会为Person创建一个局部视图。然后,您可以为每种类型的人(客户端等)创建局部视图。然后,您可以添加任何其他标记,并使用Person添加@Html.Partial("Person", Model)视图。

您可能还希望使用基类Person而不是接口,否则会覆盖FirstNameLastName的数据属性。

public abstract class Person
{
    public virtual string FirstName {get;set;}

    public virtual string LastName {get;set;}
}

public class Employee : Person
{
    [Required]
    [Display(Description = "Employee First Name", Name = "Employee First Name")]
    public override string FirstName {get;set;}

    [Required]
    [Display(Description = "Employee Last Name", Name = "Employee Last Name")]
    public override string LastName {get;set;}

    public int NumberOfYearsWithCompany {get;set;}
}

public class Client : Person
{
    [Required]
    [Display(Description = "Your first Name", Name = "Your first Name")]
    public override string FirstName {get;set;}

    [Display(Description = "Your last Name", Name = "Your last Name")]
    public override string LastName {get;set;}

    [Display(Description = "Company Name", Name = "What company do you work for?")]
    public string CompanyName {get;set;}
}

查看/共享/ Person.cshtml

@model Person

<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => model.FirstName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FirstName)
        @Html.ValidationMessageFor(model => model.FirstName)
    </div>
</div>
<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => model.LastName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(model => model.LastName)
        @Html.ValidationMessageFor(model => model.LastName)
    </div>
</div>

查看/雇员/ Edit.cshtml

@model Employee

@Html.Partial("Person", Model);

<div class="clear paddingbottomxxsm">
    <div class="editor-label">
        @Html.LabelFor(model => model.CompanyName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(model => model.CompanyName)
        @Html.ValidationMessageFor(model => model.CompanyName)
    </div>
</div>

控制器/ EmployeesController.cs

public class EmployeesController : Controller
{
    public ActionResult Edit(int id)
    {
         var model = GetEmployee(id); // replace with your actual data access logic

         return View(model);
    }
}

答案 2 :(得分:1)

我发现这篇文章在完全相同的问题上挣扎。

(旁注:我已经在此示例后面实现了自己的DataAnnotationsModelMetadataProvider,以便能够访问我的编辑器模板中的自定义属性: http://weblogs.asp.net/seanmcalinden/archive/2010/06/11/custom-asp-net-mvc-2-modelmetadataprovider-for-using-custom-view-model-attributes.aspx。如果您想为此问题使用自己的DataAnnotationsModelMetadataProvider,请确保不要错过应用程序启动步骤。

所以在几乎放弃让它工作之后我决定调试我的CreateMetadata覆盖以查看我能抓住那里的东西。我也找到了这篇文章:

Obtain containing object instance from ModelMetadataProvider in ASP.NET MVC

结合DataAnnotationsModelMetadataProvider类的一些反映,我将引出以下解决方案:

public class MyModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes,
        Type containerType,
        Func<object> modelAccessor,
        Type modelType,
        string propertyName)
    {
        //If containerType is an interface, get the actual type and the attributes of the current property on that type.
        if (containerType != null && containerType.IsInterface)
        {
            object target = modelAccessor.Target;
            object container = target.GetType().GetField("container").GetValue(target);
            containerType = container.GetType();
            var propertyDescriptor = this.GetTypeDescriptor(containerType).GetProperties()[propertyName];
            attributes = this.FilterAttributes(containerType, propertyDescriptor, Enumerable.Cast<Attribute>((IEnumerable)propertyDescriptor.Attributes));
        }

        var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

        //This single line is for the "sidenote" in my text above, remove if you don't use this:
        attributes.OfType<MetadataAttribute>().ToList().ForEach(x => x.Process(modelMetadata));

        return modelMetadata;
    }
}

所以现在我可以拥有一个具有接口类型作为模型的EditorTemplate,然后使用它的不同实现,以便能够通过数据注释获得不同的字段名称和验证规则。我正在使用它来获取一个带有三个不同地址的表单;家庭住址,工作地址和发票地址。这些输入组的用户界面完全相同,但验证规则不同。

这当然是一种基于约定的解决方案,它说当编辑器模板模型是一个接口时,这种行为应该始终适用。如果你有现有的编辑器模板,其中模型是一个自己有数据注释的接口类型,这个解决方案当然会破坏它。对于我的情况,我们刚刚开始使用MVC,现在这个约定将起作用。将接口和实际类型的属性组合发送到基础实现会很有趣,但我会保存该实验以供日后使用。

如果您是一位了解此解决方案存在严重缺陷的读者,请告诉我。