himanago

Azure・C#などのMS系技術やLINE関連技術など、好きな技術について書くブログ

Azure Functions の DI でエラー

この記事は Microsoft Azure Advent Calendar 2019 の 17日目の記事…ではありません(笑)

qiita.com

アドカレ記事は別途書いてます。今日中には上げます!

[追記] 書きました↓
Durable Functions を使ってコードのみでアンケート回答に便利な LINE Bot を簡単につくる - Qiita

で、この記事は、アドカレ記事を書いていたときに遭遇したエラーについてのメモです。

エラーの内容

前提としては、

  • Azure Functions で LINE Bot を作っていた。
  • Startup で Messaging API SDK 関連のクラスを2つ DI しようとした

という状況。

以下のように Startup を書いたら…

using System;
using LineDC.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Sample.LineBot.Startup))]
namespace Sample.LineBot
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<IDurableWebhookApplication, EnqBotApp>(_ =>
                new EnqBotApp(LineMessagingClient.Create(
                    Environment.GetEnvironmentVariable("ChannelAccessToken")),
                    Environment.GetEnvironmentVariable("ChannelSecret")));
        }
    }
}

なぜかこんなエラーが。

2019-12-17T03:21:41.080 [Error] Executed 'HttpStart' (Failed, Id=5cf2c546-9406-4870-89cc-bae17e4e4e5d)
System.InvalidOperationException : Unable to resolve service for type 'Sample.LineBot.EnqBotApp' while attempting to activate 'Sample.LineBot.DurableEnqFunctions'.
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp,Type type,Type requiredBy,Boolean isDefaultParameterRequired)
   at lambda_method(Closure ,IServiceProvider ,Object[] )
   at Microsoft.Azure.WebJobs.Host.Executors.DefaultJobActivator.CreateInstance[T](IServiceProvider serviceProvider) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\DefaultJobActivator.cs : 37
   at Microsoft.Azure.WebJobs.Host.Executors.DefaultJobActivator.CreateInstance[T](IFunctionInstanceEx functionInstance) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\DefaultJobActivator.cs : 32
   at Microsoft.Azure.WebJobs.Host.Executors.ActivatorInstanceFactory`1.<>c__DisplayClass1_1.<.ctor>b__0(IFunctionInstanceEx i) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\ActivatorInstanceFactory.cs : 20
   at Microsoft.Azure.WebJobs.Host.Executors.ActivatorInstanceFactory`1.Create(IFunctionInstanceEx functionInstance) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\ActivatorInstanceFactory.cs : 26
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`2.CreateInstance(IFunctionInstanceEx functionInstance) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionInvoker.cs : 44
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ParameterHelper.Initialize() at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 846
   at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.TryExecuteAsyncCore(IFunctionInstanceEx functionInstance,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 116

メッセージで調べるといろいろ出てくるのですが、根本的な原因・解決策はすぐにはわからず。

解決策

とりあえずいろいろやってみましたが、以下のように記述を変えたらエラーが出なくなることがわかりました。

using System;
using LineDC.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Sample.LineBot.Startup))]
namespace Sample.LineBot
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var client = LineMessagingClient.Create(Environment.GetEnvironmentVariable("ChannelAccessToken"));

            builder.Services
                .AddSingleton<ILineMessagingClient>(_ => client)
                .AddSingleton<EnqBotApp>(_ => new EnqBotApp(client, Environment.GetEnvironmentVariable("ChannelSecret")));
        }
    }
}

このエラーが出たら DI のしかたを見直してみるといいのかも。

※ 追記 ※

WebhookApplication クラスのスコープがまずかったので修正。

using System;
using LineDC.Messaging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Sample.LineBot.Startup))]
namespace Sample.LineBot
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services
                .AddSingleton<ILineMessagingClient>(_ => LineMessagingClient.Create(Environment.GetEnvironmentVariable("ChannelAccessToken")))
                .AddTransient<EnqBotApp>(s => new EnqBotApp(s.GetService<ILineMessagingClient>(), Environment.GetEnvironmentVariable("ChannelSecret")));
        }
    }
}

今回またかずきさんに DI に関するご指摘をいただきました(技術書典7 に続き3か月ぶり2回目)。 s.GetService<T>() で DI コンテナに登録しているサービスをとれるとのことも教えていただいた。知らなかった。

DI まわりはちゃんと勉強して毎回ちゃんとスコープに気を付けないとあぶないですね。