ProxyApi: Automatic JavaScript Proxies for WebAPI and MVC

Taking Inspiration from SignalR

One of my favourite features of SignalR is the the automatic generation of JavaScript proxies for hub methods. By adding a hub class in C#…

//server
public class ExampleHub : Hub
{
	public void SendMessage(string message)
	{
		//do something with message
	}
}

…you can get the JavaScript wrapper for that method just by adding a reference to /signalr/hubs:

//client
$(function () {
    var hub = $.connection.exampleHub;

    $.connection.hub.start(function() {
        hub.sendMessage("a message from the client");
    });
});

This is such a useful feature that it has even been suggested as an alternative to Web API controllers.

But why should we use hubs as a replacement for MVC or Web API controllers? Why can’t we instead write similar functionality to work with controllers?

Introducting ProxyApi

ProxyApi is a small NuGet package that automatically generates JavaScript proxy objects for your MVC and Web API controller methods.

PM> Install-Package ProxyApi

(as an aside, this was my first attempt at creating a NuGet package and it was embarrassingly simple).

Once you’ve installed the NuGet package you just need to add a link to ~/api/proxies and the JavaScript API classes will be automatically created.

So what do these actually give you?

API Controllers

Let’s say you have started a new MVC4 project and you add a new “API controller with empty read/write actions”:

public class DataController : ApiController
{
    // GET api/data
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/data/5
    public string Get(int id)
    {
        return "value";
    }

    // POST api/data
    public void Post([FromBody]string value)
    {
    }

    // PUT api/data/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/data/5
    public void Delete(int id)
    {
    }
}

Ordinarily if you wanted to call these methods from JavaScript you would need to write something like the example below using jQuery:

$.post("/Data/123", function(data) {
    //do something with the result
});

This is actually pretty concise, but I have 2 problems with this approach:

  1. The code calling this method knows that it is making a POST call. What do I do when I want to switch my data source to local storage, or some other data accessor?
  2. The code knows about the URL.

Instead, what I would prefer is a JavaScript proxy object on which I can call a method – passing in appropriate parameters – without ever knowing where that method gets it’s data or how it does so. And this is what ProxyApi provides.

Add a new script tag to _Layout.cshtml to ~/api/proxies (after the jQuery reference) and you can start directly calling API methods without writing another line of code yourself:


$.proxies.data.get().done(function(allItems) {
  //allItems will contain ['value1', 'value2']
});

$.proxies.data.get(123).done(function(item) {
  //item will be 'value'
});

//will send 'value' to Post method on controller
$.proxies.data.post("value");

//will send id=1, value='value' to Put method on controller
$.proxies.data.put(123, "value");

//will send 123 to Delete method on controller
$.proxies.data.put(123, "value");

These proxy objects can now be passed to any code that needs to perform data access without ever exposing how that data is sourced. You can easily mock them for unit testing, replace them if needed, and call them without needing to know where the website is hosted.

Complex Types

This is all well and good for simple data types like the strings in the example above, but what about when you want to manipulate complex types?

public class Person
{
	public int Id { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
}

public class DataController : ApiController
{
    [HttpPost]
    public void UpdatePerson(Person value)
    {
    }
}

In this case, just pass a JSON object to the generated method:

$.proxies.data.updatePerson({
    Id: 123,
    FirstName: 'Steve',
    LastName: 'Greatrex'
});

This will send the JSON object as POST data to the Post method on the controller.

And when you have both URL and body data, such as in the auto-generated Put method? Just decorate the parameters with [FromBody] or [FromUri] and the rest will be taken care of:

public void Put([FromUri]int id, [FromBody]Person value)
{
}

Note: it generally isn’t necessary to use [FromUri] as ProxyApi will assume that anything is a URL parameter unless told otherwise. The only exception to this is for POST methods that take a single parameter, which will be assumed to be POSTed.

Non-Conventional Method Names

All the examples so far have been using conventionally-named methods, but there is no requirement for this: any method name will work:

public class DataController : ApiController
{
	public void DoSomething(int id)
	{
		// --> $.proxies.data.dosomething(123) (GET)
	}

	[HttpPost]
	public void DoSomethingElse(Person person)
	{
		// --> $.proxies.data.dosomethingelse({ ... }) (POST)
	}
}

Appropriate HTTP verbs will be used for any method based on the following rules (in priority order):

  • [Http*] attribute (e.g. [HttpPost], [HttpGet] etc.)
  • [AcceptVerbs(...)] attribute
  • Method naming conventions, e.g. DeletePerson() == DELETE
  • GET for everything else

You can also specify custom names for both the proxy objects and methods using the [ProxyName] attribute.

[ProxyName("custom")]
public class DataController : ApiController
{
	[ProxyName("method")]
	public void DoSomething(int id)
	{
		// --> $.proxies.custom.method()
	}
}

Excluding and Including Elements

By default, ProxyApi will automatically include every method in every type in the current AppDomain that inherits from either System.Web.Mvc.Controller or System.Web.Http.ApiController. You can change this behaviour to exclude everything by default by changing the ProxyGeneratorConfiguration.Default.InclusionRule property:

ProxyGeneratorConfiguration.Default.InclusionRule = InclusionRule.ExcludeAll;

You can also explicitly include or exclude any element by decorating it with one of the [ProxyInclude] or [ProxyExclude] attributes:


[ProxyExclude] //excludes entire controller
public class ExcludedController : ApiController
{}

[ProxyInclude] //includes entire controller and all methods...
public class IncludedController : ApiController
{
    [ProxyExclude] //...except for explicitly excluded methods
    public void ExcludedMethod() {}
}

public class DefaultController : ApiController
{
    [ProxyExclude] //will always be excluded
    public void ExcludedMethod() {}

    [ProxyInclude] //will always be included
    public void IncludedMethod() {}

    //will be included or excluded based on the globally configured default
    public void DefaultMethod() {}
}

Returning Data & Handling Errors

The generated proxy methods all return an instance of the jQuery $.Deferred object, so you can use the done, fail and complete methods to handle the results from the controller acctions:

$.proxies.person.getAllPeople()
    .done(function(people) {
        //people will contain return value of PersonController.GetAllPeople(), if it succeeds
    })
    .fail(function(err) {
        //this will be called if the controller throws an exception
        //err contains exception details
    })
    .complete(function() {
        //this will be called after success or failure
    });

You can get more information on how to use the jQuery Deferred object from the documentation

MVC Controllers

The examples above are all based around Web API, but everything will work with MVC controllers as well:

$.proxies.home.index().done(function(content) {
    //content will contain HTML from /Home/Index
});

Source Code

I’ve put the source code (including unit tests) on GitHub so feel free to take a look around and make any changes you think are useful.

This is an early version and will probably change quite quickly, so keep an eye out for new developments. If you have any feature suggestions then leave them in the comments (or fork and write them yourself)!

Advertisements

24 thoughts on “ProxyApi: Automatic JavaScript Proxies for WebAPI and MVC

  1. Pablo says:

    I’m real interested in using this code, but I’m getting this generated causing the javascript to break and was hoping you could tell me how to exclude this from apearing in the generated code, should i just remove odata alltogeher?
    $.proxies.entityset`2 = {
    get_odatapath: function() {
    return invoke(“/api/proxy/entityset`2/get_odatapath”, “get”, {});

  2. version 0.1.14.24505 (the latest I seem to get from nuget)
    I see to be having the same problem Pablo is having.

    $.proxies.locatemeapi`1 = {
    defaultOptions: {},
    antiForgeryToken: defaultAntiForgeryTokenAccessor,

    is being generated because locatemeapicontroller is a generic base class derived from Controller, even though its in another dll, not my web project.

    also, is there a way to set it to proxyexclude by default. To fix the above, I’ve had to proxy exclude all my basecontroller classes especially the generics.

    Thanks.

    • Hi – sorry to hear you’re having a problem.

      The controller locator looks for any non-abstract extensions of Controller or ApiController that aren’t excluded so it doesn’t automatically filter out generics – perhaps it should…

      You could make your base controllers abstract, but that’s not really any better than adding the ProxyExclude attribute.

      There isn’t a global configuration for settings at the moment so you can’t exclude by default.

      Obviously you can make any of these changes yourself at http://GitHub.com/stevegreatrex/proxyapi (or even just raise the issues)

  3. Avinash says:

    ProxyApi is working great when the proxies are a part of the same mvc project.

    I was wondering how I can generate proxies for WebApiControllers that are a part of another WebApi project. I have all my APIs in another project and need to call them regularly from my website. Is there anyway I can use ProxyApi to call those WebApiControllers?

  4. John Thomas Warner says:

    I’m having issues getting this to work in VS2013 Update 2.
    Let me apologize up front for my lack of understanding.
    I performed the following steps:
    1. installed the package to the project from Package Manager Console
    a. I noticed that the reference got added
    2. I added ProxyInclude & ProxyName attributes to one of my controllers
    3. I clicked Rebuild
    Questions:
    1. What other steps am I missing in order to generate the .js file?
    2. What is the default output directory for the .js file?

    Thanks

    • Hi John. The .js is generated on the fly so you can just add a script tag pointing to ~/api/proxies to load it into your pages.

      There is a sample project in the Github repo if you are having problems…

  5. Hi Steve this is excellent.

    i am trying to use proxy api with MVC Controllers in different MVC Areas but generated proxies are same.

    For example if i have HomeController in 2 different areas i am getting the same proxy.

    Can you check this?

    Thank you.

  6. Karthik Sundharesan says:

    Hi, I would like to use the proxy generator to expose my methods to another service in C#. Do you have a sample project on how to do that in c# instead of java script?

    • Karthik Sundharesan says:

      I want to provide more details. My intention is to use the proxy generator to create a client to expose the methods in my web api controller. Then I am planning on creating nuget package of the client internally to the consumer of the web api service that I am working on.

  7. SemanticBeeng says:

    A similar, typesafe but not .Net solution would be to use Scala.js to compile (say) Play Framework action/controller APIs to JavaScript. One could then write executable specifications for that layer using uTest.

    Some references
    https://github.com/scala-js/scala-js/blob/master/examples/testing/src/test/scala/ElementCreatorTest.scala

    Then one can use ‘AutoWire’ to have client side code and server side implementation share a common, again, typesafe interface.

    See https://github.com/lihaoyi/autowire/blob/master/shared/test/scala/autowire/UpickleTests.scala
    Doing so one gets the JSON marshalling for free with uPickle.

    For large apps this will make a difference due to ability to refactor both implementation on server and client application logic.

  8. burak says:

    Hi ,I have a problem, how to set request content type from form to json. All my request content types are application/x-www-form-urlencoded; charset=UTF-8 and I cannot read datetime at server side properly.

    • Hi Burak,

      You can set the defaultOptions to specify the content type:

      $.proxies.yourController.defaultOptions.contentType = “application/x-www-form-urlencoded; charset=UTF-8”;

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s