Azure Functions 中的 DI

Posted

技术标签:

【中文标题】Azure Functions 中的 DI【英文标题】:DI in Azure Functions 【发布时间】:2018-02-05 07:45:50 【问题描述】:

我在我的 ASP.NET Web API 应用程序中使用了一些类库来处理我的所有后端内容,例如对 Azure SQL 数据库、Cosmos DB 等多个数据库的 CRUD 操作。

我不想重新发明***并能够在我在 Visual Studio 2017 中创建的新 Azure Functions 中使用它们。我所有的存储库方法都使用一个接口。那么,我将如何在我的新 Azure 函数中实现依赖注入?

我没有看到任何对 DI 的支持,但我有点困惑。 Azure Functions 似乎基于与 WebJobs 相同的 SDK,我认为去年微软已经开始在 WebJobs 中支持 DI - 我很确定,因为我使用 Ninject 实现了它。

有没有办法让我可以在我的新 Azure Functions 项目中使用我现有的库?

【问题讨论】:

Autofac Dependency Injection in Azure Function的可能重复 DI 在两年多前加入 WebJobs。我有点惊讶它在 Functions 中仍然不可用。 (再说一次,我也很困惑 Functions 是基于 static 的。)这个答案说明了 WebJobs DI 的简单性(BuildServiceProvider 将是 Core DI 的“myContainer”等价物)——***.com/a/30454556/152997 在这里投票:feedback.azure.com/forums/355860-azure-functions/suggestions/… 见:Create .NET 6 Azure Function App with Dependency Injection and the Key Vault as Configuration Provider 【参考方案1】:

除了服务定位器(反)模式之外,我还看到了这两种技术。我也向 Azure Functions 团队询问了他们的 cmets。

https://blog.wille-zone.de/post/azure-functions-dependency-injection/

https://blog.wille-zone.de/post/azure-functions-proper-dependency-injection/

【讨论】:

这是我的两分钱。此答案中的链接使用方法注入。还有另一种方法——使用static 属性的属性注入。我为此写了一篇博文,针对 V1,devkimchi.com/2017/11/16/azure-functions-with-ioc-container,针对 V2,blog.mexia.com.au/dependency-injections-on-azure-functions-v2 Azure Functions v2 现在支持开箱即用的 DI:docs.microsoft.com/en-us/azure/azure-functions/…【参考方案2】:

关于此事有open feature request on the GitHub pages for Azure Functions。

但是,我解决这个问题的方法是使用某种“包装器”入口点,使用服务定位器解决这个问题,然后从那里启动函数。

这看起来有点像(简化)

var builder = new ContainerBuilder();
//register my types

var container = builder.Build();

using(var scope = container.BeginLifetimeScope())

  var functionLogic = scope.Resolve<IMyFunctionLogic>();

  functionLogic.Execute();

这当然有点老套,但这是目前最好的(据我所知)。

【讨论】:

【参考方案3】:

我在 willie-zone 博客上看到了很多关于这个主题的提及,但你不需要走那条路来将 DI 与 Azure 函数一起使用。

如果您使用的是版本 2,您可以使您的 Azure 函数成为非静态的。然后,您可以添加一个公共构造函数来注入您的依赖项。下一步是添加一个 IWebJobsStartup 类。在您的启动课程中,您将能够像注册任何其他 .Net Core 项目一样注册您的服务。

我有一个使用这种方法的公共仓库:https://github.com/jedi91/MovieSearch/tree/master/MovieSearch

这里是启动类的直接链接:https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Startup.cs

这里是函数:https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Functions/Search.cs

希望这种方法有所帮助。如果您想让 Azure Functions 保持静态,那么 willie-zone 方法应该可以工作,但我真的很喜欢这种方法,它不需要任何第三方库。

需要注意的一点是 Directory.Build.target 文件。此文件会将您的扩展复制到主机文件中,以便在将功能部署到 Azure 后 DI 将起作用。在本地运行该函数不需要此文件。

【讨论】:

试过你的方法。看起来我的启动代码没有被调用,我确实在顶部添加了程序集属性。由于缺少依赖项,我的函数类无法实例化。此依赖项应在 Startup.cs 中设置。有什么指点吗? 更新到 Microsoft.NET.Sdk.Functions 1.0.26 后修复。 很高兴更新 SDK 解决了您的问题。我在为目标版本使用 netcore 2.1 及更高版本时遇到了问题。起初我只使用 2.0,但后来得知我只需要最新版本的 Webjobs 扩展 nuget 包。我猜更新到最新的 SDK 会将该 nuget 包作为依赖项引入。 此功能现在在最新版本的 Azure Functions 上已损坏。为了让它在 Azure 中工作,您需要将版本指定为 2.0.12342.0,其他版本也可以工作。目前在 GitHub repo 上有一个未解决的 issue 来解决这个问题:github.com/Azure/azure-functions-host/issues/4203 我遇到了同样的问题。将 Microsoft.NET.Sdk.Functions 包升级到 1.0.26 并没有帮助。我还必须将 Microsoft.Azure.WebJobs NuGet 包升级到最新版本(在这种情况下为 3.0.6)。【参考方案4】:

在 MSBuild 2019 上宣布了 Azure Functions 依赖注入。以下是有关如何执行此操作的示例:

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace

    public class Startup : FunctionsStartup
    
        public override void Configure(IFunctionsHostBuilder builder)
        
            builder.Services.AddHttpClient();
            builder.Services.AddSingleton((s) => 
                return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
            );
            builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
        
    

GitHub Example Documentation

【讨论】:

【参考方案5】:

如上所述,它刚刚在 Build 2019 上宣布。现在它几乎可以像在 ASP .Net Core 应用中一样进行设置。

Microsoft Documentation

Short Blog I Wrote

【讨论】:

【参考方案6】:

实际上,Microsoft 提供了一种更好、更简单的方法。虽然有点难找。您只需在此处创建一个启动类并添加所有必需的服务,然后您就可以像在常规 Web 应用程序和 Web API 中一样使用构造函数注入。

这就是你需要做的。

首先我创建我的启动类,我调用我的 Startup.cs 以与 Razor Web 应用程序保持一致,虽然这是针对 Azure Functions,但仍然是 Microsoft 的方式。

using System;
using com.paypal;
using dk.commentor.bl.command;
using dk.commentor.logger;
using dk.commentor.sl;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using org.openerp;

[assembly:Microsoft.Azure.WebJobs.Hosting.WebJobsStartup(typeof(dk.commentor.starterproject.api.Startup))]
namespace dk.commentor.starterproject.api


    public class Startup : IWebJobsStartup
    

        public void Configure(IWebJobsBuilder builder)
        
            builder.Services.AddSingleton<ILogger, CommentorLogger>();
            builder.Services.AddSingleton<IPaymentService, PayPalService>();
            builder.Services.AddSingleton<IOrderService, OpenERPService>();
            builder.Services.AddSingleton<ProcessOrderCommand>();
            Console.WriteLine("Host started!");
        
    

接下来,我将函数中的方法调用从静态更改为非静态,并向类添加一个构造函数(现在也是非静态的)。在这个构造函数中,我只是添加了我需要的服务作为构造函数参数。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using dk.commentor.bl.command;

namespace dk.commentor.starterproject.api

    public class ProcessOrder
    
        private ProcessOrderCommand processOrderCommand;

        public ProcessOrder(ProcessOrderCommand processOrderCommand) 
            this.processOrderCommand = processOrderCommand;
        

        [FunctionName("ProcessOrder")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        
            log.LogInformation("C# HTTP trigger ProcessOrder called!");
            log.LogInformation(System.Environment.StackTrace);

            string jsonRequestData = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic requestData = JsonConvert.DeserializeObject(jsonRequestData);

            if(requestData?.orderId != null)
                return (ActionResult)new OkObjectResult($"Processing order with id requestData.orderId");
            else
                return new BadRequestObjectResult("Please pass an orderId in the request body");
        
    

希望这会有所帮助。

【讨论】:

【参考方案7】:

我想加我的 2 美分。我使用了主机注入 ILogger 所使用的技术。如果您查看 Startup 项目,我创建了实现 IBindingProvider 的 GenericBindingProvider。然后对于我要注入的每种类型,我将其注册如下:

builder.Services.AddTransient<IWelcomeService, WelcomeService>();
builder.Services.AddSingleton<IBindingProvider, GenericBindingProvider<IWelcomeService>>();

缺点是需要注册两次想要注入函数的类型。

示例代码:

Azure Functions V2 Dependency Injection sample

【讨论】:

【参考方案8】:

我在 Azure Functions 中一直使用 SimpleInjector 非常好。只需创建一个具有注册的类(我们称之为 IoCConfig),并在函数类中创建该类的静态实例,以便每个实例都使用现有实例。

public interface IIoCConfig

    T GetInstance<T>() where T : class;


public class IoCConfig : IIoCConfig

    internal Container Container;

    public IoCConfig(ExecutionContext executionContext, ILogger logger)
    
        var configurationRoot = new ConfigurationBuilder()
            .SetBasePath(executionContext.FunctionAppDirectory)
            .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        Container = new Container();
        Configure(configurationRoot, logger);
    

    public IoCConfig(IConfigurationRoot configurationRoot, ILogger logger)
    
        Container = new Container();
        Configure(configurationRoot, logger);
    

    private void Configure(IConfigurationRoot configurationRoot, ILogger logger)
    
        Container.RegisterInstance(typeof(IConfigurationRoot), configurationRoot);
        Container.Register<ISomeType, SomeType>();
    

    public T GetInstance<T>() where T : class
    
        return Container.GetInstance<T>();
    

然后在根目录中:

   public static class SomeFunction

    public static IIoCConfig IoCConfig;

    [FunctionName("SomeFunction")]
    public static async Task Run(
        [ServiceBusTrigger("some-topic", "%SUBSCRIPTION_NAME%", Connection = "AZURE_SERVICEBUS_CONNECTIONSTRING")]
        SomeEvent msg,
        ILogger log,
        ExecutionContext executionContext)
    
        Ensure.That(msg).IsNotNull();

        if (IoCConfig == null)
        
            IoCConfig = new IoCConfig(executionContext, log);
        

        var someType = IoCConfig.GetInstance<ISomeType>();
        await someType.Handle(msg);
    

【讨论】:

【参考方案9】:

AzureFunctions.Autofac 非常易于使用。

只需添加一个配置文件:

public class DIConfig

    public DIConfig(string functionName)
    
        DependencyInjection.Initialize(builder =>
        
            builder.RegisterType<Sample>().As<ISample>();
            ...
        , functionName);
    

添加 DependencyInjectionConfig 属性然后注入:

[DependencyInjectionConfig(typeof(DIConfig))]
public class MyFunction

    [FunctionName("MyFunction")]
    public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]HttpRequestMessage request, 
                                          TraceWriter log, 
                                          [Inject]ISample sample)
    

https://github.com/introtocomputerscience/azure-function-autofac-dependency-injection

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review 感谢您的反馈。我已经编辑了我的帖子以包含答案的基本部分。 以这种方式使用 Autofac 会为每个使用 DependencyInjectionConfig 属性的类创建一个单独的容器,因为每个类都是静态的,并且每个类都有一次该属性。这意味着在启动时,每个静态类都将运行相同的注册,这很慢。 Autofac 使 Azure Function 启动非常缓慢。为整个函数应用程序仅使用一个容器来使用 Autofac 的一种方法是使用一个部分类,该类将整个项目中的每个函数拆分为用于组织的类,这些类实际上只有一个静态类。 Autofac for DI 在扩展方面存在严重问题。 使用 Autofac 时,因为每个静态类都有单独的容器,您可能会遇到套接字异常问题,因为让每个容器共享一个 IHttpClientFactory 等确实很麻烦。选择使用第 2 版函数的 Microsoft DI,否则您的应用会受到严重影响。【参考方案10】:

我认为这是一个更好的解决方案:

https://github.com/junalmeida/autofac-azurefunctions https://www.nuget.org/packages/Autofac.Extensions.DependencyInjection.AzureFunctions

在您的项目中安装 NuGet,然后制作 Startup.cs 并将其放入其中:

[assembly: FunctionsStartup(typeof(Startup))]

public class Startup

    public override void Configure(IFunctionsHostBuilder builder)
    
        builder
            .UseAppSettings() // this is optional, this will bind IConfiguration in the container.
            .UseAutofacServiceProviderFactory(ConfigureContainer);
    

    private void ConfigureContainer(ContainerBuilder builder)
    
         // do DI registration against Autofac like normal! (builder is just the normal ContainerBuilder from Autofac)
    
    ...

然后在您的函数代码中,您可以通过 DI 进行正常的构造函数注入:

public class Function1 : Disposable

    public Function1(IService1 service1, ILogger logger)
    
        // logger and service1 injected via autofac like normal
        // ...
    

    [FunctionName(nameof(Function1))]
    public async Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem)
    
        //...

【讨论】:

【参考方案11】:

从 Azure Functions 2.x 开始支持依赖注入,这意味着 Azure 函数中的依赖注入现在可以利用 .NET Core 依赖注入功能。

在使用依赖注入之前,必须安装以下 NuGet 包:

Microsoft.Azure.Functions.Extensions Microsoft.NET.Sdk.Functions

依赖注入可以简化 DBContext、Http 客户端使用 (Httpclienfactory)、Iloggerfactory、缓存支持等。

首先,更新 Startup 类,如下所示

namespace DemoApp

    public class Startup: FunctionsStartup
    
        public override void Configure(IFunctionsHostBuilder builder)
        
            builder.Services.AddScoped<IHelloWorld, HelloWorld>();

            // Registering Serilog provider
            var logger = new LoggerConfiguration()
                .WriteTo.Console()
                .CreateLogger();
            builder.Services.AddLogging(lb => lb.AddSerilog(logger));
            //Reading configuration section can be added here etc.
        
    

二、Function类和方法级Static关键字的去除

public class DemoFunction

    private readonly IHelloWorld _helloWorld;
    public DemoFunction(IHelloWorld helloWorld)
    
        _helloWorld = helloWorld;
    

    [FunctionName("HttpDemoFunction")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    
        log.LogInformation("C# HTTP trigger function processed a request.");
    

如果我们查看上面的内容,例如IHelloWorld 是使用 .NET Core DI 注入的

**注意:**尽管拥有最新版本的 Azure 函数 v3 以启用依赖注入的几个步骤,但如上所示是手动的

github上的示例代码可以找到here

【讨论】:

以上是关于Azure Functions 中的 DI的主要内容,如果未能解决你的问题,请参考以下文章

Azure Functions 中的 HttpClient 最佳实践

Azure Functions 中的配置文件

Azure Functions 中的 DI

使用另一个项目中的 Azure Functions

如何从 Azure Functions 中的存储容器读取多个文件

Azure 事件中心 Event Grid(事件网格)+Azure Functions处理IOT Hub中的消息