According to RFC, content negotiation is defined as, selecting the best representation for a response when there are other representations available. Also this is not only format negotiation, because alternate representations are possible with same media type, but of different characters set/ languages etc.

Here the client(who is consuming our Web API) indicates its preferences, sends options with a quality factor specified against each option, indicating the preference level. It is up to the Web API to serve the request in the way the client requested. If Web API is not able to go with requested options then it can switch to default response or can also send 406 - Not Acceptable status code.

Below are request headers, which play an important role in this content negotiation process:

1. Accept: Used by client to indicate the media type preference.

- JSON (application/json)

- XML (application/xml).

2. Accept-Charset: Used by client to indicate character set preference.

- UTF-8 or UTF-16.

3. Accept-Encoding: Used by client to indicate content encoding preference.

- gzip or deflate.

4. Accept-Language: Used by client to indicate language preferences

- en-us and other languages.

In one of our previous article we discussed about content negotiation, but only for media-type. But, content negotiation contain more than that like encoding, language and character set.

Let's dive into it.

First let's set-up a sample application to use in this article:

#1. Create a new ASP.NET MVC 4 project and name it as SampleWebApi.

#2. Let's add a class(model) as Team under Models folder and some properties to this class:

public class Team
{
public int Id { get; set; }
public string Title { get; set; }
public string Owner { get; set; }
}

#3. Let's add a new controller as TeamsController, add some hard-coded team details and add that in a list. Add this list to TeamsController class, which can be used as our teams resource.

private static IList<Team> _teams = new List<Team>
{
new Team
{
Id = 100,
Title = "Team 100",
Owner = "Owner 100"
},
new Team
{
Id = 101,
Title = "Team 101",
Owner = "Owner 101"
},
// Add more teams
};

#4. Time for implementing one action method, which we will use in this article.

// GET api/teams/100
public Team Get(int id)
{
return _teams.First(t => t.Id == id);
}

#5. Ok, now press F5(run it) and open Fiddler and fire-up a request.

I am using this url: http://sample.dev/api/teams/100

As i have hosted my application as sample.dev. You can host with any url and continue.

I got this output, so it seems to be working Ok:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 26 Oct 2014 10:30:04 GMT
Content-Length: 49
{"Id":100,"Title":"Team 100","Owner":"Owner 100"}

Character set

Character set denotes how characters(letters, digits, other symbols) are represented as bits and bytes for communication(or storage). Accept-Charset can be used by a client to indicate, how the response message should be encoded. ASP.NET Web API supports UTF-8 and UTF-16.

Let's see this in action.

If we fire-up a request to url http://sample.dev/api/teams/100 in fiddler without mentioning Accept-Charset in request header, we get response as below:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 26 Oct 2014 10:30:04 GMT
Content-Length: 49
{"Id":100,"Title":"Team 100","Owner":"Owner 100"}

Notice that, we got UTF-8, because UTF-8 is the default one in case request does not contains any specific charset in header. Also just note down the content-length in above response.

Let's compose and fire-up one more request in Fiddler, but this time mention the Accept-Charset: UTF-16

This time response is:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-16
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 26 Oct 2014 10:43:30 GMT
Content-Length: 100
{"Id":100,"Title":"Team 100","Owner":"Owner 100"}

Notice charset in response is utf-16 also content-length got increased to 100. Reason is: UTF-8 takes minimum 1 byte for one character but may increase up-to 6 bytes and UTF-16 takes minimum 2 bytes for one character but may increase up-to 4 bytes.

These were for Unicode; What about DBCS(double-byte character set)? How can we make our ASP.Net Web API support that?

Basically, DBCS is applicable to a few languages like Chinese, Japanese. And some old legacy system are still out there, that does not support Unicode. So, we may end up adding support for this.

Let's see how we can do this. Shift JIS (shift_JIS) is DBCS character encoding for Japanese language. Code page 932 is Microsoft’s extension of Shift JIS. Let's modify our WebApiConfig class to include below code.

config.Formatters.JsonFormatter.SupportedEncodings.Add(Encoding.GetEncoding(932));

Now rebuild and run the application and fire-up the request to same url but with Accept-charset: shift_jis.

Response will be:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=shift_jis
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sun, 26 Oct 2014 12:20:15 GMT
Content-Length: 49
{"Id":100,"Title":"Team 100","Owner":"Owner 100"}

So, this is how we can add charsets to our JsonFormatter.

Encoding/ Compression

It is primarily used to compress response message; to make better use of available bandwidth. The most common compression schema are gzip and deflate. HTTP/1.1 specifies identity, which is the default encoding to denote the use of no transformation. Compression schema values(with optional quality factor value) can be included in Accept-Encoding request header. In response Content-Encoding contains the encoding schema and Content-Encoding is not present in response, it means, response is not encoded.

ASP.NET Web API that supports gzip and deflate compression schema in that order of preference.

We will implement encoding support as per below listing:

Accept-Encoding: gzip,deflate
Content-Encoding: gzip
Why: As both gzip and deflate default to a quality factor of 1 and Web API prefers gzip, it will choose gzip.

Accept-Encoding: gzip;q=0.8,deflate
Content-Encoding: Deflate
Why: Compare quality factor

Accept-Encoding: gzip,deflate;q=0
Content-Encoding: gzip
Why: Compare quality factor

Accept-Encoding:
No Content-Encoding
Why: As in this case identity will be used, which means no encoding.

Accept-Encoding: *
Content-Encoding: gzip
Why: As client just asked to encode in any format. So, Web API will choose it's default one that is gzip.

Accept-Encoding: identity; q=0.5, *;q=0
No encoding and Content-Encoding
Why: Compare present encoding and their quality factor

Accept-Encoding: *;q=0
No encoding and Content-Encoding. Header will be absent. Status code will be 406 - Not Acceptable.
Why: By putting quality factor 0 for all schema and not including identity, client is refusing all schema. So, 406 status will be returned.

Please download the project source code here and look into the class files under directory "WebApiEncoding" also changes in WebApiConfig class.

After checking these classes, we can import/ copy those classes and WebApiConfig changes in our application and rebuild the solutions; Then fire-up requests through Fiddler by mentioning different Accept-Encoding formats as listed above and notice the response.

As it's compressed so, Raw text of response may not make much sense, but we can see Content-Encoding there. We can also check compression type under Transformer tab in fiddler.

Language

Accept-Language can be used by client to indicate the preferred languages in the response.

Example: Accept-Language: en-us, nl-be;q=0.8, en;q=0.7

Above request header says, client prefers American English. If Web API can not support it, can accept Dutch. And if Dutch is also not supported, other types of English is also acceptable.

Accept-Language is used to specify locale preferences as well.

We can use this to set UI culture and get respective data from resource file, as per requested language in Accept-Language.

Let's see how can we do this:

Please download the project source code here and look into the class files under directory "WebApiCulture" also changes in WebApiConfig class.

Note: If you have already downloaded the project from Charset section(discussed above), then no need to download this. As, both are same project.

Also, We will see there are 2 resource files under "App_GlobalResources" directory. One is for en-us and other is for nl-be.

Now check the changes in Get action method under TeamsController class. It gets the response message from respective Resource file, as per current UI culture set, which we are doing in HttpCultureHandler class.

We can check the response message by sending request through Fiddler and changing the accept language in request header for Accept-Language.

Usage of Accept-Language is not limited to UI culture only. We can also change date time/ decimal etc format as per requested language. For some of this conversion, we may need to implement our own converter though.

Summary

We saw content negotiation is not limited to MediaType format only. It is the process of choosing the best representation for a given response from available representations.

In this process client send their preference of response format, with different quality factor. and Web API try to prepare response according to client's preferences. If not able to prepare response in format client requested then try to send default format if client agrees for this, else send 406 - Not Acceptable status code in the response.

We also discussed how Accept-Charset, Accept-Encoding and Accept-Language request headers play an important role in preparing response.

Stay tuned, if looks interesting.

Reference:
Book: Practical ASP.NET Web API [Apress]

Previous > ASP.NET WEB API Part 6 - Controlling members serialization

Discussion 1 Comment
Nine - 8 =
** To prevent abusing comments from publishing, posted comments will be reviewed and then published!
 Mritunjay Kumar
Works at Mindfire Solutions

I mostly work with C#, ASP.NET, MVC, WCF, Web API, Entity FrameWork, MS Sql.