วิธีการฉีดหรือใช้ IConfiguration ใน Azure Function V3 พร้อมการฉีดอ้างอิงเมื่อกำหนดค่าบริการ


9

โดยปกติในโครงการ. NET Core ฉันจะสร้างคลาส 'boostrap' เพื่อกำหนดค่าบริการของฉันพร้อมกับคำสั่งการลงทะเบียน DI นี่เป็นวิธีส่วนขยายIServiceCollectionที่ฉันสามารถเรียกวิธีการที่ชอบ.AddCosmosDbServiceและทุกสิ่งที่จำเป็นคือ 'อยู่ในตัวเอง' ในคลาสแบบสแตติกที่มีวิธีการนั้น กุญแจสำคัญคือว่าวิธีการได้รับIConfigurationจากStartupชั้นเรียน

ฉันเคยทำงานกับ DI ในฟังก์ชั่น Azure ในอดีต แต่ยังไม่ผ่านข้อกำหนดเฉพาะนี้

ฉันใช้IConfigurationเพื่อผูกกับคลาสที่เป็นรูปธรรมพร้อมการตั้งค่าการจับคู่คุณสมบัติจากทั้งของฉันlocal.settings.jsonและการตั้งค่าแอปพลิเคชัน dev / การผลิตเมื่อฟังก์ชั่นการใช้งานใน 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?
    }
}

เห็นได้ชัดว่าการเพิ่มข้อมูลส่วนตัวIConfigurationในStartup.csจะไม่ทำงานตามความต้องการที่จะมีประชากรที่มีบางสิ่งบางอย่างและฉันได้อ่านว่าใช้ DI สำหรับIConfigurationไม่ได้เป็นความคิดที่ดี

ฉันได้ลองใช้รูปแบบตัวเลือกตามที่อธิบายไว้ที่นี่และนำไปใช้เช่น:

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

ขณะนี้จะทำงานเพื่อฉีดIOptions<CosmosDbClientSettings>คลาสที่ไม่คงที่ฉันใช้คลาสแบบคงที่เพื่อระงับการกำหนดค่าของฉัน

ข้อเสนอแนะใด ๆ เกี่ยวกับวิธีที่ฉันสามารถทำงานนี้หรือวิธีแก้ปัญหาที่เป็นไปได้? ฉันต้องการเก็บการกำหนดค่าทั้งหมดไว้ในที่เดียว (ไฟล์ bootstrap)

คำตอบ:


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ในตัวจัดการเหตุการณ์ที่ไม่ใช่ async

อ้างอิงAsync / รอ - Best Practices ในการเขียนโปรแกรมไม่ตรงกัน

ดังนั้นตอนนี้Configureสามารถเรียกใช้ได้อย่างถูกต้อง

public class Startup : FunctionsStartup {

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

4

นี่คือตัวอย่างที่ฉันสามารถเอาชนะ; สร้างการเชื่อมต่อกับAzure App Configurationสำหรับการกำหนดค่าส่วนกลางและการจัดการคุณสมบัติ หนึ่งควรจะสามารถใช้คุณสมบัติ DI ทั้งหมดเช่นIConfigurationและIOptions<T>เช่นเดียวกับที่พวกเขาทำในตัวควบคุม ASP.NET Core

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.