Updating to ASP.NET MVC 2 Preview 2

Posted by on in Blogs
Last week, I updated our main development branch to ASP.NET MVC 2 preview 2 (from preview 1). In this post, I'll list some of the features I've found, and also issues I encountered and how I resolved them.

New Features


Some of the new features of preview 2 have been discussed elsewhere, so I won't rehash them. But I've also noticed that there is a new attribute, [RequireHttps], which does what you would expect, when added to an action, and a new HTML helper, Html.HttpMethodOverride, which makes it easy to take a POST request and code as if the request were actually PUT or DELETE, by adding a hidden input containing a special value on the HTML form. This allows you to write your server in a more RESTful style, which will be suitable for user agents which know about the PUT and DELETE verbs, while maintaining compatibility with those (like browsers) which do not.

MvcHtmlString


After installing the new assembly into the GAC, I attempted to compile our existing projects. I got a compilation error on some of our custom HTML helpers, as the MVC extension methods like Html.RouteLink have been changed to return an instance of type MvcHtmlString instead of String. In many cases, I could just change the return type. However, the clear intention of the framework designers is that any HTML helpers which return HTML (with angle brackets) instead of "plain" text should return a MvcHtmlString instead of a String. So, in addition to changing the return types of methods where necessary to get those methods to compile, I also wanted to change the return types of any custom HTML helper which returned HTML containing angle brackets. It makes no difference today, but in ASP.NET 4, there will be a new syntax to guard against XSS attacks, and it makes sense to get ready for this by returning the correct type.

The trick here is that MvcHtmlString does not have a public constructor, so it's not immediately obvious how to create one of these things. Perusing the MVC source code, I noticed that it does have a static Create method, and comments elsewhere in the source code indicate that this method should be used instead of the detected constructor. So you can now write a helper like this:

public static MvcHtmlString Fud(this HtmlHelper helper)
{
return MvcHtmlString.Create("<acronym title=\"Fear, Uncertainty, and Doubt\">FUD</acronym>");
}


JsonRequestBehavior


ASP.NET MVC will now, by default, throw an exception when an action attempts to return JSON in response to a GET request. Unless you read the release notes carefully, you won't discover this until runtime. This is in order to proactively defend against a particular cross-site attack. It is good to be safe by default, but you are only vulnerable to this attack under the following combination of circumstances:

  • The data you're returning is worth stealing.

  • The root data object in the JSON response is an array.

  • The requesting browser is not IE 8, or some other browser which doesn't allow __defineSetter__


It turns out that in our application we almost never return JSON results with the root object as an array in a GET. So the best fix was generally just to tell the framework that we have examined the risk and determined it returning JSON in this case, by changing code like:
return Json(model);

...to:
return Json(model, JsonRequestBehavior.AllowGet);

Unfortunately, I had to make this change in a lot of places. Still, I agree with the framework designers: Better safe than sorry.

JavaScript Files


The MicrosoftAjax.js, MicrosoftAjax.debug.js, MicrosoftMvcAjax.debug.js, and MicrosoftMvcAjax.debug.js files have all changed for ASP.NET MVC 2. Existing ASP.NET MVC 1.0 projects will have old versions of these files. The upgrade instructions in the Release Notes don't mention this, but (they do; I just missed it) you should replace these files with the newer versions when upgrading to MVC 2. To do this, create a new MVC 2 project, copy the JavaScript files from the Scripts folder in this new project, and paste them over the existing files in the project you are upgrading.

ModelBindingContext Changes


One of the new features of preview 2 is client-side validation and custom metadata providers for validations. In order to implement this feature, some of the implementation details of model binding have changed. This won't affect most people, but the fix was a little tricky, and required a good bit of examination of the MVC source code, so I'll list it anyway. Prior to preview 2, I had code in a unit test for a custom model binder like this:

        internal static T Bind<T>(string prefix, FormCollection collection, ModelStateDictionary modelState) where T:class
{
var mbc = new ModelBindingContext()
{
ModelName = prefix,
ModelState = modelState,
ModelType = typeof(T),
ValueProvider = collection.ToValueProvider()
};
IModelBinder binder = new MyModelBinder();
var cc = new ControllerContext();
return binder.BindModel(cc, mbc) as T;
}


With Preview 2, on the other hand, I have to do this:

        internal static T Bind<T>(string prefix, FormCollection collection, ModelStateDictionary modelState) where T:class
{
var mbc = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(T)),
ModelName = prefix,
ModelState = modelState,
ValueProvider = collection.ToValueProvider()
};
IModelBinder binder = new MyModelBinder();
var cc = new ControllerContext();
return binder.BindModel(cc, mbc) as T;
}


Not hard once you know the trick.


Comments

  • Guest
    Joshua Keel Monday, 12 October 2009

    Hey Craig,

    Have you tried out the new client side validation features yet? I've been having some trouble getting them to work. Just wanted to know your experiences.

  • Guest
    David Moorhouse Wednesday, 21 October 2009

    Hi Craig

    Did you ever get Intellisense (Code Completion) to work in Delphi Prism with strongly typed data in the aspx files ?

  • Guest
    Dick Norman Wednesday, 18 November 2009

    Craig,

    First, Kudos on your blog. I found it about the first day you started your series on MVC/JSON/JqGrid and you are one of the best of the best.

    The only thing I didn't like about your solution was the label redundancy in JSON:
    {"Total":1,"Page":1,"Records":2,"Rows":[{"ID":3,"BC_LASTNAME":"Ajax","BC_FIRSTNAME":"Mike","BC_MIDDLEINITIAL"
    :null},{"ID":1,"BC_LASTNAME":"Gutierrez","BC_FIRSTNAME":"Sandy","BC_MIDDLEINITIAL":null}],"UserData"
    :null}

    I've been very effective with essentially my own formatted JSON using the following, based upon your original guidance:

    JsonResult jres = JsonHelper.ToJsonResult(contacts, totalPages, page, totalRecords, "ID", new string[] { "ID", "BC_LASTNAME", "BC_FIRSTNAME", "BC_MIDDLEINITIAL" });
    return jres;

    My solution generates the column names only once and works great. Then comes MVC 2 with JsonRequestBehavior.AllowGet. Since it isn't built technically by Json, there doesn't appear to be a way to use the JsonRequestBehavior, yet it still fails with the JsonRequestBehavior error on VS2010.

    I haven't found a way to marry the two approaches. Any ideas?

  • Guest
    Dick Norman Wednesday, 18 November 2009

    Craig,

    Thanks for the swift response (another early riser).

    I tried something similar the other day w/o success, but maybe I got my hands crossed. I'll give it a shot.

  • Guest
    Dick Norman Thursday, 19 November 2009

    Craig,

    Yet another twist. The pre-2.0 data looks like this:

    {"total":1,"page":1,"records":2,"rows":[{"id":"3","cell":["3","Ajax","Mike"," "]},{"id":"1","cell":["1","Gutierrez","Sandy"," "]}]}

    Now jqGrid chokes - presumably because of the ContentEncoding and ContentType, though the JsonRequestBehaviour passed as expected:

    {"ContentEncoding":null,"ContentType":null,"Data":{"total":1,"page":1,"records":2,"rows":[{"id":"3","cell"
    :["3","Ajax","Mike"," "]},{"id":"1","cell":["1","Gutierrez","Sandy"," "]}]},"JsonRequestBehavior"
    :1}

  • Guest
    Dick Norman Thursday, 19 November 2009

    Craig,

    Good catch! Thanks for the help.

  • Guest
    firefly Monday, 18 January 2010

    Craig,

    Just to verify the Json data that you showed on your jqgrid demo is an array right?

    Response: No, it's one object which contains an array as a property. Did you look at it?


    In another word it might be vulnerable to the cross site attack if I leave it as it is?

    Response: No, only GETs which return an array as the "root" object are vulnerable to http://haacked.com/archive/2009/06/25/json-hijacking.aspx" rel="nofollow">this kind of attack.

  • Guest
    firefly Thursday, 21 January 2010

    I did look at it but I wasn't sure. Thanks for the verification :)

  • Guest
    Rafiqul Islam Thursday, 7 April 2011

    Hi,
    After upgrading from mvc 1 to mvc 2, I am not getting the form data in controller from the view. It's happening only in IE though. Other browswes are fine.
    Any thought??

    Thanks

  • Please login first in order for you to submit comments
  • Page :
  • 1

Check out more tips and tricks in this development video: