在 Azure 函数应用启动时读取应用配置。

huangapple go评论70阅读模式
英文:

Reading app configuration on startup in azure function ap

问题

我正在尝试理解如何在 Azure Function App 中使用 dotnet 的 in-process 模型正确配置 Startup.cs 中的服务。我在 local.settings.json 中有一些参数(在 Azure 托管时也是相同的值)

"MyOptions:BaseAddress": "https://dev.azurewebsites.net/api/",
"MyOptions:Scope": "xxx/.default",

然后,在一个扩展方法中,我将其连接如下:

private static IServiceCollection AddOptions(this IServiceCollection services)
{
    return services.AddOptions<MyOptions>()
        .Configure<IConfiguration>((settings, configuration) =>
        {
            configuration.GetSection(nameof(MyOptions)).Bind(settings);
        })
        .PostConfigure(opt => opt.ThrowIfValuesNotSet())
        .Services;
}

在配置了选项之后,我想配置一个类型化的 HTTP 客户端,该客户端至少应使用基本地址:

services
    .AddOptions()
    .AddHttpClient<IMyService, MyService>(client =>
        {
            client.BaseAddress = <从选项中获取的值>;
        }
    )

不过,我真的不知道如何实现这一点,我最接近的方法只是从环境中读取,而不依赖于读取配置的类:

var baseAddress = new Uri(Environment.GetEnvironmentVariable($"{nameof(MyOptions)}:BaseAddress"));

我稍后在一个委托处理程序中使用相同的选项来管理调用的服务的授权,但在那里,我可以像往常一样使用 DI:

public class MyAuthorizationHandler : DelegatingHandler
{
    private readonly TokenCredential _credential;
    private readonly MyOptions _options;

    public MyAuthorizationHandler(TokenCredential credential, IOptions<MyOptions> options)
    {
        _credential = credential;
        _options = options.Value;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await _credential.GetTokenAsync(
            new TokenRequestContext(new[]
            {
                _options.Scope
            }), cancellationToken);

        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
        return await base.SendAsync(request, cancellationToken);
    }
}

我知道我可以从 IFunctionsHostBuilder 中访问 IConfiguration,但是按照为 IServiceCollection 编写扩展方法的模式,这有点繁琐,因为我必须将 IConfiguration 实例传递给所有方法调用。是否有一种推荐的实现方式?

更新

正如评论中指出的,添加 HTTP 客户端时有许多重载,您可以在其中访问提供程序:

.AddHttpClient<IMyService, MyService>((provider, client) =>
{
    client.BaseAddress =
        new Uri(provider.GetRequiredService<MyOptions>().BaseAddress);
})
英文:

I am trying to understand how to properly configure services in Startup.cs in an Azure Function App using dotnet in the in-process model.

I have a couple of parameters in local.settings.json (and the same values when hosted in Azure)

&quot;MyOptions:BaseAddress&quot;: &quot;https://dev.azurewebsites.net/api/&quot;,
&quot;MyOptions:Scope&quot;: &quot;xxx/.default&quot;,

In an extension method I then wire this as follows:

private static IServiceCollection AddOptions(this IServiceCollection services)
{
    return services.AddOptions&lt;MyOptions&gt;()
        .Configure&lt;IConfiguration&gt;((settings, configuration) =&gt;
        {
            configuration.GetSection(nameof(MyOptions)).Bind(settings);
        })
        .PostConfigure(opt =&gt; opt.ThrowIfValuesNotSet())
        .Services;
}

After configuring the options, I want to configure a typed http client that should use at least the base address:

services
    .AddOptions()
    .AddHttpClient&lt;IMyService, MyService&gt;(client =&gt;
        {
            client.BaseAddress = &lt;value from options&gt;;
        }
    )

I can't really figure out how to achieve this though, and the closest I've come is just to read from the environment and not rely on my class that reads the configuration:

var baseAddress = new Uri(Environment.GetEnvironmentVariable($&quot;{nameof(MyOptions)}:BaseAddress&quot;));

I later use the same options in a delegating handler to manage authorization for the service being called, but there I can use DI as usual:

public class MyAuthorizationHandler : DelegatingHandler
{
    private readonly TokenCredential _credential;
    private readonly MyOptions _options;

    public MyAuthorizationHandler(TokenCredential credential, IOptions&lt;MyOptions&gt; options)
    {
        _credential = credential;
        _options = options.Value;
    }

    protected override async Task&lt;HttpResponseMessage&gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await _credential.GetTokenAsync(
            new TokenRequestContext(new[]
            {
                _options.Scope
            }), cancellationToken);

        request.Headers.Authorization = new AuthenticationHeaderValue(&quot;Bearer&quot;, token.Token);
        return await base.SendAsync(request, cancellationToken);
    }
}

I know that I can access the IConfiguration from IFunctionsHostBuilder, but following the pattern of writing extension methods for IServiceCollection, it gets a bit cumbersome because I have to pass the IConfiguration instance along to all method calls.

Is there a recommended way of achieving this?

Update

As pointed out to me in the comments, there are many overloads when adding the http client where you can access the provider:

.AddHttpClient&lt;IMyService, MyService&gt;((provider, client) =&gt;
{
    client.BaseAddress =
        new Uri(provider.GetRequiredService&lt;MyOptions&gt;().BaseAddress);
})

答案1

得分: 1

正如评论中指出的,添加 HTTP 客户端时有许多可用的重载方法之一,可以完全满足我所寻找的需求,例如访问提供程序:

services
    .AddOptions()
    .AddHttpClient&lt;IMyService, MyService&gt;((provider, client) =&gt;
    {
        client.BaseAddress =
            new Uri(provider.GetRequiredService&lt;IOptions&lt;MyOptions&gt;&gt;().BaseAddress);
    })

private static IServiceCollection AddOptions(this IServiceCollection services)
{
    return services.AddOptions&lt;MyOptions&gt;()
        .Configure&lt;IConfiguration&gt;((settings, configuration) =&gt;
        {
            configuration.GetSection(nameof(MyOptions)).Bind(settings);
        })
        .PostConfigure(opt =&gt; opt.ThrowIfValuesNotSet())
        .Services;
}

请注意,您必须使用 IOptions&lt;MyClass&gt; 来解析您的选项类,因为我们使用 AddOptions 将其添加到服务集合中。

英文:

As pointed out to me in the comments, there are many overloads available when adding the http client. One of them achieves exactly what I was looking for, e.g. accessing the provider:

services
    .AddOptions()
    .AddHttpClient&lt;IMyService, MyService&gt;((provider, client) =&gt;
    {
        client.BaseAddress =
            new Uri(provider.GetRequiredService&lt;IOptions&lt;MyOptions&gt;&gt;().BaseAddress);
    })

private static IServiceCollection AddOptions(this IServiceCollection services)
{
    return services.AddOptions&lt;MyOptions&gt;()
        .Configure&lt;IConfiguration&gt;((settings, configuration) =&gt;
        {
            configuration.GetSection(nameof(MyOptions)).Bind(settings);
        })
        .PostConfigure(opt =&gt; opt.ThrowIfValuesNotSet())
        .Services;
}

Note that you must resolve your options class with IOptions&lt;MyClass&gt; because we added it to the service collection using AddOptions.

huangapple
  • 本文由 发表于 2023年7月3日 18:44:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76603992.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定