This is an investigation into how Web Api handles various challenges in building HTTP services. A flights service has been built to verify and exemplify the various solutions.
In some places a comparison with SericeStack is made.
- IoC
- Testing
- Javascript interoperability
- Tracing and Profiling
- Async
- Validation
- Authentication and Authorization
- CORS
- JSONP
- OData
- PATCH
- Linux
- Composite Services
- Moving from RESTful services
There are two of ways of supporting IoC and Dependency Injection (DI) for Web Api. The first way is to set the IDependencyResolver on the HttpConfiguration object however this suffers, as do all Service Locators, by not providing any context to the GetService call which might be needed to successfully compose the dependency graph. The second way is to replace the default IHttpControllerActivator service. This provides an HttpRequestMessage every time a graph should be composed and is therefore a suitable composition root. Read this article for a full discussion.
The example in this repo uses CastleWindsor and the second method of replacing the default IHttpControllerActivator. This article was used as a guide.
See the composition root code in WebApiSetup.cs and Dependency Injection here - FlightsController.cs.
Once you use IoC then unit testing becomes relatively straight-forward. It is however a bit tricky to test controller action methods that return HttpResponseMessage requiring extra config. Read this article for a discussion. See the example unit tests in FlightsControllerTests.cs which also test the POST action which returns an HttpResponseMessage.
Integration testing of Web Api controllers is also straight-forward. They can either be tested against an in-memory HttpServer or against a fully running HttpServer using HttpSelfHostServer and both methods have their place - see here for a quick discussion. The former method is relatively fast and does not involve exercising the full network stack, and as such could easily be run on a build server as part of CI. It can also serve to test the application routing and IoC setup which is illustrated in FlightsControllerIntegrationTests.cs. This example also demonstrates how a Web Api service can be called by c# client code rather than by js in a browser.
With the new ServiceStack API it does not seem so straight-forward to write pure unit tests, now that services generally derive from Service rather than IService. See a discussion here. No pure unit tests are documented on the ServiceStack site, rather in-memory integration tests seem to be the norm.
There are a number of ways of handling errors with Web Api. By default most exceptions are translated into an HTTP response with status code 500. In addition you can throw an HttpResponse exception and specify the http status code. You can also use an exception filter to handle any exception, that is not an HttpResponseException, in a uniform way. Finally you can return an HttpResponseMessage message directly using HttpError to describe the error. This allows custom error responses to be created. Read here as a starting point.
In any case the HTTP response has the appropriate status code and the content has details of the error. The content of a standard error response might look like this:
{ "Message": "No HTTP resource was found that matches the request URI 'http://localhost/Foo'.", "MessageDetail": "No type was found that matches the controller named 'Foo'." }
The response to an exception when error detail is turned on with config.IncludeErrorDetailPolicy is like this:
{ "Message": "An error has occurred.", "ExceptionMessage": "Index was outside the bounds of the array.", "ExceptionType": "System.IndexOutOfRangeException", "StackTrace": " at WebApiTest.TestController.Post(Uri uri) in c:\\Temp\\WebApiTest\\WebApiTest\\TestController.cs:line 18 at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClassf.<GetExecutor>b__9(Object instance, Object[] methodParameters) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments) at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)" }
If error detail is turned off then only the Messsage is included in the response content.
I have not yet implemented the html client which will use backbone to automatically connect to the RESTful Web API to persist flights, however I know this is straight-forward. See here for an example.
Tracing can be added to Web Api by implementing ITraceWriter and replacing the default SimpleTracer.
See here
for the Microsoft introduction which includes some performance profiling.
Sophisticated tracing using Systems.Diagnostocs can also be simply enabled with
SystemDiagnosticsTraceWriter -
read here for an intro. It can
also be seen in action when you run the example
integration tests.
For a discussion of the merits of System.Diagniostics see here.
For completeness here
is an ITraceWriter implementation using log4net.
Asynchronous operations are fully supported by Web Api and the HttpClient - see here for the Microsoft introduction to handling asynchronous operations in HttpClient, and here, here, and here, for async streaming with Web Api.
Validation of incoming data in Web Api is supported by DataAnnotations on the DTO. Read the Microsoft doc for an introduction. You can also configure a filter to return validation errors back to the client in a uniform way for all controllers and actions or you can define attributes to decorate individual controllers or actions to handle validation errors.
If you need more sophisitcated DTO validation such as might be required for PATCH requests you can roll your own validation as I have done in the example application. This should really return a more verbose error message detailing the particular validation failure(s).
Authentication is not provided directly by the Web Api itself. Either authentication is handled by the host in which case you can configure your project to use any of the authentication modules built in to IIS or ASP.NET, or write your own HTTP module to perform custom authentication. Or you can put authentication logic into an HTTP message handler. In that case, the message handler examines the HTTP request and sets the principal. Here is the Microsoft introductory article.
Authorization is supported with Authorization filters and the [Authorize] attribute which can be used to restrict access globally, at the controller level, or at the level of individual actions.
Here is a great video about securing Web Apis - the associated code is here.
Unitl recently you could either roll your own CORS support or use a 3rd party library - here is a good stackoverflow question.
Now, if you have .NET 4.5+, you can get the soon to be built-in CORS support - see here. In the latest build the EnableCorsAttribute call requires parameters
- see WebApiSetup.cs for a commented out example which would enable full global CORS access for all controllers in the app.
At the time of writing the Web Api does not have built-in support for jsonp but you can use 3rd party code which extends a JsonMediaTypeFormatter - see here for a stackoverflow question on the topic.
In WebApiSetup.cs you can see that a jsonp formatter is added using Rick Strahl's implementation.
In short Web Api supports oData queries by the simple expedient of returning an IQueryable<T> from a controller action. See here for the Microsoft introduction. See here for an opinion by ServiceStack on the shortcomings of oData.
To build a modestly complex REST API it soon becomes apparent that PATCH semantics are required to partially update an object. Without PATCH it is necessary for a client to do a GET followed by a PUT which can be undesirable. At the time of writing Patch has been supported with a supplementary package for the Web Api. You can read about it in this MSDN blog post. A PATCH command is included in FlightsController.cs.
At the time of writing the Web Api is not fully supported in Mono AFAIK.
In ServiceStack you can delegate and create composite services using the base.ResolveService<T>() method which returns an auto-wired instance of the selected service. AFAIK no such capability exists in Web Api.
In ServiceStack it is possible to create services which can both be called via Web Service or via MQ - see here. This is not possible with Web Api.