前回は、Azure FunctionsにC#コードを投げつけるとRoslyn Scriptingで評価して結果を返すところまでやりました。
次にやりたくなるのが、独自クラス、メソッドもRoslynに評価させることですね。自分用ヘルパーをうまく読みこませられないかやってみましょう。
どうやって独自クラスなどを読みこませるか
Roslyn ScriptingのReferenceを見てみると、Add references
つまり、参照の追加があります。これを使えば外部DLLの追加が可能そうです。
https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples#addref
var result = await CSharpScript.EvaluateAsync("System.Net.Dns.GetHostName()", ScriptOptions.Default.WithReferences(typeof(System.Net.Dns).Assembly));
外部DLL を AzureFunctions で参照する
Azure Functionsでは、#r
キーワードを使うことで外部DLLを読み込むことができます。
外部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の外において参照できないことを意味します。
相対参照できるか試しても読み込めないことがエラーで示されます。
https://gist.github.com/guitarrapc/03ffffa514962a6a9fcbfecb4e41ecfb
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
と「読み込んでいるはずのアセンブリが見つからないエラー」が出ています。
https://gist.github.com/guitarrapc/404bcc9ed30823328cf0e3cb75753acf
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
のビルド後処理でコピーするようにしてみました。
https://gist.github.com/guitarrapc/e68ee4f6d59d8a998c24055ca7660c55
ポイントは、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/
を追加します。
https://gist.github.com/guitarrapc/a1d66f79221869e55985aa36b6e22ce5
GitHub上でdllが見えますね?
さぁ準備完了です。
.csx で外部アセンブリを参照してみる
さっそく参照してみましょう。まずは、CSharpCompilerWebhookCSharp Functionでやります。
まず、呼び出し元のrun.csx
でdllを参照しusing <NameSpace>
します。その上で、呼び出し先のCSharpScripting.csx
でも、using <NameSpace>
を行いします。
あとは、Roslyn Scripting APIの通り、.WithImports()
にusing対象の名前空間、.WithReferences()
でアセンブリかアセンブリパスを渡します。
https://gist.github.com/guitarrapc/66736cdec4654a65b048c9a9bad42545
これでコンパイルが無事に通れば、準備完了です。サンプルJSONを投げてみましょう。
https://gist.github.com/guitarrapc/faba8968229a6149a5d4fd4db4c20bc7
無事に結果が返りました。
SlackのOutgoting Webhook用のFunction、CSharpCompilerSlackOuthookCSharp でも試してみましょう。
https://gist.github.com/guitarrapc/744b5f41ff617ebbe296fe3e779e9de1
無事に実行されました。
まとめ
今回の内容もGitHubに上げました。
これで任意の外部アセンブリを読み込ませていい感じでRoslyn Scripting APIでも読みこませられますね!
かなりやりたいことは網羅できるはずです。