Recent ASP.NET Web API Updates – April 24
Here’s a few updates coming your way in ASP.NET Web API. The code has been checked into our CodePlex source code repository but hasn’t yet been released as part of our official installer. Think of this as a sneak-peak for you to try out and comment on as part of our source code repository (see Getting Started With ASP.NET Web Stack Source on CodePlex for details).
Cookie Support
Until now the HTTP Cookie and Set-Cookie headers were only exposed as raw strings and not structured classes in the HttpRequestMessage and HttpResponseMessage classes. This made it cumbersome and error prone to work with cookies in ASP.NET Web API. To fix this we introduced two new classes called CookieHeaderValue and CookieState that follow the RFC 6265 HTTP State Management Mechanism. Each Cookie or Set-Cookie header is represented as one CookieHeaderValue. A CookieHeaderValue contains information about the domain, path, expiration, and other common cookie information as well as one or more CookieStates. Each CookieState contains a name and whatever state is associate with that name. This representation allows for multiple related CookieStates to be carried within the same Cookie header while still providing separation between each cookie state.
To see how this works, look at the sample cookie exchange where a server sends a cookie to the client using the Set-Cookie header including the cookie state as well as domain and expiration information:
- Set-Cookie: StateA=n1=v1&n2=v2; StateZ=n3=v3&n4=v4; Domain=domain1; Path=path1; Expires=Mon, 23 Apr 2012 08:49:37 GMT
The client then returns the cookie on a subsequent request passing just the cookie state:
- Cookie: StateA=n1=v1&n2=v2; StateZ=n3=v3&n4=v4
In this example, there is one CookieHeaderValue with two CookieStates in the Set-Cookie response and also in the subsequent Cookie request. The CookieStates have names StateA and StateZ respectively. The CookieState names are followed by a value consisting of name-value pairs encoded as URL-encoded form data (also known as application/x-www-form-urlencoded) so that you can pass relatively structured data back and forth. In the above example, StateA has two name/value pairs (n1=v1 and n2=v2).
Getting all the CookieHeaderValues from a request:
- var cookies = Request.Headers.GetCookies();
Getting a CookieHeaderValue with a particular CookieState name:
- var cookie = Request.Headers.GetCookies(“StateA”);
Adding one or more Set-Cookie headers to a response
- Response.Headers.AddCookies(cookies);
You can access the CookieState using C# indexers. For example, the following would return “v2”:
- var value = cookie[“StateA”][“n2”];
Push Content
The HttpContent model supports writing data by either pushing data to the output stream directly or pulling data from an input stream and then copying it to the output stream (see Push and Pull Streams using HttpClient). Pull is supported out of the box in the form of StreamContent but until there was no equivalent built-in support for pushing data.
The PushStreamContent class enables scenarios where a data producer wants to write directly (either synchronously or asynchronously) using a stream. When the PushStreamContent is ready to accept data it calls out to an action with the output stream. The developer can then write to the stream for as long as necessary and close the stream when writing has completed. The PushStreamContent detects the closing of the stream and completes the underlying asynchronous Task for writing out the content.
PushStreamContent is entirely asynchronous and does not block any threads. It is up to the user of the PushStreamContent to write data either synchronously or asynchronously which of course then will determine whether a thread is blocked or not. For more details, see this commit. Here’s a sample push controller which writes out the plaintext string “Here’s some more data” every second. On the wire this will be sent using HTTP Chunked Encoding where each string will be an individual chunk.
1: public class PushController : ApiController
2: {
3: private static readonly Lazy<Timer> _timer = new Lazy<Timer>(() => new Timer(TimerCallback, null, 0, 1000));
4: private static readonly ConcurrentDictionary<StreamWriter, StreamWriter> _outputs = new ConcurrentDictionary<StreamWriter, StreamWriter>();
5:
6: public HttpResponseMessage GetUpdates(HttpRequestMessage request)
7: {
8: Timer t = _timer.Value;
9: request.Headers.AcceptEncoding.Clear();
10: HttpResponseMessage response = request.CreateResponse();
11: response.Content = new PushStreamContent(OnStreamAvailable, "text/plain");
12: return response;
13: }
14:
15: private static void OnStreamAvailable(Stream stream, HttpContentHeaders headers, TransportContext context)
16: {
17: StreamWriter sWriter = new StreamWriter(stream);
18: _outputs.TryAdd(sWriter, sWriter);
19: }
20:
21: private static void TimerCallback(object state)
22: {
23: foreach (var kvp in _outputs.ToArray())
24: {
25: try
26: {
27: kvp.Value.Write("Here's some more data...");
28: kvp.Value.Flush();
29: }
30: catch
31: {
32: StreamWriter sWriter;
33: _outputs.TryRemove(kvp.Value, out sWriter);
34: }
35: }
36: }
37: }
Buffered Media Type Formatter
The BufferedMediaTypeFormatter provides a convenient way for writing a MediaTypeFormatter that primarily is writing or reading small, synchronous pieces of data. This is for example the case by many serializers which don’t quite support asynchronous reading and writing. Instead of having to implement the Task-oriented MediaTypeFormatter for reading and writing the BufferedMediaTypeFormatter exposes a simplified model backed by a buffer that is well suited to handle many small reads or writes. For more details, see this commit.
Below is a sample plaintext formatter written using the BufferedMediaTypeFormatter which supports writing or reading plain text using either UTF8 or UTF-16 as determined automatically by the built-in content negotiation algorithm (see ASP.NET Web API Content Negotiation and Accept-Charset):
1: public class SampleBufferedMediaTypeFormatter : BufferedMediaTypeFormatter
2: {
3: public SampleBufferedMediaTypeFormatter()
4: {
5: // Set supported media type
6: SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
7:
8: // Set default supported character encodings
9: SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
10: SupportedEncodings.Add(new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true));
11: }
12:
13: public override bool CanReadType(Type type)
14: {
15: return type == typeof(string);
16: }
17:
18: public override bool CanWriteType(Type type)
19: {
20: return type == typeof(string);
21: }
22:
23: public override object ReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
24: {
25: Encoding effectiveEncoding = SelectCharacterEncoding(contentHeaders);
26: using (StreamReader sReader = new StreamReader(stream, effectiveEncoding))
27: {
28: return sReader.ReadToEnd();
29: }
30: }
31:
32: public override void WriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders)
33: {
34: Encoding effectiveEncoding = SelectCharacterEncoding(contentHeaders);
35: using (StreamWriter sWriter = new StreamWriter(stream, effectiveEncoding))
36: {
37: sWriter.Write(value);
38: }
39: }
40: }
What do you think? Please use the CodePlex discussion forum and go vote on your favorite issue in Issue Tracker – we would love to hear you feedback.
Have fun!
Henrik
del.icio.us Tags: asp.net,webapi,rest,http
Comments
Anonymous
April 24, 2012
Thanks Henrik, good stuff as always!Anonymous
June 12, 2012
Hi Henrik, I'm not sure if you've seen my post here: aspnetwebstack.codeplex.com/.../359056 Do you have any advice about the use case I describe?