Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A deep dive into the ASP.NET Web API Runtime Architecture

Pedro Felix
December 05, 2013

A deep dive into the ASP.NET Web API Runtime Architecture

Slides for the NDC London 2013 session.

Pedro Felix

December 05, 2013
Tweet

More Decks by Pedro Felix

Other Decks in Programming

Transcript

  1. whoami • Professor at the Lisbon Polytechnic Institute • Independent

    Consultant • Web APIs, Identity and Access Control • Co-author of Designing Evolvable Web APIs with ASP.NET (to be published in 2014 by O’Reilly) 2
  2. Concrete Controller HTTP HTTP is a stateless request/response protocol that

    operates by exchanging messages An HTTP "server" is a program that accepts connections in order to service HTTP requests by sending HTTP responses. In Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing 3 Request Message Response Message ? ASP.NET Web API
  3. Outline • “What” - Message Representation • The System.Net.Http model

    • “How” - Message Processing • Processing layers • Extensibility points 4
  4. System.Net.Http 6 New in .NET 4.5 Extended by System.Net.Http.Formatting (Install-Package

    Microsoft.AspNet.WebApi.Client) Same model on the cliente and on the server
  5. System.Net.Http 7 New in .NET 4.5 Extended by System.Net.Http.Formatting (Install-Package

    Microsoft.AspNet.WebApi.Client) Messages as Values with some encoding and validation behavior Rich typed class model Easy to instantiate Same model on the cliente and on the server
  6. Instantiability and testability 8 [Fact] public void HttpRequestMessage_is_easy_to_instantiate() { var

    request = new HttpRequestMessage( HttpMethod.Get, new Uri("http://www.ietf.org/rfc/rfc2616.txt")); Assert.Equal(HttpMethod.Get, request.Method); Assert.Equal("http://www.ietf.org/rfc/rfc2616.txt", request.RequestUri.ToString()); Assert.Equal(new Version(1,1), request.Version); } [Fact] public void HttpResponseMessage_is_easy_to_instantiate() { var response = new HttpResponseMessage(HttpStatusCode.OK); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(new Version(1,1), response.Version); }
  7. Typed headers 10 [Fact] public void Header_classes_expose_headers_in_a_strongly_typed_way() { var request

    = new HttpRequestMessage(); request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); HttpHeaderValueCollection<MediaTypeWithQualityHeaderValue> accept = request.Headers.Accept; Assert.Equal(4, accept.Count); MediaTypeWithQualityHeaderValue third = accept.Skip(2).First(); Assert.Equal("application/xml", third.MediaType); Assert.Equal(0.9, third.Quality); Assert.Null(third.CharSet); }
  8. Custom content 15 public class FileContent : HttpContent { private

    readonly Stream _fstream; public FileContent(string path, string mediaType = "application/octet-stream") { _fstream = new FileStream(path, FileMode.Open, FileAccess.Read); base.Headers.ContentType = new MediaTypeHeaderValue(mediaType); } protected override Task SerializeToStreamAsync(Stream stream, …) { return _fstream.CopyToAsync(stream); } protected override bool TryComputeLength(out long length) { if (!_fstream.CanSeek){ length = 0; return false; } else{ length = _fstream.Length; return true; } } protected override void Dispose(bool disposing) { _fstream.Dispose(); } }
  9. System.Net.Http 16 • Faithful to RFC 2616 • Easier to

    instantiate and test • Both typed and untyped access to message members (e.g. headers) • Extensible HTTP content class hierarchy • Based on the Task Asynchronous Pattern • Similar on both the client and server sides
  10. Runtime Architecture Web Host | Self Host | OWIN Host

    Message Handler ApiController Concrete Controller GetAction PostAction ... 18 HttpRequestMessage HttpResponseMessage HttpRequestMessage HttpResponseMessage HttpRequestMessage HttpResponseMessage Message Handler Message Handler • Controller Layer • From messages to method invocations • Controllers, actions, parameters • Model binding, validation and formatters • Filters • Message Handling Layer • Message transformation • HTTP intermediaries • Host adaptation Layer • Request message generation • Response message consuption
  11. E.g. Basic Authentication 23 async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage

    request, CancellationToken cancellationToken) { if (HasAuthorizationHeaderWithBasicScheme(request)) { var username = TryExtractAndValidateCredentials( request.Headers.Authorization.Parameter); if (username == null) return UnauthorizedResponseMessage(_realm); SetPrincipal(request, username); }
  12. E.g. Basic Authentication 24 async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage

    request, CancellationToken cancellationToken) { if (HasAuthorizationHeaderWithBasicScheme(request)) { var username = TryExtractAndValidateCredentials( request.Headers.Authorization.Parameter); if (username == null) return UnauthorizedResponseMessage(_realm); SetPrincipal(request, username); } var response = await base.SendAsync(request, cancellationToken);
  13. E.g. Basic Authentication 25 async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage

    request, CancellationToken cancellationToken) { if (HasAuthorizationHeaderWithBasicScheme(request)) { var username = TryExtractAndValidateCredentials( request.Headers.Authorization.Parameter); if (username == null) return UnauthorizedResponseMessage(_realm); SetPrincipal(request, username); } var response = await base.SendAsync(request, cancellationToken); if(response.StatusCode == HttpStatusCode.Unauthorized) response.Headers.WwwAuthenticate.Add( new AuthenticationHeaderValue("Basic", string.Format("realm={0}", _realm))); return response; }
  14. 31 Web Host | Self Host | OWIN Hosts ApiController

    Concrete Controller GetAction PostAction ... HttpRequestMessage HttpResponseMessage HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler Route Dispatcher Controller Dispatcher Per-Route Handler Configuration • Routing HttpRouteCollection Routes { get; } • Extensibility points Collection<DelegatingHandler> MessageHandlers { get; } HttpFilterCollection Filters { get; } MediaTypeFormatterCollection Formatters { get; } ParameterBindingRulesCollection ParameterBindingRules { get; } • Service location IDependencyResolver DependencyResolver { get; set; } ServicesContainer Services { get; internal set; }
  15. Host Adaptation Layer Web Host | Self Host | OWIN

    Host 33 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler …
  16. OWIN Adapter • Creates HttpRequestMessage from OwinRequest • Uses StreamContent

    • Buffers the Input stream, if needed • IHostBufferPolicySelector • Sends the request to the Web API pipeline • Buffers the output stream, if needed • Writes the response into the OwinResponse HttpMessageHandlerAdapter : OwinMiddleware 35 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware Server IDictionary<string, object>
  17. HttpRequestContext • Creates HttpRequestMessage from OwinRequest • Uses StreamContext •

    Defines the HttpRequestContext • Buffers the Input stream, if needed • IHostBufferPolicySelector • Sends the request to the Web API pipeline • Buffers the output stream, if needed • Writes the response into the OwinResponse HttpMessageHandlerAdapter : OwinMiddleware 36 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware Server IDictionary<string, object> // New in v2 public class HttpRequestContext { public virtual X509Certificate2 ClientCertificate { get; set; } public virtual IPrincipal Principal { get; set; } … } // Accessed via extension messages on HttpRequestMessage public static HttpRequestContext GetRequestContext(this HttpRequestMessage request) { return request.GetProperty<HttpRequestContext>(HttpPropertyKeys.RequestContextKey); } public static void SetRequestContext(this HttpRequestMessage request, HttpRequestContext context) { request.Properties[HttpPropertyKeys.RequestContextKey] = context; }
  18. OWIN Web API Registration • Creates HttpRequestMessage from OwinRequest •

    Uses StreamContext • Defines the HttpRequestContext • Buffers the Input stream, if needed • IHostBufferPolicySelector • Sends the request to the Web API pipeline • Buffers the output stream, if needed • Writes the response into the OwinResponse HttpMessageHandlerAdapter : OwinMiddleware 37 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware Server IDictionary<string, object> using(var server = TestServer.Create(app => { var config = new HttpConfiguration(); app.UseWebApi(config); })){ … } public static IAppBuilder UseWebApi(this IAppBuilder builder, HttpConfiguration configuration) { return builder.Use(typeof(HttpMessageHandlerAdapter), new HttpServer(configuration), …) }
  19. In-memory hosting with Katana 38 HttpMessageHandlerAdapter : OwinMiddleware 38 HttpRequestMessage

    HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware TestServer IDictionary<string, object> Message Handler HttpClient
  20. In-memory hosting without Katana • Since a HttpServer is also

    a delegating handler... 39 Message Handler HttpClient Delegating Handler Http Configuration Delegating Handler … [Fact] public async Task Memory_host_by_connecting_the_client_to_the_server() { var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); var server = new HttpServer(config); var client = new HttpClient(server); var resp = await client.GetAsync("http://does.not.matter/resources"); Assert.Equal(HttpStatusCode.OK, resp.StatusCode); }
  21. In-memory hosting • Because a HttpServer is also a delegating

    handler... 40 Message Handler HttpClient Delegating Handler Http Configuration Delegating Handler …
  22. Service Bus Relay Hosting 41 Service Bus Relay HttpMessageHandlerAdapter :

    OwinMiddleware 41 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware AzureServiceBusOwinServer IDictionary<string, object>
  23. Controller Dispatching 43 IHttpController Concrete Controller GetAction PostAction ... HttpControllerDispatcher

    HttpResponseMessage HttpRequestMessage HttpControllerContext HttpRequestMessage HttpControllerDescriptor HttpResponseMessage ApiController (actions, filters, model binding, formatters) Concrete Controller ExecuteAsync
  24. ApiController.ExecuteAsync • Initialize properties (e.g. Request, User) from controller context

    • Use IHttpActionSelector to get HttpActionDescriptor • Filters • Parameter binding • Result convertion • Create HttpActionContext • Create action pipeline • filters, parameter binding and result conversion • Invoke pipeline 45
  25. Action pipeline 49 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding action arguments

    Action method HttpRequestMessage AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters
  26. Action pipeline 50 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding AuthorizationFilter ActionFilter

    HttpResponseMessage action arguments Action method HttpRequestMessage AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters
  27. Action pipeline 51 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding AuthorizationFilter ActionFilter

    HttpResponseMessage action arguments Action method object HttpRequestMessage IHttpActionResult AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters
  28. Action pipeline 52 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding AuthorizationFilter ActionFilter

    HttpResponseMessage action arguments Action method ResultConverter object HttpResponseMessage HttpRequestMessage IHttpActionResult AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters
  29. Action pipeline 53 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding AuthorizationFilter ActionFilter

    HttpResponseMessage action arguments Action method ResultConverter object HttpResponseMessage HttpRequestMessage IHttpActionResult AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters
  30. IHttpActionResult • New in v2 • Just an async factory

    for HttpResponseMessage • Similar to ActionResult in MVC • Many concrete implementations: JsonResult, OkResult, RedirectResult • Many ApiController protected methods for creating them • Used for • Unit testing • Delayed message construction 54 public interface IHttpActionResult { Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken); }
  31. Authorization and action filters 55 public interface IActionFilter : IFilter

    { Task<HttpResponseMessage> ExecuteActionFilterAsync( HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation); } public interface IAuthorizationFilter : IFilter { /* identical */ }
  32. Authentication filters - AuthenticateAsync 56 public class BasicAuthenticationFilter : IAuthenticationFilter

    { public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) { var req = context.Request; if (req.HasAuthorizationHeaderWithBasicScheme()) { var principal = await req.TryGetPrincipalFromBasicCredentialsUsing(_validator); if (principal != null) { context.Principal = principal; } else { context.ErrorResult = new UnauthorizedResult( new AuthenticationHeaderValue[0], context.Request); } } } }
  33. Authentication filters - ChallengeAsync 57 public class BasicAuthenticationFilter : IAuthenticationFilter

    { public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken) { context.Result = new ActionResultDelegate(context.Result, async (ct, next) => { var res = await next.ExecuteAsync(ct); if (res.StatusCode == HttpStatusCode.Unauthorized) { res.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", _realm)); } return res; }); return Task.FromResult<object>(null); } }
  34. The "Content-Type" header field indicates the media type of the

    associated representation The indicated media type defines both the data format and how that data is intended to be processed by a recipient In Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content (bolds are mine) • Examples • text/plain, text/html, application/json, application/xml • application/hal+json, application/vnd.collection+json • application/vnd.github+json • See http://www.iana.org/assignments/media-types 58
  35. MediaTypeFormatter Media Type Formatters 59 HttpRequestMessage Request URI Headers Body

    Content-Type: media-type stream MediaTypeFormatter CLR Object
  36. MediaTypeFormatter • Examples • JsonMediaTypeFormatter • XmlMediaTypeFormatter • FormUrlEncodedMediaTypeFormatter •

    MediaTypeFormatter is selected based on • The content media type and the action’s parameter CLR type • The action’s return CLR type and request info (e.g. Accept header) • Can be explicitly define when producing a response 60 Request.CreateResponse(HttpStatusCode.OK, new {msg = "hello"}, new JsonMediaTypeFormatter());
  37. Result conversion • Object returned by the action  HttpResponseMessage

    • If IHttpActionResult – just calls ExecuteAsync • Otherwise – uses a ActionResultConverter • IActionResultConverter interface • HttpResponseMessage Convert( HttpControllerContext controllerContext, object actionResult); • Implementations • ResponseMessageResultConverter • ValueResultConverter • VoidResultConverter 61
  38. ValueResultConverter • Uses a MediaTypeFormatter to convert CLR object 

    byte sequence • The MediaTypeFormatter is chosen based on • The CLR object type • The request message, namely • The Accept header • The request URI • Server-driven content negotiation 62
  39. Parameter binding • Handled by HttpParameterBinding derived classes • ModelBinderParameterBinding

    • Based on model binders and value providers (similar to ASP.NET MVC) • Used to bind from request URI • Related to FromUriAttribute • Multiple parameters • Default for simple types • FormatterParameterBinding • Based on the MediaTypeFormatter concept • Used to bind from request body • Related to FromBodyAttribute • One parameter only • Default for complex types 63
  40. Custom parameter binding 64 public class IPAddressParameterBinding : HttpParameterBinding {

    public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) { var request = actionContext.Request; var ipString = GetClientIpAddressFrom(request); SetValue(actionContext, IPAddress.Parse(ipString)); return Task.FromResult<object>(null); } public static Func<HttpParameterDescriptor, HttpParameterBinding> Rule(Func<HttpRequestMessage, string> getip) { return prm => prm.ParameterType == typeof (IPAddress) ? new IpAddressParameterBinding(prm, getip) : null; } } config.ParameterBindingRules.Add( IpAddressParameterBinding.Rule(req => req.GetOwinContext().Request.RemoteIpAddress))
  41. Conclusions and final remarks • Layered runtime architecture • Hosting

    Adaptation • Message Handling Pipeline • Controller Layer • HTTP messages are first-class using the new class model • Multiple extensibility points 65
  42. References • “Use the source, Luke” git clone https://git01.codeplex.com/aspnetwebstack git

    clone https://git01.codeplex.com/katanaproject • G. Block, P. Cibraro, P. Félix, H. Dierking & D. Miller Designing Evolvable Web APIs with ASP.NET • To be published by O’Reilly in 2014 • Preview available at O’Reilly Atlas - http://chimera.labs.oreilly.com/books/1234000001708 • Samples at https://github.com/pmhsfelix 66