Cách tiêm hoặc sử dụng cấu hình ICon trong Azure Function V3 với Dependency Injection khi định cấu hình dịch vụ


9

Thông thường trong một dự án .NET Core, tôi sẽ tạo một lớp 'boostrap' để định cấu hình dịch vụ của mình cùng với các lệnh đăng ký DI. Đây thường là một phương thức mở rộng trong IServiceCollectionđó tôi có thể gọi một phương thức như thế nào .AddCosmosDbServicevà mọi thứ cần thiết là 'khép kín' trong lớp tĩnh chứa phương thức đó. Điều quan trọng là phương thức này nhận được IConfigurationtừ Startuplớp.

Tôi đã từng làm việc với DI trong Azure Chức năng trong quá khứ nhưng chưa gặp phải yêu cầu cụ thể này.

Tôi đang sử dụng IConfigurationđể liên kết với một lớp cụ thể với các cài đặt khớp với các thuộc tính từ cả local.settings.jsoncài đặt ứng dụng dev / sản xuất của tôi khi Hàm được triển khai trong Azure.

CosmosDbClientSinstall.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?
    }
}

Rõ ràng việc thêm một lĩnh vực riêng tư IConfigurationvào Startup.cssẽ không hoạt động vì nó cần phải được phổ biến với một cái gì đó và tôi cũng đã đọc rằng sử dụng DI cho IConfigurationkhông phải là một ý tưởng tốt .

Tôi cũng đã thử sử dụng mẫu tùy chọn như được mô tả ở đây và triển khai như sau:

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

Trong khi điều này sẽ hoạt động để tiêm một IOptions<CosmosDbClientSettings>lớp không tĩnh, tôi đang sử dụng một lớp tĩnh để giữ công việc cấu hình của tôi.

Bất kỳ đề xuất về cách tôi có thể làm cho công việc này hoặc một cách giải quyết có thể? Tôi muốn giữ tất cả cấu hình ở một nơi (tệp bootstrap).

Câu trả lời:


5

Các ví dụ liên kết được thiết kế kém (In My Opinion). Nó khuyến khích khớp nối chặt chẽ và trộn lẫn các cuộc gọi không đồng bộ và chờ cuộc gọi.

IConfigurationđược thêm vào bộ sưu tập dịch vụ theo mặc định như một phần của quá trình khởi động, vì vậy tôi khuyên bạn nên thay đổi thiết kế của mình để tận dụng độ phân giải của các phụ thuộc bị trì hoãn để IConfigurationcó thể giải quyết thông qua IServiceProviderđại biểu được xây dựng bằng cách sử dụng một đại biểu nhà máy.

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;
    }
}

Lưu ý cách tiếp cận được thực hiện để giải quyết vấn đề phải sử dụng async voidtrong trình xử lý sự kiện không đồng bộ.

Tham khảo Async / Await - Thực tiễn tốt nhất trong lập trình không đồng bộ .

Vì vậy, bây giờ Configurecó thể được gọi đúng.

public class Startup : FunctionsStartup {

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

4

Đây là một ví dụ mà tôi đã có thể đánh đòn; nó thiết lập kết nối với Cấu hình ứng dụng Azure để quản lý tính năng và cấu hình tập trung. Một sẽ có thể sử dụng tất cả các tính năng DI, chẳng hạn như IConfigurationIOptions<T>, giống như đang trong một bộ điều khiển ASP.NET Core.

Phụ thuộc 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"]}!'");
    }
}

Với cách tiếp cận này, tôi có một vấn đề là host.jsoncác tham số không được sử dụng, đặc biệt,routePrefix
Andrii

1
@Andrii Thú vị, tôi sẽ phải thực hiện một số nghiên cứu và sẽ chỉnh sửa bài đăng của tôi nếu tìm thấy giải pháp; cảm ơn rất nhiều cho những người đứng đầu
Kittoes0124
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.