我有以下工作系统并正在寻找使其干燥的方法:
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
Controller显然是使用正确的内容初始化它们,并且在视图中我运行了每个联系人具有TemplateEditor的ContactVMs的foreach循环: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)
...显然我已定义的每个视图模型都有更少的编辑器模板。
所以简单来说 - 非常相似的联系类型在水印,命名,验证方面存在细微差别,但基本上所有字符串都具有相同的字段(地址是一个长字符串而不是结构,所有字符都相同)。 / p>
我的问题不是特定于水印,它可以是任何属性 - 名称,描述,提示等。
[Display(Name="name", Description="description", Prompt="prompt")]
它完全正常工作并显示正确的标签,每个水印,但它似乎是巨大的DRY违规,因为除模型类型外,所有模板编辑器都完全相同。我在这里展示的是简化以集中精力处理手头的问题,主视图和编辑器模板比你在这里看到的要复杂得多,所以重复是巨大的。
你们中的任何人都可以提出更好的方法吗?不要复制那么多代码吗?
谢谢!
答案 0 :(得分: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
}
简单,简单,直接,易于理解。是的,它需要视图中的一些代码重复(意味着两个EditorFor
s后跟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)
}
动态模型更新
现在,如果我们在模型中有可变数量的数据项(我现在每天都在讨论这个问题时会更加讨厌),上面的所有内容都不会起作用,所以现在我们必须应对太。我们试图实现的是与上面相同的视图代码,但现在我们的模型将不包含这些属性。所有的魔力都在于两件事。
Dictionary
- 类似,包含指向字符串数据的字符串键。我们如何填充它并不重要。ModelMetadataProvider
,它将覆盖我们的模型类的默认行为,具体如下:
GetProperties
来响应ModelMetadata
ModelMetadata
都应包含与我们的字典中的值相对应的ModelValue ModelMetadata
的所有其他属性都是根据我们的意愿填充的(我们不再需要DataAnnotations,我们将在提供者中注入所有值)这需要大量的工作和一些尝试,但最终它有效,我会从我的经验说。但我不推荐这种整体设计方法,因为它需要......你已经看到了大量的工作,而这只是一个开始。
答案 1 :(得分:1)
(您的问题源于您的视图模型不是特定的,而是一种“针对每个问题的一种通用解决方案”。)
但是,仍然有一个非常简单的解决方案:为所有类型创建一个名为的编辑器模板,将ContactVM
作为模型类型,并使用@Html.EditorFor(m => contact, "YourTemplateName")
。
答案 2 :(得分:1)
回答我自己的问题:
它是直接的解决方案,但比建议的更简单,最重要的是与简单的解决方案相比需要最少的重复(尽管考虑到我们处于面向对象的世界并不太优雅):
所以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限制缺乏数据注释的多态性。
答案 3 :(得分: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