Dependency Injection with Model State Validation in ASP.NET Core

(caveat: this may or may not work in normal ASP.NET, I haven’t tried it yet)

“Validation” is a wonderfully ambiguous term. Usually, in simple examples, it’s restricted to ensuring that required fields are present, that dates are in the correct format, and so on. All of these only require access to the object being validated, which can be very easily done using data annotations:

using System;
using System.ComponentModel.DataAnnotations;

public class ValidateMe
{
  [Required]
  [StringLength(50)]
  public string ImARequiredField { get; set; }
}

You can take this a step further if, for example, you want to run rules on the data in the object by implementing IValidatableObject. An example:

using System;
using System.ComponentModel.DataAnnotations;

public class ValidateMe : IValidatableObject
{
  [StringLength(50)]
  public string ImARequiredField { get; set; }

  public bool TheFieldIsRequired { get; set; }

  public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
  {
    if (TheFieldIsRequired)
    {
      yield return new ValidationResult("The field is required", new string[] { nameof(ImARequiredField) });
    }
  }
}

Great! Now we can wire this up in our controller:

public IActionResult DoStuff(ValidateMe model)
{
  if (ModelState.IsValid)
  {
    // do stuff!
  }
  else
  {
    return View(model);
  }
}

Fantastic! But wait, what’s this? Oh dear, it’s the Real World(TM) come to burst our bubble.

Let’s take a more realistic example. Let’s say that we have a model that needs to set up a new user account. In our system, email addresses must be unique, and so we need to validate this before saving the user. We want to have a nice, clean, de-coupled architecture with injected dependencies, but where can we inject the dependencies into our validation system? Without them, we’d end up like this:

public IActionResult AddUser(AddUserModel model, [FromServices] IEmailValidator emailValidator)
{
  if (!emailValidator.IsEmailAvailable(model.Email))
  {
    ModelState.AddModelError("Email", "Email address is taken");
  }

  if (ModelState.IsValid)
  {
    // do stuff!
  }
  else
  {
    return View(model);
  }
}

Well, that’s ugly – it’s not terrible, but it’s bloating our controller, and, it turns out, we don’t need to do it at all, because enter ValidationContext!

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
  // magic happens here!
  var emailValidator = validationContext.GetService(typeof(IEmailValidator));

  // now I has dependency, I can use ftw!
  if (!emailValidator.IsEmailAvailable(Email))
  {
    yield return new ValidationResult("Email address is taken", new string[] { nameof(Email) });
  }
}

Without doing anything special, ASP.NET is clever enough to pass its IServiceProvider into ValidationContext, and so we now have access to any dependencies that our application needs. The only slight downside is that the Validate method isn’t async, and so we’ll need to wrap any async calls in Task.Run(...) calls, but that’s a small price to pay for keeping our controllers slim.

Merging ModelState validation with Knockout models

Scenario: you have a server-side object in .NET, which you then serialize across to a client-side Knockout view model. Knockout supplies validation on the client-side, but you have one or two rules that you want to enforce on the server and only on the server, and for whatever reason you don’t want to run these asynchronously.

So let’s assume that you’ve got something like this:

public class LoginModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}

This maps to a Knockout model which looks a little like:

function loginModel() {
  this.Username = ko.observable('').extend({ required: true });
  this.Password = ko.observable('').extend({ required: true });
}

All good. Let’s say that your controller action does this:

public IActionResult Login(LoginModel model)
{
  if (!CheckPassword(model.Username, Model.Password))
    ModelState.AddModelError("Password", "Invalid password");

  if (ModelState.IsValid)
  {
    // continue
  }

  return HttpBadRequest(ModelState);
}

Now, in your UI code, you can use the following snippet the map the server-side validation errors into your client-side model:

/**
 * Applies errors returned via the .NET model state error collection to a Knockout
 * view model by matching property names.
 * @param koModel Knockout view model to bind to
 * @param modelState Model state response (usually returned as JSON from an API call)
 */
function applyModelStateErrors(koModel, modelState) {
    // loop all properties of the `modelState` object
    for (var x in modelState) {
        if (modelState.hasOwnProperty(x)) {
            // try to get a property of our KO object with the same name
            var koProperty = koModel[x];

            // we're only interested in KO observable properties
            if (koProperty &amp;&amp; ko.isObservable(koProperty)) {
                var error = modelState[x];
                var message = "";

                // .NET returns errors as an array per-property, but we
                // can check the type just to be safe
                if (error instanceof Array) {
                    message = error.join(", ");
                } else if (typeof error == "string") {
                    message = error;
                } else {
                   message = error.toString();
                }

                // set the error state for this property
                koProperty.setError(message);
            }
        }
    }
}

Given this, you can catch the 400 error in your AJAX call, use the function above, and map the server-side errors into your local model.