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を工夫したらマルチテナントで遊べると思います。
おまけ。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } | |
| } |

