Web API powerful Custom Model Validation with FluentValidation

Since DataAnnotations were introduced, developers found an easy way for setting up their database (especially those who use Code First development) while adding at the same time validation logic to their domain objects. In the Web API side, when an object is posted to one of it’s actions, you can check if the object posted is valid with something like this..

if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
else
    {
    // 
}

While all these sounds great, what happens when you have to add custom validation to your objects? What happens, if you want a User object posted with a FirstName property to be marked as Invalid when it’s first letter is not capital? Can you do this with Attributes? This very question reveals the real problem here, that is it’s not your domain models that needs to be validated but the ViewModel, the one that is transferred between the client and the server. Some times, your ViewModel objects may be the same with your domain objects which is something I always try to avoid. Anyway, I thought it would be good to post about this and make things a little bit clearer for those who are confused.
A clean solution to this problem is to split the logic where you define your database configuration (with DataAnnotation attributes or not) and the logic that validates your ViewModel objects. Here’s what we ‘re gonna see on this post:

  1. Set up the database (Code First development)
  2. Add database configuration using Fluent API
  3. Add custom validation configuration using FluentValidation

For this solution I chose on purpose not to use custom ViewModel objects so that you can see that model validation can be much more things than database configuration. Let’s start.
I created a new black solution in VS 2013 and added three projects:

  1. Domain (Class Library)
  2. Data Access (Class Library)
  3. Web API Empty Web Application with MVC / Web API templates checked

Suppose that we want to create a Registration form so that user can sign up to our application. Starting from the Domain class library project add the following classes.

public class User
    {
        public int ID { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }

        public string BirthDate { get; set; }

        public string EmailAddress { get; set; }

        // Relationship with custom Foreign Key name
        public Gender Sex { get; set; }
        public int SexID { get; set; }
    }
public class Gender
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public List<User> Users { get; set; }
    }

You can see that I have defined a Gender class which is going to hold only two records, one for Male and another one for Female users. There will be a One to Many relationship between them (not defined yet) and we will declare that SexID will be the User’s foreign key to Gender table.
Switch to the DataAccess project and install Entity Framework using Nuget Packages. Also make sure you add reference to the Domain project. First of all, we need to introduce the Entity Framework our classes. Add a UsersContext class as follow:

public class UsersContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public DbSet<Gender> Genders { get; set; }
    }

To setup Dababase configurations you have two options. The one is to use DataAnnotations and the other is to use Fluent API. I prefer the latter cause I like to keep my domain objects clean. I usually add a “Configurations” folder in the project and setup a specific configuration for each of my domain objects. Do this and add two classes that inherits the EntityTypeConfiguration class as follow:

public class UserConfiguration : EntityTypeConfiguration<User>
    {
        public UserConfiguration()
        {
            HasKey(u => u.ID);

            Property(u => u.ID).HasColumnName("UserID");
            Property(u => u.FirstName).IsRequired().HasMaxLength(50);
            Property(u => u.LastName).IsRequired().HasMaxLength(50);
            Property(u => u.Username).IsRequired().HasMaxLength(100);
            Property(u => u.BirthDate).IsRequired().HasMaxLength(20);
            Property(u => u.EmailAddress).IsRequired().HasMaxLength(100);
            
            // Our custom foreign key
            HasRequired(u => u.Sex).WithMany(s => s.Users).HasForeignKey(u => u.SexID);
        }
    }
public class GenderConfiguration : EntityTypeConfiguration<Gender>
    {
        public GenderConfiguration()
        {
            HasKey(g => g.ID);
            
            // We will set ID values manually
            Property(g => g.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
        }
    }

As you can see these are basic database configurations that will have impact in the way Entity Framework Code First will set the database for us. You need to add those two configurations in the UsersContext class:

public class UsersContext : DbContext
    {

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new UserConfiguration());
            modelBuilder.Configurations.Add(new GenderConfiguration());
            base.OnModelCreating(modelBuilder);
        }

        public DbSet<User> Users { get; set; }
        public DbSet<Gender> Genders { get; set; }
    }

While at the same project, you need to set an initializer class that will always re-create our database, seeding two records for the Gender table. On production projects you will propably need migration logic but this is enough for this post.

public class DbInitializer : DropCreateDatabaseAlways<UsersContext>
    {
        protected override void Seed(UsersContext context)
        {
            GetGenders().ForEach(g => context.Genders.Add(g));
        }

        private static List<Gender> GetGenders()
        {
            return new List<Gender>
            {
                new Gender {
                    ID = 1,
                    Name = "Male"
                },
                new Gender {
                    ID = 2,
                    Name = "Female"
                }
            };
        }
    }

Now switch to the WebAPI project and for start install Entity Framework through Nuget Packages. Then add references to both of the class library projects. Open Global.asax.cs and add the following line into the Application_Start method, in order to set our database initializer.

void Application_Start(object sender, EventArgs e)
        {
            // Code that runs on application startup
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            // Initialize the users database.
            Database.SetInitializer(new DbInitializer());
        }

Adding a connectionString element in to the Web.config file and declare where the database will be created.

  <connectionStrings>
    <add name="UsersContext" providerName="System.Data.SqlClient" connectionString="Server=(localdb)\v11.0; Database=UsersDB; Trusted_Connection=true"/>
  </connectionStrings>

Add a new Web API Controller class named UsersController for retrieving and posting User objects. As soon as we invoke one of this controller actions, our database will be created.

    public class UsersController : ApiController
    {
        private UsersContext db = new UsersContext();

        // GET: api/Users
        public IQueryable<User> GetUsers()
        {
             return db.Users;
        }

        // GET: api/Users/5
        [ResponseType(typeof(User))]
        public async Task<IHttpActionResult> GetUser(int id)
        {
            User user = await db.Users.FindAsync(id);
            if (user == null)
            {
                return NotFound();
            }

            return Ok(user);
        }

        // POST: api/Users
        [ResponseType(typeof(User))]
        public async Task<IHttpActionResult> PostUser(User user)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            else
            {
                try
                {
                    db.Users.Add(user);
                    await db.SaveChangesAsync();
                }
                catch (Exception ex)
                {
                    // Log
                }
                return CreatedAtRoute("DefaultApi", new { id = user.ID }, user);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool UserExists(int id)
        {
            return db.Users.Count(e => e.ID == id) > 0;
        }
    }
Before starting your application you may have to set System.Web.Mvc reference, Copy Local property to True, from the properties window.

Fire your application and invoke /api/users action through your browser. You should see the UsersDB database in your local server as follow:
fluent-validation-01
If you get any errors never mind, you can download this solution from the bottom of this post. We started this post to set custom validations to our object without affecting our database configurations. To check this, create an MVC Controller named HomeController with an Index method.

public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
    }

Right click inside the View and select Add view. This will create an Index.cshtml file. I prefer not to paste all of it’s code here since it’s quite big. I will explain though what I did and what I am posting from this page. You can create your own implementation for this or just open the attached solution at the bottom of this post. I have created a registration form using AngularJS and Bootstrap which simply posts form values to our UsersController post method.
fluent-validation-02
If you write your own implementation just make sure on Create my Account button click event, to post all form values without running any javascript validation. Here is the AngularJS function running when the button is clicked.

 $scope.registerUser = function () {

                $scope.modelSubmitted = false;

                var newUser = {
                    FirstName: $scope.firstName,
                    LastName: $scope.lastName,
                    Username: $scope.userName,
                    Password: $scope.password,
                    BirthDate: $scope.birthMonth + '/' + $scope.birthDay + '/' + $scope.birthYear,
                    EmailAddress: $scope.emailAddress,
                    SexID: $scope.sex
                };

                $http.post(apiUrl, newUser)
                    .success(function (data, status, headers, config) {
                        $scope.newUserLocation = headers('Location');
                        $scope.hasModelErrors = false;
                        $scope.modelSubmitted = true;

                        // reset $scope values
                        $scope.firstName = '';
                        $scope.lastName = '';
                        $scope.userName = '';
                        $scope.password = '';
                        $scope.birthMonth = '01';
                        $scope.birthDay = 1;
                        $scope.birthYear = 1985;
                        $scope.emailAddress = '';
                    })
                    .error(function (error) {
                        console.log(error);
                        $scope.error = JSON.stringify(error.ModelState);
                        $scope.hasModelErrors = true;
                    }).finally(function () {
                    });

            }

It’s time to setup the Validation logic. We saw that the User.Username property has been created from Code First configuration as an [nvarchar(100), not null] property in the database. Hence, if the user selects a username with inapropriate or offensive words, the username of course will be valid and the stored in the database.
fluent-validation-03
What we want to do is to add validation logic so that if certain words found on the usename property, automatically mark the ModelState as invalid. This can be done using the FluentValidation. You can see that we try to inject a model validation logic that has nothing to do with database table properties. Switch to the Domain class library and add a new folder named Validations. Install the FluentValidation package running the following command:

Install-package FluentValidation

Add a UserValidator class that inherits the AbstractValidator class.

public UserValidator()
        {
            RuleFor(user => user.FirstName)
                .Length(10, 50)
                .WithMessage("First name must be between 10 to 50 characters.");

            RuleFor(user => user.FirstName)
                .Must(HasNotDigits)
                .WithMessage("First name cannot contain digits");

            RuleFor(user => user.SexID)
                .NotEqual(0)
                .WithMessage("Please select a Gender");


            RuleFor(user => user.Username).NotNull().WithMessage("Username cannot be null");
            RuleFor(user => user.FirstName).NotNull().WithMessage("First Name cannot be null");

            RuleFor(user => user.Username)
                .Must(NotOffensive)
                .WithMessage("Username has invalid keywords. Try another one.");

            RuleFor(user => user.EmailAddress).EmailAddress();


        }

        private bool HasNotDigits(string name)
        {
            if (!string.IsNullOrEmpty(name))
                return !name.Any(char.IsDigit);
            else return true;
        }

        private bool NotOffensive(string username)
        {
            if (!string.IsNullOrEmpty(username))
            {
                bool isOffensive = InvalidKeywords.Contains(username.ToLower());
                return !isOffensive;
            }
            else return true;
        }

        // Pool from Database..
        private static List<string> InvalidKeywords = new List<string>()
        {
            "thef***word",
            "keyword_02",
            "keyword_03",
            "keyword_04"
        };
    }

I have highlighted only the validations added for the username. I added some more for other properties so you can study them later. I declared a Rule for the username property which says that userame must not be offensive and if so, return the relative message. You need to attach the validation configuration to the specific object you want to validate, that is the User class. This is the updated version.

public class User : IValidatableObject
    {
        public int ID { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }

        public string BirthDate { get; set; }

        public string EmailAddress { get; set; }

        // Relationship with custom Foreign Key name
        public Gender Sex { get; set; }
        public int SexID { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var validator = new UserValidator();
            var result = validator.Validate(this);
            return result.Errors.Select(item => new ValidationResult(item.ErrorMessage, new[] { item.PropertyName }));
        }
    }

fluent-validation-04
You can see how many we can achive using FluentValidation. In the solution you will find that I defined that First name cannot contain Digits and if so return an appropriate message.

RuleFor(user => user.FirstName)
                .Must(HasNotDigits)
                .WithMessage("First name cannot contain digits");

private bool HasNotDigits(string name)
        {
            if (!string.IsNullOrEmpty(name))
                return !name.Any(char.IsDigit);
            else return true;
        }

fluent-validation-05

Food for thought

The reason I decided not to use custom ViewModel objects is to highlight the opposite: That is validation logic shouln’t necessary depent (only) on domain/entity objects but also in custom ViewModel objects. You can create custom lightweighted ViewModel classes for exchanging data between client and server and only if their ModelState is valid procceed to further processing. This way you prevent useless interaction with your database repository.

What comes next

In the next two 2 posts we will see how to create a full functional SPA application using AngularJS and pure Web API. Make sure you follow this blog in order to get notified when this occurs!

You can download the project we built from here.

Advertisements


Categories: ASP.NET, Best practices

Tags: , ,

7 replies

  1. Excellent. Thank you very much.

  2. Really nice article, thank you very much!

    p.s. Do you consider host your source code on Github instead of Dropbox?

  3. Your advice really sounds right so I will be hosting my source code on Github from now on. Thanks a lot!

  4. Just to be clear, I meant, would you have the Domain class library reference the Data Access class library?

  5. Unfortunately, the download link still points to Dropbox, and gives a 404. (D’OH!)

Trackbacks

  1. BrowseDotNET: Manage your .NET Solutions and code snippets | chsakell's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Chara Plessa

The purpose of this blog is to broaden my education, promote experimentation and enhance my professional development. Albert Einstein once said that “If you can’t explain it simply, you don’t understand it well enough” and I strongly believe him!

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Kumikoro

A Front End Developer's Blog

Muhammad Hassan

Full Stack Developer | ASP.NET | MVC | WebAPI | Advanced Javascript | AngularJS | Angular2 | C# | ES6 | SQL | TypeScript | HTML5 | NodeJS, MS candidate @LUMS, Grad & EX-Adjunct Faculty @NUCES-FAST, seasonal blogger & open-source contributor. Seattle, WA.

Software Engineering

Web development

IEvangelist

.NET, ASP.NET, C#, MVC, TypeScript, AngularJS

leastprivilege.com

Dominick Baier on Identity & Access Control

Happy DotNetting

In Love with Technology

Knoldus

Knols of experience to your advantage

knowshnet

Search - Read - Request - Share

Rahul's space

Learn, Share and Grow with me !

Dhananjay Kumar

Developer Evangelist @Infragistics | MVP @Microsoft |

SQL Authority with Pinal Dave

SQL Server Performance Tuning Expert

Conficient Blog

Random bits of tech from @conficient

Code! Code! Code!

SOLID & KISS

Code Wala

Designing and coding

Microsoft Mentalist

A way to start with Microsoft Technologies

Tony Sneed's Blog

A glimpse into the lives of Tony & Zuzana Sneed

Sriramjithendra Nidumolu

Personal Notes of Sriramjithendra

%d bloggers like this: