ASP.NET Web APIアプリで使用するいくつかのクラスライブラリがあり、すべてのバックエンドを処理します。 Azure SQL Database、Cosmos DBなどの複数のデータベースに対するCRUD操作.
車輪を再発明して、Visual Studio 2017で作成している新しいAzure Functionsでそれらを使用したくありません。すべてのリポジトリメソッドはインターフェイスを使用します。それでは、新しいAzure関数に依存関係注入をどのように実装しますか?
DIのサポートはありませんが、少し混乱しています。 Azure FunctionsはWebJobsと同じSDKに基づいているようであり、昨年MicrosoftはWebJobsでDIのサポートを開始したと思います。
新しいAzure Functionsプロジェクトで既存のライブラリを使用できるようにする方法はありますか?
サービスロケーター(アンチ)パターンに加えて、これら2つの手法があります。 Azure Functionsチームにもコメントを求めました。
https://blog.wille-zone.de/post/Azure-functions-dependency-injection/
https://blog.wille-zone.de/post/Azure-functions-proper-dependency-injection/
Azure FunctionsのGitHubページで機能のオープンリクエスト がこの問題に関してあります。
ただし、これにアプローチする方法は、何らかの種類の「ラッパー」エントリポイントを使用し、サービスロケーターを使用してこれを解決し、そこから機能を開始することです。
これはこのように見えます(簡略化)
var builder = new ContainerBuilder();
//register my types
var container = builder.Build();
using(var scope = container.BeginLifetimeScope())
{
var functionLogic = scope.Resolve<IMyFunctionLogic>();
functionLogic.Execute();
}
もちろん、これは少しハッキーですが、現時点で(私の知る限り)になるまでは最高です。
このトピックに関しては、willie-zoneブログで多くのことを言及しましたが、Azure機能でDIを使用するためにそのルートに行く必要はありません。
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アプローチが機能するはずですが、私はこのアプローチが本当に好きで、サードパーティのライブラリを必要としません。
注意すべきことの1つは、Directory.Build.targetファイルです。このファイルは拡張機能をホストファイルにコピーするため、関数がAzureにデプロイされるとDIが機能します。関数をローカルで実行する場合、このファイルは必要ありません。
Azure Functions Depdendency Injectionは、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>();
}
}
}
実際には、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");
}
}
}
これが役に立てば幸いです。
2セントを追加したいと思います。 ILoggerをホストするホストが使用する手法を使用しました。スタートアッププロジェクトを見ると、IBindingProviderを実装するGenericBindingProviderを作成しました。次に、注入するタイプごとに、次のように登録します。
builder.Services.AddTransient<IWelcomeService, WelcomeService>();
builder.Services.AddSingleton<IBindingProvider, GenericBindingProvider<IWelcomeService>>();
欠点は、関数に2回挿入するタイプを登録する必要があることです。
サンプルコード:
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