Как “высушить” атрибуты C# в Моделях и ViewModels?

Этот вопрос был вдохновлен моей борьбой с ASP.NET MVC, но я думаю, что он относится к другим ситуациям также.

Скажем, у меня есть ORM-сгенерированная Модель и два ViewModels (один для представления "деталей" и один для представления "редактирования"):

Модель

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

Дисплей 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; }
}

Редактирование 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; }
}

Обратите внимание, что атрибутами на ViewModels не является DRY - большая информация повторяется. Теперь вообразите этот сценарий умноженным на 10 или 100, и Вы видите, что это может быстро стать довольно утомительным и подверженным ошибкам для обеспечения непротиворечивости через ViewModels (и поэтому через Представления).

Как я могу "высушить" этот код?

Перед ответом "Просто поставьте все атрибуты FooModel," Я попробовал это, но это не работало, потому что я должен сохранить свой ViewModels "плоским". Другими словами, я не могу только составить каждый ViewModel с Моделью - мне нужен мой ViewModel, чтобы иметь только свойства (и атрибуты), который должен быть использован Представлением, и Представление не может прятаться в подсвойства для достигания значений.

Обновление

Ответ LukLed предлагает использовать наследование. Это определенно уменьшает объем кода не-DRY, но он не устраняет его. Обратите внимание что, в моем примере выше, DisplayName атрибут для Category свойство должно было бы быть записано дважды, потому что тип данных свойства отличается между дисплеем и редактированием ViewModels. Это не будет грандиозным предприятием на мелком масштабе, но как размер, и сложность проекта увеличивается (вообразите намного больше свойств, больше атрибутов на свойство, больше представлений на модель), существует все еще потенциально для "повторения себя" изрядное количество. Возможно, я беру DRY слишком далеко здесь, но у меня все еще были бы все свои "дружественные имена", типы данных, правила проверки, и т.д. вывели только однажды.

14
задан LukLed 16 February 2010 в 05:58
поделиться

5 ответов

Я предполагаю, что вы делаете это, чтобы воспользоваться преимуществами 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();
    }
}

Затем все, что вам нужно сделать, это зарегистрировать его, например, добавить свой собственный ViewEngine или ControllerFactory внутри Global Начало приложения .asax:

ModelMetadataProviders.Current = new ConventionModelMetadataProvider();

Теперь, чтобы показать вам, что я не обманываю, это модель представления, которую я использую, чтобы получить тот же 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; }
    }
7
ответ дан 1 December 2019 в 13:59
поделиться

Как сказал LukLed, вы можете создать базовый класс, от которого наследуются модели View и Edit, или вы также можете просто получить одну модель представления из Другие. Во многих приложениях модель редактирования в основном такая же, как и у View, плюс некоторые дополнительные вещи (например, списки выбора), поэтому может иметь смысл вывести модель редактирования из модели View.

Или, если вас беспокоит «взрыв класса», вы можете использовать одну и ту же модель представления для обоих и передавать дополнительные элементы (например, SelectLists) через ViewData. Я не рекомендую этот подход , потому что я думаю, что это сбивает с толку передачу одного состояния через модель, а другого состояния через ViewData, но это вариант.

Другой вариант - просто охватить отдельные модели. Я все о том, чтобы логика была СУХОЙ, но меня меньше беспокоят несколько избыточных свойств в моих DTO (особенно в проектах, использующих генерацию кода для генерации 90% моделей представления для меня).

1
ответ дан 1 December 2019 в 13:59
поделиться

Первое, что я заметил - у вас есть 2 модели просмотра. Подробности см. В моем ответе здесь .

Другие вещи, которые приходят в голову, уже упоминались (классический подход к применению DRY - наследование и соглашения).


Думаю, я был слишком расплывчатым. Моя идея состоит в том, чтобы создать модель представления для каждой модели предметной области, а затем - объединить их в модели представления, соответствующие конкретному представлению. В вашем случае: =>

public class FooViewModel {
  strange attributes everywhere tralalala
  firstname,lastname,bar,fizz,buzz
}

public class FooDetailsViewModel {
   public FooViewModel Foo {get;set;}
   some additional bull**** if needed
}

public class FooEditViewModel {
   public FooViewModel Foo {get;set;}
   some additional bull**** if needed
}

Это позволяет нам создавать более сложные модели представлений (для каждого представления) =>

public class ComplexViewModel {
    public PaginationInfo Pagination {get;set;}
    public FooViewModel Foo {get;set;}
    public BarViewModel Bar {get;set;}
    public HttpContext lol {get;set;}
}

Вы можете найти этот мой полезным вопросом.

хм ... оказывается, я действительно предлагал создать 3 модели представления. В любом случае, этот фрагмент кода отражает мой подход.

Еще один совет - я бы пошел с механизмом на основе фильтров и соглашений (например, по типу), который заполняет данные представления необходимым списком selectList (инфраструктура mvc может автоматически связывать selectList из viewData по имени или чему-то еще).

И еще один совет - если вы используете AutoMapper для управления своей моделью представления, у него есть хорошая особенность - он может сглаживать граф объектов . Следовательно - вы можете создать модель представления (которая предназначена для каждого представления), которая напрямую имеет свойства модели представления (которая предназначена для каждой модели предметной области), независимо от того, насколько глубоко вы хотите зайти (Хаак сказал , что все в порядке).

1
ответ дан 1 December 2019 в 13:59
поделиться

Объявить базовую модель, наследовать и добавить другие свойства:

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

ИЗМЕНИТЬ

О категориях. Не следует редактировать модель представления, имеющую общедоступную строку CategoryName {get; набор; } и public List Категории {get; набор; } вместо SelectList? Таким образом вы можете разместить публичную строку CategoryName {get; набор; } в базовом классе и оставьте DRY. Режим редактирования расширяет возможности класса, добавляя List .

7
ответ дан 1 December 2019 в 13:59
поделиться

Эти отображаемые имена (значения), возможно, могут отображаться в другом статическом классе с большим количеством константных полей. Не избавит вас от множества экземпляров DisplayNameAttribute, но позволит быстро и легко изменить имя. Очевидно, это бесполезно для других мета-атрибутов.

Если бы я сказал своей команде, что им пришлось бы создавать новую модель для каждой маленькой перестановки одних и тех же данных (и впоследствии писать для них определения автомапперов), они восстали бы и линчевали меня. Я бы предпочел моделировать метаданные, которые были в некоторой степени осведомлены об использовании. Например, создание обязательного атрибута свойств вступит в силу только в сценарии «Добавить» (Model == null). Тем более, что я бы даже не стал писать два представления для добавления / редактирования. У меня было бы одно представление для обработки обоих из них, и если бы я начал использовать разные классы моделей, у меня возникли бы проблемы с объявлением моего родительского класса ... бит ... ViewPage.

0
ответ дан 1 December 2019 в 13:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: