Fluent Validation + NInject3 + MVC5

Fluent Validation + NInject + MVC - Why & How : Part 1

http://fluentvalidation.codeplex.com/

http://www.techmyview.com/post/2014/04/27/Fluent-Validation-NInject-MVC-Why-and-How-Part-1


Validation is one of the most important aspects of programming. “To err is human”. Every human being is prone to commit errors. Our application must be smart enough to catch those errors and let user know about the actions he/she needs to perform in order to proceed further. But when we say that application must have validation logic, we also consider this aspect from development and maintenance perspective. Our validation logic must be easy to integrate, easy to test and easy to change as well if the need be. Taking into consideration let’s first dig why do we need a third party validation library instead of using built in MVC validation framework.

Why to use FluentValidation.Net library instead of MVC built-in Validation Framework?

This should be first thing which needs to be answered before we explore any new framework or any alternative to existing thing? Why? Why should I go for FluentValidation when I have built-in validation framework in MVC and it is working just fine. Does FluentValidation do anything different than built-in MVC validation framework? When we understand “why” part, understanding the framework and playing with it becomes much much easier. Let’s take the question again?

Q - Does FluentValidation do anything different than built-in MVC validation framework?
The answer is NO. But it does it differently and in a much more better way. All validation frameworks do the same thing – Validate the model and make the validation errors available to the MVC framework so that the same can be displayed on the view. But how do they do it is more important.

Let’s consider the built-in MVC validation framework. It makes use of Data Annotation attributes to perform the validation. Consider following example.

public class Student
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    [EmailAddress]
    public string Email { get; set; }

    [Range(2, 5)]
    public int NoOfSubjects { get; set; }
}


In order to add Data Annotations we need to add namespace - System.ComponentModel.DataAnnotations;
Data Annotations are nothing but attributes over the desired properties. Required attribute indicated MVC that this field in required on the form. If user doesn’t fill this value and post back occurs then MVC framework would show an error stating that Name field is required. On the same line Email and NoOfSubjects would be validated (If you are new to Data Annotation thing then please see a running example at – MSDN). So what is wrong with above validation? Logically nothing. It would work seamlessly the way we want it. But there are few drawbacks with this approach.

  1. Models becomes dirty – “Single Responsibility” principle of the software engineering says that every class should be responsible for carrying out only that task which is assigned to it. In our case the task of Model/ViewModel is nothing but to show the data to the user. Why does it need to have validation logic as well? Some people may argue that Model’s responsibility is to show the data to the user and validation is nothing but the provision to the data correct. But in my honest opinion these two are different responsibilities. Don’t overburden our model. Let it do it’s own job only – show data to the view. If you don’t agree with me, please read second and third point and come back to first point and agree with me now.
  2. Unit Testing of the Validation logic – I won’t say that with this logic you can’t unit test the validators but it is tricky. When we cover FluentValidation you would realize how. For those who don’t know how to unit test Data Annotations they may find this post useful.
  3.  Conditional Validation – Conditional validation is not straightforward to perform using Data Annotation approach. e.g. If we wish to perform validation on Email in above example only when NoOfSubjects field is not empty then using Data Annotation it will be a difficult task. We would end up writing custom validator for that.


In order to overcome all the above three main issues of Data Annotation approach we need some framework. It would provide us to write validation logic in different class than model and still be able to bind the logic to the model with least efforts, to perform unit testing of validation rules, to perform conditional validation, to have better control over validation rules etc. If you are looking for these things then FluentValidation.Net is the answer for it. FluentValidation.Net is a very small library written in .Net which lets us write Validation rules using clean, easy to understand lambda expressions.

Fluent Validation in Action

Enough of the theory part. Now that we understood the need of FluentValidation, let’s put it in some action. We would create a simple MVC5 application and would perform validation of model/view model using fluent validation and then we would put NInject into action for dependency injection of validators. We will use Visual Studio Express 2013 for Web to build our demo project in step by step way.

image

 

image

Create an MVC5 application and add FluentValidation nuget package as shown below.

image

image

If you are using MVC4 or MVC3, still you can follow same steps. Just replace FluentValidation.MVC5 with MVC4 or MVC3 whichever version you are using. Click on install. It would add FluentValidation references to the project. You are all set to use FluentValidation now.

Add a class in model folder. I have added following class -

public class Student
{
    public string Name { get; set; }

    public string Email { get; set; }

    public string AddressLine1 { get; set; }

    public string AddressLine2 { get; set; }

    public string City { get; set; }
}


I have added a simple plain class. Let’s add one view also to show this model.

@model FluentValidationMVC5Demo.Models.Student

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>


@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Student</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name)
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email)
                @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.AddressLine1, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.AddressLine1)
                @Html.ValidationMessageFor(model => model.AddressLine1)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.AddressLine2, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.AddressLine2)
                @Html.ValidationMessageFor(model => model.AddressLine2)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.City, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.City)
                @Html.ValidationMessageFor(model => model.City)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
 

I have also added two action methods in HomeController.

 
[HttpGet]
public ActionResult Index()
{
    Student model = new Student();
    return View(model);
}

[HttpPost]
public ActionResult Index(Student model)
{
    return View(model);
}

I hope you are familiar with HTTPGet and HTTPPost methods. Also I assumed that you have understood the Index view. I have simple added the code to show model and submit button on the view. No validation logic yet. Let’s add validation rules for the Student class. Create a new class with the name StudentValidator. I use this convention that validator class name would be model name + Validator. You may use different convention if you wish. My validator class looks like following -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using FluentValidation;

namespace FluentValidationMVC5Demo.Models
{
    public class StudentValidator : AbstractValidator<Student>
    {
        public StudentValidator()
        {

        }
    }
}

Note the namespace at the top – FluetValidation. We need to add this namespace in order to use the validation framework.  Now let’s understand the code. We are saying that this class would be the validator for Student class then we need to tell the Fluent Validator by inheriting the validator class with the generic class – AbstractValidaor<>. Also this generic class would accept Student as the entity to validate. So first line says that StudentValidator would server as the validator for the Student class. Simple?

Now, second thing is you just need a parameter-less constructor in this validator class. We would write all the validation rules inside this constructor. Also we would end up using lambda expression to write validation rule.

RuleFor(x => x.Name)
    .NotEmpty();
.WithMessage("Please enter Name!!!");

I have written this simple rule inside the constructor. Let’s understand the line. RuleFor is the method of AbstractValidator and since we are inheriting from it we get direct access to this method inside the constructor. It takes lambda expression as input. I hope you understand lambda expression. So the code simple means that we are writing validation rule for property Name of Student class. If you press . after RuleFor(x => x.Name) you would get the whole list of validation rules available. It includes many rules viz. NotEmpty, LessThan, GreaterThan etc. You can explore those validation rules on your own. Let’s stick to NotEmpty for time being. It means that Name property is required field. And then third line says that if validation fails you have show this error message. You can explore all the validation rules and validation methods at the home page.

Now we have written validation logic also. Let’s wire it with the MVC framework. How would MVC framework know that validation rules for Student class are there in StudentValidator class? We need to tell it. Right?

I assume that you know NInject and Dependency Injection. If you are new to NInject then I suggest you to go through NInject Tutorial and NInject Home Page. We would directly proceed here assuming you having basic knowledge of Dependency Injection. Add NInject Nuget package to our application. You can add Nuget package the same we added FluentValidation or you can go to View –> Other Window –> Package Manager Console and type folllwing -

Install-Package Ninject.MVC4 -Version 3.2.0

It does the same job as that of Nuget dialogue. I told this way just to show you another way of adding Nuget references.

MVC is all plug-n-play framework. You can plug your validator framework, authentication framework etc into MVC and MVC would use this plugged framework and neglect it’s built in framework for that particular module. We are going to use this feature of MVC. We are going to override MVC’s built in validation framework. For that, first thing we have to do is we need to write some logic which would return a validator class for a given model/view model. Let’s name it as FluentValidationConfig. It would create and return specific validator given model as input. e.g. It would return StudentValidator if we give Student as input. (Don’t worry we don’t have to do anything, MVC & NInject would take care of everything).

As a next step, we would create this class under App_Start folder. Just to be consistent with MVC folder structure, we would put all config related files under App_Start folder. I have written following code in the file -

public class FluentValidationConfig : ValidatorFactoryBase
{
    private IKernel kernel;

    public FluentValidationConfig()
    {
        kernel = new StandardKernel();
    }

    public void AddBinding()
    {

    }

    public override IValidator CreateInstance(Type validatorType)
    {
            
    }
}

Let’s understand the code first. If we are writing our custom validator factory overriding MVC’s built in feature then we need to implement IValidatorFactory interface. It has following definition -

public interface IValidatorFactory {
  IValidator<T> GetValidator<T>();
  IValidator GetValidator(Type type);
}
 
But instead of implementing this interface, FluentValidation makes us available a simpler solution by exposing ValidatorFactoryBase. It would do most of the work for us. We just to need to override CreateInstance method of this class which would return the specific validator. We have created an instance of StandardKernel in the constructor which would deal with bindings. In the AddBindigs method we would wire the validator and it’s viewmodel. It is done as follows -
 
kernel.Bind<IValidator<ViewModell>>().To<ViewModelValidator>();
 

Where ViewModel is nothing but our model and ViewModelValidator is nothing but it’s Validator. So in our case it get’s transformed as -

 
kernel.Bind<IValidator<Student>>().To<StudentValidator>();
 
Before you put this code in AddBindings method, you may need to add using for the Model namespace as Student and StudentValidaor class are in Models folder. Now last part, we need to add code in CreateInstance method.
 
return (validatorType == null) ? null : (IValidator)kernel.TryGet(validatorType);
 
The code is self explanatory. NInject would simple resolve IValidator<Student> to StudentValidator and we are done. Full code of FluentValidationConfig file is as follows -
 
using FluentValidation;
using Ninject;
using FluentValidationMVC5Demo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace FluentValidationMVC5Demo.App_Start
{
    public class FluentValidationConfig : ValidatorFactoryBase
    {
        private IKernel kernel;

        public FluentValidationConfig()
        {
            kernel = new StandardKernel();
            AddBinding();
        }

        public void AddBinding()
        {
            kernel.Bind<IValidator<Student>>().To<StudentValidator>();
        }

        public override IValidator CreateInstance(Type validatorType)
        {
            return (validatorType == null) ? null : (IValidator)kernel.TryGet(validatorType);
        }
    }
}
 
And last step, we need to inform MVC to use this validation configuration when it wants to get validator for any model. This would be done in Global.asax file. Just one line -
 
 
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure(
                provider => { provider.ValidatorFactory = new FluentValidationMVC5Demo.App_Start.FluentValidationConfig(); });
 
 
It is self explanatory again. It configures FluentValidationConfig as the default validator factory for our MVC application. Now run the application and navigate to home page and just click on submit button, we should get that “Please enter name” error. We added the validation error only for Name fields, now you try to add validation for as many fields as you want and the way you want.
 
image

If we look at the AddBinding method, we observe that we need to add binding for each validator to it’s model. So everytime we add new model and it’s respective validator we would have to change this function to accommodate this new model. It is difficult to maintain. Right? Don’t worry, there is a workaround for that also. NInject offers Assembly scanning. We would instruct NInject to scan entire assembly to look for all classes which inherit AbstractValidator and add the binding automatically. It can be done in following way – AddBinding function code changes to

AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly())
                            .ForEach(match => kernel.Bind(match.InterfaceType)
                                .To(match.ValidatorType));
 

If we put this code in AddBinding then we do not need to worry about any binding manually. Validators would be auto bound to it’s respective models. Now replace AddBinding code with above lines and see if your code works.

 

Done!!!

 

Now if you see, Our model class is clean and it’s validation logic is clean too. Validation rules are easy to write, easy to maintain and easy to change without having to change model class any way. In next article we would learn how to unit test and use conditional validators in FluentValidation.

 

I hope you enjoyed the article. Feel free to comment if you have any doubt, suggestion or any comment on this article.

Be the first to rate this post

原文地址:https://www.cnblogs.com/Leo_wl/p/4362461.html