1

我有以下工作系统并寻找使其干燥的方法:

public class EMailMetaData
{
  [Display(Prompt="myemail@mydomain.com"])
  public string Data;
}
public class PhoneMetaData
{
  [Display(Prompt="+1 (123) 456-7890"])
  public string Data;
}
public class AddressMetaData
{
  [Display(Prompt="Central st. W., St Francisco, USA"])
  public string Data;
}
// 7 more metadata templates

public class ContactVM
{
  [Required]
  public string DataLabel { get; set; }

  [Required(ErrorMessage="Please fill in the data field")]
  public string Data { get; set; }
}

[MetadataType(typeof(EmailMetaData))]
EmailVM : ContactVM
{
}
[MetadataType(typeof(PhoneMetaData))]
PhoneVM : ContactVM
{
}
[MetadataType(typeof(AddressMetaData))]
AddressVM : ContactVM
{
}
// 7 more contact view models

控制器显然是用正确的内容初始化它们,在视图中,我运行了 ContactVMs 的 foreach 循环,每个联系人都有 TemplateEditor:EmailVM.cshtml、PhoneVM.cshtml、AddressVM.cshtml UrlVM.cshtml 等。

主视图看起来(省略所有设置和细节,如下所示:

@model ContactsVM
foreach (var contact in Model.Contacts)
{
  @Html.EditorFor(m => contact)
}

在 EditorTemplates 下

@model EmailVM
@Html.EditorFor(model => model.DataLabel)
@Html.EditorFor(model => model.Data)
<br />
@Html.ValidationMessageFor(model => model.DataLabel)
@Html.ValidationMessageFor(model => model.Data)

...显然,我定义的每个视图模型都没有更多的编辑器模板。

所以简单来说 - 非常相似的联系人类型,在水印、命名、验证方面略有不同,但基本上所有字符串都具有相同的字段(地址是一个长字符串而不是结构,对所有这些都相同)。

我的问题不是针对水印的,它可以是任何属性——名称、描述、提示等。

[Display(Name="name", Description="description", Prompt="prompt")]

它几乎可以正常工作并显示正确的标签和水印,但这似乎是巨大的 DRY 违规,因为除了模型类型之外,所有模板编辑器都完全相同。我在这里展示的是简化以专注于手头的问题,主视图和编辑器模板比您在此处看到的要复杂得多,因此重复是巨大的。

你们中的任何人都可以提出更好的方法让它不重复这么多代码吗?

谢谢!

4

4 回答 4

2

(我正在添加另一个答案,因为这是解决问题的完全不同的方法)

视图模型和视图的结构都表明我们正在查看一些元元元数据,我开始讨厌这种模式,抱歉。

让我们首先采取一种简单的方法,找出我们的模型是什么。我想它是这样的:

public class ContactsVM
{
    [Required]
    public string EmailLabel {get;set;}

    [Display(Prompt="myemail@mydomain.com"])
    [Required(ErrorMessage="Please fill in the data field")]
    public string Email {get;set;}

    [Required]
    public string PhoneLabel {get;set;}

    [Display(Prompt="+1 (123) 456-7890"])
    [Required(ErrorMessage="Please fill in the data field")]
    public string Phone {get;set;}

    [Required]
    public string AddressLabel {get;set;}

    [Display(Prompt="Central st. W., St Francisco, USA"])
    [Required(ErrorMessage="Please fill in the data field")]
    public string Address {get;set;}

    // 7 more property pairs
}

通俗易懂,简单明了,通俗易懂。是的,它需要在视图中进行一些代码重复(意味着两个EditorFors 后跟ValidationMessage),但根据我的经验,这不是问题,因为在大多数情况下,有一天您将不得不为一个(并且只有一个)属性调整此代码。如果您不喜欢这样 - 现在,还有另一种解决方案:

@* let's assume that props is a string[] holding meaningful property names, e.g "Email", "Phone", "Address". You can even get it dynamically from reflection
@foreach (var property in props)
{
    @Html.Editor(property + "Label") @Html.Editor(property)
    <br />
    @Html.Validation(property + "Label") @Html.Validation(property)
}

动态模型更新

现在,如果我们在模型中有可变数量的数据项(当我每天使用它时我变得更加讨厌它),上述所有方法都将不起作用,所以现在我们也必须应对它。我们试图实现的实际上是与上面相同的视图代码,但现在我们的模型将不包含这些属性。所有的魔法都在于两件事。

  1. 我们将模型变成类似的东西Dictionary,包含指向字符串数据的字符串键。我们如何填充它并不重要。
  2. 我们提供了我们自己的ModelMetadataProvider,它将覆盖我们模型类的默认行为,如下所示:
    1. 给定模型实例,它将GetProperties通过枚举上述字典并ModelMetadata从这些字符串创建来响应
    2. 每个创建ModelMetadata的都应该包含对应于我们字典中的值的 ModelValue
    3. 的所有其他属性ModelMetadata都根据我们的意愿填充(我们不再需要 DataAnnotations,我们将在 provider 中注入所有值)

这需要大量的工作和一些尝试,但最终它会奏效,我会根据我的经验说。但我不会推荐这种整体设计方法,因为它需要……你已经看到了大量的工作,这只是一个开始。

于 2012-08-03T07:01:25.420 回答
1

(您的问题源于您的视图模型不是特定的,而是一种“针对所有问题的通用解决方案”。)

但是,仍然有一个非常简单的解决方案:为所有类型创建一个命名编辑器模板,具有ContactVM模型类型,并使用@Html.EditorFor(m => contact, "YourTemplateName").

于 2012-08-02T13:34:14.657 回答
1

回答我自己的问题:

这是一种生硬的解决方案,但比建议的解决方案要简单得多,最重要的是,与直接解决方案相比,它需要最少的重复(尽管考虑到我们处于面向对象的世界中,这并不太优雅):

  1. 定义一个带有基类(或接口,但是你定义它)的视图作为你的模型。
  2. 在视图中将模型变量转换为正确的类
  3. 在视图中,使用 cast 变量而不是模型

因此 Contact.cshtml 将如下所示:

@model ContactVM
@* Do tons of stuff that is the same between views (not depending on data annotation) *@
@Html.Partial("_ContactDataAnnotation", Model)
@* Continue doing lots of stuff that is the same between all those classes

调用 Contact.cshtml 编辑器模板将以下列方式进行(感谢 Serg):

@foreach (var c in Model.Contacts)
{
  @Html.EditorFor(m => c, "Contact")
}

专用于仅显示正确数据注释 _ContactDataAnnotation.cshtml 的局部视图将如下所示:

@using <My Model Namespace>
@model ContactVM
@switch (Model.GetType().Name)
{
  case "EmailVM":
    EmailVM e = Model as EmailVM;
    @Html.EditorFor(model => e.DataLabel)
    @Html.EditorFor(model => e.Data)
    <br />
    @Html.ValidationMessageFor(model => e.DataLabel)
    @Html.ValidationMessageFor(model => e.Data)
    break;
  case "PhoneVM":
    PhoneVM p = Model as PhoneVM;
    @Html.EditorFor(model => p.DataLabel)
    @Html.EditorFor(model => p.Data)
    <br />
    @Html.ValidationMessageFor(model => p.DataLabel)
    @Html.ValidationMessageFor(model => p.Data)
    break;
  // same thing 7 more times for all children of ContactVM, since MVC does not applying polymorphism to data annotations UNFORTUNATELY
  default:
    @Html.EditorFor(model => model.DataLabel)
    @Html.EditorFor(model => model.Data)
    <br />
    @Html.ValidationMessageFor(model => model.DataLabel)
    @Html.ValidationMessageFor(model => model.Data)
  }

这样,重复将只存在于需要的地方,而不是用不同的名称和模型类重复相同的编辑器模板 10 次。

我知道这与“最佳实践”问题的标题相矛盾,但不幸的是,这是最简单、最简约的方法。正如 Serg 所指出的,其他解决方案过于复杂,需要深度 MVC 基础设施干预,我不喜欢在这方面花费时间,以及注入提示和工具提示,而不是在数据注释中定义,这似乎是一个标准装饰和验证模型的方式。

我认为我的解决方案是一种解决 MVC 限制的方法,即缺乏数据注释的多态性。

于 2012-08-03T14:10:48.397 回答
0

您应该能够创建接口,例如

public interface IContactData
{
string Data{get; set;};
.
.
.
}

在您的类中实现该接口

public class EMailMetaData : IContactData
{
  [Display(Prompt="myemail@mydomain.com"])
  public string Data{get; set;};
...
}
public class PhoneMetaData  : IContactData
{
  [Display(Prompt="+1 (123) 456-7890"])
  public string Data{get; set;};
....
}

并在您的编辑器模板中(一应俱全)使用

@model IContactData
于 2012-08-02T15:50:38.200 回答