Blog

Dealing with javascript or JSON results after an AJAX call with Ajax.ActionLink, unobtrusive AJAX and MVC 3

27-02-2011 by Sergi Papaseit

I’ve recently updated an MVC 3 project to use unobtrusive ajax. If you create a new ASP.NET MVC 3 project unobtrusive ajax will be enabled by default, but this was a project I had once migrated from MVC 2. Instead of physically adding the unobtrusive ajax JavaScript file to my solution I got it from the Microsoft Ajax CDN.

The unobtrusive ajax libraries not only leave your html cleaner by using the HTML 5 compatible “data-” attribute, but return a much clearer set of results objects from the controller.

The latter was, for me, a great relief, because I always ended up taking care of responses to ajax calls in several different ways within one application. Do I return strings from the controller action or do I go with Json? Or should I always return a HttpStatusCodeResult?

I’ve finally found a pattern, for lack of a better word, that I find clean, clear and consistent. Here’s how.

 

Calling the controller

Imagine we want to make an ajax call to a controller to, say, change the status of an order. In our view we simply do:

@Ajax.ActionLink("Set to pending", "setstatus", "order", 
    new { id = Model.ID, status = 0 }, new AjaxOptions 
        { OnFailure = "Error", OnSuccess = "StatusChanged", 
            HttpMethod = "POST" })

 

Two things to notice in the AjaxOptions object:

  1. I explicitly set the HttpMethod to POST. We will be returning a Json object from the controller and the unobtrusive ajax library “encourages” us to avoid going to the server with a GET request for security reasons. You’ll know you have this problem because an exception will be thrown saying “This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.
  2. I’ve defined two callback methods, one for error and another for success. These methods will be passed a default set of arguments by the unobtrusive ajax library which we will be able to use from our action method to return the status of the operation and any meaningful messages we deem necessary. These are the method signatures: 
function Error(response, status, error) {
}

function StatusChanged(response, status, data) {
} 

 

Action

We want our action to give a meaningful error message when things go wrong, and to return for instance the id and new status of the order if everything goes OK.

[HttpPost]
public ActionResult SetStatus(int id, int status)
{
    try
    {
       // Code to update the order status here

        return Json(new
        {
            ID = id, 
            NewStatus = order.StatusDescription
        });
    }
    catch
    {
        return new HttpStatusCodeResult(418, 
            "Couldn't change the status of the order");
    }
}

Notice how we’ve marked our action to accept HttpPost request only? This is of course because we specified POST as the HttpMethod in our ActionLink to solve JsonReuqestBehavior problem mentioned above. Another solution is to ignore the Http verbs and methods altogether and add JsonRequestBehavior .AllowGet as second parameter of the Json result.

 

Dealing with the results

Imagine we’ve just changed the status successfully; our action method returns a Json result containing an anonymous type with two properties, ID and NewStatus. We can define these with whatever name we like, or even create a property that is itself an anonymous type. It will all be serialized to Json by ASP.NET MVC. How do we use these on the view? Quite easy really:

function StatusChanged(response, status, data) {
    alert("order #:" + response.ID + "has a new status: " 
        + response.NewStatus);
}

Of course, you’d want to do something a tad more meaningful with your results, but the essence of it is this: The properties you defined in your anonymous type are directly available through the response parameter. Had you defined a property that was itself an anonymous type, named for instance Extras, with other properties in it, you’d simply do response.Extras.WhateverProperty.

 

And if things go south?

As you can see we have a nice try..catch in our action. When an exception is thrown or there’s an error we want to signal we need make it known in the response. That basically means returning a status code other than 200 (“OK”). Here I’m returning the Http Status Code 418: “I’m a  teapot” :þ I promise it is a valid http status code; if you don’t believe me, check Wikipedia!

Anyway, when returning a status code other than 200 our Error function will be called. How doe we deal with the error message? As easy as before:

function Error(response, status, error) {
    alert("Oops! " + response.statusText);
}

 

So there you have it. Do you know of a better/different way? Let me know!

 

Leave a comment

14 comment(s)

gravatar
Andrew 2011-03-01 08:51

Good post!

gravatar
Matt 2011-03-04 01:01

Hey, great post. I'm new to MVC and am just starting with MVC3. I can't seem to get this working.

I'm just setting this up as POC, and have this in my EventsController:

[HttpPost]
public ActionResult CheckIn(string contact)
{
try
{
return Json(new
{
Contact = contact
});
}
catch
{
return new HttpStatusCodeResult(418,
"Couldn't register this person");
}
}

Javascript:
function StatusChanged(response, status, data) {
alert("contact: " + response.contact + "has been registered");
}

HTML:
@Ajax.ActionLink("Register", "CheckIn", "Events",
new { contact = att.Name }, new AjaxOptions
{ OnFailure = "Error", OnSuccess = "StatusChanged",
HttpMethod = "POST" })

My action link looks like this:

[a href="/Events/Register/5?contact=Chris" data-ajax-success="StatusChanged" data-ajax-method="POST" data-ajax-failure="Error" data-ajax="true"]Register[/a]

But the URL isn't posting back to my controller. I don't know if I need to map my route to /Events/Checkin or what. But clicking the link just takes me to 404 not found.

Thoughts?

gravatar
Matt 2011-03-04 01:01

whoops, sorry about the lack of formatting.

gravatar
Sergi 2011-03-04 15:03

Hey Matt,

No worries, the lack of formatting is my fault for not allowing html ; )

I think I know what the problem is: you action is defined as public ActionResult CheckIn(string contact), but in you generated link you have 2 parameters, the number 5 (probably an ID) and the contact parameter.

This is because of this bit in your html:
new { contact = att.Name } (inside the ActionLink). Just change it to new { id = att.Name } and your action signature to:
public ActionResult CheckIn(string id) and it should work.

If you really want the parameter to be called "contact" you could change your route definitions to accept a parameter called "contact" instead of "id".

Hope this helps

gravatar
Matt 2011-03-04 15:22

Thanks Sergei. Yours was the best explanation of using the Ajax.ActionLink. Thanks for posting it. I did get it working. I was missing the reference jquery.unobtrusive-ajax.min.js. Another question: I am using an image to be the button to click to perform the ajax action, in this case marking someone as "checked in". I couldn't see a way to set the image using ActionLInk, so I just took the generated html link and wrapped it around an image. Is there a better way to do this?

gravatar
Sergi 2011-03-04 15:45

You're welcome. Check out this question on StackOverflow for a good answer to using images with ActionLinks: http://stackoverflow.com/questions/341649/asp-net-mvc-ajax-actionlink-with-image

gravatar
Matt 2011-03-04 16:42

Thanks. I'm coming from an aspx, .net 3.5 world and am a couple weeks into my MVC3/EF4 worldview conversion. It is so hard to find good help online for MVC3 since it is so new. Your site is now bookmarked ;)

gravatar
Sergi 2011-03-04 17:33

Hehe Happy to help. I have a lot more fuin developing MVC 3 apps than I ever had with aspx. Hope you enjoy it too. Is there anything in particular you'd like to read about? It'd help me as well got get new ideas for a next post ; )

gravatar
Martijn Muurman 2011-05-27 09:14

Thanks Matt. I had exactly the same problem (forgot the unobtrusive framework).

gravatar
Carlo 2011-06-22 18:42

Great post about organizing ajax calls and making it more "uniform".

A friendly question though... what's wrong with GET? I quite hear this a lot with developers nowadays? Shouldn't setting up the content type of your ajax call to "application/json; charset=utf-8" resolve the "security issues"?

I think a lot has misunderstood it during the Asp.net Ajax 1.0 days when the asp.net team resolved the cross site request forgery attack issue. What asp.net did really was to check whether the content-type of the request is "application/json", if the Content-Type HTTP header content type is application/json then asp.net will accept the request, if not then it will reject it.

I believe GET has its own perks, for one, (please correct me if I'm wrong) browsers cache GET requests so that when exactly the same GET request is made, then they will display the cached result rather than returning the entire request. Really helpful especially when you really don't need to "post" anything to the request.

gravatar
Sergi 2011-06-22 19:11

@Carlo, Thanks a lot.

About making Ajax.ActionLink work with GET, you can do that no problem, but as I mention in the post, you'll need to add "JsonRequestBehavior.AllowGet" as a second parameter to the JsonResult, as in:

return Json("", JsonRequestBehavior.AllowGet);

Otherwise MVC will block the call.

gravatar
Sneha Mandavia 2011-11-12 10:51

Hi Sergi,
I have tried same example in my MVC2, nut in java script in return un defined.
My code in view

<%=Ajax.ActionLink("Search", "CriteriaSubmit", "Search",
new { FieldID = Model.FieldID, ConditionID = Model.ConditionID }, new AjaxOptions
{
UpdateTargetId = "divSearchCriteria",
OnSuccess = "fillList",
HttpMethod = "POST"
}) %>

in controller

public ActionResult CriteriaSubmit(int? FieldID, int? ConditionID, FormCollection collection, ScanMyCard.Models.CardModel Model)
{
return Json(new
{
Status = true
});
}


and in java script
function fillList(result){
alert(result.Status)
}

gravatar
Zia Ahmed Shaikh 2012-04-01 14:44

Wawoo. Nice post, just tried it for the first time and it worked !

Thanks Sergi.

gravatar
2012-04-09 13:57

6

Leave a comment