서비스를 구성 할 때 종속성 주입으로 Azure Function V3에서 IConfiguration을 주입하거나 사용하는 방법


9

일반적으로 .NET Core 프로젝트에서는 DI 등록 명령과 함께 서비스를 구성하는 'boostrap'클래스를 만듭니다. 이것은 일반적으로 같은 메소드를 IServiceCollection호출 할 수 있는 확장 메소드이며 .AddCosmosDbService필요한 모든 것이 해당 메소드를 포함하는 정적 클래스에서 '자체 포함'됩니다. 핵심은 메서드가 클래스 IConfiguration에서 가져 오는 것 입니다 Startup.

과거에 Azure Functions에서 DI를 사용해 봤지만 아직이 특정 요구 사항을 충족하지 못했습니다.

함수를 Azure에 배포 할 때 개발자 및 프로덕션 응용 프로그램 설정뿐만 아니라 IConfiguration내 설정과 일치하는 속성으로 콘크리트 클래스에 바인딩하기 위해 를 사용하고 local.settings.json있습니다.

CosmosDbClientSettings.cs

/// <summary>
/// Holds configuration settings from local.settings.json or application configuration
/// </summary>    
public class CosmosDbClientSettings
{
    public string CosmosDbDatabaseName { get; set; }
    public string CosmosDbCollectionName { get; set; }
    public string CosmosDbAccount { get; set; }
    public string CosmosDbKey { get; set; }
}

BootstrapCosmosDbClient.cs

public static class BootstrapCosmosDbClient
{
    /// <summary>
    /// Adds a singleton reference for the CosmosDbService with settings obtained by injecting IConfiguration
    /// </summary>
    /// <param name="services"></param>
    /// <param name="configuration"></param>
    /// <returns></returns>
    public static async Task<CosmosDbService> AddCosmosDbServiceAsync(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        CosmosDbClientSettings cosmosDbClientSettings = new CosmosDbClientSettings();
        configuration.Bind(nameof(CosmosDbClientSettings), cosmosDbClientSettings);

        CosmosClientBuilder clientBuilder = new CosmosClientBuilder(cosmosDbClientSettings.CosmosDbAccount, cosmosDbClientSettings.CosmosDbKey);
        CosmosClient client = clientBuilder.WithConnectionModeDirect().Build();
        CosmosDbService cosmosDbService = new CosmosDbService(client, cosmosDbClientSettings.CosmosDbDatabaseName, cosmosDbClientSettings.CosmosDbCollectionName);
        DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(cosmosDbClientSettings.CosmosDbDatabaseName);
        await database.Database.CreateContainerIfNotExistsAsync(cosmosDbClientSettings.CosmosDbCollectionName, "/id");

        services.AddSingleton<ICosmosDbService>(cosmosDbService);

        return cosmosDbService;
    }
}

Startup.cs

public class Startup : FunctionsStartup
{

    public override async void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddHttpClient();
        await builder.Services.AddCosmosDbServiceAsync(**need IConfiguration reference**); <--where do I get IConfiguration?
    }
}

에 대해 분명히 개인 필드를 추가 IConfiguration에서 Startup.cs하지 않습니다 일이 무엇인가로 채워해야하고 나는 또한 읽었다로 에 대한 DI를 사용하는 것은 IConfiguration좋은 생각되지 않습니다 .

또한 여기에 설명 된 옵션 패턴을 사용해 보았고 다음 과 같이 구현했습니다.

builder.Services.AddOptions<CosmosDbClientSettings>()
    .Configure<IConfiguration>((settings, configuration) => configuration.Bind(settings));

이것은 IOptions<CosmosDbClientSettings>정적이 아닌 클래스 에 주입하는 데 효과적이지만 정적 클래스를 사용하여 구성 작업을 유지하고 있습니다.

이 작업을 수행하는 방법 또는 가능한 해결 방법에 대한 제안 사항이 있습니까? 모든 구성을 한 곳에 보관하고 싶습니다 (부트 스트랩 파일).

답변:


5

링크 된 예는 제대로 (제 생각에) 설계되었습니다. 긴밀한 연결과 비동기 대기 및 차단 호출의 혼합을 권장합니다.

IConfiguration시작의 일부로 기본적으로 서비스 컬렉션에 추가되므로 종속성의 지연된 해결을 활용하도록 디자인을 변경 하여 팩토리 대리자를 사용 IConfiguration하여 빌드 IServiceProvider를 통해 해결할 수 있도록하는 것이 좋습니다 .

public static class BootstrapCosmosDbClient {

    private static event EventHandler initializeDatabase = delegate { };

    public static IServiceCollection AddCosmosDbService(this IServiceCollection services) {

        Func<IServiceProvider, ICosmosDbService> factory = (sp) => {
            //resolve configuration
            IConfiguration configuration = sp.GetService<IConfiguration>();
            //and get the configured settings (Microsoft.Extensions.Configuration.Binder.dll)
            CosmosDbClientSettings cosmosDbClientSettings = configuration.Get<CosmosDbClientSettings>();
            string databaseName = cosmosDbClientSettings.CosmosDbDatabaseName;
            string containerName = cosmosDbClientSettings.CosmosDbCollectionName;
            string account = cosmosDbClientSettings.CosmosDbAccount;
            string key = cosmosDbClientSettings.CosmosDbKey;

            CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
            CosmosClient client = clientBuilder.WithConnectionModeDirect().Build();
            CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerName);

            //async event handler
            EventHandler handler = null;
            handler = async (sender, args) => {
                initializeDatabase -= handler; //unsubscribe
                DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
                await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
            };
            initializeDatabase += handler; //subscribe
            initializeDatabase(null, EventArgs.Empty); //raise the event to initialize db

            return cosmosDbService;
        };
        services.AddSingleton<ICosmosDbService>(factory);
        return service;
    }
}

async void비동기가 아닌 이벤트 처리기에서 사용해야하는 문제를 해결하기위한 접근 방법에 유의하십시오 .

참고 비동기 / 기다리고 있습니다 - 비동기 프로그래밍 모범 사례 .

이제는 Configure올바르게 호출 할 수 있습니다.

public class Startup : FunctionsStartup {

    public override void Configure(IFunctionsHostBuilder builder) =>
        builder.Services
            .AddHttpClient()
            .AddCosmosDbService();
}

4

다음은 내가 채찍질 할 수있는 예입니다. 중앙 집중식 구성 및 기능 관리를 위해 Azure App Configuration에 대한 연결을 설정 합니다. ASP.NET Core 컨트롤러에서 와 마찬가지로 IConfigurationand 등 모든 DI 기능을 사용할 수 있어야합니다 IOptions<T>.

NuGet 종속성

  • Install-Package Microsoft.Azure.Functions.Extensions
  • Install-Package Microsoft.Extensions.Configuration.AzureAppConfiguration

Startup.cs

[assembly: FunctionsStartup(typeof(Startup))]

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder hostBuilder) {
        var serviceProvider = hostBuilder.Services.BuildServiceProvider();
        var configurationRoot = serviceProvider.GetService<IConfiguration>();
        var configurationBuilder = new ConfigurationBuilder();
        var appConfigEndpoint = configuration["AppConfigEndpoint"];

        if (configurationRoot is IConfigurationRoot) {
            configurationBuilder.AddConfiguration(configurationRoot);
        }

        if (!string.IsNullOrEmpty(appConfigEndpoint)) {
            configurationBuilder.AddAzureAppConfiguration(appConfigOptions => {
                // possible to run this locally if refactored to use ClientSecretCredential or DefaultAzureCredential
                appConfigOptions.Connect(new Uri(appConfigEndpoint), new ManagedIdentityCredential());
            });
        }

        var configuration = configurationBuilder.Build();

        hostBuilder.Services.Replace(ServiceDescriptor.Singleton(typeof(IConfiguration), configuration));

        // Do more stuff with Configuration here...
    }
}

public sealed class HelloFunction
{
    private IConfiguration Configuration { get; }

    public HelloFunction(IConfiguration configuration) {
        Configuration = configuration;
    }

    [FunctionName("HelloFunction")]
    public void Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log) {
        log.LogInformation($"Timer Trigger Fired: 'Hello {Configuration["Message"]}!'");
    }
}

이 방법을 host.json사용하면 특히 매개 변수가 사용되지 않는 문제가 있습니다 .routePrefix
Andrii

1
@Andrii 흥미롭게도, 몇 가지 조사를해야하고 해결책이 발견되면 게시물을 편집 할 것입니다. 고마워요!
Kittoes0124
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.