Skip to content
Go back

ASP.NET Core进阶:通过Delegating Handlers扩展HttpClient,日志记录、弹性策略与身份验证一网打尽!

Published:  at  10:51 AM

ASP.NET Core进阶:通过Delegating Handlers扩展HttpClient,日志记录、弹性策略与身份验证一网打尽! 🚀

什么是Delegating Handlers?🤔

在ASP.NET Core开发中,Delegating Handlers是一种非常强大的机制,它的作用类似于ASP.NET Core中间件。不同之处在于:

通过它,我们可以为HttpClient添加额外行为,例如日志记录、请求重试、身份验证等。这些功能在开发云原生应用和微服务架构时尤为重要。

今天,我们将深入探讨如何利用Delegating Handlers来优化HTTP请求处理,并通过丰富的代码示例实现以下功能:


配置HttpClient:第一步👨‍💻

我们从一个简单的应用开始,它使用HttpClient调用GitHub API,并通过GitHubService类实现一个类型化客户端(Typed Client)。如下是代码:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
});

var app = builder.Build();

app.MapGet("api/users/{username}", async (
    string username,
    GitHubService gitHubService) =>
{
    var content = await gitHubService.GetByUsernameAsync(username);
    return Results.Ok(content);
});

app.Run();

什么是类型化客户端?

类型化客户端封装了HttpClient的行为,让我们可以通过强类型的API发送请求,而无需直接操作HttpClient。它的优势包括:

示例代码:

public class GitHubService(HttpClient client)
{
    public async Task<GitHubUser?> GetByUsernameAsync(string username)
    {
        var url = $"users/{username}";
        return await client.GetFromJsonAsync<GitHubUser>(url);
    }
}

日志记录:让HTTP请求透明化 📜

第一步是为HTTP请求添加日志记录功能。我们通过自定义Delegating Handler实现这一点。

创建LoggingDelegatingHandler

以下代码展示了如何创建一个自定义Delegating Handler,用于在发送HTTP请求前后记录日志:

public class LoggingDelegatingHandler(ILogger<LoggingDelegatingHandler> logger)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        try
        {
            logger.LogInformation("Before HTTP request");

            var result = await base.SendAsync(request, cancellationToken);

            result.EnsureSuccessStatusCode();

            logger.LogInformation("After HTTP request");

            return result;
        }
        catch (Exception e)
        {
            logger.LogError(e, "HTTP request failed");
            throw;
        }
    }
}

注册Handler到DI容器

将Handler注册为Transient服务,并绑定到GitHubService

builder.Services.AddTransient<LoggingDelegatingHandler>();

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
})
.AddHttpMessageHandler<LoggingDelegatingHandler>();

弹性策略:打造云端稳定应用 🌐

为了提高应用在网络波动或服务故障时的稳定性,我们可以通过Polly库为HTTP请求增加重试策略。

创建RetryDelegatingHandler

以下是一个实现重试机制的代码示例:

public class RetryDelegatingHandler : DelegatingHandler
{
    private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy =
        Policy<HttpResponseMessage>
            .Handle<HttpRequestException>()
            .RetryAsync(2);

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var policyResult = await _retryPolicy.ExecuteAndCaptureAsync(
            () => base.SendAsync(request, cancellationToken));

        if (policyResult.Outcome == OutcomeType.Failure)
        {
            throw new HttpRequestException(
                "Something went wrong",
                policyResult.FinalException);
        }

        return policyResult.Result;
    }
}

配置多层Handler

我们可以将多个Delegating Handlers链式绑定,实现叠加功能:

builder.Services.AddTransient<RetryDelegatingHandler>();

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
})
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddHttpMessageHandler<RetryDelegatingHandler>();

身份验证:安全通信的必备 🔒

在与外部API交互时,身份验证是必不可少的。以下是两个示例实现:

  1. 简单的Access Token认证。
  2. 基于Keycloak的OAuth 2.0认证。

示例1:Access Token认证

通过在请求头中添加Authorization字段实现简单认证:

public class AuthenticationDelegatingHandler(IOptions<GitHubOptions> options)
    : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        request.Headers.Add("Authorization", options.Value.AccessToken);
        request.Headers.Add("User-Agent", options.Value.UserAgent);

        return base.SendAsync(request, cancellationToken);
    }
}

注册到服务:

builder.Services.AddTransient<AuthenticationDelegatingHandler>();

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com");
})
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddHttpMessageHandler<RetryDelegatingHandler>()
.AddHttpMessageHandler<AuthenticationDelegatingHandler>();

示例2:Keycloak OAuth 2.0认证

以下是更复杂的实现,使用Keycloak进行授权,并获取Access Token:

public class KeyCloakAuthorizationDelegatingHandler(
    IOptions<KeycloakOptions> keycloakOptions)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var authToken = await GetAccessTokenAsync();

        request.Headers.Authorization = new AuthenticationHeaderValue(
            JwtBearerDefaults.AuthenticationScheme,
            authToken.AccessToken);

        var httpResponseMessage = await base.SendAsync(
            request,
            cancellationToken);

        httpResponseMessage.EnsureSuccessStatusCode();

        return httpResponseMessage;
    }

    private async Task<AuthToken> GetAccessTokenAsync()
    {
        var params = new KeyValuePair<string, string>[]
        {
            new("client_id", _keycloakOptions.Value.AdminClientId),
            new("client_secret", _keycloakOptions.Value.AdminClientSecret),
            new("scope", "openid email"),
            new("grant_type", "client_credentials")
        };

        var content = new FormUrlEncodedContent(params);

        var authRequest = new HttpRequestMessage(
            HttpMethod.Post,
            new Uri(_keycloakOptions.TokenUrl))
        {
            Content = content
        };

        var response = await base.SendAsync(authRequest, cancellationToken);

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<AuthToken>() ??
               throw new ApplicationException();
    }
}

总结与启发 ✨

通过Delegating Handlers,我们可以轻松扩展HttpClient,解决许多常见的开发问题,例如:



Previous Post
🔥让MCP服务器轻松上云!Azure Functions最新实验功能全解析
Next Post
🚀揭开.NET 10的新篇章:`extension`关键字让扩展方法更强大!