RSS

Monthly Archives: March 2011

ASP.NET MVC Routing

Url Routing is a cool feature that MVC depends upon. In this video, Scott Hanselman refers to the url routing as the “unsung hero” of MVC. He also demonstrated a Url Routing Debugger which will be discussed here. While I’m sure that there are quite a few things to url routing, in this post I’ll focus on what is probably a typical use of Url Routing.

Quite simply, in MVC the Url Routing’s job is to get you to the desired controller and invoke the correct method (action). Routing is performed in the Global.asax with a certain default provided by MVC templates. When you open a new ASP.NET MVC 3 project, Global.asax has a RegisterRoutes method which looks like this:

1: public static void RegisterRoutes(RouteCollection routes)
2: {
3:     routes.IgnoreRoute("{resource}.axd/{*pathInfo}" );
4:
5:     routes.MapRoute(
6:         "Default" , // Route name 
7:         "{controller}/{action}/{id}", // URL with parameters 
8:         new  { controller = "Home", action = "Index", id = UrlParameter .Optional } // Parameter defaults 
9:     );
10: }

This represents the basics. The MapRoute method presented by default consists of a “route name” (which can be anything), a url pattern (that’s the important stuff) and defaults. The url specified as the default basically means that any url in the form of http://<server>/controller/action/id will be understood as the controller to redirect to, the method to invoke and a parameter to that action. For example, a url of http://<server>/scores/list/1 will be interpreted as “go to the scoresController class, run the ‘list’ method with an id of 1”. The defaults argument in this case represents a situation where some of the url’s items are missing. So, having a url of http://<server&gt;, http://<server>/home, http://<server>/home/index or http://<server>/home/index/1 will all go to home controller and invoke the index method. The route uses the defaults to make up for missing url parameters. On the other hand, http://<server>/scores or http://<server>/home/edit will result at this stage in a 404 “resource cannot be found”, because there’s no scores controller, and no edit method in home controller (as long as we don’t add them, of course). One final thing regarding the default route: as long as you require only the most basic routing of http://<server>/controller/action and optional id, this route will be sufficient for your requests.

Adding additional routes means that you have custom route requirements which should be handled. These would probably be custom Urls to handle, which should direct the user to the different controllers and actions. In order to ensure that you’ve configured your routes correctly, it’s best to familiarize yourself with the Routing Debugger. In short, what this assembly does, is to render information summarizing the routes configured and how each url you test is being handled by those routes. In order to use the debugger, just download it from the blog above, extract it and reference the RouteDebugger.dll. After that you enter a single line of code in Application_Start (Line 8 below):

1: protected void Application_Start()
2: {
3:     AreaRegistration.RegisterAllAreas();
4:
5:     RegisterGlobalFilters(GlobalFilters.Filters);
6:     RegisterRoutes(RouteTable.Routes);
7:
8:     RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
9: }

Any url typed in will now be reflected in the rendered html, with a “true”/”false” indication as to which route handles it. This makes it so much easier to understand routing. The picture below is a part of the information rendered concerning the routing for a default url (‘/’). It’s quite clear that the first row ignores the url, and that the second row of the table is the first route which corresponds to the url (and it is the route that will be invoked in this case). The last route is added automatically and will “catchall” the routes.

Now we can add our custom routes. For example, if we wish the user to be able to enter the following url: http://<server>/scores/list/123, this can be done like so (lines 5-9):

1: public static void  RegisterRoutes(RouteCollection  routes)
2: {
3:     routes.IgnoreRoute("{resource}.axd/{*pathInfo}" );
4:
5:     routes.MapRoute(
6:         "scores" ,
7:         "scores/list/{id}" ,
8:         new { controller = "Scores" , action = "List"  }
9:     );
10:
11:     routes.MapRoute(
12:         "Default" , // Route name 
13:         "{controller}/{action}/{id}" , // URL with parameters 
14:         new { controller = "Home" , action = "Index" , id = UrlParameter .Optional } // Parameter defaults 
15:     );
16: }

It’s important to notice that as our custom route refers to a hard coded controller (“scores”) and action (“list”), we had to supply the controller and action parameters in Line 8, which will instruct the route which controller and action will be invoked.

The route debugger will show:


Because our custom route was placed above the default route, we can see in the debugger that row 2 will be the first to capture our Url. This means that there’s an importance to the order routes are being set. Routes preceding other routes will take precedence even if they all answer to the same Url.

When you’re done with the route debugger, simply comment out the line and recompile in order to proceed with the development.

As for the Url’s arguments (e.g. “id”), the controller’s action can either handle these via method arguments, or by using the RouteData.Values dictionary, but you can’t have both at the same time or you’ll get a AmbiguousMatchException (“The current request for action … on controller type … is ambiguous between the following action methods”):

1: public ActionResult List(int id)
2: {
3:     // code here ... 
4:
5:     return View();
6: }

 

1: public ActionResult List()
2: {
3:     int id = Convert.ToInt32(RouteData.Values["id"]);
4:
5:     // code here ... 
6:
7:     return View();
8: }

 

One more thing I think is worth mentioning is handling “greedy routes” and constraints. As you can easily observe, routes can easily become “greedy”, which means that they can pick up Urls that you basically intended for them to skip, in order for a different route to handle them. For example, the default route is quite greedy, as almost any url you enter will be picked up by it (whether or not you actually have a controller and action to handle them). You may also come across situations that you’d like the user to have several Urls which are very similar to each other, but they should be handled by different controllers or actions. For example, a Url can easily have an argument of a textual type and an integer type. So you might want different controllers or actions to handle them according to their data types. In all these cases you have the option of contraining a route using regular expressions. By constraining a route, routes will become less greedy and can be skipped over. As an example, lets assume that the Urls need to differ so that one of the parameters can be either a string or an integer, but with different controllers handling each one.

1: routes.MapRoute( // http://<server>/scores/3 
2:     "int scores" ,
3:     "scores/{id}" ,
4:     new { controller = "Scores" , action = "IntegerAction"  },
5:     new { id = @"\d+" }
6: );
7:
8: routes.MapRoute( // http://<server>/scores/a2 
9:     "text scores" ,
10:     "scores/{id}" ,
11:     new { controller = "Scores" , action = "TextualAction"  },
12:     new { id = "[A-Za-z].+" }
13: );

In lines 1 & 8 you can see examples of the Urls being handled. Note that we may omit line 12 and have the second route “greedier”, because we know that the first route will handle integers anyhow. Naturally, you can have different controllers assigned to handle each route.

Finally, if you insist on having actions (i.e. methods) of the same name with different overloads to handle the routes, you can place a single route to handle both integers and strings, and differentiate between the overloads using attributes, as shown here. I’m not sure why this is better than having constraints on your routes, but it’s always good to know your alternatives.

Advertisements
 
4 Comments

Posted by on 27/03/2011 in Software Development

 

Tags: , , ,

ASP.NET MVC Ajax using jQuery – Quick Start sample

Ajax is heavily used over the past few years to provide a great UI to the web site. In ASP.NET Web Forms there are several alternatives and my preferred choice is ASP.NET Ajax from Microsoft. In this post I’m excluding Partial Rendering (a.k.a. UpdatePanels), and I’m focusing on implementing a kind of PageMethods¬† / WebMethods solution in MVC. Partial Rendering is good for some cases (although many would disagree one way or another), but this is a “different discussion”.

Back to WebMethods: What I really like about them, is that the infrastructure lets you enjoy Ajax easily. In short, JavaScript proxy classes are generated for the WebMethods/PageMethods, and they easily used with JavaScript callback functions. JSON objects are handled completely automatically from and to the server.

In ASP.NET MVC things are different. No ScriptManager is there to provide the client proxies and I was expecting lots of work. However, to my surprise, Ajax in MVC was quite easy to perform using jQuery. Credits are in order: you can read a summary of the different methods available for Ajax here. The difference between this post and the referenced post, is that this post is designated to provide a quick start to performing PageMethod-like implementation of Ajax in MVC, and will not cover each and every possibility and case in which a more through reading is required.

Assuming you have an updated jQuery which supports Ajax, basically there are just 2 things to take care of in order to accomplish a successful Ajax implementation:

  1. jQuery client code which performs an Ajax request.
  2. Server-side method which will handle that request.

Starting at the Server-side, here’s a sample code (no [WebMethod] attribute is required):

1: public ActionResult GetGreeting(string name)
2: {
3:     return Content("Hello " + name);
4: }

Note that in Line 3, Content returns plain text response to the client. There are other alternatives such as Json(…), which will return, well, Json.

On the client:

1: <script type="text/javascript" src="../../Scripts/jquery-1.4.4.min.js"></script>
2: <script type="text/javascript">
3: function go() {
4:     $.get("@Url.Action("GetGreeting")" ,
5:         {
6:             name: "Joe"
7:         }, function (data){
8:             alert(data);
9:         }
10:     );
11: }
12: </script>
  • Lines 4-10 represents a simple GET Ajax call from the client, and a callback function upon success.
  • Line 4 specifies the ‘$.get’ for a GET operation (can be substituted for ‘post’ if required).
  • Also in line 4 you can see the server side code which renders an appropriate url for reaching the GetGreeting method. The Action method allows to specify a different controller than the View’s default (as assumed in this sample), and it is also possible to specify Route values.
  • Lines 5-7 represents the data sent to the server. The data is a map of keys and values. The keys have to match the server side argument names.
  • Lines 7-9 represent a callback function in the case of success (failure will be discussed below).

Basically, you can create a new MVC project, copy-paste the code sections to a Controller and a View, set the routing in the global.asax, and it’s supposed to work:

jQuery has different Ajax alternatives. These can be viewed in the documentation. Although this sample accomplishes the minimum required for a successful Ajax operation, one last thing that you might want to consider adding is error handling. There are several methods that error handling can be accomplished (especially if you decide to use $.ajax). One way to handle error handling is as follows:

1: $(document).ready(function (){ 
2:     $("#log" ).ajaxError(function (e, xhr, settings, exception) {
3:         $(this ).text(xhr.statusText);
4:     });
5: });
  • Line 1 isn’t really mandatory if you’re certain that #log has been loaded by the time ajaxError is called. You can also bind the ajaxError to $(document).
  • Line 3 simply injects the status of the error as text within #log (naturally, there are other properties that you can query and display).

Assuming that you want to send arrays to the server, this is a little ‘trick’ in jQuery 1.4 and above, as the default serialization of jQuery has changed. So, assuming that you add an int[] age array to your server side method, your client code should look something like this:

1: function go() {
2:     $.get("@Url.Action("GetGreeting")" ,
3:         $.param({
4:                     name: "Joe" , 
5:                     age: [50,30]
6:         }, true ),
7:         function (data){
8:             alert(data);
9:         }
10:     );
11: }

Line 3 uses jQuery’s param, which is what performs the serialization. The ‘true’ in line 6 specifies that you would like to use the traditional method (which serializes it so that MVC gets it correctly).

You can read more about sending arrays ‘traditionally’ here.

 
8 Comments

Posted by on 15/03/2011 in Software Development

 

Tags: , , , ,

Overriding Browser Caching with ASP.NET MVC

It’s a well-known issue that most browsers by default cache static content such as CSS or JavaScript files. You can make changes to these files on the server, and force your own browser to refresh it’s cache in order to view the latest version of these files, but you can’t force other people’s browsers to perform cache invalidation. True, you can use ETags in order to control this to a certain extent, but I found this to be non-trivial as the altervative. As a result, clients may experience what would seem like a broken html/css (or JavaScript) experience, which is bad.

One known trick is to “timestamp” your css/js references. Simply done, this means that you add some sort of version (or timestamp) to your css/js reference as a query string item. While meaningless to the css/js file itself, the browser caches according to the complete url, with query string, so providing a url which is new to the browser will cause it to download the updated file from the server.

For example:

<script type='text/javascript' src='http://.../myScripts.js?v=1'></script> 

While this was an easy solution, I still had to remember to increment the “version” specified in the query string, each time we updated the file. In development, this is quite often and therefore proved to be quite a “nagging” experience.

In ASP.NET WebForms, I used to handle this problem automatically when it came down to JavaScript files. In the different projects we used ScriptManager (and ScriptManagerProxy) often, so all JavaScripts were referenced using asp:ScriptReference. This was originally intended to solve JavaScript relativity issues with the files locations. You could simply specify a tilde (~), and the file location was resolved automatically by ASP.NET. Consider this code for example:

1: <asp:ScriptManager  runat="server"> 
2:     <Scripts> 
3:         <asp:ScriptReference  Path="~/Scripts/jquery-1.4.1.min.js"  /> 
4:     </Scripts> 
5: </asp:ScriptManager>

Using ScriptReference also provided a solution to “timestamping” the file. Using a recursion, I iterated over all ScriptManager and ScriptManagerProxy controls, usually later in the lifecycle, and added a timestamp to the ScriptReference. The only trick required was to add a version automatically so that the browser will actually load the latest version from the server, without losing the browser caching itself for files not updated. In other words, we only need to to “override” the browser cache when a file was actually updated. If not updated, we’d still like to enjoy browser caching. So the recursion mentioned earlier, which timestamped the ScriptReferences, did exactly that: we added to the querystring the actual modification date/time of the file. As long as the file was not updated, the browser used the cached version of the file. Once updated, the rendered Html contained a file reference with a newer timestamp.

While this solved the problem for JavaScript files, it didn’t provide a solution to CSS files in App_Themes, as we had no control over these (actually, it’s possible that these can be controlled too…)

Funnily enough, one of the advantages of ASP.NET MVC in this area is that there is no ScriptManager. This meant that I had to search for an alternative way to automatically timestamping the file references. Although ASP.NET MVC “lacks” WebForm server side controls, it is very convenient and flexible in the markup View. So I came up with a simple piece of code which solved the timestamping and the file relativity problem all together. No longer do I write html <script> tags when I reference a JavaScript file. Instead, I use the following extension method:

@Url.Script("~/Scripts/jquery-1.4.4.min.js" )

The implementation is as follows:

1: public static HtmlString Script(this UrlHelper helper, string contentPath)
2: {
3:     return new HtmlString (string.Format("<script type='text/javascript' src='{0}'></script>" , LatestContent(helper, contentPath)));
4: }
5:
6: public static string LatestContent(this UrlHelper helper, string contentPath)
7: {
8:     string file = HttpContext.Current.Server.MapPath(contentPath);
9:     if  (File.Exists(file))
10:     {
11:         var dateTime = File.GetLastWriteTime(file);
12:         contentPath = string.Format("{0}?v={1}", contentPath, dateTime.Ticks);
13:     }
14:
15:     return helper.Content(contentPath);
16: }

Short explanation:

  • Lines 1-4 is the extension method used in the View. These call the LatestContent method in lines 6-16.
  • Line 8 resolves the path of file on the server side and line 9 actually checks for its existence.
  • Line 11 checks for the timestamp of the file and Line 12 concats the timestamp as ticks to the query string.

Having this extension method split up into two extension methods, allowed me to add one more extension method:

1: public static HtmlString Css(this UrlHelper helper, string contentPath)
2: {
3:     return new HtmlString(string.Format("<link rel='stylesheet' type='text/css' href='{0}' media='screen' />" , LatestContent(helper, contentPath)));
4: }

So now I was also able to provide an easy solution for downloading the latest css files using the same code. So the overall code in the View looks as follows:

1: @Url.Css("~/Content/Site.css" )
2: @Url.Script("~/Scripts/jquery-1.4.4.min.js" )
 
6 Comments

Posted by on 05/03/2011 in Software Development

 

Tags: , , ,