Using JSON.NET with ASP.NET Web API
Json.Net is a popular framework for working with JSON. In particular, it has a bunch of features that are not supported by the DataContractJsonSerializer such as being much more flexible in what kind of types it can serialize and exactly how they should be serialized. The ASP.NET Web API supports an open-ended set of formatters that can read and write data to and from any media type you want to support. For example, if you want to support the vCard format which has the media type text/vcard (previously it was text/directory) media type then you can write a formatter for vCard and register it for the media type (or types) in question.
Note: JSON.NET is now an integral part of ASP.NET Web API so you can just us it out of the box.
This sample shows how to hook in Json.Net as the default formatter replacing the built in DataContractJsonSerializer formatter (in the beta bits DataContractJsonSerializer is the default formatter). There are already a bunch of Json.Net formatters provided by the community that may well be more full-featured but this should allow you to get started.
Building the Formatter
The first thing we do is building the formatter. The key part of the formatter is to provide support for reading and writing content of a given media type. The sample formatter derives from the base MediaTypeFormatter class; we are working on a buffered media type formatter that will help working with lots of small reads and writes but for this sample the goal is to keep things simple. The formatter looks like this:
1: public class JsonNetFormatter : MediaTypeFormatter
2: {
3: private JsonSerializerSettings _jsonSerializerSettings;
4:
5: public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
6: {
7: _jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();
8:
9: // Fill out the mediatype and encoding we support
10: SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
11: Encoding = new UTF8Encoding(false, true);
12: }
13:
14: protected override bool CanReadType(Type type)
15: {
16: if (type == typeof(IKeyValueModel))
17: {
18: return false;
19: }
20:
21: return true;
22: }
23:
24: protected override bool CanWriteType(Type type)
25: {
26: return true;
27: }
28:
29: protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
30: {
31: // Create a serializer
32: JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
33:
34: // Create task reading the content
35: return Task.Factory.StartNew(() =>
36: {
37: using (StreamReader streamReader = new StreamReader(stream, Encoding))
38: {
39: using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
40: {
41: return serializer.Deserialize(jsonTextReader, type);
42: }
43: }
44: });
45: }
46:
47: protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext)
48: {
49: // Create a serializer
50: JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
51:
52: // Create task writing the serialized content
53: return Task.Factory.StartNew(() =>
54: {
55: using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
56: {
57: serializer.Serialize(jsonTextWriter, value);
58: jsonTextWriter.Flush();
59: }
60: });
61: }
62: }
Building a Sample ApiController
Next we need a controller to try things out. For illustrative purposes we create a type that would not serialize well with DataContractJsonSerializer but other than that this is a completely vanilla controller that knows nothing about serialization:
1: public class HomeController : ApiController
2: {
3: public HomeInfo Get()
4: {
5: return new HomeInfo();
6: }
7: }
8:
9: public class HomeInfo
10: {
11: private readonly DateTime _created = DateTime.UtcNow;
12: private readonly Dictionary<int, string> _colorMap = new Dictionary<int, string>
13: {
14: { 1, "blue"},
15: { 2, "red" },
16: { 3, "green" },
17: { 4, "black" },
18: { 5, "white" },
19: };
20:
21: public DateTime Created { get { return _created; } }
22:
23: public IDictionary<int, string> ColorMap { get { return _colorMap; } }
24: }
Hosting the Controller
Now that we have the controller we can host it in either ASP or as selfhost. In this case we selfhost the controller in a simple console application but it would work exactly the same if hosted in ASP.
The first part is to configure the selfhost server and injecting the JsonNetFormatter as the first formatter in the configuration so that it becomes the default formatter. We also configure Json.Net to serialize DateTime types using ISO 8601 format instead of the more esoteric "/Date(1240718400000)/” format. The part of the console app that configures and starts the server looks like this:
1: // Set up server configuration
2: HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("https://localhost:8080");
3: config.Routes.MapHttpRoute("Default", "{controller}", new { controller = "Home" });
4:
5: // Create Json.Net formatter serializing DateTime using the ISO 8601 format
6: JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
7: serializerSettings.Converters.Add(new IsoDateTimeConverter());
8: config.Formatters[0] = new JsonNetFormatter(serializerSettings);
9:
10: // Create server
11: server = new HttpSelfHostServer(config);
12:
13: // Start listening
14: server.OpenAsync().Wait();
Note: In order to successfully start the selfhost server you have to run as admin (or configure http.sys with the appropriate URI prefix using netsh).
Trying it Out
Once the controller is running, we can access it using any HTTP client. In this case we use HttpClient to access it and print out the result to the console. If we put both the server configuration and the client in the same Main then we get something like this:
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: HttpSelfHostServer server = null;
6: try
7: {
8: // Set up server configuration
9: HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("https://localhost:8080");
10: config.Routes.MapHttpRoute("Default", "{controller}", new { controller = "Home" });
11:
12: // Create Json.Net formatter serializing DateTime using the ISO 8601 format
13: JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
14: serializerSettings.Converters.Add(new IsoDateTimeConverter());
15: config.Formatters[0] = new JsonNetFormatter(serializerSettings);
16:
17: // Create server
18: server = new HttpSelfHostServer(config);
19:
20: // Start listening
21: server.OpenAsync().Wait();
22:
23: // Create HttpClient, do an HTTP GET on the controller, and show the output
24: HttpClient client = new HttpClient();
25: client.GetAsync("https://localhost:8080").ContinueWith(
26: (requestTask) =>
27: {
28: // Get HTTP response from completed task.
29: HttpResponseMessage response = requestTask.Result;
30:
31: // Check that response was successful or throw exception
32: response.EnsureSuccessStatusCode();
33:
34: // Read response asynchronously as string and write out
35: response.Content.ReadAsStringAsync().ContinueWith(
36: (readTask) =>
37: {
38: Console.WriteLine(readTask.Result);
39: });
40: });
41:
42: Console.WriteLine("Hit ENTER to exit...");
43: Console.ReadLine();
44:
45: }
46: finally
47: {
48: if (server != null)
49: {
50: // Stop listening
51: server.CloseAsync().Wait();
52: }
53: }
54: }
55: }
The resulting output written to the console is
{"Created":"2012-02-18T00:54:06.8447642Z","ColorMap":{"1":"blue","2":"red","3":"green","4":"black","5":"white"}}
Note the ISO date and the nice serialization of the dictionary!
Henrik
del.icio.us Tags: asp.net,webapi,mvc,rest,httpclient
Comments
Anonymous
February 17, 2012
Hey Henrik, I was wondering why there isn't any interfaces for the HttpClient? I'd like to inject it as a dependency in my classes and an interface would make possible for me to mock it using something like Moq. Thanks!Anonymous
February 19, 2012
We don't have interfaces but you can actually just new up the HttpRequestMessage and HttpResponseMessage etc. types directly and use them in your tests without a lot of mocking.Anonymous
February 19, 2012
Henrik, I am having trouble on the client side. Can you create some examples of a client sending CRUD calls using HttpClient? Thanks.Anonymous
February 19, 2012
Terrence, Sure, what in particular are you looking for?Anonymous
February 19, 2012
The comment has been removedAnonymous
February 20, 2012
Well I am working on a data app for the windows store. I need an example of submitting a poco to add, update and delete using the httpclient. I know it may seem obvious, but I am a simpleton and nothing is obvious to me with these new technologies. Thanks or any help you can provide.Anonymous
February 20, 2012
Hey Henrik, thanks for answering. I got some Glenn Block on twitter on how to deal with it. I've blogged about it so maybe it will help the next person that comes along. gbogea.com/.../web-api-testing-with-httpclientAnonymous
February 20, 2012
Are there any examples out there posting files to the new Web Api with Form Data? For example in MVC 3 you can have a model with properties and one property with the HttpPostedFileWrapper. I haven't been able to get this to work with the ApiControllerAnonymous
February 21, 2012
I would like to use my traditional services using ASP.NET MVC4 Web API (i.e. i would like to replace WCF Web API), can you please provide blog post on this. Thanks a million in advance.Anonymous
February 21, 2012
This code doesn't work for me. It sometimes throws a HttpException and terminates IIS Express: OnWriteToStreamAsync => "The remote host closed the connection. The error code is 0x800704CD." A first chance exception of type 'System.Web.HttpException' occurred in System.Web.dll. But it seems to me I've found the solution in your JsonMediaTypeFormatter class: return TaskHelpers.RunSynchronously(...);Anonymous
February 21, 2012
Unfortunately, I was wrong. This is some other problems. Here is the last three stack trace entries: at System.Web.Hosting.IIS7WorkerRequest.RaiseCommunicationError(Int32 result, Boolean throwOnDisconnect) at System.Web.Hosting.IIS7WorkerRequest.ExplicitFlush() at System.Web.HttpResponse.Flush(Boolean finalFlush)Anonymous
February 21, 2012
Could you provide some hints as to how I could develop a MediaTypeFormatter that passed control to the standard ASP.Net MVC rendering engine so that I could easily support text/html along side application/xml, application/json?Anonymous
February 23, 2012
Unfortunately there was a bug in JsonNetFormatter.OnWriteToStreamAsync in the formatter when used on the client side causing the stream to be closed too early. The blog has been updated to contain a working version of OnWriteToStreamAsync. Sorry about that!Anonymous
February 24, 2012
I have tried it but it is some other problem. JsonTextWriter's Flush/Serialize method also throws a HttpException. Moreover, it sometimes stops IIS Express: "A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread." (IIS7WorkerRequest - RaiseCommunicationError). It is worth to mention that this code (without async support) works fine with WCF Web API preview versions.Anonymous
February 24, 2012
Hi, skimming through your article I can'f find where the Configuration is to be acquired in an ASP hosted solution. Guessing GlobalConfiguration.Configuratioin and then add the custom formatter there? Have not tried it, hence guessing. //DanielAnonymous
February 24, 2012
Configuration: yup that is correct!Anonymous
February 26, 2012
Hi Henrik. Thanks for calling out the Date issues. This is one of the main reasons to use a different serializer like Json.Net or even ServiceStack.Text. The ISO 8601 format solves lots of issues, and DataContractJsonSerializer is buggy even in its own implementation (see connect.microsoft.com/.../723368) I appreciate the detail of your post! Cheers, MattAnonymous
February 26, 2012
It's not clear how you would integrate the JsonNetFormatter with a stand-alone HttpClient; how do you register alternative MediaTypeFormatter implementations with HttpClient; hopefully not via a HttpSelfHostConfiguration since that is just for hosting.Anonymous
March 01, 2012
James, there are overloads for passing in the formatters on the client side so that's how you can enable your own formatter(s).Anonymous
March 07, 2012
I was trying to add your JsonNetFormatter class in a non-self hosted environment. I created an ASP.NET Web API Project. I added JsonNetFormatter in Global.asax.cs as such: static void Configure(HttpConfiguration config) { // Create Json.Net formatter serializing DateTime using the ISO 8601 format JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); serializerSettings.Converters.Add(new IsoDateTimeConverter()); config.Formatters.Add(new JsonNetFormatter(serializerSettings)); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); BundleTable.Bundles.RegisterTemplateBundles(); Configure(GlobalConfiguration.Configuration); } I found that JsonNetFormatter OnWriteToStreamAsync() was never getting called. I learned the problem was that there already was a formatter for application/xml that was being used instead. So I had to remove that formatter and JsonNetFormatter was then being used. To remove the formatter I added the following at the beginning of the Configure method: var formatter = config.Formatters .Where(f => { return f.SupportedMediaTypes.Any(v => v.MediaType.Equals("application/json", StringComparison.CurrentCultureIgnoreCase)); }) .FirstOrDefault(); if (formatter != null) { config.Formatters.Remove(formatter); } Just thought I would add this comment in case anyone else is trying the same.Anonymous
March 07, 2012
Unexpected token parsing date. Expected String, got Date. apicontroller public Contact PostContact(Contact value) { _contacts.Add(value); return value; } model. public class Contact { public Contact() { LastModified = DateTime.Now; } public int Id { get; set; } public string Name { get; set; } public string Phone { get; set; } public string Email { get; set; } public DateTime LastModified { get; set; } }Anonymous
March 10, 2012
Hi as Aaron Williams noted JSON.NET dll is no longer needed in order to output JSON as default instead of xml.
- Create MVC4 Web Api Project.
- Modify Global.asax. Add this method. This will remove the xml formatter static void Configure(HttpConfiguration config) { var formatter = config.Formatters.FirstOrDefault(f => f.SupportedMediaTypes.Any(v => v.MediaType.Equals("application/xml", StringComparison.CurrentCultureIgnoreCase))); if (formatter != null) { config.Formatters.Remove(formatter); } }
- Call the above method from Application_Start() Configure(GlobalConfiguration.Configuration); That's all. The API will give you json. I hope this will help others too
Anonymous
March 13, 2012
Using formatters[0] instead of formatters.Add to override default JSON formatter. JSON.NET is needed for better DateTime format.Anonymous
March 14, 2012
Using JSON.NET works well with DateTime Output correct ISO8601 Input Dates now works well - 2012-03-15T09:21:59.2630521Z end up as a Datetime.Kind=Utc instead of local great. But my RouteParameter Id don't work now id is always null api/client/100 Did I miss something ?Anonymous
March 18, 2012
would this be easy to do while debugging in visual studio?Anonymous
March 18, 2012
Please see this comment on a Stackoverflow post: stackoverflow.com/.../9681171. While JSON.NET solves the date issues, it appears that it's incompatible with upshot.js / DbDataContext, and so POSTing new records breaks. Any suggested workarounds?Anonymous
March 26, 2012
Hi Henrik, I used the same custom formatter. When I called API a file is getting downloaded which has the serialised output. ThanksAnonymous
April 03, 2012
Per the Web API roadmap - aspnetwebstack.codeplex.com/wikipage It looks like JSON.NET will be the defacto serialization method when Web API goes into release. Looking forward to this.Anonymous
April 29, 2012
Hi Henrik, I just translate your code to MVC4 WebAPI, it is great and it works. I was worried because I have to work with entities, and I couldn't fit IsReference=True to False, and this code solve my problem. Thank you very much.Anonymous
June 01, 2012
great post. Love to see what changes are required with ASP.NET MVC 4 RCAnonymous
July 10, 2012
Seems like that in RC version your code have some troubles. Some classes disappeared from System.Net.Http.Formatting . Can you suggest me how to fix?Anonymous
July 13, 2012
any updates on this to use with the RC version?Anonymous
October 28, 2012
Awesome article very much help full, I have got date serialized as 2012-02-18T00:54:06.8447642Z. Is there any way to get the date in specified format like 2012/02/08 etc. thanks in advance.Anonymous
November 27, 2012
anyone knows where do they keep IKeyValueModel in the release version?Anonymous
February 25, 2015
There is no IKeyValueModel and FormatterContext and etc. Seems to be copypaste from some of your project.