[ASPNET Core 2] – Middleware

For English version: [ASPNET Core 2] – Middleware

Sometimes you get an awkward request: write Hello world in ASP.NET

Dead easy: dotnet new mvc, then edit Views/Home/Index.cshtml so it returns one line

<p>hello world</p>

Done, right? There is another way :D

1. What is middleware

Imagine your asp.net app is a water pipe. Data is the water. The water at the start of the pipe (the request) is as dirty as the Nhiêu Lộc canal. You want the water at the end of the pipe (the response) to be as clean as Lavie mineral water. There must be some filter in the middle of the pipe, right? Middleware is exactly that filter. It plugs into the app to handle requests and responses

middleware

middleware can decide whether to pass the request it handled on to the next middleware or not. When it cuts things off and passes nothing on, we call that a short-circuit

2. The kinds of middleware

There are 3 kinds of middleware, classified by how you implement it

Kind Can short-circuit Used for
Use Yes Short-circuiting a request
Logic that produces a response
Run No Ending the pipeline
Map
MapWhen
No Branching the pipeline based on the request path
MapWhen branches based on a condition
Supports Nesting (multi-level branching)

Compared with the ancient ASP.NET MVC5

ASP.NET MVC5 ASP.NET Core 2
The concept HTTP Handlers

HTTP Modules
middleware
How is it chosen? HTTP Handlers => based on the filename extension

HTTP Modules => hooked into the life cycle using events
Defined in a specific order with specific keywords

Run => ends the pipeline

Use => short-circuits and handles logic

Map => branches based on the path
Easy to use? Requires a deep understanding of the ASP.NET life-cycle

Hard to use because many modules can hook into the same event
Requires a specific order

Only 1 pipeline, easy to understand/use/debug

3. Default middleware

When you create a new asp.net core project, the .net cli adds a few middlewares for you

  1. Exception Handler: handles exceptions from the middlewares below it
  2. Static Files: returns files in wwwroots
  3. Mvc: routes requests to the actions in the controllers

here’s the source code:

public static void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error/500");
    }
 
    app.UseStaticFiles();
    app.UseAuthentication();
    app.UseSession();
 
    if (!env.IsDevelopment())
    {
        app.UseMiddleware<ErrorHandlingMiddleware>();
    }
 
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Here is the list of built-in middlewares

4. Writing a middleware

4.1. Using a delegate

add code to Startup.cs, the Configure method

app.Use((context, next) =>
{
    var cultureQuery = context.Request.Query["culture"];
    if (!string.IsNullOrWhiteSpace(cultureQuery))
    {
        var culture = new CultureInfo(cultureQuery);
        CultureInfo.CurrentCulture = culture;
        CultureInfo.CurrentUICulture = culture;
    }
    // Call the next delegate/middleware in the pipeline
    return next();
});

4.2. Using your own class

more complicated, but more flexible in return. First, the code for the middleware

public class RequestCultureMiddleware
{
    private readonly RequestDelegate _next;
    public RequestCultureMiddleware(RequestDelegate next) { _next =next; }
    public Task InvokeAsync(HttpContext context)
    {
        var cultureQuery = context.Request.Query["culture"];
        if (!string.IsNullOrWhiteSpace(cultureQuery))
        {
            var culture = new CultureInfo(cultureQuery);
            CultureInfo.CurrentCulture = culture;
            CultureInfo.CurrentUICulture = culture;
        }
        // Call the next delegate/middleware in the pipeline
        return this._next(context);
    }
}

then the code for the extension so you can use that middleware in the Configure method

// Expose through IApplicationBuilder
public static class RequestCultureMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestCultureMiddleware>();
    }
}

finally, use it in the Configure method

// Use in Startup.cs => Configure method
public void Configure(IApplicationBuilder app)
{
    app.UseRequestCulture();
    app.Run(async (context) =>
    {
        await context.Response.WriteAsync($"Hello{CultureInfo.CurrentCulture.DisplayName}");
    });
}

notes when using Dependency Injection

A Scoped lifetime service must be injected into the Invoke or InvokeAsync method. Injecting a scoped lifetime service through the constructor forces that service into being a singleton

there are 3 lifetimes for a service in asp.net: transient, scoped and singleton

  • Transient lifetime services are created every time they are called. This kind fits lightweight, stateless services.
  • Scoped lifetime services are created once per request.
  • Singleton lifetime services are created on the first request (or when ConfigureServices runs, if you create an instance of the service there) and every request after that reuses this instance.