learn forever RSS 2.0
# Friday, April 30, 2010

Ok, so I have a pretty basic web page that displays a paginated grid of a collection of my business objects.  This grid gets displayed when I perform a simple string search on let’s say my entity name.  Of course, everyone hates dealing with full postbacks, so I want to perform this search via javascript, or more specifically via an AJAX call.

Using the typical form post method common in many ASP.NET MVC applications doesn’t really work in my scenario because I’m not mapping all the necessary fields upon landing on my page. 

I know I want to use JQuery’s AJAX capabilities to post my search string information, but I couldn’t figure out a way to do it without passing in an “ugly” set of query strings and having to accept 3 parameters in my Controller Action method. 

Basically I didn’t want to have to have something like this:

  1. [NoCache][AcceptVerbs(HttpVerbs.Post)]
  2. public ActionResult SearchEntities(string searchValue, int pageNumber, int itemsPerPage)
  3. {
  4.     var allSearchResults = _entityAdministrationService.SearchEntities(searchValue);
  5.     var searchResultsForPage = allSearchResults.Skip((pageNumber - 1) * itemsPerPage).Take(itemsPerPage);
  6.  
  7.     return Json(new EntitySearchResultsInput
  8.     {
  9.         Entities = searchResultsForPage,
  10.         ItemsPerPage = itemsPerPage,
  11.         PageNumber = pageNumber,
  12.         TotalResults = allSearchResults.Count()
  13.     });
  14. }

(oh by the way, going forward in this post, ignore my custom “NoCache” attribute in any of my code snippets. It’s not related to this post, but I use it all the time now because of the automatic post caching feature in IE).

Ok, so if you look at the method signature, it really doesn’t look THAT bad right? Well it’s not bad, but the problem is that I’m sure I’ll have other pages that need to perform AJAX calls that will pass in other sets of parameters. I want one to have a nice POCO that I can work with that gets “automatically” mapped for me. 

I want something like this:

  1. [NoCache][AcceptVerbs(HttpVerbs.Post)]
  2. public ActionResult SearchEntities(EntitySearchOutput entitySearchOutput)
  3. {
  4.     var allSearchResults = _entityAdministrationService.SearchEntities(entitySearchOutput.SearchValue);
  5.     var searchResultsForPage = allSearchResults.Skip((entitySearchOutput.PageNumber - 1) * entitySearchOutput.ItemsPerPage).Take(entitySearchOutput.ItemsPerPage);
  6.  
  7.     return Json(new EntitySearchResultsInput
  8.     {
  9.         Entities = searchResultsForPage,
  10.         ItemsPerPage = entitySearchOutput.ItemsPerPage,
  11.         PageNumber = entitySearchOutput.PageNumber,
  12.         TotalResults = allSearchResults.Count()
  13.     });
  14. }

Where the definition of EntitySearchOutput is:

  1. public class EntitySearchOutput
  2. {
  3.     public string SearchValue { get; set; }
  4.     public int PageNumber { get; set; }
  5.     public int ItemsPerPage { get; set; }
  6. }

So, making use of Google’s jquery.json library, and javascript objects, I’m able to write the following javascript code that calls my “SearchEntities” controller action.

  1. var searchValue = GetSearchValue();
  2. var currentPageNumber = GetCurrentPageNumber();
  3. var itemsPerPage = 20;
  4.  
  5. var searchData = {
  6.     'SearchValue': searchValue,
  7.     'PageNumber': currentPageNumber,
  8.     'ItemsPerPage': itemsPerPage
  9. };
  10.  
  11. var searchDataJSON = $.toJSON(searchData);
  12. searchDataJSON = searchDataJSON.replace(/null/g, '');
  13. $.post(
  14.     '../ApplicationManagement/SearchEntities',
  15.     { EntitySearchOutput: searchDataJSON },
  16.     function(jsonResult) {
  17.         DisplaySearchResults(jsonResult);
  18.     }, "json");

 

So what this actually does is builds out my “searchData” javascript object and then runs through the “$.toJSON” function to convert the javascript object to a JSON object called “searchDataJSON”.  This in turn gets passed to my “SearchEntities” method as a JSON string.  As a side note, line 12 is necessary for IE so that the JSON string gets created in a way that can be de-serialized by .NETs JavascriptSerializer as you’ll see soon.

So how does the application know to convert what I passed in javascript to my EntitySearchOutput class? That’s where an IModelBinder implementation comes into play.

  1. public class JsonModelBinder:IModelBinder where T:class
  2.     {
  3.         #region IModelBinder Members
  4.  
  5.         public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  6.         {
  7.             string jsonString = controllerContext.RequestContext.HttpContext.Request.Params[0];
  8.             JavaScriptSerializer serializer = new JavaScriptSerializer();
  9.             var result = serializer.DeserializeObject(jsonString);
  10.             return serializer.ConvertToType(result);
  11.         }
  12.  
  13.         #endregion
  14.     }

As you can see, this JsonModelBinder class does not say anything about EntitySearchOutput.  As a matter of fact, it is a generic class which means, yes, it can be used for any class provided it is Javascript serializable.  So you can do this with deep complex arrays.   The one method you must implement is called “BindModel” which gives you a reference to the HttpContext. The json String exists in the HttpContext.Request parameter.  Simple use the JavaScriptSerializer framework class and deserialize to the generic type T and return the results.

The final piece of this to tie in the model binder comes in the Global.asax.cs

  1. protected void Application_Start()
  2. {
  3.     RegisterRoutes(RouteTable.Routes);
  4.     SetupIoCContainer();           
  5.     SetupModelBinders();           
  6. }
  7.  
  8. private static void SetupModelBinders()
  9. {
  10.     ModelBinders.Binders.Add(typeof(EntitySearchOutput), new JsonModelBinder<EntitySearchOutput>());
  11. }

 

The “SetupModelBinders” method makes a call that tells the MVC Framework to always using the JsonModelBinder class to create an EntitySearchOutput object when it is specified as a parameter of a controller action.

Now the controller action will work as expected:

  1. [NoCache][AcceptVerbs(HttpVerbs.Post)]
  2. public ActionResult SearchEntities(EntitySearchOutput entitySearchOutput)
  3. {
  4.     var allSearchResults = _entityAdministrationService.SearchEntities(entitySearchOutput.SearchValue);
  5.     var searchResultsForPage = allSearchResults.Skip((entitySearchOutput.PageNumber - 1) * entitySearchOutput.ItemsPerPage).Take(entitySearchOutput.ItemsPerPage);
  6.  
  7.     return Json(new EntitySearchResultsInput
  8.     {
  9.         Entities = searchResultsForPage,
  10.         ItemsPerPage = entitySearchOutput.ItemsPerPage,
  11.         PageNumber = entitySearchOutput.PageNumber,
  12.         TotalResults = allSearchResults.Count()
  13.     });
  14. }

The final part of this method returns back JSON to our javascript AJAX call, it JSON serializes an “EntitySearchResultsInput” class defined here:

  1. public class EntitySearchResultsInput
  2. {
  3.     public IEnumerable<Entity> Entities { get; set; }
  4.     public int ItemsPerPage { get; set; }
  5.     public int PageNumber { get; set; }
  6.     public int TotalResults { get; set; }
  7. }

So that the following “jsonResult” object is populated on line 17 below:

  1. var searchValue = GetSearchValue();
  2. var currentPageNumber = GetCurrentPageNumber();
  3. var itemsPerPage = 20;
  4.  
  5. var searchData = {
  6.     'SearchValue': searchValue,
  7.     'PageNumber': currentPageNumber,
  8.     'ItemsPerPage': itemsPerPage
  9. };
  10.  
  11. var searchDataJSON = $.toJSON(searchData);
  12. searchDataJSON = searchDataJSON.replace(/null/g, '');
  13. $.post(
  14.     '../ApplicationManagement/SearchEntities',
  15.     { EntitySearchOutput: searchDataJSON },
  16.     function(jsonResult) {
  17.         DisplaySearchResults(jsonResult);
  18.     }, "json");

 

You can access properties on “jsonResult” simply by typing in: jsonResult.TotalResults or jsonResult.Entities[0].Name

 

So that’s an example of one implementation of IModelBinder.  You might find other things you want to do with other types of ModelBinders.  I actually created another one for a specific scenario I had with arrays of integers.  Here is that ModelBinder. I’ll leave it to you to see how you might use it.

  1. public class CommaSeperatedStringToIntegerEnumModelBinder : IModelBinder
  2. {
  3.     #region IModelBinder Members
  4.  
  5.     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  6.     {
  7.         string commaSeperatedString = controllerContext.RequestContext.HttpContext.Request.Params["Ids"];
  8.         string[] intStringArray = commaSeperatedString.Split(',');
  9.         IEnumerable<int> intArray = intStringArray.ToList().ConvertAll<int>(delegate(string str) { return int.Parse(str); });
  10.         return intArray;
  11.     }
  12.  
  13.     #endregion
  14. }
Friday, April 30, 2010 4:15:59 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
ASP.Net MVC | IModelBinder | Inversion of Control (IoC) | JQuery
Dave Arlin
Archive
<April 2010>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Dave Arlin
Sign In
Statistics
Total Posts: 9
This Year: 2
This Month: 0
This Week: 0
Comments: 0
All Content © 2010, Dave Arlin
DasBlog theme 'Business' created by Christoph De Baene (delarou)