.NET 3.1和.NET 6.0之间的依赖注入差异

发布于 2025-02-12 21:07:59 字数 3641 浏览 0 评论 0原文

目前,我们在对Azure功能从.NET 3.1到.NET 6的升级中遇到了意外行为。

使用Singleton实现的httpClient提供的文档ClientProvider注册为Singleton Service。为了执行安全检查,将委托人添加到httpclient中。此消息处理程序检查自定义标头的存在,以及标头值与我们的SecurityContext中的标头值相同。

public class DocumentClientProvider : IDocumentClientProvider
{
    private static HttpClient singletonInstance;
    private static readonly object padlock = new object();

    public DocumentClientProvider(DelegatingHandler messageHandler)
    {
        if (singletonInstance == null)
        {
            lock (padlock)
            {
                if (singletonInstance == null)
                {
                    singletonInstance =
                        new HttpClient(messageHandler);
                }
            }
        }
    }

    public HttpClient GetClient() => singletonInstance;
}

public class MyMessageHandler : DelegatingHandler
{
    private readonly IServiceProvider serviceProvider;

    public MyMessageHandler(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var securityContext = serviceProvider.GetRequiredService<ISecurityContext>();

        var requestHeaderValue = request.Headers.GetValues("X-Custom-Header").FirstOrDefault();
        var validHeaderValue = $"{securityContext.ClientId.ToString().ToLower()}";

        if (requestHeaderValue != validHeaderValue)
        {
            var message = $"Value of custom header is {requestHeaderValue} and it should be {validHeaderValue}.";
            throw new Exception(message);
        }

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

SecurityContext是一个小型数据类,其中包含有关该功能呼叫者身份的信息,并注册为范围的服务。

public class SecurityContext : ISecurityContext
{
    public Guid ClientId { get; set; }
}

功能只需接收呼叫,通过文档ClientProvider检索HTTPCLIENT并调用端点。

public class Function
{
    private readonly HttpClient httpClient;
    private readonly ISecurityContext securityContext;

    public Function(IDocumentClientProvider documentClientProvider, ISecurityContext securityContext)
    {
        httpClient = documentClientProvider.GetClient();
        this.securityContext = securityContext;
    }

    [FunctionName("Function")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
        ILogger log)
    {
        securityContext.ClientId = Guid.NewGuid();

        string name = req.Query["name"];
        name = name ?? "Dummy";

        var httpRequestMessage = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri($"https://www.google.com/search?q={name}"),
            Headers = {
              { "X-Custom-Header", securityContext.ClientId.ToString() }
            }
        };

        var response = await httpClient.SendAsync(httpRequestMessage);

        return new OkObjectResult(response);
    }
}

不同位的连接如下:

builder.Services.AddSingleton<IDocumentClientProvider>(ctx => 
    new DocumentClientProvider(new MyMessageHandler(ctx) { 
        InnerHandler = new HttpClientHandler() 
}));     
builder.Services.AddScoped<ISecurityContext, SecurityContext>();

在.NET 3.1中,该函数按预期工作。在整个函数执行过程中,将委托人中的SecurityContext解析为相同的对象。但是,在.NET 6中,将委托人中的安全性解决方案解决了另一个对象。 SecurityContext中的clientId是一个空的GUID,并且会引发异常。

有人对为什么不同版本的Azure函数具有与依赖注入有关的不同行为有任何见解吗?

We are currently encountering unexpected behaviour in our upgrade of an Azure Function from .NET 3.1 to .NET 6.

A DocumentClientProvider providing an HttpClient using a singleton implementation is registered as a singleton service. In order to perform a security check, an DelegatingHandler is added to the HttpClient. This message handler checks the existence of a custom header and if the header value is the same as the one in our SecurityContext.

public class DocumentClientProvider : IDocumentClientProvider
{
    private static HttpClient singletonInstance;
    private static readonly object padlock = new object();

    public DocumentClientProvider(DelegatingHandler messageHandler)
    {
        if (singletonInstance == null)
        {
            lock (padlock)
            {
                if (singletonInstance == null)
                {
                    singletonInstance =
                        new HttpClient(messageHandler);
                }
            }
        }
    }

    public HttpClient GetClient() => singletonInstance;
}

public class MyMessageHandler : DelegatingHandler
{
    private readonly IServiceProvider serviceProvider;

    public MyMessageHandler(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        var securityContext = serviceProvider.GetRequiredService<ISecurityContext>();

        var requestHeaderValue = request.Headers.GetValues("X-Custom-Header").FirstOrDefault();
        var validHeaderValue = 
quot;{securityContext.ClientId.ToString().ToLower()}";

        if (requestHeaderValue != validHeaderValue)
        {
            var message = 
quot;Value of custom header is {requestHeaderValue} and it should be {validHeaderValue}.";
            throw new Exception(message);
        }

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

The SecurityContext is a small data class containing information on the identity of the caller of the function and is registered as a scoped service.

public class SecurityContext : ISecurityContext
{
    public Guid ClientId { get; set; }
}

A function simply receives a call, retrieves the HttpClient via the DocumentClientProvider and invokes an endpoint.

public class Function
{
    private readonly HttpClient httpClient;
    private readonly ISecurityContext securityContext;

    public Function(IDocumentClientProvider documentClientProvider, ISecurityContext securityContext)
    {
        httpClient = documentClientProvider.GetClient();
        this.securityContext = securityContext;
    }

    [FunctionName("Function")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
        ILogger log)
    {
        securityContext.ClientId = Guid.NewGuid();

        string name = req.Query["name"];
        name = name ?? "Dummy";

        var httpRequestMessage = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(
quot;https://www.google.com/search?q={name}"),
            Headers = {
              { "X-Custom-Header", securityContext.ClientId.ToString() }
            }
        };

        var response = await httpClient.SendAsync(httpRequestMessage);

        return new OkObjectResult(response);
    }
}

The different bits are wired up as follows:

builder.Services.AddSingleton<IDocumentClientProvider>(ctx => 
    new DocumentClientProvider(new MyMessageHandler(ctx) { 
        InnerHandler = new HttpClientHandler() 
}));     
builder.Services.AddScoped<ISecurityContext, SecurityContext>();

In .NET 3.1, the function works as expected. The SecurityContext in the DelegatingHandler is resolved to the same object throughout the complete function execution. However, in .NET 6, the SecurityContext in the DelegatingHandler is resolved to a different object. The ClientId in the SecurityContext is an empty Guid and an exception is thrown.

Does anyone have any insights as to why the different versions of the Azure Functions present different behaviour related to dependency injection?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文