tech.guitarrapc.cóm

Technical updates

Azure Functions - C# で外部アセンブリ読み込みとRoslynコンパイラに渡してみる

前回は、Azure Functions に C#コードを投げつけると Roslyn Scripting で評価して結果を返すところまでやりました。

tech.guitarrapc.com

次にやりたくなるのが、独自クラス、メソッドも Roslynに評価させることですね。自分用ヘルパーなどはみなさんお持ちだと思うので、うまく読みこませられないかやってみましょう。

目次

どうやって独自クラスなどを読みこませるか

Roslyn Scripting の Reference を見てみると、Add references つまり、参照の追加があります。これを使えば外部DLL の追加が可能そうです。

github.com

var result = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()", 
     ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly));
外部DLL を AzureFunctions で参照する

Azure Functions では、#r キーワードを使うことで外部DLL を読み込むことができます。

github.com

azure.microsoft.com

外部DLL の配置は Function単位

1つ注意があります。#r で外部 DLLを読み込むときは Function単位で binフォルダを作ってそこに配置する必要があります。

If you need to reference a private assembly, you can upload the assembly file into a bin folder relative to your function and reference it by using the file name (e.g. #r "MyAssembly.dll").

つまりこうですね。

これは、↓図のような #loadのようにFunctionの外において参照することはできないことを意味します。

相対参照できるか試しても読み込めないことがエラーで示されます。

gist.github.com

2016-05-08T17:35:37.376 run.csx(2,1): error CS0006: Metadata file '../MyExtesnsions.dll' could not be found
2016-05-08T17:35:37.690 run.csx(7,7): error CS0246: The type or namespace name 'MyExtesnsions' could not be found (are you missing a using directive or an assembly reference?)
2016-05-08T17:35:37.690 run.csx(22,24): error CS0246: The type or namespace name 'EnumerableExtensions' could not be found (are you missing a using directive or an assembly reference?)

絶対参照なら一見読み込んでコンパイルが通るようにみえますが、今度はUnable to find assembly と、読み込んでいるはずのアセンブリが見つからないエラーがでます。

gist.github.com

2016-05-08T18:17:23.599 Function started (Id=478b950c-d69c-454a-a8c9-12de5f8f2fb5)
2016-05-08T18:17:23.770 Unable to find assembly 'MyExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Are you missing a private assembly file?
2016-05-08T18:17:23.833 Function completed (Failure, Id=478b950c-d69c-454a-a8c9-12de5f8f2fb5)
2016-05-08T18:17:23.942 Exception while executing function: Functions.Test. mscorlib: Exception has been thrown by the target of an invocation. ƒ-Test#ℛ*b2ec9bce-cf2c-4b67-bee1-e7188b743111#29-0: Could not load file or assembly 'MyExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

ふと シンボリックリンクで回避できるか試すも、特権がないので mklink も弾かれます。

グローバルに参照させる方法は打つ手にゃいのかにゃ。。。。 ということで、諦めて Function内に bin フォルダを作って配置しましょう。

外部アセンブリクラスを作成する

適当にクラスライブラリを作成します。

今回は、2つの Function CSharpCompilerWebhookCSharpCSharpCompilerSlackOuthookCSharp でこの外部アセンブリを参照します。そこで、MyExtensions.csproj のビルド後処理でコピーするようにしてみました。

gist.github.com

ポイントは、SolutionDir 変数の定義と、Targetによるdllコピー処理の記述、AfterBuildイベントのフックです。

特にVS2015 以降では、SolutionDir が .csproj で初期状態で記述されなくなったのでめんどくささ半端ありません。

  <PropertyGroup>
    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
  </PropertyGroup>
// 省略
  <Target Name="CopyDll">
    <Copy SourceFiles="$(SolutionDir)MyExtensions\bin\$(ConfigurationName)\MyExtensions.dll" DestinationFolder="$(SolutionDir)CSharpCompilerSlackOuthookCSharp\bin" ContinueOnError="true" />
    <Copy SourceFiles="$(SolutionDir)MyExtensions\bin\$(ConfigurationName)\MyExtensions.dll" DestinationFolder="$(SolutionDir)CSharpCompilerWebhookCSharp\bin" ContinueOnError="true" />
  </Target>
  <Target Name="AfterBuild">
    <CallTarget Condition="'$(Configuration)' == 'Debug'" Targets="CopyDll" />
    <CallTarget Condition="'$(Configuration)' == 'Release'" Targets="CopyDll" />
  </Target>

この MyExtensionsクラスをビルドすることで、MyExtensions.dllCSharpCompilerWebhookCSharp\binCSharpCompilerSlackOuthookCSharp\bin にコピーされればokです。

本当は CI を使うところですが、めんどうなので Github連携のまま行きます。ということで、せっかくコピーした dll が Githubリポジトリに入らないことが無いように .gitignore[Bb]in/ をコメントアウトして MyExtensions/[Bb]in/を追加します。

gist.github.com

Github上で dll が見えますね?

さぁ準備完了です。

.csx で外部アセンブリを参照してみる

さっそく参照してみましょう。まずは、CSharpCompilerWebhookCSharp Function でやります。

まず、呼び出し元の run.csx で dllを参照しusing <NameSpace>します。その上で、呼び出し先のCSharpScripting.csx でも、using <NameSpace>を行いします。

あとは、Roslyn Scripting API の通り、.WithImports() に using 対象の名前空間、.WithReferences() でアセンブリかアセンブリパスを渡します。

gist.github.com

これでコンパイルが無事に通れば、準備完了です。サンプル JSON を投げてみましょう。

gist.github.com

無事に結果が返りました。

Slackの Outgoting Webhook用の Function、CSharpCompilerSlackOuthookCSharp でも試してみましょう。

gist.github.com

無事に実行されました。

まとめ

今回の内容も Github に上げました。

github.com

これで任意の外部アセンブリを読み込ませていい感じで Roslyn Scripting API でも読みこませられますね!

かなりやりたいことは網羅できるはずです。