DI в лазурных функциях

У меня есть некоторые библиотеки классов, которые я использую в своем приложении ASP.NET Web API, которые обрабатывают все мои бэкэнда, например операции CRUD, в несколько баз данных, таких как база данных Azure SQL, база данных Cosmos DB и т.д.

Я не хочу повторно изобретать колесо и использовать их в новых функциях Azure, которые я создаю в Visual Studio 2017. Все мои методы репозитория используют интерфейс. Итак, как мне реализовать инъекцию зависимостей в моей новой функции Azure?

Я не вижу поддержки для DI, но я немного смущен. Похоже, что функции Azure основаны на том же SDK, что и WebJobs, и я думаю, что в прошлом году Microsoft начала поддерживать DI в WebJobs - я точно знаю, потому что я реализовал его с помощью Ninject.

Есть ли способ обойти это, чтобы я мог использовать мои существующие библиотеки в моем новом проекте Azure Functions?

Ответ 2

Существует открытый запрос функции на страницах GitHub для 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

Я видел, что в блоге Вилли-Зона часто упоминается эта тема, но вам не нужно идти по этому пути, чтобы использовать 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 оставались статичными, то подход "зона Вилли" должен работать, но мне действительно нравится этот подход, и он не требует сторонних библиотек.

Стоит обратить внимание на файл Directory.Build.target. Этот файл скопирует ваши расширения в файл хоста, чтобы DI работал после развертывания функции в Azure. Для локального запуска функции этот файл не требуется.

Ответ 4

Я хотел бы добавить к нему 2 цента. Я использовал технику, которую использовал Хозяин, вводящий ILogger. Если вы посмотрите на проект Startup, я создал GenericBindingProvider, который реализует IBindingProvider. Затем для каждого типа, который я хочу ввести, я зарегистрирую его следующим образом:

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

Недостатком является то, что вам нужно зарегистрировать тип, который вы хотите ввести в функцию дважды.

Образец кода:

Лазерные функции V2 Зависимость образца для инъекций

Ответ 5

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

Ответ 6

Поддержка функций DI была добавлена для функций Azure. Вот как это можно сделать.

Поскольку функция Azure основана на ASP.Net Core 2.2, вы можете определить класс Startup реализующий IWebJobsStartup:

[assembly: WebJobsStartup(typeof(Startup))]

public class Startup : IWebJobsStartup
{
    public void Configure(IWebJobsBuilder webJobsBuilder)
    {
        ConfigureServices(webJobsBuilder.Services);
    }

    private void ConfigureServices(IServiceCollection services)
    {
        // Registers all your services.
        services.AddTransient<IService, ServiceImpl>();
    }
}

Затем вы можете определить функцию Azure следующим образом:

public sealed class MyFunction
{
    private readonly IService _service;

    public MyFunction(IService service)
    {
        _service = service;
    }

    public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "GET", Route = "v1/resource/{resourceId}")] HttpRequest httpRequest, string resourceId)
    {
         // Do awesome stuff. 
    }
}

Ответ 7

Внедрение Depdendency для функций 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>();
        }
    }
}

Ответ 9

На самом деле Microsoft предлагает гораздо более простой и удобный способ. Это немного трудно найти, хотя. Вы просто создаете класс запуска и добавляете сюда все необходимые сервисы, а затем можете использовать инжектор конструктора, как в обычных веб-приложениях и веб-интерфейсах.

Это все, что вам нужно сделать.

Сначала я создаю свой начальный класс, я называю свой файл Startup.cs совместимым с веб-приложениями Razor, хотя это относится к функциям Azure, но все же это путь 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");
        }
    }
}

Надеется, что это помогает.