Cara menyuntikkan atau menggunakan IConfiguration di Azure Function V3 dengan Dependency Injection saat mengonfigurasi layanan

9

Biasanya dalam proyek .NET Core saya akan membuat kelas 'boostrap' untuk mengkonfigurasi layanan saya bersama dengan perintah pendaftaran DI. Ini biasanya merupakan metode ekstensi di IServiceCollectionmana saya dapat memanggil metode seperti .AddCosmosDbServicedan semua yang diperlukan adalah 'mandiri' di kelas statis yang berisi metode itu. Kuncinya adalah bahwa metode ini mendapat IConfigurationdari Startupkelas.

Saya telah bekerja dengan DI di Fungsi Azure di masa lalu tetapi belum menemukan persyaratan khusus ini.

Saya menggunakan IConfigurationuntuk mengikat ke kelas yang konkret dengan pengaturan properti yang cocok dari kedua saya local.settings.jsonserta pengaturan aplikasi dev / produksi ketika Fungsi dikerahkan di Azure.

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

Jelas menambahkan bidang pribadi untuk IConfigurationdi Startup.cstidak akan berfungsi karena perlu diisi dengan sesuatu dan saya juga membaca bahwa menggunakan DI untuk IConfigurationbukan ide yang baik .

Saya juga telah mencoba menggunakan pola opsi seperti yang dijelaskan di sini dan diimplementasikan sebagai berikut:

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

Meskipun ini akan berfungsi untuk menyuntikkan IOptions<CosmosDbClientSettings>ke kelas non-statis, saya menggunakan kelas statis untuk menahan pekerjaan konfigurasi saya.

Ada saran tentang bagaimana saya bisa membuat ini berfungsi atau solusi yang mungkin? Saya lebih suka menyimpan semua konfigurasi di satu tempat (file bootstrap).

Jason Shave
sumber

Jawaban:

5

Contoh tertaut dirancang dengan buruk (In My Opinion). Ini mendorong kopling ketat dan campuran panggilan menunggu async dan memblokir.

IConfigurationditambahkan ke kumpulan layanan secara default sebagai bagian dari start up, jadi saya akan menyarankan mengubah desain Anda untuk mengambil keuntungan dari resolusi dependensi yang ditangguhkan sehingga IConfigurationdapat diselesaikan melalui dibangun IServiceProvidermenggunakan delegasi pabrik.

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

Perhatikan pendekatan yang diambil untuk menyiasati keharusan untuk menggunakan async voidevent handler non-async.

Referensi Async / Menunggu - Praktik Terbaik dalam Pemrograman Asynchronous .

Jadi sekarang Configurebisa dipanggil dengan benar.

public class Startup : FunctionsStartup {

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

Berikut ini adalah contoh yang bisa saya lakukan; itu membuat koneksi ke Azure App Configuration untuk konfigurasi terpusat dan manajemen fitur. Seseorang harus dapat menggunakan semua fitur DI, seperti IConfigurationdan IOptions<T>, sama seperti yang akan mereka lakukan dalam pengontrol Inti ASP.NET.

Ketergantungan 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"]}!'");
    }
}
Kittoes0124
sumber
Dengan pendekatan ini, saya memiliki masalah bahwa host.jsonparameter tidak digunakan, khususnya,routePrefix
Andrii
1
@Andrii Menarik, saya harus melakukan penelitian dan akan mengedit posting saya jika ada solusi; terima kasih banyak untuk kepala!
Kittoes0124