Slide 1

Slide 1 text

A deep dive into the ASP.NET Web API Runtime Architecture NDC London 2013 Pedro Félix @pmhsfelix [email protected] 1

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Outline • “What” - Message Representation • The System.Net.Http model • “How” - Message Processing • Processing layers • Extensibility points 4

Slide 5

Slide 5 text

System.Net.Http 5

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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); }

Slide 9

Slide 9 text

Typed headers 9

Slide 10

Slide 10 text

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 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); }

Slide 11

Slide 11 text

HttpContent 11

Slide 12

Slide 12 text

HttpContent 12 ReadAsAsync extension method Push Pull

Slide 13

Slide 13 text

HttpContent 13 CustomContent

Slide 14

Slide 14 text

HttpContent 14 CustomContent

Slide 15

Slide 15 text

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(); } }

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Message Processing 17

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Message Handlers 19 HttpRequestMessage HttpResponseMessage Message Handler HttpRequestMessage Task DelegatingHandler HttpRequestMessage HttpResponseMessage

Slide 20

Slide 20 text

Message Handlers 20 HttpRequestMessage HttpResponseMessage Message Handler HttpRequestMessage Task DelegatingHandler HttpRequestMessage HttpResponseMessage

Slide 21

Slide 21 text

Message Handlers 21 HttpRequestMessage HttpResponseMessage Message Handler HttpRequestMessage Task DelegatingHandler HttpRequestMessage HttpResponseMessage Same model on the client and server sides

Slide 22

Slide 22 text

E.g. Basic Authentication 22 async protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) {

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

E.g. Basic Authentication 24 async protected override Task 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);

Slide 25

Slide 25 text

E.g. Basic Authentication 25 async protected override Task 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; }

Slide 26

Slide 26 text

HttpServer 26 Delegating Handler Http Configuration Route Dispatcher Controller Dispatcher Per-Route Handler Delegating Handler

Slide 27

Slide 27 text

HttpServer 27 Delegating Handler Http Configuration Route Dispatcher Controller Dispatcher Per-Route Handler Delegating Handler

Slide 28

Slide 28 text

HttpServer 28 Delegating Handler Http Configuration Route Dispatcher Controller Dispatcher Per-Route Handler Delegating Handler

Slide 29

Slide 29 text

HttpServer 29 Delegating Handler Http Configuration Route Dispatcher Controller Dispatcher Per-Route Handler Delegating Handler

Slide 30

Slide 30 text

HttpServer 30 Delegating Handler Http Configuration Route Dispatcher Controller Dispatcher Per-Route Handler Delegating Handler

Slide 31

Slide 31 text

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 MessageHandlers { get; } HttpFilterCollection Filters { get; } MediaTypeFormatterCollection Formatters { get; } ParameterBindingRulesCollection ParameterBindingRules { get; } • Service location IDependencyResolver DependencyResolver { get; set; } ServicesContainer Services { get; internal set; }

Slide 32

Slide 32 text

Message Processing Host Adaptation Layer 32

Slide 33

Slide 33 text

Host Adaptation Layer Web Host | Self Host | OWIN Host 33 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler …

Slide 34

Slide 34 text

OWIN 34 Middleware Middleware Server IDictionary …

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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 // 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(HttpPropertyKeys.RequestContextKey); } public static void SetRequestContext(this HttpRequestMessage request, HttpRequestContext context) { request.Properties[HttpPropertyKeys.RequestContextKey] = context; }

Slide 37

Slide 37 text

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 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), …) }

Slide 38

Slide 38 text

In-memory hosting with Katana 38 HttpMessageHandlerAdapter : OwinMiddleware 38 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware TestServer IDictionary Message Handler HttpClient

Slide 39

Slide 39 text

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); }

Slide 40

Slide 40 text

In-memory hosting • Because a HttpServer is also a delegating handler... 40 Message Handler HttpClient Delegating Handler Http Configuration Delegating Handler …

Slide 41

Slide 41 text

Service Bus Relay Hosting 41 Service Bus Relay HttpMessageHandlerAdapter : OwinMiddleware 41 HttpRequestMessage HttpResponseMessage Delegating Handler Http Configuration Delegating Handler … Middleware AzureServiceBusOwinServer IDictionary

Slide 42

Slide 42 text

Message Processing Controller Layer 42

Slide 43

Slide 43 text

Controller Dispatching 43 IHttpController Concrete Controller GetAction PostAction ... HttpControllerDispatcher HttpResponseMessage HttpRequestMessage HttpControllerContext HttpRequestMessage HttpControllerDescriptor HttpResponseMessage ApiController (actions, filters, model binding, formatters) Concrete Controller ExecuteAsync

Slide 44

Slide 44 text

Controller selection, instantiation and invocation 44

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Action pipeline 46 HttpActionContext Action method HttpResponseMessage HttpRequestMessage

Slide 47

Slide 47 text

Action pipeline 47 HttpActionContext Action method HttpRequestMessage AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage

Slide 48

Slide 48 text

Action pipeline 48 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter Action method HttpRequestMessage AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage

Slide 49

Slide 49 text

Action pipeline 49 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding action arguments Action method HttpRequestMessage AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters

Slide 50

Slide 50 text

Action pipeline 50 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding AuthorizationFilter ActionFilter HttpResponseMessage action arguments Action method HttpRequestMessage AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters

Slide 51

Slide 51 text

Action pipeline 51 AuthorizationFilter HttpResponseMessage HttpActionContext AuthorizationFilter HttpActionBinding AuthorizationFilter ActionFilter HttpResponseMessage action arguments Action method object HttpRequestMessage IHttpActionResult AuthorizationFilter AuthenticationFilter HttpResponseMessage HttpRequestMessage Parameters

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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 ExecuteAsync(CancellationToken cancellationToken); }

Slide 55

Slide 55 text

Authorization and action filters 55 public interface IActionFilter : IFilter { Task ExecuteActionFilterAsync( HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation); } public interface IAuthorizationFilter : IFilter { /* identical */ }

Slide 56

Slide 56 text

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); } } } }

Slide 57

Slide 57 text

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(null); } }

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

MediaTypeFormatter Media Type Formatters 59 HttpRequestMessage Request URI Headers Body Content-Type: media-type stream MediaTypeFormatter CLR Object

Slide 60

Slide 60 text

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());

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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(null); } public static Func Rule(Func getip) { return prm => prm.ParameterType == typeof (IPAddress) ? new IpAddressParameterBinding(prm, getip) : null; } } config.ParameterBindingRules.Add( IpAddressParameterBinding.Rule(req => req.GetOwinContext().Request.RemoteIpAddress))

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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