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

Anatomy of ASP.NET Core Requests (60m)

Steve Gordon
June 04, 2020
3.3k

Anatomy of ASP.NET Core Requests (60m)

Have you ever wondered how a browser/client request actually results in a response from ASP.NET Core? Have you ever been constrained by the default behaviour of ASP.NET Core and wanted to change how it works? If so, then this talk is for you.

In this session, Steve will explore the lifecycle of incoming requests in ASP.NET Core, touring through the layers from Kestrel, to hosting, to middleware and then on into MVC itself. You'll see how the pieces all fit together and learn about the places where you can inject your own implementations to customise, augment and override the ASP.NET Core defaults.

Steve will show the hidden extension points that can take you to power-user levels when building your ASP.NET Core applications, such as applying custom conventions, filters and model binding behaviour. You'll leave this talk with ideas for improving your ASP.NET Core applications and reducing code by refactoring cross-cutting concerns.

Don't fight the framework, customise it!

In this session, I provide an in-depth look at how ASP.NET Core processes HTTP requests. I'll describe the flow through the application host, the Kestrel web server, middleware and finally through MVC. The intent is to use this narrative to explain some advanced features of ASP.NET Core which support customising its behaviour.

Key take-aways:
- How application hosting works
- How Kestrel receives requests from the network Socket and parses them
- How the middleware pipeline can be used to respond to requests
- How endpoint routing works
- How filters can be used to intercept and modify requests within MVC
- How MVC finally handles a request.

This session is aimed at ASP.NET Core developers of intermediate/advanced experience.

Steve Gordon

June 04, 2020
Tweet

Transcript

  1. Recursive Resolver Client Root-Level DNS Top Level Domain DNS (.uk)

    Second-Level DNS (.co.uk) queries refers refers IP Address Domain Name Server (stevejgordon.co.uk) refers
  2. @stevejgordon www.stevejgordon.co.uk GET /api/books HTTP/1.1 Host: stevejgordon.co.uk Accept: application/json, text/plain,

    */* Accept-Language: en-GB Accept-Encoding: br, gzip, deflate User-Agent: ClientApplication/1.0 (blank line)
  3. @stevejgordon www.stevejgordon.co.uk GET /api/books HTTP/1.1 Host: stevejgordon.co.uk Accept: application/json, text/plain,

    */* Accept-Language: en-GB Accept-Encoding: br, gzip, deflate User-Agent: ClientApplication/1.0 (blank line)
  4. @stevejgordon www.stevejgordon.co.uk GET /api/books HTTP/1.1 Host: stevejgordon.co.uk Accept: application/json, text/plain,

    */* Accept-Language: en-GB Accept-Encoding: br, gzip, deflate User-Agent: ClientApplication/1.0 (blank line)
  5. @stevejgordon www.stevejgordon.co.uk GET /api/books HTTP/1.1 Host: stevejgordon.co.uk Accept: application/json, text/plain,

    */* Accept-Language: en-GB Accept-Encoding: br, gzip, deflate User-Agent: ClientApplication/1.0 (blank line)
  6. Client Internet KESTREL HTTP/S R e q u e s

    t R e s p o n s e Accept Connection ASP.NET Core Connection Middleware Listening on :80 and :443
  7. Kestrel Client Internet ASP.NET CORE {} Application Code HTTP/S HttpContext

    R e q u e s t R e s p o n s e Parse HTTP request
  8. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
  9. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
  10. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
  11. @stevejgordon www.stevejgordon.co.uk R e q u e s t R

    e s p o n s e Endpoint Handles Request Pipeline Invoked HttpsRedirectionMiddleware EndpointRoutingMiddleware AuthenticationMiddleware AuthorizationMiddleware EndpointMiddleware DeveloperExceptionPageMiddleware ForwardedHeadersMiddleware HostFilteringMiddleware
  12. @stevejgordon www.stevejgordon.co.uk R e q u e s t R

    e s p o n s e Endpoint Handles Request Pipeline Invoked HttpsRedirectionMiddleware EndpointRoutingMiddleware AuthenticationMiddleware AuthorizationMiddleware EndpointMiddleware DeveloperExceptionPageMiddleware ForwardedHeadersMiddleware HostFilteringMiddleware
  13. @stevejgordon www.stevejgordon.co.uk R e q u e s t R

    e s p o n s e Endpoint Handles Request Pipeline Invoked HttpsRedirectionMiddleware EndpointRoutingMiddleware AuthenticationMiddleware AuthorizationMiddleware EndpointMiddleware DeveloperExceptionPageMiddleware ForwardedHeadersMiddleware HostFilteringMiddleware
  14. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.Use(async

    (ctx, next) => { var sw = Stopwatch.StartNew(); await next(); // call next middleware in pipeline sw.Stop(); var recorder = ctx.RequestServices.GetRequiredService<IMetricRecorder>(); recorder.RecordRequest(ctx.Response.StatusCode, sw.ElapsedMilliseconds); }); ... }
  15. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.Use(async

    (ctx, next) => { var sw = Stopwatch.StartNew(); await next(); // call next middleware in pipeline sw.Stop(); var recorder = ctx.RequestServices.GetRequiredService<IMetricRecorder>(); recorder.RecordRequest(ctx.Response.StatusCode, sw.ElapsedMilliseconds); }); ... }
  16. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.Use(async

    (ctx, next) => { var sw = Stopwatch.StartNew(); await next(); // call next middleware in pipeline sw.Stop(); var recorder = ctx.RequestServices.GetRequiredService<IMetricRecorder>(); recorder.RecordRequest(ctx.Response.StatusCode, sw.ElapsedMilliseconds); }); ... }
  17. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.Use(async

    (ctx, next) => { var sw = Stopwatch.StartNew(); await next(); // call next middleware in pipeline sw.Stop(); var recorder = ctx.RequestServices.GetRequiredService<IMetricRecorder>(); recorder.RecordRequest(ctx.Response.StatusCode, sw.ElapsedMilliseconds); }); ... }
  18. @stevejgordon www.stevejgordon.co.uk public class MetricMiddleware { private readonly RequestDelegate _next;

    private readonly IMetricRecorder _metricRecorder; public MetricMiddleware(RequestDelegate next, IMetricRecorder metricRecorder) { _next = next; _metricRecorder = metricRecorder; } public async Task InvokeAsync(HttpContext ctx) { var stopWatch = Stopwatch.StartNew(); await _next(ctx); stopWatch.Stop(); _metricRecorder.RecordRequest(ctx.Response.StatusCode, stopWatch.ElapsedMilliseconds); } }
  19. @stevejgordon www.stevejgordon.co.uk public class MetricMiddleware { private readonly RequestDelegate _next;

    private readonly IMetricRecorder _metricRecorder; public MetricMiddleware(RequestDelegate next, IMetricRecorder metricRecorder) { _next = next; _metricRecorder = metricRecorder; } public async Task InvokeAsync(HttpContext ctx) { var stopWatch = Stopwatch.StartNew(); await _next(ctx); stopWatch.Stop(); _metricRecorder.RecordRequest(ctx.Response.StatusCode, stopWatch.ElapsedMilliseconds); } }
  20. @stevejgordon www.stevejgordon.co.uk public class MetricMiddleware { private readonly RequestDelegate _next;

    private readonly IMetricRecorder _metricRecorder; public MetricMiddleware(RequestDelegate next, IMetricRecorder metricRecorder) { _next = next; _metricRecorder = metricRecorder; } public async Task InvokeAsync(HttpContext ctx) { var stopWatch = Stopwatch.StartNew(); await _next(ctx); stopWatch.Stop(); _metricRecorder.RecordRequest(ctx.Response.StatusCode, stopWatch.ElapsedMilliseconds); } }
  21. @stevejgordon www.stevejgordon.co.uk public static class MetricMiddlewareExtensions { public static IApplicationBuilder

    UseMetrics(this IApplicationBuilder builder) { return builder.UseMiddleware<MetricMiddleware>(); } }
  22. @stevejgordon www.stevejgordon.co.uk public static class MetricMiddlewareExtensions { public static IApplicationBuilder

    UseMetrics(this IApplicationBuilder builder) { return builder.UseMiddleware<MetricMiddleware>(); } } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseMetrics(); ... }
  23. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
  24. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
  25. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } Routing Zone
  26. @stevejgordon www.stevejgordon.co.uk public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if

    (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } Endpoint Aware
  27. @stevejgordon www.stevejgordon.co.uk [ApiController] [Route("[controller]")] public class BooksController : ControllerBase {

    private readonly IBookRepository _bookRepository; public BooksController(IBookRepository bookRepository) { _bookRepository = bookRepository; } [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Get() { return Ok(_bookRepository.GetAll()); } ... }
  28. @stevejgordon www.stevejgordon.co.uk [ApiController] [Route("[controller]")] public class BooksController : ControllerBase {

    private readonly IBookRepository _bookRepository; public BooksController(IBookRepository bookRepository) { _bookRepository = bookRepository; } [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Get() { return Ok(_bookRepository.GetAll()); } ... }
  29. @stevejgordon www.stevejgordon.co.uk [ApiController] [Route("[controller]")] public class BooksController : ControllerBase {

    private readonly IBookRepository _bookRepository; public BooksController(IBookRepository bookRepository) { _bookRepository = bookRepository; } [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Get() { return Ok(_bookRepository.GetAll()); } ... }
  30. @stevejgordon www.stevejgordon.co.uk [ApiController] [Route("[controller]")] public class BooksController : ControllerBase {

    private readonly IBookRepository _bookRepository; public BooksController(IBookRepository bookRepository) { _bookRepository = bookRepository; } [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Get() { return Ok(_bookRepository.GetAll()); } ... }
  31. @stevejgordon www.stevejgordon.co.uk Controller Initialisation Action Execution View Rendering Result Execution

    Response View Result Data Result Controller Factory Controller Action Invoker
  32. Authorization Filters R e q u e s t R

    e s p o n s e Middleware Forbidden void OnAuthorization(AuthorizationFilterContext ctx)
  33. Resource Filters Authorization Filters R e q u e s

    t R e s p o n s e Middleware Short-circuit void OnResourceExecuting(ResourceExecutingContext ctx); void OnResourceExecuted(ResourceExecutedContext ctx);
  34. Resource Filters Middleware Filters Authorization Filters R e q u

    e s t R e s p o n s e Middleware Short-circuit
  35. @stevejgordon www.stevejgordon.co.uk public class CompressionMiddlewarePipeline { public void Configure(IApplicationBuilder app)

    { app.UseResponseCompression(); } } [ApiController] [Route("[controller]")] [MiddlewareFilter(typeof(CompressionMiddlewarePipeline))] public class AuthorsController : ControllerBase { // Action methods }
  36. @stevejgordon www.stevejgordon.co.uk public class CompressionMiddlewarePipeline { public void Configure(IApplicationBuilder app)

    { app.UseResponseCompression(); } } [ApiController] [Route("[controller]")] [MiddlewareFilter(typeof(CompressionMiddlewarePipeline))] public class AuthorsController : ControllerBase { // Action methods }
  37. @stevejgordon www.stevejgordon.co.uk [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Search(string keyword, int pageSize) {

    return Ok(_bookRepository.Search(keyword, pageSize)); } GET /books/search?keyword=performance&pageSize=10
  38. @stevejgordon www.stevejgordon.co.uk [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Search(string keyword, int pageSize) {

    return Ok(_bookRepository.Search(keyword, pageSize)); } GET /books/search?keyword=performance&pageSize=10
  39. @stevejgordon www.stevejgordon.co.uk [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Search(string keyword, int pageSize) {

    return Ok(_bookRepository.Search(keyword, pageSize)); } GET /books/search?keyword=performance&pageSize=10
  40. @stevejgordon www.stevejgordon.co.uk [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Search(string keyword, int pageSize) {

    return Ok(_bookRepository.Search(keyword, pageSize)); } GET /books/search?keyword=performance&pageSize=10
  41. @stevejgordon www.stevejgordon.co.uk [HttpGet] public ActionResult<IEnumerable<BookOutputModel>> Search(string keyword, int pageSize) {

    return Ok(_bookRepository.Search(keyword, pageSize)); } GET /books/search?keyword=performance&pageSize=10
  42. @stevejgordon www.stevejgordon.co.uk [HttpPost] public IActionResult CreateBook(BookInputModel book) { // Save

    the Book... return Ok(); } public class BookInputModel { public int Id { get; set; } public string Title { get; set; } public string ISBN { get; set; } }
  43. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters R e q u e s t R e s p o n s e Middleware Short-circuit void OnActionExecuting(ActionExecutingContext ctx);
  44. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters Action Execution R e q u e s t R e s p o n s e Middleware void OnActionExecuted(ActionExecutedContext ctx);
  45. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters Exception Filters Action Execution R e q u e s t R e s p o n s e Middleware void OnException(ExceptionContext context);
  46. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters Result Filters Exception Filters Action Execution R e q u e s t R e s p o n s e Middleware void OnResultExecuting(ResultExecutingContext ctx);
  47. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters Result Filters Exception Filters Action Execution ActionResult Executed Output Formatter R e q u e s t R e s p o n s e Middleware
  48. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters Result Filters Exception Filters Action Execution ActionResult Executed Resource Filters Middleware Filters Output Formatter R e q u e s t R e s p o n s e Middleware Result Filters void OnResultExecuted(ResultExecutedContext ctx);
  49. Resource Filters Middleware Filters Controller Created Model Binding Authorization Filters

    Action Filters Result Filters Exception Filters Action Execution ActionResult Executed Resource Filters Middleware Filters Output Formatter R e q u e s t R e s p o n s e Middleware Result Filters
  50. @stevejgordon www.stevejgordon.co.uk R e q u e s t R

    e s p o n s e Endpoint Handles Request Pipeline Invoked HttpsRedirectionMiddleware EndpointRoutingMiddleware AuthenticationMiddleware AuthorizationMiddleware EndpointMiddleware DeveloperExceptionPageMiddleware ForwardedHeadersMiddleware HostFilteringMiddleware
  51. @stevejgordon www.stevejgordon.co.uk HTTP/1.1 200 OK Date: Sat, 23 May 2020

    07:20:56 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Transfer-Encoding: chunked Last-Modified: Tue, 31 Mar 2020 23:00:00 GMT [{"id":1,"title":"Pro .NET Memory Management","isbn":"978-1-4842-4026- 7","datePublished":"2018-11- 13T00:00:00","authors":[{"firstName":"Konrad","lastNam e":"Kokosa"}],"lastModified":"2020-04-01T00:00:00"}]
  52. @stevejgordon www.stevejgordon.co.uk HTTP/1.1 200 OK Date: Sat, 23 May 2020

    07:20:56 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Transfer-Encoding: chunked Last-Modified: Tue, 31 Mar 2020 23:00:00 GMT [{"id":1,"title":"Pro .NET Memory Management","isbn":"978-1-4842-4026- 7","datePublished":"2018-11- 13T00:00:00","authors":[{"firstName":"Konrad","lastNam e":"Kokosa"}],"lastModified":"2020-04-01T00:00:00"}]
  53. @stevejgordon www.stevejgordon.co.uk HTTP/1.1 200 OK Date: Sat, 23 May 2020

    07:20:56 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Transfer-Encoding: chunked Last-Modified: Tue, 31 Mar 2020 23:00:00 GMT [{"id":1,"title":"Pro .NET Memory Management","isbn":"978-1-4842-4026- 7","datePublished":"2018-11- 13T00:00:00","authors":[{"firstName":"Konrad","lastNam e":"Kokosa"}],"lastModified":"2020-04-01T00:00:00"}]
  54. @stevejgordon www.stevejgordon.co.uk HTTP/1.1 200 OK Date: Sat, 23 May 2020

    07:20:56 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Transfer-Encoding: chunked Last-Modified: Tue, 31 Mar 2020 23:00:00 GMT [{"id":1,"title":"Pro .NET Memory Management","isbn":"978-1-4842-4026- 7","datePublished":"2018-11- 13T00:00:00","authors":[{"firstName":"Konrad","lastNam e":"Kokosa"}],"lastModified":"2020-04-01T00:00:00"}]
  55. @stevejgordon www.stevejgordon.co.uk Summary • HTTP request received and parsed by

    Kestrel • Middleware pipeline executed • An endpoint (MVC) handles the request • Filter pipeline • Action execution • ActionResult execution