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.
|
|||
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 IRepositorywhere 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.
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.