Tuesday 1 April 2014

Essentials of MVC.Net - A Deep Dive



           
When I started working in an MVC project for the first time, there are so many words such as Model Binding, Routing, and Action Result which seems a too much Jargon. So I bought a book named ASP.Net MVC in action by Palermo to get hold of this pattern which is widely used and spoken a lot these days. I can assure, only knowing the below 7 listed will help you become a MASTER in MVC.NET.  I have used MVC in my previous projects but they just used the pattern concepts but not actual MVC.NET.

I have handpicked myself, only the essentials that you need to know to have implement MVC in a project or to work in a project that has already implemented the MVC.


            Before diving into the essentials we need to understand the main concept between URL mapping in traditional web architecture and MVC.Net architecture.

In traditional architecture, URL is always mapped to a resource, for e.g. products/Wishlist.aspx will return the resource Wishlist.aspx to the client as response to URL request.

            In case of MVC Architecture, URL’s are mapped to specific methods and controllers, where it executes the method and returns either View or a structured data.

Routing

           
            If you want to understand routing make sure you understand the above two paragraph before you proceed to this concept. Because the whole concept of MVC lies in the above two paragraph.
           
            MVC.Net Routing enables you to use the URL to map to contents, which does not have physical existence, but uses a readable URL which conveys some meaning to the end user and also to the server.

            Default routes will be registered in the Application_Start where the routes will be added to the MVC.Net.           
routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new 
            { 
                controller = "Home", 
                action = "Index", 
                id = UrlParameter.Optional
            }
        );


In above example default is the name of the route and any URL which has format controller/action/Id will accepted, if there are any parameters missing then the Home/Index/Id will be replaced. Check the example below for substitution of value

Request parameter from client
Substituted/Actual Parameter value
Home
Controller – Home
Action – Index

Main
Controller – Main
Action – Index

Main/Index.aspx
Controller – Main
Action – Index

Main/Index.aspx/Id =10
Controller – Main
Action – Index
Id – 10


In addition to the default values that can be set to your URL, we can also set constraints for the parameters.

 
routes.MapPageRoute("",
        "Category/{action}/{categoryName}",
        "~/categoriespage.aspx",
        true,
        new RouteValueDictionary {{"categoryName", "food"}, {"action", "show"}},
        new RouteValueDictionary {{"locale", "[a-z]{2}-[a-z]{2}"},{"year", @"\d{4}"}}
       );


Above can be used to set default values and constraint for the URL’s where the URL is more readable by the user and also by the server that process the request URL.

Action Result Return Type


            Before understanding action result return type, we need to know what a Controller is; Controller is class which the MVC map URL’s to.

 Methods in the Controller are called as Action Method.  Action method can return object of any type including primitive data types, user defined types and action results. Action Result is a base class of all action results in MVC. Net.


Action Result
Description
Renders a view as a Web page.
Renders a partial view, which defines a section of a view that can be rendered inside another view.

Redirects to another action method by using its URL.
Redirects to another action method.
Returns a user-defined content type.
Returns a serialized JSON object.
Returns a script that can be executed on the client.
Returns binary output to write to the response.
Represents a return value that is used if the action method must return a null result (void).

            Most used Action result return types are ViewResult, JsonResult and EmptyResult. You can always return user defined types, but in MVC.NET web application it is better to use action result defined in MVC, if you need a custom based return type for every module, redesign your module to accommodate the action result type, so we can have common mechanism and implementation that is available throughout the application because otherwise each and every module will be implementing their own view to have their data type handled. 

Filters

            Sometime it is necessary to perform certain common Operation before or after the call of the action method in a controller. Such as fetch/save objects in cache before/after accessing the database.  To implement the pre/post and operation, we need to use filters in MVC.

Four types of filter are available in MVC for various purposes.

            Authorization filters are used in places where security decision has to be taken on an action method on or before performing authentication or validating the user request.

            Action Filters are used in places where you perform action before or after action method is called.
           
            Result Filters are used in places where we need to wrap up the execution result of the action method. The OutputCacheAttribute class is one example of a result filter.

            Exception filters are used in places where we need to execute piece of code whenever the application throws an unhandled exception, it is used mostly in logging and error handling. The HandleErrorAttribute class is one example of an exception filter.

Model Binding

           
            If reusability is your concern in your application (which should be) then this is an important concept that needs to be implemented in your application. Model binding by itself will make your code extensible and Neat. In MVC.Net, we need to pass lots of parameter to the controller for processing of our request through form or through query string, they are resolved based on their name of the parameters.

In the below example


public ActionResult Index(string Name, int age)
        {
            return View();
        }


Index accepts Name and age and this can be passed to the controller through the query string or form parameters. When our application enhances, then the parameter gets added for every enhancement, so it might reach a point where your parameters counts is 25 for Index action method, which not makes the action method unmaintainable and you need to change in view and in controller for every parameter addition. To fix this issue can you use  model binder which will take care of the resolving  the parameters and passing the parameters in model as below.


        public ActionResult Index(Employee emp)
        {
            return View();
        }


In above example we use the employee model which will be evaluated by the model binder, to use model binder we need to create a class which implements IModelBinder. In below example, EmployeeModelBinder resolves the parameter into Employee model.

public class EmployeeModelBinder: IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, 
                                ModelBindingContext bindingContext)
        {
            HttpRequestBase request = controllerContext.HttpContext.Request;

            return new Employee{
                Name = request["Name"],
                Age = Convert.ToInt16(request["Age"])
            };

        }
    }


In order model binder to work we need to register the binding between the model and the model binder which resolves the value in the application. Place the below code in the Application_Start() in Global.asax.cs.

protected void Application_Start()
        {
             //Other code 
             ModelBinders.Binders.Add(typeof(Employee), new EmployeeModelBinder());

        }

            Whenever there is an action method which has the model registered with model binder the MVC.Net will pass the request to the controller through the model binder. It is the model binder responsibility to the resolve the model based on the application logic.

 In order to implement the model binder, change your action method to accept model as parameter, add model binder and register the model and model binder in the Application_Start() of Global.asax.cs.

Smart Binders


            Model Binder does a great job, but smart binder will go to next level by removing the duplicate code. For example, if many controllers use the same object type on a database to get data, we can use the custom binder (smart binder) to get the code in a common place where it can be used by the all controllers which consumes same object type.  

In this we will see same employee example, implemented using the Smart binder.

Define a repository that is used in common by most of the controllers

public interface IRepository where T:IDAOModel
    {
        IDAOModel GetData(Guid Id);
    }

            Now we can handle all the controllers which consume the repository that has implemented IRepository.

Next, define the interface for the model binder.

public interface IFilteredModelBinder : IModelBinder
    {
        bool IsMatch(Type modelType);
    }


We have a model binder that has implemented IFilteredModelBinder which has the logic implemented that will be called when the model is being used. This removes the common code that should be available to the entire controller which takes care of resolving the repository type which should say whether smart binder should be used or not.


public class EntityModelBinder: IFilteredModelBinder
    {
        public object BindModel(ControllerContext controllerContext, 
                                ModelBindingContext bindingContext)
        {
            HttpRequestBase request = controllerContext.HttpContext.Request;
            Guid id = new Guid(request["id"]);
            Type repositoryType = 
                 typeof(IRepository<>).MakeGenericType(bindingContext.ModelType);

            //IDAOModel currentModelType = (IDAOModel) bindingContext.ModelType;
            var repository = new EmployeeRepository();
            IDAOModel daoModel = ((IRepository)repository).GetData(id);

            return daoModel;
           
        }

        public bool IsMatch(Type modelType)
        {
            return typeof(IDAOModel).IsAssignableFrom(modelType);
        }
    }



All the model binder will use the IFilteredModelBinder to implement IsMatch() for their model binder to confirm their type.

public class SmartBinder:DefaultModelBinder
    {
        private  IFilteredModelBinder[] filteredModelBinder;

        public SmartBinder(params  IFilteredModelBinder[] filteredModelBinder)
        {
            this.filteredModelBinder = filteredModelBinder;
        }

        public override object BindModel(ControllerContext controllerContext,
                                         ModelBindingContext bindingContext)
        {
            foreach (var modelBinder in filteredModelBinder)
                if (modelBinder.IsMatch(bindingContext.ModelType))
                    return modelBinder.BindModel(controllerContext, bindingContext);

            return base.BindModel(controllerContext, bindingContext);
        }

    }

In the above code the filteredModelBinder will be added with the list binders that will use match the binder type. On match of the binder the bindmodel will be called based on the EntityModelBinder’s BindModel will be called.

To  consume the model binder, register the model binder in the Application_Start() of Global.asax.cs.

protected void Application_Start()
        {
          //Other Code
          ModelBinders.Binders.DefaultBinder = new SmartBinder(new EntityModelBinder());
        }

            Smartbinder will have interface implemented which tells the type of the repository and list of binders that match the binding that needs to be processed . Smart binder will process the request and send the call to the appropriate binder which has the common code implemented in their area. 
           

Validation using Model


            Validation in MVC.Net is the best thing. which has reduced many lines of code. We can have inbuilt validation attributes that can be used with the model, where the validation happens henceforth. We will have brief overview on this topic since it is self-explanatory.

Use namespace System.ComponentModel.DataAnnotations for MVC.Net validation, there are multiple attributes that can be used in the model. For example our employee model can be

public class Employee : IDAOModel
    {
        [Required(AllowEmptyStrings = true)]
        [StringLength(1, ErrorMessage = "Lenghth can be maximum of 6 Characters")]
        [DataType(DataType.Text)]
        public string Name { get; set; }

        [Required]
        [DataType(DataType.Text)]
        public Int16 Age { get; set; }

        [Required]
        [DataType(DataType.Text)]
        public Guid Id { get; set; }
    }


Here we have used Required and DataType attribute for our employee model, this will automatically validate the controller when ModelState.IsValid

public ActionResult Index(Employee emp)
        {
            if (ModelState.IsValid)
            {
                return View();
            }
            else
            {
                return Content("Please enter required value") ;
            }
        }

            For validation in the MVC.Net we only need to add attribute to the model and call the ModelState.IsValid, which will take care of the validation of the model. We can have custom validation if we add the custom attribute, which must inherit ValidationAttribute.
      


Testing Routes


            Testing routes is one of the essential that cannot be neglected, since the application that we start will be entirely different from what it is then when it was started. Adding unit testing for the routes should be available for any project using MVC.Net. In our example we will be using mvccontrib’s dll for testing our routes.

Download the mvccontrib from http://mvccontrib.codeplex.com/.

To add routes to the route table we need to add the TestInitialize attribute to the method which registers the route. 

[TestInitialize]
        public void Setup()
        {
            MvcApplication1.MvcApplication.RegisterRoutes(RouteTable.Routes);
        }


Below method tests the url routing for the employee controller mapping to the "~/employee/About”. This URL does not have any parameter that will be passed.



[TestMethod]
        public void Should_Map_AboutUrl_Url_ToAbout()
        {
            
            "~/employee/About".Route().ShouldMapTo(c => c.About());

        }

Below method tests the URL routing for the employee controller mapping to the "~/employee/index/10".URL tested to handle controller where the parameter is passed.

[TestMethod]
        public void Should_Map_Index_Param_Url_ToIndex()
        {
            "~/employee/index/10".Route().ShouldMapTo(c => c.Index(null));
        }

Checklist for testing routing

  • Add following namespace
using MvcContrib;
using MvcContrib.TestHelper;
using MvcContrib.Routing;

  • Add dependency Rhino.Mocks
  • You cannot test the model binder by passing the object to the controller, Check Should_Map_Index_Param_Url_ToIndex(), for Index method null is passed as parameter. Always use query parameters

Routing testing can be done using MVCContrib for any controllers which are paremeterless and parameter controller. Route testing will help to solve lots of regression issues.


            In the blog, all the features of MVC.Net has been chosen based on the practical experience from my previous projects. Filters, Model Binding, Smart Binders, Validation using Model and Route Testing are frequently used in day to day purpose; these helped us a lot in keeping our code maintainable, extensible and reliable. Please feel free to add your comments which are the features you find most interesting and widely used.

Leave a comment to share what you learned, and hit the “like” button to let others know about it (make sure you’re logged into Facebook to like and comment). 

No comments:

Post a Comment

Note: only a member of this blog may post a comment.

Build Bot using LUIS