Sdílet prostřednictvím


ASP.NET Web API Updates – May 3

Some interesting updates were checked in over the last week. As in the previous update this is a sneak-peak of code checked into our CodePlex source code repository but hasn’t yet been released as part of our official installer. See Using Nightly ASP.NET Web Stack NuGet Packages for how to try out these features.

Per-Route Message Handlers

Support for per-route message handlers has been a popular request on our issues list. The issue is that HttpMessageHandlers is a flexible extensibility point for dealing with HTTP requests and responses but they are configured once per application within the HttpConfiguration. In some scenarios you have different needs for different services so a more flexible registration is needed.

Brad Wilson describes the per-route message handler feature thusly: The implementation of this feature allows users to specify the "final" message handler in the request chain for a given route. This will enable two scenarios:

  1. Support for adding message handlers on a per-route basis.
  2. Allow “ride-along” frameworks to use routing to dispatch to their own (non-IHttpController) endpoints.

To illustrate the change in the pipeline, this is what the pipeline looked like before this change:

OldPipeline

The old HttpControllerDispatcher was in charge of

  1. Routing lookup (if not already done)
  2. Generating a 404 when no routes match
  3. Generate a 404 when no controllers match
  4. Dispatch to IHttpController

In the new pipeline we have split this into two handlers like this:

NewPipeline

The new responsibilities are as follows:

HttpRoutingDispatcher

  1. Routing lookup (if not already done)
  2. Generating a 404 when no routes match
  3. Dispatch to IHttpRoute.Handler (HttpControllerDispatcher by default when null)

HttpControllerDispatcher

  1. Generating a 404 when no controllers match
  2. Dispatch to IHttpController

What this means in practical terms is that, if you want to provide one or more message handlers on a per-route basis, those message handlers will have to run after routing has run (so, basically last), and if you want to preserve the dispatching to controllers, your final handler must delegate to HttpControllerDispatcher. To generate a message handler pipeline you can use the HttpClientFactory as described in the previous update.

As an example of how to use this, we introduce a simple “hello world” message handler that simply responds with a static HTTP response like this:

    1: public class HelloWorldMessageHandler : HttpMessageHandler
    2: {
    3:     protected override Task<HttpResponseMessage> SendAsync(
    4:         HttpRequestMessage request,
    5:         CancellationToken cancellationToken)
    6:     {
    7:         var response = request.CreateResponse(HttpStatusCode.OK, "Hello, world!");
    8:  
    9:         var tcs = new TaskCompletionSource<HttpResponseMessage>();
   10:         tcs.SetResult(response);
   11:         return tcs.Task;
   12:     }
   13: }

We can now hook it in by adding a route with the new handler – in this case we use a self-host snippet:

    1: class Program
    2: {    
    3:     static void Main(string[] args)
    4:     {
    5:         var config = new HttpSelfHostConfiguration("https://localhost:8080/");
    6:  
    7:         IHttpRoute route = config.Routes.CreateRoute(
    8:             routeTemplate: "hello",
    9:             defaults: new HttpRouteValueDictionary("route"),
   10:             constraints: null,
   11:             dataTokens: null,
   12:             parameters: null,
   13:             handler: new HelloWorldMessageHandler());
   14:         config.Routes.Add("HelloRoute", route);
   15:         
   16:         config.Routes.MapHttpRoute(
   17:           name: "default",
   18:           routeTemplate: "api/{controller}/{id}",
   19:           defaults: new { controller = "Home", id = RouteParameter.Optional });
   20:         
   21:         var server = new HttpSelfHostServer(config);
   22:         server.OpenAsync().Wait();
   23:         Console.WriteLine("Listening on {0}...", config.BaseAddress);
   24:         Console.ReadLine();
   25:         server.CloseAsync().Wait();
   26:     }
   27: }

If you now access https://localhost:8080/hello you will get the response from HelloWorldMessageHandler and for requests under https://localhost:8080/api/* you will get the regular Web API controllers. Because message handlers can be chained you can also insert the default HttpControllerDispatcher at the end – the opportunities are endless!

Reading Form Data

In addition to doing model binding in your ApiController actions where you get the form data directly as part of your action signature, you can parse form data throughout Web API (both client side and server side). Two common ways to get form data is either as a NameValueCollection or as a JToken which is the Json.NET LINQ to JSON data model. This commit rounds out the functionality by providing equal support for reading form data from a URI or from the body of an HTTP message.

Reading as NameValueCollection

You can read the query component of a URI as a NameValueCollection doing something like this (first line creates some sample content and the second line reads it):

     Uri address = new Uri("https://example.com/path?a[]=1&a[]=5&a[]=333");
    NameValueCollection nvcUri = address.ParseQueryString();

Similarly you can read HTML form URL encoded data from an HttpContent which can be part of an HttpRequestMessage or HttpResponseMessage like this (first line creates some sample content and the second line reads it):

     StringContent content = new StringContent("a[]=1&a[]=5&a[]=333",
        Encoding.UTF8, "application/x-www-form-urlencoded");
    NameValueCollection nvcContent = content.ReadAsFormDataAsync().Result;

In both cases will you get a NameValueCollection with one key (“a”) and three values (“1”, “5”, and “333”).

Reading as JObject

You can also read the query component of a URI as a JToken which is the Json.NET LINQ to JSON data model (we use the same sample URI as above):

     JObject jObjectUri;
    address.TryReadQueryAsJson(out jObjectUri);

Similar you can read the HTML form URL encoded data from an HttpContent as JObject like this (we use the same sample content as above):

     JObject jObjectContent = content.ReadAsAsync<JObject>().Result;

In both cases will you get a JObject which you can use to traverse the data as you wish.

Accepted Pull Requests

Finally we have accepted a set of pull requests – thanks for the contributions!

  1. Andre Azevedo: Correct unit tests by replacing culture
  2. Patrick McDonald: Modified System.Web.Mvc.RouteCollectionExtensions.MapRoute to work with dictionary defaults and/or constraints
  3. Patrick McDonald: Fixed RouteCollectionExtensions.MapRoute to work when defaults and constraints are dictionaries, currently method only works with anonymous types or similar

Have fun!

Henrik

del.icio.us Tags: asp.net,webapi,httpclient

Comments

  • Anonymous
    June 06, 2012
    What happened to ReadAsFormDataAsync? Did that not make it into RC?