Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Built-in Message Handlers trong ASP.NET WebAPI C#

Message handler là class nhận HTTP request và trả về HTTP response. Message handlers derive từ abstract HttpMessageHandler class. Message handlers cho phép process, edit, hoặc decline incoming request trước khi nó đến HttpControllerDispatcher.

Message handlers execute sớm hơn nhiều trong request processing pipeline, do đó chúng là nơi tuyệt vời để implement cross-cutting concerns trong Web API. Message Handlers là chuỗi các classes (có thể system-defined hoặc custom) nằm cạnh process HTTP request/response qua pipeline.

Server-Side Message Handlers

Trên server side, Web API pipeline dùng built-in message handlers:

  • HttpServer - Nhận request từ host.
  • HttpRoutingDispatcher - Dispatch request dựa trên route.
  • HttpControllerDispatcher - Gửi request đến Web API controller.

Ta có thể thêm custom handlers vào pipeline. Message handlers tốt cho cross-cutting concerns operate ở HTTP message level (thay vì controller actions). Ví dụ message handler có thể:

  • Read hoặc modify request headers.
  • Add response header vào responses.
  • Validate requests trước khi đến controller.

Client-Side HTTP Message Handlers

Trên client side, class HttpClient dùng message handler xử lý requests. Default handler là HttpClientHandler, gửi request qua network và nhận response từ server. Ta có thể insert custom message handlers vào client pipeline.

Pipeline Architecture

┌─────────────────────────────────────────────────────────────┐
│                        HTTP REQUEST                         │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  HttpServer (Top-level handler)                             │
│  - Receive request from host (IIS, Self-host, OWIN)        │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  Custom DelegatingHandlers (Added to config.MessageHandlers)│
│  1. LoggingHandler        → Pre/Post process                │
│  2. AuthenticationHandler → Validate token                  │
│  3. CorsHandler           → Add CORS headers                │
│  4. CompressionHandler    → Compress response               │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  HttpRoutingDispatcher                                      │
│  - Match route, select controller                           │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  HttpControllerDispatcher                                   │
│  - Create controller instance                               │
│  - Execute action, filters                                  │
│  - Return response                                          │
└─────────────────────────┬───────────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────────┐
│                    HTTP RESPONSE (Reverse order)            │
└─────────────────────────────────────────────────────────────┘

HttpMessageHandler Base Class

public abstract class HttpMessageHandler {
    protected internal abstract Task SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken);
    
    protected virtual void Dispose(bool disposing) { ... }
    public void Dispose() { ... }
}

DelegatingHandler (Chaining)

public class DelegatingHandler : HttpMessageHandler {
    protected HttpMessageHandler InnerHandler { get; set; }
    
    protected override async Task SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken) {
        // Pre-processing
        var response = await InnerHandler.SendAsync(request, cancellationToken);
        // Post-processing
        return response;
    }
}

Custom Handler Examples

1. Request/Response Logging

public class LoggingHandler : DelegatingHandler {
    protected override async Task SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken) {
        
        var startTime = DateTime.UtcNow;
        var requestBody = await request.Content.ReadAsStringAsync();
        
        Debug.WriteLine($"REQUEST: {request.Method} {request.RequestUri}");
        Debug.WriteLine($"Headers: {request.Headers}");
        Debug.WriteLine($"Body: {requestBody}");
        
        var response = await base.SendAsync(request, cancellationToken);
        
        var duration = DateTime.UtcNow - startTime;
        var responseBody = response.Content != null 
            ? await response.Content.ReadAsStringAsync() 
            : "";
        
        Debug.WriteLine($"RESPONSE: {response.StatusCode} ({duration.TotalMs}ms)");
        Debug.WriteLine($"Body: {responseBody}");
        
        return response;
    }
}

2. API Key Validation (Short-circuit)

public class ApiKeyHandler : DelegatingHandler {
    private const string ApiKeyHeader = "X-API-Key";
    private readonly string _validApiKey;
    
    public ApiKeyHandler(string validApiKey) {
        _validApiKey = validApiKey;
    }
    
    protected override async Task SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken) {
        
        if (!request.Headers.TryGetValues(ApiKeyHeader, out var values) 
            || values.FirstOrDefault() != _validApiKey) {
            
            var response = request.CreateResponse(HttpStatusCode.Unauthorized);
            response.Content = new StringContent("Invalid API Key");
            return response; // Short-circuit - không gọi base.SendAsync
        }
        
        return await base.SendAsync(request, cancellationToken);
    }
}

3. Custom Header Injection

public class CustomHeaderHandler : DelegatingHandler {
    protected override async Task SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken) {
        
        var response = await base.SendAsync(request, cancellationToken);
        
        // Thêm custom headers vào response
        response.Headers.Add("X-Powered-By", "ASP.NET Web API");
        response.Headers.Add("X-Version", "1.0.0");
        response.Headers.Add("X-Response-Time", DateTime.UtcNow.ToString("O"));
        
        return response;
    }
}

Client-Side Handlers (HttpClient)

var handler = new HttpClientHandler();
var client = new HttpClient(new LoggingHandler(handler) {
    InnerHandler = new RetryHandler(3, TimeSpan.FromSeconds(1)) {
        InnerHandler = new AuthHandler("token") {
            InnerHandler = handler
        }
    }
});

// Hoặc dùng extension (System.Net.Http.Extensions)
var client = HttpClientFactory.Create(
    new LoggingHandler(),
    new RetryHandler(3),
    new AuthHandler("token"),
    new HttpClientHandler()
);

Registration Order Quan Trọng

config.MessageHandlers.Add(new LoggingHandler());      // 1. Outer-most (first request, last response)
config.MessageHandlers.Add(new AuthenticationHandler()); // 2.
config.MessageHandlers.Add(new CorsHandler());           // 3.
config.MessageHandlers.Add(new CompressionHandler());    // 4. Inner-most (last request, first response)

Quy tắc: Request flow: 1→2→3→4→Controller. Response flow: Controller→4→3→2→1.