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; | |
| } | |
| } | |
| } |

