Для чего это стоит, вот что я нашел в моем текущем проекте:
У меня есть Models
, Repositories
(вы можете назвать их Services
, если хотите) и ViewModels
. Я стараюсь избегать написания пользовательских связывателей моделей, потому что (а) это скучно и (б) странное место для проверки, ИМХО. Для меня модель связующего просто берет элементы из запроса и помещает их в объект. Например, PHP не выполняет никакой проверки при вставке элементов из заголовка в массив $ _POST; это вещь, в которую мы подключаем массив, который заботится о его содержимом.
Мои Model
объекты обычно никогда не позволяют себе войти в недопустимое состояние. Это означает, что обязательные параметры передаются во время конструктора, и свойства будут генерировать исключения, если их попытаться установить с недопустимыми значениями. И, в общем, я пытаюсь сделать мои Model
объекты неизменяемыми. Например, у меня есть объект Address
для почтовых адресов, созданный с помощью объекта AddressBuilder
, в котором рассматриваются требования к полям для данной страны путем проверки AddressScheme
, который можно получить из AddressSchemeRepository
. Уф. Но я думаю, что это хороший пример, потому что он берет что-то концептуально простое («проверить почтовый адрес») и усложняет его в реальном мире («мы принимаем адреса из более чем 30 стран, и эти правила форматирования находятся в базе данных, а не в моем коде ").
Поскольку создание этого Model
объекта является чем-то вроде боли - так и должно быть, так как он довольно специфичен в отношении данных, которые загружаются в него - у меня есть, скажем, InputAddressViewModel
объект, который мой вид привязывается к. InputAddressViewModel
реализует IDataErrorInfo
, так что я получаю ASP.NET MVC DefaultModelBinder
для автоматического добавления ошибок в ModelState
. Для простых процедур проверки, которые я знаю заранее (форматирование номера телефона, имя, требуемый формат, адрес электронной почты), я могу реализовать их прямо в InputAddressViewModel
.
Другое преимущество наличия модели представления состоит в том, что, поскольку она бесстыдно адаптирована к конкретному виду, ваша реальная модель более пригодна для повторного использования, поскольку ей не нужно идти на какие-то странные уступки, чтобы сделать ее подходящей для отображения пользовательского интерфейса (например, необходимо реализовать INotifyPropertyChanged
или Serializable
или любое из этого беспорядка).
Другие ошибки проверки адреса, о которых я не узнаю, пока не взаимодействую с моим AddressScheme
в моем фактическом Model
. Эти ошибки будут там, где контроллер выполняет оркестровку в ModelState
. Что-то вроде:
public ActionResult InputAddress(InputAddressViewModel model)
{
if (ModelState.IsValid)
{
// "Front-line" validation passed; let's execute the save operation
// in the our view model
var result = model.Execute();
// The view model returns a status code to help the
// controller decide where to redirect the user next
switch (result.Status)
{
case InputAddressViewModelExecuteResult.Saved:
return RedirectToAction("my-work-is-done-here");
case InputAddressViewModelExecuteResult.UserCorrectableError:
// Something went wrong after we interacted with the
// datastore, like a bogus Canadian postal code or
// something. Our view model will have updated the
// Error property, but we need to call TryUpdateModel()
// to get these new errors to get added to
// the ModelState, since they were just added and the
// model binder ran before this method even got called.
TryUpdateModel(model);
break;
}
// Redisplay the input form to the user, using that nifty
// Html.ValidationMessage to convey model state errors
return View(model);
}
}
switch
может показаться отталкивающим, но я думаю, что это имеет смысл: модель представления является просто старым классом и не имеет никакого знания о Request
или HttpContext
. Это делает логику модели представления легкой для тестирования в изоляции, не прибегая к насмешкам, и оставляет код контроллера оставленным, ну, в общем, для управления , интерпретируя результат модели таким образом, который имеет смысл на веб-сайте. - может перенаправлять, может устанавливать файлы cookie и т. д.
И InputAddressViewModel
методы Execute()
выглядят примерно так (некоторые люди настаивают на том, чтобы поместить этот код в объект Service, который будет вызывать контроллер, но для меня модель представления будет сильно влиять на данные чтобы он соответствовал реальной модели, которую имеет смысл поместить здесь):
public InputAddressViewModelExecuteResult Execute()
{
InputAddressViewModelExecuteResult result;
if (this.errors.Count > 0)
{
throw new InvalidOperationException(
"Don't call me when I have errors");
}
// This is just my abstraction for clearly demarcating when
// I have an open connection to a highly contentious resource,
// like a database connection or a network share
using (ConnectionScope cs = new ConnectionScope())
{
var scheme = new AddressSchemeRepository().Load(this.Country);
var builder = new AddressBuilder(scheme)
.WithCityAs(this.City)
.WithStateOrProvinceAs(this.StateOrProvince);
if (!builder.CanBuild())
{
this.errors.Add("Blah", builder.Error);
result = new InputAddressViewModelExecuteResult()
{
Status = InputAddressViewModelExecuteStatus
.UserCorrectableError
};
}
else
{
var address = builder.Build();
// save the address or something...
result = new InputAddressViewModelExecuteResult()
{
Status = InputAddressViewModelExecuteStatus.Success,
Address = address
};
}
}
return result;
}
Имеет ли это смысл? Это лучшая практика? Я понятия не имею; это, конечно, многословно; это то, что я только что придумал за последние две недели, подумав об этой проблеме. Я думаю, что у вас будет некоторое дублирование проверки - ваш пользовательский интерфейс не может быть полным имбецилом и не знать, какие поля являются обязательными или нет, прежде чем отправлять их в вашу модель / репозитории / службы / что угодно - иначе форма может просто сгенерировать себя.
Я должен добавить, что стимулом для этого является то, что я всегда ненавидел менталитет Microsoft «установить одно свойство -> проверить одно свойство», потому что в действительности ничто не работает так. И вы всегда в конечном итоге получаете недействительный объект, потому что кто-то забыл вызвать IsValid
или что-то подобное по пути в хранилище данных. Итак, еще одна причина наличия модели представления заключается в том, что она адаптируется к этой уступке, поэтому мы получаем большую CRUD-работу по извлечению элементов из запроса, ошибкам проверки в состоянии модели и т. Д. без необходимости поставить под угрозу целостность самой нашей модели. Если у меня в руке есть объект Address
, я знаю, что это хорошо. Если у меня в руке есть объект InputAddressViewModel
, я знаю, что мне нужно вызвать его метод Execute()
, чтобы получить этот золотой Address
объект.
Я с нетерпением жду, чтобы прочитать некоторые другие ответы.