如何“干"模型和视图模型中的 C# 属性?

How to quot;DRY upquot; C# attributes in Models and ViewModels?(如何“干模型和视图模型中的 C# 属性?)

本文介绍了如何“干"模型和视图模型中的 C# 属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

This question was inspired by my struggles with ASP.NET MVC, but I think it applies to other situations as well.

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

Let's say I have an ORM-generated Model and two ViewModels (one for a "details" view and one for an "edit" 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; }
}

显示视图模型

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

编辑视图模型

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

请注意,ViewModels 上的属性不是 DRY——很多信息是重复的.现在想象一下这个场景乘以 10 或 100,您会发现它很快就会变得非常乏味且容易出错,以确保 ViewModel 之间(以及 View 之间)的一致性.

Note that the attributes on the ViewModels are not DRY--a lot of information is repeated. Now imagine this scenario multiplied by 10 or 100, and you can see that it can quickly become quite tedious and error prone to ensure consistency across ViewModels (and therefore across Views).

我怎样才能干掉"这段代码?

在您回答只需将所有属性放在 FooModel 上"之前,我已经尝试过了,但它没有奏效,因为我需要保持我的 ViewModel平坦".换句话说,我不能只用一个模型来组合每个 ViewModel——我需要我的 ViewModel 只具有 View 应该使用的属性(和属性),而 View 不能深入到子属性中获取价值.

Before you answer, "Just put all the attributes on FooModel," I've tried that, but it didn't work because I need to keep my ViewModels "flat". In other words, I can't just compose each ViewModel with a Model--I need my ViewModel to have only the properties (and attributes) that should be consumed by the View, and the View can't burrow into sub-properties to get at the values.

更新

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

LukLed's answer suggests using inheritance. This definitely reduces the amount of non-DRY code, but it doesn't eliminate it. Note that, in my example above, the DisplayName attribute for the Category property would need to be written twice because the data type of the property is different between the display and edit ViewModels. This isn't going to be a big deal on a small scale, but as the size and complexity of a project scales up (imagine a lot more properties, more attributes per property, more views per model), there is still the potentially for "repeating yourself" a fair amount. Perhaps I'm taking DRY too far here, but I'd still rather have all my "friendly names", data types, validation rules, etc. typed out only once.

推荐答案

我假设您这样做是为了利用 HtmlHelpers EditorFor 和 DisplayFor 并且不希望在整个过程中隆重地声明同一事物 4000 次的开销应用程序.

I'll assume that your doing this to take advantage of the HtmlHelpers EditorFor and DisplayFor and don't want the overhead of ceremoniously declaring the same thing 4000 times throughout the application.

干这个最简单的方法是实现你自己的 ModelMetadataProvider.ModelMetadataProvider 是读取这些属性并将它们呈现给模板助手的东西.MVC2 已经提供了一个 DataAnnotationsModelMetadataProvider 实现来让事情顺利进行,因此继承这让事情变得非常容易.

The easiest way to DRY this up is to implement your own ModelMetadataProvider. The ModelMetadataProvider is what is reading those attributes and presenting them to the template helpers. MVC2 already provides a DataAnnotationsModelMetadataProvider implementation to get things going so inheriting from that makes things really easy.

为了让您从这里开始,这里有一个简单的示例,它将驼峰式属性名称分成空格,FirstName => First Name:

To get you started here is a simple example that breaks apart camelcased property names into spaces, 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:

Then all you have to do is register it like adding your own custom ViewEngine or ControllerFactory inside of Global.asax's Application Start:

ModelMetadataProviders.Current = new ConventionModelMetadataProvider();

现在只是为了告诉你我没有作弊,这是我用来获得相同 HtmlHelper 的视图模型.*.作为你装饰的 ViewModel 的体验:

Now just to show you I'm not cheating this is the view model I'm using to get the same HtmlHelper.*.For experience as your decorated 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# 属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:如何“干"模型和视图模型中的 C# 属性?

基础教程推荐