Tìm hiểu về Middleware trong ASP.NET Core

6 min read

Middleware đóng vai trò quan trọng trong việc phát triển phần mềm trong ASP.NET Core, cho phép lập trình viên phát triển web apps một cách linh hoạt và dễ dàng mở rộng, thêm mới các chức năng cho hệ thống hiện tại.

Trong bài viết này, chúng ta sẽ tìm hiểu về các đặc điểm của middleware, thứ tự chạy middleware trong request pipeline và một số các trường hợp sử dụng middleware thường thấy.

Middleware là gì?

Trong ASP.NET Core, middleware là một thành phần phần mềm được gắn vào chuỗi xử lý của ứng dụng để xử lý các HTTP request và response. Mỗi thành phần:

  • Được cấu hình trong request pipeline. Ta có thể hiểu request pipeline giống như một chuỗi các tác vụ để xử lý HTTP request và response theo thứ tự, trong đó mỗi middleware là một bước trong chuỗi này.
  • Có thể can thiệp vào cả quá trình xử lý request và response. Mỗi middleware đều có thể xử lý, thay đổi request và quyết định việc chuyển tiếp request tới middleware tiếp theo trong pipeline.
  • Nhận một HttpContext và một request delegate để gọi middleware tiếp theo trong pipeline.

Request delegate là một hàm / phương thức được dùng để xử lý các HTTP request.

Request delegate pipeline trong ASP.NET Core
Request delegate pipeline trong ASP.NET Core

Ví dụ về middleware

Dưới đây là một ví dụ đơn giản nhất về middleware, trong đó một anonymous function sẽ được gọi vào mỗi request và cập nhật response mới:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world from anonymous function middleware!");
});

app.MapGet("/", () => "Hello World!");

app.Run();

Ta sẽ nhận được kết quả trả về như sau khi request đến endpoint / của app:

anonymous function middleware

Ta cũng có thể tách middleware ra class riêng biệt. Điều này giúp việc tổ chức code được rõ ràng hơn, đồng thời cũng tăng tính tái sử dụng và giúp việc kiểm thử middleware được dễ dàng hơn. Ngoài ra, viết middleware ra class riêng cũng dễ đọc hiểu hơn, nhất là đối với nhưng member mới join dự án.

namespace MiddlewareSample
{
    public class SimpleMiddleware
    {
        private readonly RequestDelegate _next;

        public SimpleMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            await context.Response.WriteAsync("Hello from SimpleMiddleware\n");

            await _next(context);
        }
    }
}

Sau khi tạo class SimpleMiddleware.cs, ta có thể gắn middleware này vào request pipeline trong Program.cs

using MiddlewareSample;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseMiddleware<SimpleMiddleware>();

app.Run();
simple middleware from class

Extension method UseRun

Trong ASP.NET Core, UseRun đều được sử dụng để đăng ký middleware vào request pipeline. Ta có thể nối chuỗi nhiều middleware bằng cách sử dụng Use. Tham số next đại diện cho delegate tiếp theo trong request pipeline. Như vậy ta có thể chuyển tiếp request đến middleware tiếp theo bằng cách gọi đến delegate này.

Ví dụ về chuỗi middleware:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello from 1st middleware\n");
    await next.Invoke();
});
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Hello from 2nd middleware\n");
    await next.Invoke();
});

app.Run();
chaining multiple middlewares

Delegate được đăng ký với method Run được gọi là terminal middleware. Middleware này sẽ chỉ xử lý yêu cầu mà không chuyển tiếp yêu cầu đến middleware tiếp theo, kết thúc request pipeline và không call next.Invoke(). Nếu nhiều terminal middlewares được đăng ký, chỉ có terminal middleware được đăng ký đầu tiên được chạy.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Multiple terminal middlewares registered, only the first one will be executed
app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 1st terminal middleware\n");
});
app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd terminal middleware\n");
});

app.Run();
terminal middleware

Thứ tự middleware

Thứ tự các middleware được add vào Program.cs định nghĩa ra thứ tự thực thi các middleware này khi có request và ngược lại khi gửi response. Thứ tự này vô cùng quan trọng đối với việc bảo mật, hiệu năng và chức năng.

Sơ đồ sau đây cho thấy toàn bộ request pipeline của ASP.NET Core MVC và Razor Pages app. Có thể thấy được trong một application thông thường, các middleware có sẵn được sắp xếp và các custom middleware được thêm vào ở đâu trong request pipeline. Ta có thể sắp xếp lại các middleware hiện có hoặc thêm mới các custom middlewares tùy vào yêu cầu của dự án.

middleware-pipeline

Middleware Endpoint trong sơ đồ trên sẽ thực thi pipeline bộ lọc (Filter) cho loại app tương ứng – MVC hoặc Razor Pages.

endpoint middlewares
Endpoint middlewares per app type

Middleware Routing trong sơ đồ trên được đặt sau middleware Static Files. Đây là thứ tự middleware được các project template sử dụng bằng cách gọi extension method UseRouting. Nếu bạn không dùng UseRouting, mặc định middleware Routing sẽ được thực thi ở đầu request pipeline.

Một số ứng dụng của middleware

Logging middleware

Ta có thể sử dụng middleware để ghi lại các thông tin về request và response nhằm phục vụ cho việc giám sát, debug và phân tích. Middleware này có thể ghi lại các thông tin như phương thức HTTP, đường dẫn URL, mã trạng thái phản hồi, thời gian xử lý, và nhiều thông tin khác.

Ví dụ sau log thời gian thực thi của middleware:

using System.Diagnostics;

namespace MiddlewareSample
{
    public class RequestLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RequestLoggingMiddleware> _logger;

        public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            var stopwatch = Stopwatch.StartNew();

            _logger.LogInformation("Handling request: {0}", context.Request.Path);

            await _next(context);

            stopwatch.Stop();

            _logger.LogInformation("Finished handling request.");

            _logger.LogInformation($"Outgoing Response: {context.Response.StatusCode}. Request took: {stopwatch.ElapsedMilliseconds}ms");
        }
    }
}

Application log:

logging middleware app log

Ta có thể mở rộng logging middleware để ghi lại nhiều thông tin hơn như request / response headers, body, thông tin người dùng nếu có …

Exception handling middleware

Trong ASP.NET Core, middleware có thể được sử dụng để xử lý các ngoại lệ (exceptions) xảy ra trong quá trình xử lý request. Việc này nhằm đảm bảo các ngoại lệ được bắt và xử lý một cách có tổ chức, cung cấp response phù hợp cho người dùng và ghi lại các thông tin phục vụ cho quá trình debug.

Ví dụ sau sẽ bắt ngoại lệ và tạo response trả về cho người dùng:

using System.Net;

namespace MiddlewareSample
{
    public class ExceptionHandlingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ExceptionHandlingMiddleware> _logger;

        public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred");
                
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                context.Response.ContentType = "application/json";

                var response = new
                {
                    context.Response.StatusCode,
                    Message = "Internal Server Error",
                    ExceptionMessage = ex.Message
                };

                await context.Response.WriteAsJsonAsync(response);
            }
        }
    }
}
exception handling middleware

Authentication và Authorization

Authentication và authorization là hai khía cạnh quan trọng của việc bảo mật ứng dụng web. Authentication middleware xác định danh tính của người dùng, trong khi Authorization middleware kiểm tra xem người dùng có quyền truy cập vào tài nguyên cụ thể hay không.

Ví dụ sau setup phần authentication và authorization trong web app sử dụng cookie auth:

Trong file Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// Configure authentication and authorization
builder.Services.AddAuthentication("CookieAuth")
    .AddCookie("CookieAuth", options =>
    {
        options.Cookie.Name = "SimpleAuthCookie";
        options.LoginPath = "/api/auth";
    });
builder.Services.AddAuthorization(config =>
{
    config.AddPolicy("AdminOnly", policy => policy.RequireClaim("Admin"));
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/error");
    app.UseHsts();
}

//app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

// Authentication and authorization middlewares
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

Ta có thể setup các endpoint như sau để authenticate user

 [HttpPost("authenticate")]
 public async Task<IActionResult> Authenticate()
 {
     var claims = new List<Claim>
     {
         new(ClaimTypes.Name, "User")
     };

     var identity = new ClaimsIdentity(claims, "CookieAuth");
     var principal = new ClaimsPrincipal(identity);

     await HttpContext.SignInAsync("CookieAuth", principal);

     return Ok("User authenticated.");
 }

Bằng cách sử dụng attribute Authorize, ta có thể xác định một endpoint chỉ có thể access được khi user đã đăng nhập

[HttpGet("secured")]
[Authorize]
public IActionResult Get()
{
    return Ok("This is a secured endpoint");
}

Ngoài một số ví dụ kể trên, middleware còn được ứng dụng trong nhiều trường hợp khác như routing, cấu hình sử dụng static files, set up CORS (Cross-Origin Resource Sharing), nén response …

Kết luận

Middleware là một phần không thể thiếu trong việc xây dựng các ứng dụng web với ASP.NET Core. Nắm được các đặc điểm của middleware, thứ tự chạy và các trường hợp sử dụng khác nhau của middleware là điều cần thiết để xây dựng các ứng dụng web mạnh mẽ và linh hoạt.

Hy vọng rằng bài viết này đã cung cấp cho bạn cái nhìn tổng quan và các ví dụ cụ thể để bắt đầu với middleware trong ASP.NET Core.

Code sample

https://github.com/silver311/aspnet-core-middleware

Nguồn tham khảo

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0

Avatar photo

Clean Code: Nguyên tắc viết hàm trong lập trình…

Trong quá trình phát triển phần mềm, việc viết mã nguồn dễ đọc, dễ hiểu là yếu tố then chốt để đảm bảo code...
Avatar photo Dat Tran Thanh
3 min read

Clean Code: Nguyên tắc comment trong lập trình

Trong lập trình, code không chỉ là một tập hợp các câu lệnh để máy tính thực thi, mà còn là một hình thức...
Avatar photo Dat Tran Thanh
3 min read

Clean Code: Nguyên tắc xử lý lỗi (Error Handling)

Trong quá trình phát triển phần mềm, việc xử lý lỗi không chỉ là một phần quan trọng mà còn ảnh hưởng trực tiếp...
Avatar photo Dat Tran Thanh
4 min read

Leave a Reply

Your email address will not be published. Required fields are marked *