Blog

ASP.NET Core + FluentValidation + Swagger

Starting off on the right foot

In past projects, documenting my APIs was a “nice-to-have” (so it never happened). Unfortunately, once you’ve created a sizeable code footprint, going back and adding missing documentation after the fact is a Herculean task. Fortunately, with ASP.NET Core, supporting API documentation is as simple as adding some code and comments to API controllers.

First, be sure your web project generates XML documentation by going into the project Properties and clicking the Build tab.

Out of the box, this will cause Visual Studio to start warning you about every missing XML comment in the project. You can suppress the warning by adding 1591 above.

Swagger is just a specification, not an implementation. Its official name is OpenAPI, but most people still refer to it as Swagger. The two main implementations for .NET are Swashbuckle and NSwag. I’ll be using Swashbuckle in my examples.

Open the NuGet package manager, search for Swashbuckle.AspNetCore, and add it to your project.

Once installed, go to your Startup.cs file and add a few lines of code. In ConfigureServices, provide the following code:

    services.AddSwaggerGen(c =>
    {
      c.SwaggerDoc("v1", new Info() { Title = "Web App", Version = "v1" });
      // Set the comments path for the Swagger JSON and UI.
      var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
      var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
      c.IncludeXmlComments(xmlPath);
    });

Then, in Configure, add the following lines of code:

    app.UseSwagger();
    if (env.IsDevelopment())
    {
      app.UseSwaggerUI(c =>
      {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web App V1");
      });
    }

At this point, you can debug your application. By default, swashbuckle adds a route to your application, so if you navigate to /swagger, SwaggerUI will display.

using Microsoft.AspNetCore.Mvc;
namespace WebApplication.ApiControllers
{
   /// <summary>
   /// Verifies that the swagger documentation generator works as expected.
   /// </summary>
   [Route("api/[controller]")]
   [ApiController]
   public class TestController : ControllerBase
   {
     /// <summary>
     /// Retrieves test data.
     /// </summary>
     /// <returns>The test data.</returns>
     [HttpGet]
     public IActionResult GetTestData()
     {
        var model = new
        {
           Message = "Hello, world!"
        };
        return Ok(model);
     }
   }
}

Bringing up SwaggerUI again, we can see our new API end-point is listed.

Notice the <summary> comment appears next to the route. We can provide additional information about different responses using the [SwaggerResponse] attribute.

   /// <summary>
   /// Retrieves test data.
   /// </summary>
   /// <returns>The test data.</returns>
   [HttpGet]
   [SwaggerResponse((int)HttpStatusCode.OK, Description = "The data was returned successfully.")]
   [SwaggerResponse((int)HttpStatusCode.Unauthorized, Description = "You are not authorized to access this resource.")]
   public IActionResult GetTestData()
   {
      var model = new
      {
         Message = "Hello, world!"
      };
      return Ok(model);
   }
This will update the swagger documentation:

I especially like that you can associate a different return-type to each response, so you can specify that on success, return the normal model but on error, return an error model.

Note: At the time of this writing, you must specify the method (e.g., [HttpGet]), or SwaggerUI will return an error.

Model Validation

When I started using MVC (version 3 back in 2012), I was using Razor heavily. Razor incorporated a lot of functionality surrounding validation, centering around the ModelState and data annotations. With the introduction of SPAs and WebAPI, I stopped using ModelState entirely and went back to writing explicit if-statements at the top of my API calls for validation.

ModelState worked by defining data annotations on API models, from the System.ComponentModel.DataAnnotations namespace. These annotations covered the most common forms of validation: Required? Has Min Length? Has Max Length? Valid Email? Matches Regex? In some applications that’s all you really need, but in most instances, validation can be as complex as the business logic itself.

Attributes are a compile-time artifact, which means you cannot perform complex logic easily. For example, you would have to implement your own validation attributes (or inherit from IValidatableObject) to read from a configuration file or database. However, at that point, you’re mixing concerns and may have to jump through loops to get access to the IoC (though this is much easier with ASP.NET Core). Personally, I like my API models to be POCOs.

For now, let’s consider a model that uses data annotations and the IValidatableObject interface:

public class AddressModel : IValidatableObject
{
   [Required]
   [MaxLength(100)]
   public string Line1 { get; set; }

   [MaxLength(100)]
   public string Line2 { get; set; }

   [Required]
   [MaxLength(100)]
   public string City { get; set; }

   [Required]
   [MaxLength(2)]
   public string State { get; set; }

   [Required]
   [RegularExpression(@"^\d{5}(-?\d{4})?$")]
   public string Zip { get; set; }

   public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
   {
      var stateRepository = (IStateRepository)validationContext.GetService(typeof(IStateRepository));
      var state = stateRepository.GetState(State);
      if (state == null)
      {
          yield return new ValidationResult("Unknown state", new[] { nameof(State) });
      }
   }
}

Here’s what the swagger documentation looks like for that model:

Our AddressModel is certainly not a simple POCO anymore, and grabbing the IStateRepository from the underlying DI container is ugly. Furthermore, with APIs tending toward asynchronous operations, this synchronous validation becomes unacceptable. So, do we go back to mile-long if-statements?

FluentValidation for the win!

If you’re like me, you were greatly relieved when Entity Framework 5 introduced fluent configurations. Instead of using a nasty EDMX file or more attributes developers could define the configuration using chained method calls. FluentValidation is a library that achieves the same fluent configuration chaining, but for validation.

Here is an equivalent validation for the AddressModel using FluentValidation:

public class AddressModelValidator : AbstractValidator<AddressModel>
{
   private readonly IServiceProvider serviceProvider;

   public AddressModelValidator(IServiceProvider serviceProvider)
   {
      this.serviceProvider = serviceProvider;

      RuleFor(x => x.Line1).NotEmpty();
      RuleFor(x => x.Line1).MaximumLength(100);
      RuleFor(x => x.Line2).MaximumLength(100);
      RuleFor(x => x.City).NotEmpty();
      RuleFor(x => x.City).MaximumLength(100);
      RuleFor(x => x.State).NotEmpty();
      RuleFor(x => x.State).MaximumLength(2);
      RuleFor(x => x.Zip).NotEmpty();
      RuleFor(x => x.Zip).Matches(@"^\d{5}(-?\d{4})?$");

      RuleFor(x => x.State).MustAsync(IsKnownState).When(x => x.State != null);
   }

   private async Task<bool> IsKnownState(string abbreviation, CancellationToken token)
   {
      var stateRepository = serviceProvider.GetRequiredService<IStateRepository>();
      var state = await stateRepository.GetStateAsync(abbreviation, token);
      return state != null;
   }
}

Notice I was able to easily inject an IServiceProvider into the constructor, which allows me to easily retrieve dependencies on-demand. I’m also able provide an asynchronous implementation, using MustAsync, to check the validity of the provided state.

Also notice I did not directly inject the IStateRepository interface into the constructor. This is an artifact of the way lifetime scopes are managed by the dependency injection framework. You’ll also notice I used GetRequiredServices, which is extension method that makes working with the IServiceProvider interface easier.

To let ASP.NET Core know to use FluentValidation, we must update the Startup.cs file again. First, open the NuGet package manager and add FluentValidation.AspNetCore to your project. In the ConfigureServices method, tag a call to AddFluentValidation onto the AddMvc method.

services.AddMvc()
   .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
   .AddFluentValidation(o =>
{
   o.RegisterValidatorsFromAssemblyContaining<AddressModelValidator>();
});

The good news is FluentValidation will reuse the dependency injection configuration provided by ASP.NET Core. The call to RegisterValidatorsFromAssemblyContaining automatically registers all validator classes in an assembly with the service collection. That’s what allows me to pass things like IServiceProvider or IValidatorFactory to the constructor. The AddFluentValidation method provides several methods for configuring FluentValidation to work with ASP.NET Core.

Registering FluentValidation with Swagger

If you open SwaggerUI again, you’ll notice we’ve lost all our model validation info. By default, Swashbuckle only knows how to generate documentation for data annotations.

Fortunately, some other smart people already went through the pain of integrating Swashbuckle and FluentValidation. Simply add the MicroElements.Swashbuckle.FluentValidation NuGet package, and now you can make a simple modification to the AddSwaggerGen call:

services.AddSwaggerGen(c =>
{
   c.SwaggerDoc("v1", new Info() { Title = "Web App", Version = "v1" });
   // Set the comments path for the Swagger JSON and UI.
   var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
   var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
   c.IncludeXmlComments(xmlPath);
   c.AddFluentValidationRules();
});

This single line of code lets swagger inspect your validator classes to build the equivalent documentation that came with data annotations. So now, when you open SwaggerUI, you can view the same level of detail you had originally:

As I’ve shown, adding API documentation to ASP.NET Core is extremely simple. So far, I love how flexible ASP.NET Core is. I don’t feel like I’m jumping through hoops to access dependency injection or swap in my own libraries. It just works!

Lastly, if your organization needs assistance when it comes to Customer Engagement, Analytics and Insights, Cloud and Hybrid IT, or Enterprise Mobility, please don't hesitate to reach out. Our expert consultants would love to get you started.

References

FluentValidation Docs - https://github.com/JeremySkinner/FluentValidation/wiki/a.-Index
Swagger Configuration - https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-2.1&tabs=visual-studio%2Cvisual-studio-xml
Swagger/FluentValidation Integration - https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation
Customizing Swagger Docs - https://www.schaeflein.net/adding-implementation-notes-to-swagger-ui-via-swashbuckle-attributes/
ASP.NET Model Validation - https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1

Travis Parks
Travis Parks Software Architect

For over a decade, Travis Parks has served as technical lead and software architect with a focus on building web-facing and enterprise-level software systems across countless industry verticals. Travis holds a BS in Computer Science from Lock Haven University and is a certified Scrum Master.