Table of Contents
Introduction
Hello all, long time since you heard from me, huh? Well, I have not been dormant, I am actually working on a pretty large Open Source
tool for developers; it is kind of an organizational tool which is eating up all my time. In fact, it's not just my time it is eating,
it is also eating up a fair chunk of fellow CodeProjecter Pete O'Hanlon's
time, who foolishly, um nicely, volunteered to help me out with creating this tool.
The tool is coming along nicely, it is actually an ASP MVC 3 web site, and some of the stuff that I have done in it is pretty useful I think,
so I will be creating a few articles about some of the stuff it uses, and I will of course be writing up an article or two about how it works.
So what is this article all about? Well, one thing that the tool that Pete O'Hanlon and
I are working on needs to do is expose a JSON (Java Script Object Notation) API that people can use. This same API also needs to be
callable from within a standard .NET application. That is really what this article is all about. It shows one way (there are loads of ways, this is just one way)
of how you might go about exposing a JSON API that is callable both from the web and via web based APIs from a standard desktop .NET client app,
with the minimum of fuss, no config, other than the standard one you get with .NET installed.
It does however make use of ASP MVC 3 so that is assumed, as is .NET 4 and VS2010.
Available Frameworks
At first glance, you might think of the problem as being quite suited to some sort of WCF service that could be called by both JavaScript and standard .NET
code. So that really gives us two possibilities really:
RESTFul WCF
This came out in .NET 3.5 SP1 and allowed to write a normal WCF service that you could attribute up with special web attributes, something like:
[ServiceContract]
public interface ICalculator
{
[OperationContract]
[WebInvoke(UriTemplate = "add?x={x}&y={y}")]
long Add(long x, long y);
[OperationContract]
[WebInvoke(UriTemplate = "sub?x={x}&y={y}")]
long Subtract(long x, long y);
[OperationContract]
[WebInvoke(UriTemplate = "mult?x={x}&y={y}")]
long Multiply(long x, long y);
[OperationContract]
[WebInvoke(UriTemplate = "div?x={x}&y={y}")]
long Divide(long x, long y);
[OperationContract]
[WebGet(UriTemplate = "hello?name={name}")]
string SayHello(string name);
}
public class CalcService : ICalculator
{
public long Add(long x, long y)
{
return x + y;
}
public long Subtract(long x, long y)
{
return x - y;
}
public long Multiply(long x, long y)
{
return x * y;
}
public long Divide(long x, long y)
{
return x / y;
}
public string SayHello(string name)
{
return "Hello " + name;
}
}
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8000/");
WebServiceHost svcHost =
new WebServiceHost(typeof(CalcService), baseAddress);
try
{
svcHost.Open();
Console.WriteLine("Service is running");
Console.WriteLine("Press enter to quit...");
Console.ReadLine();
svcHost.Close();
}
catch (CommunicationException cex)
{
Console.WriteLine("An exception occurred: {0}", cex.Message);
svcHost.Abort();
}
}
}
But you also need to use a new WebServiceHost
to host this service, and you also have to use a specific port for the service.
I actually wrote a pretty substantial article on this a while back; if you are interested, you can read about that
at http://www.codeproject.com/KB/smart/GeoPlaces.aspx.
WCF Web API
The second option available to your average .NET developer is to use the WCF Web API which is being
pushed by one of my favourite chaps at Microsoft, Mr. Glenn Block. It can be kind of seen as a progression of the RESTful WCF stuff. It is quite new, and not
part of the .NET Framework (yet).
Here is a small example of how you might configure a service using the WCF Web APIs, where
this would typically reside in a web site (such as an ASP MVC one):
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Net;
using System.Net.Http;
using Microsoft.ApplicationServer.Http.Dispatcher;
namespace RESTFul_WCF
{
[ServiceContract]
public class EFResource
{
int dummyCounter = 0;
[WebGet(UriTemplate = "",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
public IQueryable<Models.Author> Get()
{
List<Models.Author> authors = new List<Models.Author>();
for (int i = 0; i < 5; i++)
{
authors.Add(new Models.Author(
i,string.Format("Some Author_{0}", i.ToString())));
}
return authors.AsQueryable();
}
[WebInvoke(UriTemplate = "", Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
public Models.Author Post(Models.Author author)
{
if (author == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
author.Id = dummyCounter++;
return author;
}
}
}
Where you might have this in your Global.asax.cs file (this is ASP MVC specific, there will be an equivalent setting if not using ASP MVC, I am sure):
private void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapServiceRoute<EFResource>("ef");
}
Note: One of the readers of the article actually pointed out that you can get the WCF Web APIs
to work nicely with ASP MVC and its routing goodness. Although this is not in the attached demo app, this is how you would do it.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new ServiceRoute("api/resource",
new HttpServiceHostFactory(), typeof(EFResource)));
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
And that is all there is to the service part (OK, there is a very small bit of config, but way less than the old versions of WCF).
So what about a consuming client? What would that look like? Well, it looks like this, this is it in its entirety, no config needed at all:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Xml.Serialization;
using Microsoft.ApplicationServer.Http;
using Models;
namespace RestFul_Test
{
class Program
{
private enum MimeFormat { JSON, Xml };
static void Main(string[] args)
{
string uri = "http://localhost:8300/ef";
HttpClient client = GetClient(uri, MimeFormat.JSON);
var response = client.Get(uri);
Console.WriteLine("=========GET==============");
foreach (Author author in response.Content.ReadAs<List<Author>>())
{
LogAuthorResultToConsole(author);
}
Console.WriteLine("\r\n");
Console.WriteLine("=========POST==============");
var newAuthor = new Author { Name = "Xml Barber" };
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));
request.Content = new ObjectContent<Author>(newAuthor, "application/json");
request.Headers.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
response = client.Send(request);
var receivedAuthor = response.Content.ReadAs<Author>();
LogAuthorResultToConsole(receivedAuthor);
Console.ReadLine();
}
private static void LogAuthorResultToConsole(Author author)
{
Console.WriteLine(string.Format(CultureInfo.InvariantCulture,
"\r\n Author Name: {0}, Author Id: {1}", author.Name, author.Id));
}
private static HttpClient GetClient(string uri, MimeFormat format)
{
var client = new HttpClient(new Uri(uri));
switch (format)
{
case MimeFormat.Xml:
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/xml"));
break;
case MimeFormat.JSON:
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
break;
}
return client;
}
}
}
I really like this, but it requires the use of quite a few extra DLLs, even the client needs a few extra DLLs.
Note: I have included a small demo app of this at the top of the article, as I thought it may be of interest to some folk out there.
Problems With These Frameworks
There are several problems which need to be considered, which I will go into one by one below:
RESTFul WCF
Using RESTful WCF (which was available in .NET 3.5 SP1) would have worked, but there are several hurdles:
- It needs to use an extra port. Now since this is part of a web site, I am already using one port for the actual web
site. Using the RESTful WCF stuff, I would now need to maintain another port.
- The .NET client needs WCF DLLs as well as some special REST WCF DLLs.
- Need to create a client side proxy.
- Maintain an App.Config for all the WCF configuration client and server.
- Use of eTags if doing things properly.
- Maybe have to get into MessageInspector to change
ContextType
for HttpRequest
; JSON is supported, so that would have been OK, but you see what I mean?
WCF Web API
I am a massive fan of the WCF Web API that Glenn Block and his team are working on; however, this too has a few issues for me, namely:
- The thing that put me off using the WCF Web API was the sheer amount of extra DLLs that were needed on the server; I was quite
shocked when I installed the nuget package for the WCF Web API.
- Even the client needed extra DLLs (
HttpClient
and others).
The good thing is there is zero config needed, it just works. OK, you do need some of the extra WCF Contrib stuff to make it work really well with
DataContract
serialization, but that's tolerable.
That said, there is quite good support for the WCF Web APIs in ASP MVC, which you can read
about here: http://wcf.codeplex.com/wikipage?title=Getting%20started:%20Building%20a%20simple%20web%20api.
As I say, I really liked the WCF Web APIs and if it was not for all the DLLs that were needed,
I would have probably gone with this.
A Possible Solution (worked for me at least)
So now that I have outlined what I found objectionable with the existing solutions, what other choice did I really have left available to me? What I
really wanted was no extra port to maintain/open, no configuration, no extra DLLs, the code should work when called via JavaScript, say via
jQuery and from a standard .NET client app using standard .NET web APIs.
Use ASP MVC
The logical conclusion I came to was to just use ASP MVC itself. This is good for a number of reasons, namely:
- ASP MVC controllers can be called from any JavaScript including jQuery obviously
- The ASP MVC routing is already there
- The web site is already available on a port, no need to make another one available
- ASP MVC controller is just a URL, as such can be called by standard .NET web APIs with ease
- ASP MVC 3 (which is what I am using) actually comes pre-canned with certain
ValueProviderFactory
objects, of which
JsonValueProviderFactory
is one
Basically, ASP MVC does lots of work for you.
So what would this typically look like? Well, let's have a look, shall we?
Here is a full ASP MVC 3 controller that accepts JSON and also returns JSON. There are three different actions there, which we will talk about later within this article.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Common;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
using CodeStash.Filters;
using System.Json;
using RestfulWebSite.ModelBinders;
namespace RestfulWebSite.Controllers
{
public class RestController : Controller
{
[JSONFilter(Param = "person", RootType = typeof(Person))]
public ActionResult Index(Person person)
{
int age = person.Age;
List<Person> people = new List<Person>();
for (int i = 0; i < 5; i++)
{
string s = string.Format("{0}_{1}", "hardCoded", i.ToString());
people.Add(new Person(i, s, new Parent(1, "parent")));
}
return Json(people);
}
public ActionResult NoFilterJSON(Person person)
{
int age = person.Age;
List<Person> people = new List<Person>();
for (int i = 0; i < 5; i++)
{
string s = string.Format("{0}_{1}", "hardCoded", i.ToString());
people.Add(new Person(i, s, new Parent(1, "parent")));
}
return Json(people);
}
public ActionResult JsonValue(
[DynamicJson(typeof(Person))]
JsonValue person
)
{
var x = person.AsDynamic();
int age = x.Age;
List<Person> people = new List<Person>();
for (int i = 0; i < 5; i++)
{
string s = string.Format("{0}_{1}", "hardCoded", i.ToString());
people.Add(new Person(i, s, new Parent(1, "parent")));
}
return Json(people);
}
}
}
Customising MVC Behaviour
For the first action shown above, one thing that must be done when calling this ASP MVC controller code directly from a standard .NET client (at least the client
code associated with this article) is to use a custom ActionFilterAttribute
to supply the controller action model data.
This is shown below:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Text;
using System.Json;
namespace CodeStash.Filters
{
public class JSONFilter : ActionFilterAttribute
{
public string Param { get; set; }
public Type RootType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
try
{
string json = filterContext.HttpContext.Request.Form[Param];
string contentType = filterContext.HttpContext.Request.ContentType;
switch (contentType)
{
case "application/x-www-form-urlencoded":
if (json == "[]" || json == "\",\"" || String.IsNullOrEmpty(json))
{
filterContext.ActionParameters[Param] = null;
}
else
{
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
filterContext.ActionParameters[Param] =
new DataContractJsonSerializer(RootType).ReadObject(ms);
}
}
break;
case "application/json":
return;
default:
filterContext.ActionParameters[Param] = null;
break;
}
}
catch
{
filterContext.ActionParameters[Param] = null;
}
}
}
}
This code runs, thanks to the use of this specialized ActionFilterAttribute
which we use as follows in the demo app attached:
[JSONFilter(Param = "person", RootType = typeof(Person))]
public ActionResult Index(Person person)
{
.....
}
Some of the more eagle eyed amongst you will notice that we do two things in there:
- If the HTTP ContentType is "application/x-www-form-urlencoded", we try and use a
DataContractJsonSerializer
to provide the data
for the controller action model. Why do we do that? Well, in the attached demo code, the .NET client app uses the WebClient
web class
which makes use of the "application/x-www-form-urlencoded
" HTTP ContentType. It does not allow you to change that, which is fair enough
as the WebClient
exposes a values collection which could accept anything, so it must use this ContentType. You will see more on this later on in the article. - If the HTTP ContentType is "
application/json
", we allow ASP MVC to supply the JSON data as it has inbuilt support for this, thanks to the inbuilt
JsonValueProviderFactory
. It does a great job, so let it do it.
JSONValue
Before we go onto to look at the .NET client and the jQuery demos, I just wanted to take a slight diversion, which is to talk about an interesting API which has come out of the
WCF Web API work but is also available as a standalone nuget package.
This standalone package is called JSONValue
and is available by adding a Package Reference from within Visual Studio.
The third of the demo controller actions uses JSONValue
.
What JSONValue
brings to the table is that it has certain wrappers/converters around JavaScript objects, it also has dynamic support.
I think one of the best ways is to see an example of this.
So as we extended ASP MVC before with a custom ActionFilterAttribute
,
ASP MVC also supports extensibility via the use of specialized model binders, so let's have a look at an example of that, shall we?
So in our controller we have this, see how it simply accepts a JsonValue
and in the actual controllers action, it is able to use the AsDynamic()
method to get the correct property values for the type that was received.
public ActionResult JsonValue(
[DynamicJson(typeof(Person))]
JsonValue person
)
{
var x = person.AsDynamic();
int age = x.Age;
}
Where it can be seen that I am using a special DynamicJsonAttribute
which looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace RestfulWebSite.ModelBinders
{
public class DynamicJsonAttribute : CustomModelBinderAttribute
{
private Type rootType;
public DynamicJsonAttribute(Type rootType)
{
this.rootType = rootType;
}
public override IModelBinder GetBinder()
{
return new DynamicJsonBinder(rootType);
}
}
}
This attribute is responsible for creating the data that is passed as the model value for the attributed object. It does this
by using the internal instance of a DynamicJsonBinder
class, which looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;
using System.Json;
using System.Runtime.Serialization.Json;
using System.Text;
namespace RestfulWebSite.ModelBinders
{
public class DynamicJsonBinder : IModelBinder
{
private Type rootType;
public DynamicJsonBinder(Type rootType)
{
this.rootType = rootType;
}
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
try
{
var inpStream = controllerContext.HttpContext.Request.InputStream;
inpStream.Seek(0, SeekOrigin.Begin);
string bodyText;
using (StreamReader reader = new StreamReader(
controllerContext.HttpContext.Request.InputStream))
{
bodyText = reader.ReadToEnd();
}
string contentType = controllerContext.HttpContext.Request.ContentType;
object result=null;
switch(contentType)
{
case "application/json":
case "application/json; charset=UTF-8":
if (!String.IsNullOrEmpty(bodyText))
{
result= JsonValue.Parse(bodyText);
}
break;
default:
result= null;
break;
}
return result;
}
catch(Exception ex)
{
return null;
}
}
}
}
This may also sound a bit much for now, but hopefully when you see the calling code, it will all make sense. Let's proceed to look at that now, shall we?
Demo: Browser Support
All three of the demo controller actions accept JSON produced by the following three pieces of jQuery, which are all the same actually,
apart from the actual ASP MVC controller action they call:
$(document).ready(function () {
var person = { Age: 1, Name: "person1" };
$("#btnActionFilter").click(function () {
$.ajax({
url: "/Rest/Index",
type: "POST",
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(person),
success: function (response) {
alert(response);
$("#personContainer").empty()
$("#personTemplate").tmpl(response).appendTo("#personContainer");
}
});
});
$("#btnNoFilterJSON").click(function () {
$.ajax({
url: "/Rest/NoFilterJSON",
type: "POST",
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(person),
success: function (response) {
alert(response);
$("#personContainer").empty()
$("#personTemplate").tmpl(response).appendTo("#personContainer");
}
});
});
$("#btnJSONValue").click(function () {
$.ajax({
url: "/Rest/JsonValue",
type: "POST",
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(person),
success: function (response) {
alert(response);
$("#personContainer").empty()
$("#personTemplate").tmpl(response).appendTo("#personContainer");
}
});
});
});
One thing that I have also included (just for kicks) is the use
of the jQuery Templates that allow you to do repeated bit
of HTML with placeholders where the placeholders are filled in using JSON objects.
Here is a jQuery Template inside the demo code:
<div>
<h1>Results</h1>
<div id="personContainer"></div>
</div>
<script id="personTemplate" type="text/x-jQuery-tmpl">
<div>
<p><strong>Person</strong>
<br/>
Age:${Age}
<br/>
Name:${Name}
<br/>
TimeStamp:${TimeStamp}
<br/>
</div>
</script>
This is basically what is used to show the results of calling the action methods on the controllers. Nice, no?
jQuery Template has much more to offer, it is worth digging deeper
if you have the time.
Demo: Desktop .NET Client Support
All that remains is for me to show you how to call the ASP MVC controller using a standard .NET client which uses standard .NET web APIs.
For the demo app, the client code looks like this (where we are calling the REST controller's Index
action from this code).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections.Specialized;
using System.Web.Script.Serialization;
using Common;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
namespace ConsoleApplication1
{
class Program
{
internal static DataContractJsonSerializer jss;
private static WebClient client = new WebClient();
private static NameValueCollection values =
new System.Collections.Specialized.NameValueCollection();
static void Main(string[] args)
{
Person pers = new Person(1, "name", null);
AddValue(values,"person", pers);
Byte[] results = client.UploadValues("http://localhost:8300/Rest/Index", values);
List<Person> people = GetValue<List<Person>>(results);
Console.ReadLine();
}
internal static T GetValue<T>(Byte[] results) where T : class
{
using (MemoryStream ms = new MemoryStream(results))
{
jss = new DataContractJsonSerializer(typeof(T));
return (T)jss.ReadObject(ms);
}
}
internal static void AddValue(NameValueCollection values, string key, object value)
{
jss = new DataContractJsonSerializer(value.GetType());
using (MemoryStream ms = new MemoryStream())
{
jss.WriteObject(ms, value);
string json = Encoding.UTF8.GetString(ms.ToArray());
values.Add(key, json);
}
}
}
}
It is all pretty easy stuff. We just make use of WebClient
along with DataContractJsonSerializer
to form our NameValueCollection
and make a request to the web site. All standard stuff, but it did take me a while to get all the pieces to work nicely together, so I thought this article may benefit others.
As I have been banging on, this needs just standard .NET classes, zero config, no port specific to the call, just the web site URL, and we
do our own very lean serialization. Actually, using JSON as a serialization format is pretty good, as it is very lightweight when compared
to other forms of serialization, and it is also very human readable, something SOAP formatting suffers from a bit, I feel.
More
Since I 1st wrote this article, a codeproject user "Simonotti Paolo" was also doing what I am doing, we kind of both had a nirvana moment, Whoa we are not alone. Anyway Simonotti showed a way in which you can use the ContentType application/json
and the WebClient
, and he has kindly agreed for me to include it here. So if you really want to use the ContentType application/json
. Here is how
Instead of my Desktop Client code you would have something like this
public static class JsonExtensionMethods
{
public static T JsonInvoke<T>(this WebClient client, string methodUri, object argument) where T : class
{
T res = default(T);
client.Headers.Add("Content-Type", "application/json");
byte[] jsonRequest = Encoding.UTF8.GetBytes(argument.ToJson());
byte[] jsonResponse = client.UploadData(methodUri, jsonRequest);
res = jsonResponse.JsonDeserializeTo<T>();
return res;
}
public static string ToJson(this object obj)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
return jss.Serialize(obj);
}
public static T JsonDeserializeTo<T>(this byte[] bytes) where T:class
{
T res = null;
DataContractJsonSerializer jss = new DataContractJsonSerializer(typeof(T));
using (MemoryStream ms = new MemoryStream(bytes))
{
res=(T)jss.ReadObject(ms);
}
return res;
}
}
Where you would use it in your desktop app like this
string uri="http://localhost:8300/Rest/NoFilterJSON";
Person pers1 = new Person(1, "àèìòù", null);
Person pers2 = new Person(2, "àèìòù2", null);
Dictionary<string, object> values = new Dictionary<string, object>()
{
{ "person", pers1 },
{ "person2", pers2 }
};
List<Person> people1 = client.JsonInvoke<List<Person>>(uri, values);
List<Person> people2 = client.JsonInvoke<List<Person>>(uri, new { person=pers1, person2=pers2 } );
I feel both approaches have their merits, I will let you choose, you have the tools, have fun
That's It
Anyway that is all I wanted to say for now. I realise this is not my normal WPF type article, but I think it is still useful
and it shows you how to create some code that is web/desktop callable with zero config, and requires very little code.
If you liked the article, spare some time to give me a vote/comment, it would be appreciated.