Azure 函数中的 DI

DI in Azure Functions

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

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

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

实现了它

有没有办法解决这个问题,以便我可以在我的新 Azure Functions 项目中使用我现有的库?

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

但是,我处理这个问题的方法是使用某种 'wrapper' 入口点,使用服务定位器解决这个问题,然后从那里启动该功能。

这看起来有点像这样(简化)

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

var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
  var functionLogic = scope.Resolve<IMyFunctionLogic>();

  functionLogic.Execute();
}

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

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

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

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

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

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

缺点是需要将要注入的类型注册两次。

示例代码:

Azure Functions V2 Dependency Injection sample

我看到 willie-zone 博客在谈到这个主题时提到了很多,但你不需要走那条路来使用 DI 和 Azure 功能。

如果您使用的是 Version2,则可以使您的 Azure 函数成为非静态的。然后你可以添加一个 public 构造函数来注入你的依赖项。下一步是添加 IWebJobsStartup class。在您的初创公司 class 中,您将能够像注册任何其他 .Net Core 项目一样注册您的服务。

我有一个 public 仓库在这里使用这种方法:https://github.com/jedi91/MovieSearch/tree/master/MovieSearch

这里是直接 link 启动 class: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 可以正常工作。 运行 本地函数不需要这个文件。

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

Azure 函数依赖注入已在 MSBuild 2019 上发布。以下是有关操作方法的示例:

[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>();
        }
    }
}

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

Microsoft Documentation

Short Blog I Wrote

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

这就是您需要做的全部。

首先我创建了我的启动 class,我将我的 Startup.cs 称为与 Razor 网络应用程序一致,虽然这是针对 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!");
        }
    }
}

接下来,我将函数中的方法调用从静态更改为 non-static,并将构造函数添加到 class(现在也是 non-static)。在此构造函数中,我只是将所需的服务添加为构造函数参数。

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

希望对您有所帮助。

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

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

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

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)
    {
        //...

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

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

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

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

首先,更新启动 class 如下所示

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

其次,在函数class和方法级别

中删除静态关键字
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 function v3 用于依赖注入来启用几个步骤是手动的,如上所示

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