Azure函数中的DI

时间:2017-08-28 05:28:29

标签: asp.net azure asp.net-web-api azure-functions

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

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

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

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

11 个答案:

答案 0 :(得分:23)

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

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

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

答案 1 :(得分:8)

关于此事有一个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();
}

这当然有点笨拙,但它是最好的,直到现在(据我所知)。

答案 2 :(得分:5)

关于该主题,我已经看到willie-zone博客提到了很多,但是您无需走这条路就可以将DI与Azure函数一起使用。

如果使用的是Version2,则可以使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即可工作。在本地运行该功能不需要此文件。

答案 3 :(得分:1)

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

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

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

示例代码:

Azure Functions V2 Dependency Injection sample

答案 4 :(得分:1)

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

答案 5 :(得分:1)

实际上,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");
        }
    }
}

希望这会有所帮助。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

Microsoft Documentation

Short Blog I Wrote

答案 8 :(得分:0)

我一直在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>();
    }
}

然后以root身份登录

   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 :(得分:0)

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

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

在您的项目中安装NuGet,然后创建一个Startup.cs并将其放入其中:

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>

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

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

答案 10 :(得分:0)

对依赖关系注入的支持始于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