Windows Azure Blog Storage上にアセンブリを置いて動的に読み込んでみましょう。
何の変哲もないインターフェース用意して
適当に2つほど実装して個別のアセンブリを作っておきます。
MarshalByRefObjectがポイントですね。
ビルドしたアセンブリはBlobにUpしておきましょう。
一応Privateコンテナです。
さて今回はサンプルなのでWorkerRole内でアセンブリをロードして定期的に実行するようにします。
で、実際にアセンブリをロードするわけです。
private void LoadAssembly(string AssemblyUrl,string AssemblyTypeName) { if (SampleAppDomain != null) { AppDomain.Unload(SampleAppDomain); } PermissionSet trustedLoadFromRemoteSourceGrantSet = new PermissionSet(PermissionState.Unrestricted); AppDomainSetup trustedLoadFromRemoteSourcesSetup = new AppDomainSetup(); trustedLoadFromRemoteSourcesSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; SampleAppDomain = AppDomain.CreateDomain("SampleDomain", null, trustedLoadFromRemoteSourcesSetup, trustedLoadFromRemoteSourceGrantSet); var webpermission = new WebPermission( NetworkAccess.Connect, AssemblyUrl ); SampleAppDomain.PermissionSet.SetPermission(webpermission); SampleAppDomain.PermissionSet.Demand(); SampleAssembly = (ISample.ISample)SampleAppDomain.CreateInstanceFromAndUnwrap(AssemblyUrl, AssemblyTypeName); }
こんな感じですね。リモートホスト上にアセンブリがあるのでWebPermissionで権限を付けてます。それ以外はいたって普通。
あとBlobをプライベートコンテナに置いているので、共有アクセス署名(SAS: Shared Access Signature)を付けないとアクセスできません。
private string GetAssemblyUrl() { var assemblyName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobName"); var assemblyContainerName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobContainerName"); ; var connectionString = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobStorage"); ; var account = CloudStorageAccount.Parse(connectionString); var client = account.CreateCloudBlobClient(); var assemblyBlobContainer = client.GetContainerReference(assemblyContainerName); var policy = new SharedAccessPolicy { Permissions = SharedAccessPermissions.Read, SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-5), SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(10) }; CloudBlob blob = assemblyBlobContainer.GetBlobReference(assemblyName); var accessurl = blob.Uri.ToString() + blob.GetSharedAccessSignature(policy); Trace.WriteLine(accessurl); return accessurl; }
SAS付けた状態のURLを返してもらって、そいつをCreateInstanceFromAndUnwrapに渡します。
ポイントはアクセス期限の開始時刻を現在時刻からマイナス5分してるところぐらいですかね。。。Blob Storage上の時刻よりローカルPCのほうが進んでたために、アクセスした時刻が署名で許可してる期間外になってFileNotFoundExceptionになるというヒドイ目を見ました。ふぅ。
最後にロードしたいアセンブリの情報をサービス設定ファイルに書いておきましょう。
で、サービス設定ファイルが更新されたらアセンブリをロードしなおすようにします。
public override bool OnStart() { LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName")); RoleEnvironment.Changed += (s, e) => { if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange)) { LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName")); } }; return base.OnStart(); }
とりあえずこんな感じ。
じゃ実行してみましょう。
起動した時はSample1.dllがロードされて、サービス設定ファイルをSample2.dllに変更してUpdateしたらちゃんとロードしなおされてますね!
あとは気が向いたらBlob上のDLLを入れ替えるなりしてロードしなおせばいいわけです。
もちろんロードした後はローカルのキャッシュで動作してるので、SASの期限切れても問題ないですしBlob上からファイルがなくなっても問題ないです。
拡張モジュールの実装とかいろいろ出来そうですね。AppDomainを工夫したらマルチテナントで遊べると思います。
おまけ。
using System; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Net; | |
using System.Security; | |
using System.Security.Permissions; | |
using System.Threading; | |
using Microsoft.WindowsAzure; | |
using Microsoft.WindowsAzure.ServiceRuntime; | |
using Microsoft.WindowsAzure.StorageClient; | |
namespace WorkerRole1 | |
{ | |
public class WorkerRole : RoleEntryPoint | |
{ | |
private ISample.ISample SampleAssembly; | |
private AppDomain SampleAppDomain; | |
public override void Run() | |
{ | |
while (true) | |
{ | |
Trace.WriteLine(SampleAssembly.Execute()); | |
Thread.Sleep(5000); | |
} | |
} | |
public override void OnStop() | |
{ | |
if (SampleAppDomain != null) | |
{ | |
AppDomain.Unload(SampleAppDomain); | |
} | |
base.OnStop(); | |
} | |
public override bool OnStart() | |
{ | |
LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName")); | |
RoleEnvironment.Changed += (s, e) => | |
{ | |
if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange)) | |
{ | |
LoadAssembly(GetAssemblyUrl(), RoleEnvironment.GetConfigurationSettingValue("AssemblyTypeName")); | |
} | |
}; | |
return base.OnStart(); | |
} | |
private void LoadAssembly(string AssemblyUrl,string AssemblyTypeName) | |
{ | |
if (SampleAppDomain != null) | |
{ | |
AppDomain.Unload(SampleAppDomain); | |
} | |
PermissionSet trustedLoadFromRemoteSourceGrantSet | |
= new PermissionSet(PermissionState.Unrestricted); | |
AppDomainSetup trustedLoadFromRemoteSourcesSetup = new AppDomainSetup(); | |
trustedLoadFromRemoteSourcesSetup.ApplicationBase = | |
AppDomain.CurrentDomain.SetupInformation.ApplicationBase; | |
SampleAppDomain = AppDomain.CreateDomain("SampleDomain", null, trustedLoadFromRemoteSourcesSetup, trustedLoadFromRemoteSourceGrantSet); | |
var webpermission = new WebPermission( | |
NetworkAccess.Connect, | |
AssemblyUrl | |
); | |
SampleAppDomain.PermissionSet.SetPermission(webpermission); | |
SampleAppDomain.PermissionSet.Demand(); | |
SampleAssembly = (ISample.ISample)SampleAppDomain.CreateInstanceFromAndUnwrap(AssemblyUrl, AssemblyTypeName); | |
} | |
private string GetAssemblyUrl() | |
{ | |
var assemblyName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobName"); | |
var assemblyContainerName = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobContainerName"); ; | |
var connectionString = RoleEnvironment.GetConfigurationSettingValue("AssemblyBlobStorage"); ; | |
var account = CloudStorageAccount.Parse(connectionString); | |
var client = account.CreateCloudBlobClient(); | |
var assemblyBlobContainer = client.GetContainerReference(assemblyContainerName); | |
var policy = new SharedAccessPolicy | |
{ | |
Permissions = SharedAccessPermissions.Read, | |
SharedAccessStartTime = DateTime.UtcNow.AddMinutes(–5), | |
SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(10) | |
}; | |
CloudBlob blob = assemblyBlobContainer.GetBlobReference(assemblyName); | |
var accessurl = blob.Uri.ToString() + blob.GetSharedAccessSignature(policy); | |
Trace.WriteLine(accessurl); | |
return accessurl; | |
} | |
} | |
} |