3

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

我有一个界面:

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

人员编辑视图:视图/人员/编辑如下:

@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>

员工编辑视图:视图/员工/编辑:

@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 的结果如下:

名字 [文本字段]

姓氏 [文本字段]

你在什么公司工作?[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>

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

更新 经过多次讨论这是否是拆箱,我还没有找到从(更基本的)具体类中获取数据注释的简单解决方案。使用视图(或助手)中的反射来获取具体类的数据注释确实会破坏简单性的目标。

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

4

3 回答 3

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 中可用

于 2015-10-15T12:57:17.603 回答
1

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

但是,我认为您可能需要重新设计它的工作方式(例如,调用RenderAction以将另一个视图包含到当前视图中是代码异味)。

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

您可能还想使用基类Person而不是接口,否则覆盖FirstNameand的数据属性会很棘手LastName

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);
    }
}
于 2012-10-29T21:25:19.170 回答
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 覆盖,看看我能在那里得到什么。我还发现了这个帖子:

从 ASP.NET MVC 中的 ModelMetadataProvider 获取包含对象实例

这与 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,现在这个约定将起作用。将接口中的属性和实际类型的组合发送到基本实现会很有趣,但我将把这个实验留到以后。

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

于 2013-11-02T09:21:56.517 回答