如何在模型和ViewModel中"干掉"C#属性?

dev*_*xer 14 c# asp.net-mvc attributes dry

这个问题的灵感来自我与ASP.NET MVC的斗争,但我认为它也适用于其他情况.

假设我有一个ORM生成的模型和两个ViewModel(一个用于"详细信息"视图,一个用于"编辑"视图):

模型

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; }
}
Run Code Online (Sandbox Code Playgroud)

显示ViewModel

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; }
}
Run Code Online (Sandbox Code Playgroud)

编辑ViewModel

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; }
}
Run Code Online (Sandbox Code Playgroud)

请注意,ViewModel上的属性不是DRY - 重复了很多信息.现在假设这个场景乘以10或100,你可以看到它很快变得非常乏味且容易出错,以确保ViewModel之间的一致性(因此也可以跨视图).

我怎么能"干掉"这段代码?

在你回答之前,"只是把所有的属性都放在上面FooModel,"我试过了,但它没有用,因为我需要保持我的ViewModel"平坦".换句话说,我不能只用模型组合每个ViewModel - 我需要我的ViewModel只有View应该使用的属性(和属性),而View不能挖掘子属性得到价值观.

更新

LukLed的回答建议使用继承.这肯定减少了非DRY代码的数量,但它并没有消除它.请注意,在上面的示例中,DisplayName属性的Category属性需要写入两次,因为属性的数据类型在显示和编辑ViewModel之间是不同的.这在小规模上不是什么大问题,但随着项目的规模和复杂性的扩大(想象更多的属性,每个属性更多的属性,每个模型更多的视图),仍然有可能"重复自己"相当多.也许我在这里干得太远了,但我仍然宁愿让我所有的"友好名字",数据类型,验证规则等只输入一次.

Luk*_*Led 7

声明BaseModel,继承并添加其他属性:

public class BaseFooViewModel
{
    [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 class FooDisplayViewModel : BaseFooViewModel
{
    [DisplayName("ID Number")]
    public int Id { get; set; }
}

public class FooEditViewModel : BaseFooViewModel
Run Code Online (Sandbox Code Playgroud)

编辑

关于类别.不应该编辑视图模型public string CategoryName { get; set; }public List<string> Categories { get; set; }不是SelectList?这样你就可以放在public string CategoryName { get; set; }基类中并保持DRY.编辑视图通过添加来增强类List<string>.


Joh*_*ell 7

我假设你这样做是为了利用HtmlHelpers EditorFor和DisplayFor,并且不希望在整个应用程序中隆重地宣布同样的事情4000次.

干掉它的最简单方法是实现自己的ModelMetadataProvider.ModelMetadataProvider正在读取这些属性并将它们呈现给模板助手.MVC2已经提供了一个DataAnnotationsModelMetadataProvider实现来实现这一目标,因此继承它会让事情变得非常简单.

为了帮助您入门,这是一个将camelcased属性名称拆分为空格的简单示例,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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你需要做的就是注册它就像在Global.asax的Application Start中添加你自己的自定义ViewEngine或ControllerFactory:

ModelMetadataProviders.Current = new ConventionModelMetadataProvider();
Run Code Online (Sandbox Code Playgroud)

现在只是为了告诉你我不是在欺骗这是我用来获得相同HtmlHelper的视图模型.*.作为装饰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; }
    }
Run Code Online (Sandbox Code Playgroud)