这个问题的灵感来自于我与 ASP.NET MVC 的斗争,但我认为它也适用于其他情况。
假设我有一个 ORM 生成的模型和两个 ViewModel(一个用于“详细信息” View ,一个用于“编辑” View ):
型号
public class FooModel // ORM generated
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public int Age { get; set; }
public int CategoryId { get; set; }
}
显示 View 模型
public class FooDisplayViewModel // use for "details" view
{
[DisplayName("ID Number")]
public int Id { get; set; }
[DisplayName("First Name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
[DisplayName("Email Address")]
[DataType("EmailAddress")]
public string EmailAddress { get; set; }
public int Age { get; set; }
[DisplayName("Category")]
public string CategoryName { get; set; }
}
编辑 View 模型
public class FooEditViewModel // use for "edit" view
{
[DisplayName("First Name")] // not DRY
public string FirstName { get; set; }
[DisplayName("Last Name")] // not DRY
public string LastName { get; set; }
[DisplayName("Email Address")] // not DRY
[DataType("EmailAddress")] // not DRY
public string EmailAddress { get; set; }
public int Age { get; set; }
[DisplayName("Category")] // not DRY
public SelectList Categories { get; set; }
}
请注意,ViewModel 上的属性不是 DRY——很多信息是重复的。现在想象一下这个场景乘以 10 或 100,您会发现它很快就会变得非常乏味并且容易出错,以确保跨 ViewModel(因此跨 View )的一致性。
我怎样才能“干掉”这段代码?
在您回答“只需将所有属性放在 FooModel
上”之前,我已经尝试过了,但它没有用,因为我需要让我的 ViewModel 保持“平坦”。换句话说,我不能只将每个 ViewModel 与一个 Model 组合起来——我需要我的 ViewModel 只具有 View 应该使用的属性(和属性),并且 View 不能深入到子属性中获得值(value)。
更新
LukLed 的回答建议使用继承。这肯定会减少非 DRY 代码的数量,但不会消除它。请注意,在我上面的示例中,Category
属性的 DisplayName
属性需要写入两次,因为显示和编辑 ViewModel 的属性数据类型不同.这在小规模上不会是什么大问题,但随着项目规模和复杂性的扩大(想象更多的属性,每个属性更多的属性,每个模型更多的 View ),仍然有可能“重复自己”相当多。或许我在这里使用 DRY 太过分了,但我仍然宁愿只输入一次我所有的“友好名称”、数据类型、验证规则等。
最佳答案
我假设您这样做是为了利用 HtmlHelpers EditorFor 和 DisplayFor,并且不希望在整个应用程序中隆重声明同一事物 4000 次。
干这件事的最简单方法是实现您自己的 ModelMetadataProvider。 ModelMetadataProvider 正在读取这些属性并将它们呈现给模板助手。 MVC2 已经提供了一个 DataAnnotationsModelMetadataProvider 实现来让事情继续进行,所以继承它让事情变得非常简单。
这里是一个简单的示例,它将驼峰属性名称拆分为空格,FirstName => First Name :
public class ConventionModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
HumanizePropertyNamesAsDisplayName(metadata);
if (metadata.DisplayName.ToUpper() == "ID")
metadata.DisplayName = "Id Number";
return metadata;
}
private void HumanizePropertyNamesAsDisplayName(ModelMetadata metadata)
{
metadata.DisplayName = HumanizeCamel((metadata.DisplayName ?? metadata.PropertyName));
}
public static string HumanizeCamel(string camelCasedString)
{
if (camelCasedString == null)
return "";
StringBuilder sb = new StringBuilder();
char last = char.MinValue;
foreach (char c in camelCasedString)
{
if (char.IsLower(last) && char.IsUpper(c))
{
sb.Append(' ');
}
sb.Append(c);
last = c;
}
return sb.ToString();
}
}
然后您所要做的就是注册它,就像在 Global.asax 的 Application Start 中添加您自己的自定义 ViewEngine 或 ControllerFactory 一样:
ModelMetadataProviders.Current = new ConventionModelMetadataProvider();
现在只是为了向您展示我没有作弊,这是我用来获得相同 HtmlHelper 的 View 模型。*。作为装饰 ViewModel 的体验:
public class FooDisplayViewModel // use for "details" view
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[DataType("EmailAddress")]
public string EmailAddress { get; set; }
public int Age { get; set; }
[DisplayName("Category")]
public string CategoryName { get; set; }
}
关于c# - 如何在模型和 View 模型中使用 "DRY up"C# 属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2269144/