前回は、Azure Functions に C#コードを投げつけると Roslyn Scripting で評価して結果を返すところまでやりました。
次にやりたくなるのが、独自クラス、メソッドも Roslynに評価させることですね。自分用ヘルパーなどはみなさんお持ちだと思うので、うまく読みこませられないかやってみましょう。
どうやって独自クラスなどを読みこませるか
Roslyn Scripting の Reference を見てみると、Add references
つまり、参照の追加があります。これを使えば外部DLL の追加が可能そうです。
var result = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()", ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly));
外部DLL を AzureFunctions で参照する
Azure Functions では、#r
キーワードを使うことで外部DLL を読み込むことができます。
https://azure.microsoft.com/en-us/documentation/articles/functions-reference-csharp/#referencing-external-assembliesazure.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の外において参照することはできないことを意味します。
相対参照できるか試しても読み込めないことがエラーで示されます。
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
と、読み込んでいるはずのアセンブリが見つからないエラーがでます。
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 CSharpCompilerWebhookCSharp
と CSharpCompilerSlackOuthookCSharp
でこの外部アセンブリを参照します。そこで、MyExtensions.csproj
のビルド後処理でコピーするようにしてみました。
ポイントは、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.dll
が CSharpCompilerWebhookCSharp\bin
と CSharpCompilerSlackOuthookCSharp\bin
にコピーされればokです。
本当は CI を使うところですが、めんどうなので Github連携のまま行きます。ということで、せっかくコピーした dll が Githubリポジトリに入らないことが無いように .gitignore
の [Bb]in/
をコメントアウトして MyExtensions/[Bb]in/
を追加します。
Github上で dll が見えますね?
さぁ準備完了です。
.csx で外部アセンブリを参照してみる
さっそく参照してみましょう。まずは、CSharpCompilerWebhookCSharp Function でやります。
まず、呼び出し元の run.csx
で dllを参照しusing <NameSpace>
します。その上で、呼び出し先のCSharpScripting.csx
でも、using <NameSpace>
を行いします。
あとは、Roslyn Scripting API の通り、.WithImports()
に using 対象の名前空間、.WithReferences()
でアセンブリかアセンブリパスを渡します。
これでコンパイルが無事に通れば、準備完了です。サンプル JSON を投げてみましょう。
無事に結果が返りました。
Slackの Outgoting Webhook用の Function、CSharpCompilerSlackOuthookCSharp でも試してみましょう。
無事に実行されました。
まとめ
今回の内容も Github に上げました。
これで任意の外部アセンブリを読み込ませていい感じで Roslyn Scripting API でも読みこませられますね!
かなりやりたいことは網羅できるはずです。