2

Я изучаю использование FluentValidation, поскольку он кажется элегантным API для проверки моих ViewModels при привязке модели. Я ищу мнения о том, как правильно централизовать проверку, используя эту библиотеку, а также на уровне моего бизнеса (службы) и поднимите ее до представления, не имея двух разных подходов к добавлению ошибок модели.ASP.NET MVC FluentValidation с проверкой ViewModels и бизнес-логикой

Я открыт для использования совершенно другого API, но в основном ищет решение этой стратегии валидации ветвления.

[Замечание: Одна вещь, которую я пробовал, состояла в том, чтобы переместить мой бизнес-метод в собственный класс RsvpViewModelValidator своего FluentValidation и использовать метод .Must, но было бы неправильно скрывать этот вызов там, потому что, если мне нужно было фактически использовать объект Customer они мне придется повторно запросить его еще раз, так как его из сферы]

Пример кода:

[HttpPost] 
public ActionResult AcceptInvitation(RsvpViewModel model) 
{ 
    //FluentValidation has happened on my RsvpViewModel already to check that 
    //RsvpCode is not null or whitespace 
    if(ModelState.IsValid) 
    { 
     //now I want to see if that code matches a customer in my database. 
     //returns null if not, Customer object if existing 
     customer = _customerService.GetByRsvpCode(model.RsvpCode); 
     if(customer == null) 
     { 
      //is there a better approach to this? I don't like that I'm 
      //splitting up the validation but struggling up to come up with a 
      //better way. 
      ModelState.AddModelError("RsvpCode", 
       string.Format("No customer was found for rsvp code {0}", 
           model.RsvpCode); 

      return View(model); 
     } 

     return this.RedirectToAction(c => c.CustomerDetail()); 
    } 

    //FluentValidation failed so should just display message about RsvpCode 
    //being required 
    return View(model); 
} 

[HttpGet] 
public ActionResult CustomerDetail() 
{ 
    //do work. implementation not important for this question. 
} 
+1

Вы можете извлечь такое решение на сервисный уровень, но я думаю, что вы ищете белого слона. Не путайте логику бизнеса/процесса с логикой проверки; модель может быть _valid_, без ее значительного смысла в базе данных. –

+0

Я согласен с вашим заявлением о различиях в логике. Думаю, мне интересно ... вы думаете, что код, который у меня выше, правильный или вы подойдете к другому? Мой вопрос довольно широко раскрыт «Что бы вы сделали» с точки зрения получения сообщений обратно в представление для валидации и бизнес-логики вместе. Код выше - лучший подход, который я мог бы подумать, чтобы уйти. – Scott

+1

Я всегда работаю с N-Tier, поэтому у меня бы получилось 'приглашение.Accept() '(или какой-то сервис будет описывать процесс), который либо прошел, либо не удался, и именно там будет размещена логика, но без службы следующее лучшее место находится в контроллере. лично я бы сказал, что вы сделали все, что было возможно, учитывая обстоятельства. –

ответ

2

Чтобы дать некоторое закрытие вопроса (и сделать его приемлемым), а также суммировать комментарии:

Логика бизнеса/процесса и логика проверки являются двумя объектами. Если валидация не связана с базой данных (например, для проверки уникальных записей), нет причин для групповой проверки в одном месте. Некоторые из них несут ответственность в модели, убедившись, что в ней нет ничего недействительного, и некоторые из них описывают, как проверенные значения используются в системе. Подумайте об этом с точки зрения свойств getters/seters против логики, используемой в методах с этими свойствами.

Отмечая, что процессы обработки (проверки, обработка ошибок и т. Д. - все, что не связано с пользовательским интерфейсом), могут выполняться на сервисном уровне, который также поддерживает приложение DRY. Тогда действие (действия) является/отвечает только за вызов и представление и не выполнение фактической единицы работы. (также, если различные действия в вашем приложении используют аналогичную логику, все проверки находятся в одном месте, а не вбрасываются вместе между действиями. (не помню, чтобы проверить, что есть запись в таблице клиентов?))

Кроме того, разбив его на слои, вы сохраняете проблемы модульности и проверяемости. (Принятие RSVP не зависит от действия в пользовательском интерфейсе, но теперь это метод в службе, который может быть вызван этим интерфейсом или, возможно, мобильным приложением).

Что касается пузырящихся ошибок, у меня обычно есть базовое исключение, которое пересекает каждый слой, тогда я могу расширить его в зависимости от цели. Вы могли бы легко использовать параметры Enums, Booleans, out или просто логические (Rsvp либо был, либо не был принят). Это зависит только от того, насколько конечным является ответ, который требуется пользователю для исправления проблемы, или, возможно, изменить рабочий поток, чтобы ошибка не была проблемой или что-то, что пользователю нужно исправить.

0

Вы можете иметь логику всей проверки в беглой проверки:

public class RsvpViewValidator : AbstractValidator<RsvpViewModel> 
{ 
    private readonly ICustomerService _customerService = new CustomerService(); 
    public RsvpViewValidator() 
    { 
     RuleFor(x => x.RsvpCode) 
      .NotEmpty() 
      .Must(BeAssociatedWithCustomer) 
      .WithMessage("No customer was found for rsvp code {0}", x => x.RsvpCode) 
    } 

    private bool BeAssociatedWithCustomer(string rsvpCode) 
    { 
     var customer = _customerService.GetByRsvpCode(rsvpCode); 
     return (customer == null) ? false : true; 
    } 
} 
+0

Это было частью моего первоначального вопроса и подхода, который я сделал (см. «Боковое примечание»). Я согласен с тем, что он технически работает, но он не подходит мне, но с точки зрения лучшей практики. Я волнуюсь, что после этого шаблона может протекать слишком много бизнес-логики/процесса в мои валидаторы ViewModel, чтобы получать сообщения в ModelState, поэтому трудно решить, какие вызовы идут туда. Я открыт, чтобы услышать ваши мысли, хотя если вы хотите поделиться больше. – Scott