Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>My preference is to do <em>input validation on the view models</em>, and <em>business validation on the domain models</em>.</p> <p>In other words, any data annotations such as required fields, length validation, regex, etc should be done on your view models, and added to the model state when error occurs.</p> <p>And you'll probably have business/domain rules that rely on more than just a "form", so you should do that either in the domain models (execute the validation after they're mapped back), or with a service layer. </p> <p>All our models have a method called "Validate", which we call in the services prior to persisting. They throw custom exceptions if they fail business validation, which gets caught by the controller and also added to the model state.</p> <p>May not be everyone's cup of tea, but it's consistent.</p> <p><strong>Example of business validation, as requested:</strong></p> <p>Here's an example of a domain model we have, which represents a generic "Post" (question, photo, video, etc):</p> <pre><code>public abstract class Post { // .. fields, properties, domain logic, etc public void Validate() { if (!this.GeospatialIdentity.IsValidForThisTypeOfPost()) throw new DomainException(this, BusinessException.PostNotValidForThisSpatial.); } } </code></pre> <p>You see there, i am checking against business rules, and throwing custom exceptions. <code>DomainException</code> is our base, and we have many derived implementations. We have an enum called <code>BusinessException</code>, which contains values for all our exceptions. We use extension methods on the enum to provide the resource-based error message.</p> <p>This is not simply a field on the model im checking, e.g "All posts must have a subject", because although that is part of the domain, it's input validation first and foremost, and thus is handled via the data annotations on the view model.</p> <p>Now, the controller:</p> <pre><code>[HttpPost] public ActionResult Create(QuestionViewModel viewModel) { if (!ModelState.IsValid) return View(viewModel); try { // Map to ViewModel var model = Mapper.Map&lt;QuestionViewModel,Question&gt;(viewModel); // Save. postService.Save(model); // generic Save method, constraint: "where TPost: Post, new()". // Commit. unitOfWork.Commit(); // P-R-G return RedirectToAction("Index", new { id = model.PostId }); } catch (Exception exc) { var typedExc = exc as DomainException; if (typedExc != null) { // Internationalised, user-friendly domain exception, so we can show ModelState.AddModelError("Error", typedExc.BusinessError.ToDescription()); } else { // Could be anything, e.g database exception - so show generic msg. ModelState.AddModelError("Error", "Sorry, an error occured saving the Post. Support has been notified. Please try again later."); } } return View(viewModel); } </code></pre> <p>So, by the time we get to the "Save" method on the service, the model has passed <em>input validation</em>. Then the Save method calls <code>post.Validate()</code>, invoking business rules.</p> <p>If an exception is raised, the controller catches it and displays the message. If it gets pass the Save method and another error occurs (90% of the time, it's Entity Framework, for example), we show a generic error message.</p> <p>As i said, not for everyone, but this works well for our team. We have a clear seperation of presentation and domain validation, and a consistent flow of control from the raw HTTP POST, to the redirect after success.</p> <p>HTH</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload