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?
Ответы
Ответ 1
Я вижу эти два метода в дополнение к шаблону locator (anti). Я также попросил команду Azure Functions получить их комментарии.
https://blog.wille-zone.de/post/azure-functions-dependency-injection/
https://blog.wille-zone.de/post/azure-functions-proper-dependency-injection/
Ответ 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>();
}
}
}
Ответ 8
Как было сказано выше, это было объявлено на Build 2019. Теперь его можно настроить почти так же, как в приложении ASP.Net Core.
Документация Microsoft
Короткий блог, который я написал
Ответ 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");
}
}
}
Надеется, что это помогает.