Calling ASP.NET MVC Action Methods from JavaScript

In a recent blog post, I wrote a a controller inspector to demonstrate Controller and Action Descriptors. In this blog post, I apply that knowledge to build something more useful.

One pain point when you write Ajax heavy applications using ASP.NET MVC is managing the URLs that Routing generates on the server. These URLs aren’t accessible from code in a static JavaScript file.

There are techniques to mitigate this:

  1. Generate the URLs in the view and pass them into the JavaScript API. This approach has the drawback that it isn’t unobtrusive and requires some script in the view.
  2. If you prefer the unobtrusive approach, embed the URLs in the HTML in a logical and semantic manner and the script can read them from the DOM.

Both approaches get the job done, but they start to break down when you have a list. For example, suppose I have a page that lists comic books retrieved from the server, each with a link to edit the comic book. Do I then need to generate a URL for each comic on the server and pass it into the script?

That’s not necessarily a bad idea. After all, isn’t that how Hypertext is supposed to work? When you render a set of resources, shouldn’t the response include a navigation URL for each resource?

On the other hand, you might prefer your services to return just comic books and infer the URLs by convention.

How does MvcHaack.Ajax help?

Thinking about this problem led me to build up a quick proof-of-concept prototype based on something David Fowler showed me a long time ago.

The library provides a base controller class, I tentatively named JsonController (I could extend it to support other formats, but I wanted to keep this prototype focused on one common scenario). This class sets up a custom action invoker which does a lot of the work.

With this library in place, a <script> reference pointing to the controller itself generates a jQuery based JavaScript API with methods for calling controller actions.

This API enables passing JSON objects from the client to the server, taking advantage of ASP.NET MVC’s argument model binding.

Perhaps an illustration is in order.

Lets see some codez!

The first step is to write a controller. I’ll start simple and step it up a notch later.

The controller has a single action method that returns an enumeration of anonymous objects. Since I’m inheriting from JsonController, I don’t need to specify an action result return type. I could have returned real objects too, but for the sake of simplicity, I wanted to start here.

public class ComicsController : JsonController {
  public IEnumerable List() {
    return new[] {
      new {Id = 1, Title = "Groo"},
      new {Id = 1, Title = "Batman"},
      new {Id = 1, Title = "Spiderman"}
    };
  }
}

The next step is to make sure I have a route to the controller, and not to the controller’s action. The special invoker I wrote handles action method selection. This prototype lets you use a regular route, but the JsonRoute ensures correctness.

public static void RegisterRoutes(RouteCollection routes) {
  // ... other routes
  routes.Add(new JsonRoute("json/{controller}"));
  // ... other routes ...
}

As a reminder, this second step with the JsonRoute is not required!

With this in place, I can add a script reference to the controller from an HTML page and call methods on it from JavaScript. Let’s do that and display each comic book.

First, I’ll write the HTML markup.

<script src="/json/comics?json"></script>
<script src="/scripts/comicsdemo.js"></script>
<ul id="comics"></ul>

The first script tag references an interesting URL, /json/comics?json. That URL points to the controller (not an action of the controller), but passes in a query string value. This value indicates that the controller descriptor should short circuit the request and generate a JavaScript with methods to call each action of the controller using the same technique I wrote about before.

Here’s an example of the generated script. It’s very short. In fact, most of it is pretty statick. The generated part is the array of actions passed to the $.each block and the URL.

if (typeof $mvc === 'undefined') {
    $mvc = {};
}
$mvc.Comics = [];
$.each(["List","Create","Edit","Delete"], function(action) {
    var action = this;
    $mvc.Comics[this] = function(obj) {
        return $.ajax({
            cache: false,
            dataType: 'json',
            type: 'POST',
            headers: {'x-mvc-action': action},
            data: JSON.stringify(obj),
            contentType: 'application/json; charset=utf-8',
            url: '/json/comics?invoke&action=' + action
        });
    };
});

For those of you big into REST, you’re probably groaning right now with the RPC-ness of this API. It wouldn’t be hard to extend this prototype to take a more RESTful approach. For now, I stuck with this because it more closely matches the conceptual model for ASP.NET MVC out of the box.

Reference the script, and I can now call action methods on the controller from JavaScript. For example, in the following code listing, I call the List action of the ComicsController and append the results to an unordered list. Since I didn’t need to mix client and server code to write this script, I can place it in a static script file, comicsdemo.js.

$(function() {
    $mvc.Comics.List().success(function(data) {
        $.each(data, function() {
            $('#comics').append('<li>' + this.Title + '</li>');
        });

    });
});

One more thing

It’s easy to call a parameter-less action method, but what about an action that takes in a type? Not a problem. To demonstrate, I’ll create a type on the server first.

public class ComicBook {
    public int Id { get; set; }
    public string Title { get; set; }
    public int Issue { get; set; }
}

Great! Now let’s add an action method that accepts a ComicBook as an action method parameter. For demonstration purposes, the method just returns the comic along with a message. The invoker serializes the return value to JSON for you. There is no need to wrap the return value in a JsonResult. The invoker handles that for us.

public object Save(ComicBook book) {
    return new { message = "Saved!", comic = book };
}

I can now call that action method from JavaScript and pass in a a comic book. I just need to pass in an anonymous JavaScript object with the same shape as a ComicBook. For example:

$mvc.Comics.Save({ Title: 'Adventurers', Issue: 123 })
  .success(function(data) {
      alert(data.message + ' Comic: ' + data.comic.Title);
  });

The code results in the alert pop up. This proves I posted a comic book to the server from JavaScript.

Message from webpage (2)

Get the codez!

Ok, enough talk! If you want to try this out, I have a live demo here. One of the demos shows how this can nicely fit in with KnockoutJS.

If you want to try the code yourself, it’s available in NuGet under the ID MvcHaack.Ajax.

The source code is up at Github. Take a look and let me know what you think. Should we put something like this in ASP.NET MVC 4?

What others have said

Requesting Gravatar... Dmytrii Nagirniak Aug 18, 2011 3:12 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Congratulations, you just reinvented Backbone, Spine, JavaScriptMVC etc that have it as the core.
Requesting Gravatar... Samarendra Aug 18, 2011 3:13 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Thanks for the post it's helpful
Requesting Gravatar... Bryan Aug 18, 2011 3:40 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
That looks pretty cool, gonna give it a spin ^^
Requesting Gravatar... Shiju Varghese Aug 18, 2011 5:13 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Really nice POC. Please put something like this in ASP.NET MVC 4.
Requesting Gravatar... Adam Aug 18, 2011 6:17 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
This is really interesting. I think something like this would be good in v4. I've used this (github.com/zowens/ASP.NET-MVC-JavaScript-Routing) before which I've found quite good to get the routes out for Js. Something like this maybe worth a look.

Cheers
Requesting Gravatar... ashic Aug 18, 2011 6:20 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Nice idea (albeit not entirely new) but I have one issue. The $mvc is doing too much: controlling urls AND request/response AND forcing jQuery... besides, there may be other uses of the urls other than server requests. You couyld, for example, need to display "details" urls in a list / grid. I think it would be more useful if the $mvc thing just returned the url strings rather than taking over the invoking of the request. (Keeping along the lines of resource discovery and all that.)

Might even be an idea to have a single routes script where $mvc would be initialized to all urls in the application (for all controllers and actions). A single cached larger js is better than having to load multiple small ones.
Requesting Gravatar... Andy Aug 18, 2011 6:45 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
I agree with @ashic that client side routing would be a great addition, but that it should not be tied to jQuery Ajax. Though I assume that a production version of this code would indeed allow both routing only and full Ajax scenarios.
Requesting Gravatar... Stacey Aug 18, 2011 8:37 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Re-invention or not, this is good stuff.
Requesting Gravatar... Elijah Manor Aug 18, 2011 8:41 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Phil,

Nice use of the new Deferred jqXHR to add the callbacks after the initial definition of the $.ajax()

As a side note...

"The jqXHR.success(), jqXHR.error(), and jqXHR.complete() callbacks will be deprecated in jQuery 1.8. To prepare your code for their eventual removal, use jqXHR.done(), jqXHR.fail(), and jqXHR.always() instead." http://api.jquery.com/jQuery.ajax/#jqXHR

Requesting Gravatar... Justin Aug 18, 2011 9:12 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
I've been getting into using backbone.js recently and having an api more integrated into MVC would be great.
Requesting Gravatar... Lee Smith Aug 18, 2011 10:00 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Fantastic! Defiantly worth adding it to version 4.

I would make this change to the JsonActionInvoker to fall back to default functionality if needed.


protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
{

if (actionDescriptor.ActionName == "Internal::Proxy" || actionDescriptor.ActionName == "Internal::ProxyDefinition")
{
return new JsonResult { Data = actionReturnValue, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
return base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);

}
Requesting Gravatar... Joey Robichaud Aug 18, 2011 10:23 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Instead of generating javascript when called. It should generate the javascript when the controller is saved. That way it can be referenced when writing the rest of your site's js.
Requesting Gravatar... A Concerned Fan Aug 19, 2011 12:13 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
"codez"

Good post but I really find it cringeworthy when you guys start latching on to ancient memes.
Requesting Gravatar... haacked Aug 19, 2011 7:17 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
@Dmytrii maybe it wasn't clear in the post, but part of the problem I solve can't be solved by a client framework alone. What I've done doesn't reinvent spine, nor backbone (I'm not familiar with the other library you mentioned). It complements it.

@Elijah Thanks!
Requesting Gravatar... Korayem Aug 19, 2011 5:16 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
It's funny. I wrote a story on Pivotal Tracker to implement the exact functionality only to find the "HAACK" got it "hacked" before me!
Requesting Gravatar... Sam Aug 21, 2011 12:08 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
I really like this. I don't see that it "reinvents" backbone or whatever. This is bridging the gap between client and server in nice, zero effort way.

FYI

Deprecation Notice: The jqXHR.success(), jqXHR.error(), and jqXHR.complete() callbacks will be deprecated in jQuery 1.8. To prepare your code for their eventual removal, use jqXHR.done(),
jqXHR.fail(), and jqXHR.always() instead.
Requesting Gravatar... Sam Aug 21, 2011 12:10 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Awww... I see Elijah already pointed the depreciation notice :S
Requesting Gravatar... Tom McKearney Aug 21, 2011 10:06 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
One thing I see as a potential problem here is that it doesn't filter out non-JsonResult Actions.

So, if you have a Controller that serves up Views AND Json data, which, correct or not, I seem to have in abundance, it adds javascript capabilities for calling for Views, which seems like unintended consequences, no?

Tom
Requesting Gravatar... an le Aug 23, 2011 10:50 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Great! thanks so much!
Requesting Gravatar... mike johnson Aug 23, 2011 11:30 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
don't get me wrong. this is a nice demo, but I'd really like MVC 4 to have a way to support html5 server side events or some sort of comet protocol. I really don't see anyone doing that in any major way. No matter what though, I always look to have MVC and ASP.NEt in general make the hard stuff easy, and the easy stuff trivial

Requesting Gravatar... jalchr Aug 23, 2011 6:08 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Those additions are welcome in asp.net mvc 4
+1 for Comet protocol support or/and Websockets ...
Requesting Gravatar... Shawn Welch Aug 23, 2011 10:37 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Yo, you look like Gilbert Gottfried.
Requesting Gravatar... Joseph Aug 24, 2011 3:30 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Looks pretty cool!
we expect much more from ASP.NET MVC 4 supplies structure to JavaScript-heavy applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing application over a RESTful resources
Requesting Gravatar... Pjotr Aug 24, 2011 11:23 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Demos gives Error on page:
JSON not defined ...
Requesting Gravatar... haacked Aug 26, 2011 5:07 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
@Mike check out SignalR. :)
Requesting Gravatar... Jess Chadwick Sep 08, 2011 10:10 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
This is super awesome, and definitely something I'd like to see in MVC 4. The need for this kind of thing is too great, as shown by the fact that I keep seeing the concept of "getActionUrl(actionName)" implemented all too often... always relying on the default "controller/action/id" route and never taking server routing rules into consideration.

One thing, though, if this were to make it into the framework: attribute or config setting (ala WCF JSON proxies), not a base class...
Requesting Gravatar... Marc Rubiño Sep 10, 2011 9:26 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
I love it and I have a couple of uses for this. The applications of today is essential for more accurate use of ajax calls from javascript. We must find a way to make most secure calls.
Requesting Gravatar... joel kurtz Oct 30, 2011 10:23 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
I know I'm a little late on this one. But while I like the javascript it's emitting and if we only had to support one format, json, it may look pretty "cool". But how do we handle different formats, make multiple controller that do work on the same object type? Maybe you have ideas on how to handle multiple formats? WCF Web Api has a formatter solution that seems to be on the right track. Anyways, just thinking.
Requesting Gravatar... Tom McKearney Nov 22, 2011 5:12 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
I think this is really nice, but I have run into a problem with it.
It does not seem to pass parameters through normal Model Binding.

I have yet to dig in, but if I call a method with a JSON object as its parameter, it does not seem to work unless I bypass the nice $mvc.Controller.Action methods.

I have no idea why, but I know it is rather disappointing :(
Requesting Gravatar... Shawn Welch Jan 04, 2012 12:09 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
@Shawn Welch, you look like a Vanilla Ice wannabe.
Requesting Gravatar... Beriz Jan 23, 2012 9:59 AM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Why not render your javascript files through the razor viewengine.
No need to render extra javascript client side and full support for @url.content inside your JS files server side...

I've posted the howto on stackoverflow:

stackoverflow.com/.../8969711#8969711
Requesting Gravatar... Nelson Lopes Jan 24, 2012 6:12 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Muito bom, ajudou e muito aqui na empresa.

Parabéns!
Requesting Gravatar... Kami Feb 14, 2012 6:21 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Did a proof of concept and it was SUPER easy to get working. Great stuff. Thanks.
Requesting Gravatar... Kami Feb 14, 2012 8:30 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
@Tom McKearney I was able to model bind the data, but only if I pasted in a JSON object with the name of the param. So if your action had an MyModel parameter called "model", you'd have to to send in an object like { model: { prop1: 'prop1value' } }
Requesting Gravatar... Kami Feb 14, 2012 8:48 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
@Tom McKearney Sorry, my action param was named "model" and my model also had a property called "model" which was confusing the model binder
Requesting Gravatar... Tom McKearney Mar 01, 2012 6:30 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Guess there's no way to make it a synchronous call in the current implementation, huh?
Requesting Gravatar... Mikhail Tsennykh Mar 15, 2012 4:20 PM
# re: Calling ASP.NET MVC Action Methods from JavaScript
Pretty cool stuff! Thanks Phil! Please add this to MVC4!!!

What do you have to say?

(will show your gravatar)
Please add 2 and 4 and type the answer here: