Как я могу сохранить свои представления MVC, модели и связующие устройства как можно более чистыми? - PullRequest
3 голосов
/ 08 марта 2010

Я довольно новичок в MVC, и по мере того, как я все больше и больше углубляюсь во всю инфраструктуру, я обнаруживаю, что моделирование становится все труднее поддерживать.

Позвольте мне объяснить ...

Я пишу базовое приложение CRUD-over-database.Мои доменные модели будут очень богатыми.В попытке сохранить свои контроллеры настолько тонкими, насколько это возможно, я настроил их так, чтобы в командах «Создать / Редактировать» параметр для действия представлял собой богато заполненный экземпляр модели моего домена.Чтобы сделать это, я реализовал пользовательский механизм связывания модели.

В результате, однако, этот пользовательский механизм связывания очень специфичен для вида и модели.Я решил просто переопределить DefaultModelBinder, который поставляется с MVC 2. В случае, когда поле, привязанное к моей модели, является просто текстовым полем (или чем-то более простым), я просто делегирую базовому методу.Однако, когда я работаю с раскрывающимся списком или чем-то более сложным (пользовательский интерфейс требует, чтобы дата и время были отдельными полями ввода данных, но для модели это одно свойство), я должен выполнить некоторые проверки и некоторое ручное копирование данных.

Конечный результат - у меня довольно тесные связи между View и Binder.Я архитектурно в порядке с этим, но с точки зрения обслуживания кода, это кошмар.

Например, моя модель, которую я здесь связываю, имеет тип Log (это объект, который я получу в качестве параметра в моем Action).«ServiceStateTime» является свойством в журнале.Значения формы "log.ServiceStartDate" и "log.ServiceStartTime" являются абсолютно произвольными и берутся из двух текстовых полей в форме (Html.TextBox ("log.ServiceStartTime", ...))

protected override object GetPropertyValue(ControllerContext controllerContext,
                                               ModelBindingContext bindingContext,
                                               PropertyDescriptor propertyDescriptor,
                                               IModelBinder propertyBinder)
{
        if (propertyDescriptor.Name == "ServiceStartTime")
        {
            string date = bindingContext.ValueProvider.GetValue("log.ServiceStartDate").ConvertTo(typeof (string)) as string;
            string time =
                bindingContext.ValueProvider.GetValue("log.ServiceStartTime").ConvertTo(typeof (string)) as string;
            DateTime dateTime = DateTime.Parse(date + " " + time);
            return dateTime;
        }
        if (propertyDescriptor.Name == "ServiceEndTime")
        {
            string date = bindingContext.ValueProvider.GetValue("log.ServiceEndDate").ConvertTo(typeof(string)) as string;
            string time =
                bindingContext.ValueProvider.GetValue("log.ServiceEndTime").ConvertTo(typeof(string)) as string;
            DateTime dateTime = DateTime.Parse(date + " " + time);
            return dateTime;
        }

Log.ServiceEndTime - похожее поле.

Мне это не кажется СУХИМЫМ.Во-первых, если я проведу рефакторинг ServiceStartTime или ServiceEndTime в разные имена полей, текстовые строки могут быть пропущены (хотя мой инструмент рефакторинга R # довольно хорош в подобных вещах, он не вызовет сбой во время сборкии будет пойман только при ручном тестировании).Во-вторых, если бы я решил произвольно изменить дескрипторы «log.ServiceStartDate» и «log.ServiceStartTime», я столкнулся бы с той же проблемой.Для меня ошибки молчания во время выполнения - наихудший вид ошибок там.

Итак, я вижу здесь несколько вариантов помощи, и хотел бы получить некоторую информацию от людей, которые сталкивались с некоторыми из этих проблем:

  • Измените любые текстовые строки, общие для связывателей вида и модели, на константные строки, присоединенные к объекту ViewModel, который я передаю из контроллера в представление aspx / ascx.Однако это загрязняет объект ViewModel.
  • Обеспечивает модульные тесты для всех взаимодействий.Я большой сторонник юнит-тестов и не начал реализовывать эту опцию, но у меня есть ощущение, что это не спасет меня от стрельбы по ногам.

Если это имеет значение, журнал и другие объекты в системе сохраняются в базе данных с помощью Fluent NHibernate.Я действительно хочу сохранить свои контроллеры настолько тонкими, насколько это возможно.

Итак, любые предложения здесь приветствуются!

Спасибо

1 Ответ

1 голос
/ 09 марта 2010

Вы можете использовать ViewModel:

class ViewModelClass 
{
    [DateValidationAttribute]
    public property DateTime ServiceStartTimeDate { get; set; }
    [TimeValidationAttribute]
    public property DateTime ServiceStartTimeTime { get; set; }
}

Связыватель DefaultModelBinder не требует каких-либо изменений для связывания этих полей. Затем вы можете написать функцию для объединения этих полей:

class DateUtil
{
     public static DateTime CombineDateAndTime(DateTime Date, DateTime Time);
}

Тогда вы получаете сущность из базы данных:

var entity = context.GetUpdatedEntity(id);
entity.ServiceStartTime = DateUtil.CombineDateAndTime(viewModel.ServiceStartTimeDate,viewModel.ServiceStartTimeTime);

Если вы действительно хотите, чтобы это было в связывателе модели, вы можете сделать это так:

public class DateAndTimeModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(ControllerContext controllerContext,
                                           ModelBindingContext bindingContext,
                                           PropertyDescriptor propertyDescriptor,
                                           IModelBinder propertyBinder)
    {
        if (propertyDescriptor.PropertyType == typeof(DateTime))
        {
            string time = bindingContext.ValueProvider.GetValue(CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name + "Time")).ConvertTo(typeof(string)) as string;
            var date = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
            if ((date != null) && (time != null))
                return CombineDateAndTime(date, time);
        }
        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}

Когда поле DateTime связывается, связыватель проверяет наличие дополнительного поля формы с «Time» в конце (если мы связываем «ServiceStartTime», он ищет «ServiceStartTimeTime», поэтому вы привязываете часть даты к полю «ServiceStartTime» и часть времени для "" ServiceStartTimeTime "). Если есть, он добавляет свое значение к дате. Вы пишете это один раз и будете работать для каждого поля. Код выше, безусловно, не будет работать, он нуждается в некоторых корректировках, но показывает идею.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...