dotnetConfの時期ですね。.NETがメインのバーチャルイベントです。
昨年は.NET Coreになったりなんかいろいろあった気がします。日本でも本家イベントの後でイベントありましたね。チャックさんのサプライズ誕生日とかやってた気がします。
というわけで1日目キーノートの雰囲気でも。
dotnetConfの時期ですね。.NETがメインのバーチャルイベントです。
昨年は.NET Coreになったりなんかいろいろあった気がします。日本でも本家イベントの後でイベントありましたね。チャックさんのサプライズ誕生日とかやってた気がします。
というわけで1日目キーノートの雰囲気でも。
Connect(); というイベントがありました(まだ明日もあります)。とりあえず簡単に纏め。
※ニコ生(日本語であれこれ解説してくれたり)もあります → 池澤あやかと語る MS開発者イベント Connect(); 解説・生中継 by 週アス
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; | |
| } | |
| } | |
| } |
ADSIで利用方法によってはリークするケースがあるとのことです。
[ADSI] 障害情報 : WinNT プロバイダを用いて ADsOpenObject() をコールするとメモリリーク発生
http://blogs.technet.com/jpilmblg/archive/2010/05/14/adsi-winnt-adsopenobject.aspx
この問題ですがADSIだけに限らず.NET Framework の System.DirectoryServicesを用いるアプリケーションも内部的にはADSIを利用しているので同様の影響を受けます。
参考までにSystem.DirectoryServicesがどんな構造になっているかの情報を@uikouさんからもらいましたので併せて記載しておきます。
System.DirectoryServices 名前空間
http://msdn.microsoft.com/ja-jp/library/system.directoryservices(VS.80).aspx
.NET Framework のディレクトリ サービス
http://msdn.microsoft.com/ja-jp/library/ms180826.aspx
S.DS名前空間はActive Directoryに対する操作を行うのに良く使いますので、よく利用されていると思います。
通常のアプリケーションであればそこまでシビアではないのかも知れませんが、24×365なWeb/サービスアプリや一度に扱うオブジェクト数が多いユーザーメンテ系のアプリは要注意ですね。
情報を提供して頂いた@uikouさんに感謝感謝~