Writing a Lightweight Web Service using WebApi Building Blocks
Don’t you wish you could write a web service this easily:
LiteWebServer server = new LiteWebServer("https://localhost");
server.Get("/Hello", (r) => new HttpResponseMessage() { Content = new StringContent("Hello World!") });
server.Post("/Echo", (r) => new HttpResponseMessage() { Content = new StringContent(r.Content.ReadAsStringAsync().Result) });
server.Open();
Well, with last week’s release of the Web API beta bits, now you can. Web API is a brand new framework that makes it easy to build HTTP services. You can learn more about Web API here: https://www.asp.net/web-api.
One of the goals when designing the framework was to completely decouple how messages were received from how they were being dispatched. This allows you to plug in your own custom dispatching for requests very easily. In this post, I’ll show you how you can leverage this extensibility to have messages dispatched to your own lambda callbacks.
The key thing we’ll be using is an HttpMessageHandler. HttpMessageHandlers have the following contract:
// Summary:
// A base type for HTTP message handlers.
public abstract class HttpMessageHandler : IDisposable
{
//
// Summary:
// Send an HTTP request as an asynchronous operation.
//
// Parameters:
// request:
// The HTTP request message to send.
//
// cancellationToken:
// The cancellation token to cancel operation.
//
// Returns:
// Returns System.Threading.Tasks.Task<TResult>.The task object representing
// the asynchronous operation.
protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}
SendAsync here allows you to asynchronously handle a request, and return a Task that may eventually complete with a response. Now all we need is something to actually call our message handlers. In Web API, these are called HttpServers. They’re in charge of listening for requests and pumping them through the HttpMessageHandler when they receive them. Notice how LiteWebServer below delegates to its inner HttpSelfHostServer.
public delegate HttpResponseMessage LiteMessageHandler(HttpRequestMessage request);
public class LiteWebServer
{
HttpSelfHostServer server;
HttpSelfHostConfiguration configuration;
LiteWebMessageHandler messageHandler = new LiteWebMessageHandler();
public LiteWebServer(string baseAddress)
{
configuration = new HttpSelfHostConfiguration(baseAddress);
server = new HttpSelfHostServer(configuration, messageHandler);
}
public void Get(string subPath, LiteMessageHandler handler)
{
RegisterHandler(HttpMethod.Get, subPath, handler);
}
public void Put(string subPath, LiteMessageHandler handler)
{
RegisterHandler(HttpMethod.Put, subPath, handler);
}
public void Post(string subPath, LiteMessageHandler handler)
{
RegisterHandler(HttpMethod.Post, subPath, handler);
}
public void Delete(string subPath, LiteMessageHandler handler)
{
RegisterHandler(HttpMethod.Delete, subPath, handler);
}
private void RegisterHandler(HttpMethod method, string subPath, LiteMessageHandler handler)
{
messageHandler.Handlers[method].Add(new Uri(configuration.BaseAddress, subPath).ToString(), handler);
}
public void Open()
{
server.OpenAsync().Wait();
}
public void Close()
{
server.CloseAsync().Wait();
}
private class LiteWebMessageHandler : HttpMessageHandler
{
public Dictionary<HttpMethod, RoutingMap> Handlers = new Dictionary<HttpMethod, RoutingMap>()
{
{ HttpMethod.Delete, new RoutingMap() },
{ HttpMethod.Get, new RoutingMap() },
{ HttpMethod.Head, new RoutingMap() },
{ HttpMethod.Options, new RoutingMap() },
{ HttpMethod.Post, new RoutingMap() },
{ HttpMethod.Put, new RoutingMap() },
{ HttpMethod.Trace, new RoutingMap() }
};
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LiteMessageHandler handler;
RoutingMap routingMap = Handlers[request.Method];
HttpResponseMessage response = routingMap.TryGetValue(request.RequestUri.AbsoluteUri, out handler) ?
handler(request) :
new HttpResponseMessage(HttpStatusCode.NotFound);
return FromResult(response);
}
private static Task<T> FromResult<T>(T t)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
tcs.SetResult(t);
return tcs.Task;
}
}
private class RoutingMap : Dictionary<string, LiteMessageHandler>
{
}
}
And that lets us write our web service in just a few lines of code:
// Set up server
LiteWebServer server = new LiteWebServer("https://localhost");
server.Get("/Hello", (r) => new HttpResponseMessage() { Content = new StringContent("Hello World!") });
server.Post("/Echo", (r) => new HttpResponseMessage() { Content = new StringContent(r.Content.ReadAsStringAsync().Result) });
server.Open();
// Client call - Hello World
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/Hello");
HttpResponseMessage response = new HttpClient().SendAsync(request).Result;
Console.WriteLine(response);
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
Console.WriteLine("------------------");
// Client call - Echo
request = new HttpRequestMessage(HttpMethod.Post, "https://localhost/Echo") { Content = new StringContent("This is pretty neat!") };
response = new HttpClient().SendAsync(request).Result;
Console.WriteLine(response);
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
server.Close();
There are plenty more things you can do with Web API. I’ll be repurposing this blog to discuss more about how Web API works and how it can help you write HTTP services.
In the meantime, I encourage you to download the bits today!
Comments
- Anonymous
February 27, 2012
This is great! Looking forward to more posts!