App Service の CI/CDでビルドエラー

ちょっとはまったのでメモ。(そのうち改善されると思われる)

はまった環境: Visual Studio 2017 で .NET Framework 4.6.2 なクラスライブラリでNuGetなパッケージを使用しているC#プロジェクト(NuGetはPackageReferenceを参照する)と、それを参照するASP.NETなアプリ(1つのソリューションにASP.NETとクラスライブラリなプロジェクトがある状態)
はまった理由:  App Service上(Kudu上)で msbuild 15.x がないから(たぶん)

普通にApp Service上でCI/CDを使って(今回はLocal Git)上記なプロジェクトをデプロイ(git push)すると以下のようにNuGetで取得するパッケージに含まれるアセンブリが見つからない旨のエラーがでてビルドに失敗します。(nuget restoreやdonet restoreしてるにも関わらず)

省略
:
remote: D:\Program Files (x86)\dotnet\sdk\2.0.0-preview1-005977\Microsoft.Common.CurrentVersion.targets(1964,5): warning MSB3245: Could not resolve this reference. Could not locate the assembly "Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. [D:\home\site\repository\web1\Shared\Shared.csproj]
:
以下略

カスタムデプロイスクリプトを使用するようにして、dotnet build や dotnet msbuild を使ってもダメです。
※Kudu上に現状Previewなmsbuildがありますがそちらを使ってもダメ
※ちなみにプロジェクトがPackage.configを参照するようになってたら問題なくビルドできます。PackageReferenceの場合のみおかしい。

回避策: クラスライブラリなプロジェクトファイル(.csproj)を新しい形式に変換する

変換は以下のURLを参考に。

最低限Project要素の属性を新しくして TargetFrameworkVersion を TargetFrameworkにしてv.4.6.2 とかを net462 とかにします。あとは不要な要素はバスバスけして、AssemblyInfo.csも消したりしました。
修正後、Visual Studio 2017上でちゃんとプロジェクトがロードできてビルドできればひとまず大丈夫かと思います。(Minなcsprojにしてから後でNuGetパッケージ追加したりもろもろいじってもよいかと思います。

変換後は dotnet build や dotnet publish で問題なくビルドできます。(つら)

過渡期な問題だと思いますが、もし嵌った人がいれば参考にしてもらえると。

dotnetConf 2016 Japan

7月9日(土)に dotnetConf の日本版である dotnetConf 2016 Japan が開催されました。(C#ユーザー会が主催です)

 

バナー画像はおいらが適当にコラりました。タイムテーブル的にはこんな感じですね。結構もりだくさんです。
※ 自分は裏方すらする気がなかったんですが岩永さんの無情な一言により(?)司会他やりました。

13:10 – 13:50 チャックさん dotnetConf 2016 Japan 開催にあたって ~ .NET の今と未来
14:00 – 14:45 岩永信之 .NET Standard
14:55 – 15:40 榎本さん+ちょまどさん Xamarinの新しい話とMonoの深い話
15:50 – 16:35 Brian Lagunas MVVM Done Right with Xamarin.Forms and Prism
16:45 – 17:30 @tanaka_733 .NET Core on RHEL
17:40 – 18:25 しばやん ASP.NET Core
18:35 – 19:20 ぼんぷろ .NET Core/VS/VSCode他ツール類

1発目のチャックさんがさらっと後続つぶししてたのが面白かったです(?)
岩永さんは安定、Xamarin話はEテレ、Braianはお茶目+貴重なPrism話、RHELは日本での貴重な内容、ASP.NET Coreは愛のある良いとこダメなとこ、ぼんぷろ先生はC#たん、みたいな感じでした(ひどいまとめ)

各セッション資料などは岩永さんのBlogなどにまとまってるのでそちらを参照ください。

Brianさんはほぼライブコーディングだった気がする。

当日の様子はTwitterのハッシュタグやYoutubeを追うといいかもです。

※Youtubeは最初のセッション、音声トラブルとかあったりして音が聞こえづらいかも。そのうちCh.9にあがるのを期待。

なお、Microsoftさんのご厚意により懇親会とサプライズケーキが用意されました。

https://twitter.com/aetos382/status/751961913924333568

間違えた、こっちです。

サプライズなのにケーキあることを若干漏らしてるちょまど氏

あと大量のピザ+サブウェイ!

最後に集合写真をとりましたので置いておきますね。

なおこのイベント、本家dotnetConfのローカル版ということでちゃんと本家の公式にも載ってます。

そんな感じで面白かったです。(ちょっと長丁場すぎましたがw)
みなさまお疲れ様でした&ありがとうございました。

.NET Core 1.0 RTM / Visual Studio 2015 Update 3

予定されてた通り .NET Core 1.0 がRTMになりました。おめでとうございます。また関連するツール類なども更新されています。

image

Previewの文字が取れました。

ちなみに Azure App Service Web Apps ではもうASP.NET Core 1.0が利用できますよ。

image

dotnetConf 2016 Day 1 Keynote

dotnetConfの時期ですね。.NETがメインのバーチャルイベントです。

昨年は.NET Coreになったりなんかいろいろあった気がします。日本でも本家イベントの後でイベントありましたね。チャックさんのサプライズ誕生日とかやってた気がします。

というわけで1日目キーノートの雰囲気でも。

続きを読む

動的にモジュールを読み込む

Windows Azure Blog Storage上にアセンブリを置いて動的に読み込んでみましょう。

何の変哲もないインターフェース用意して

image

適当に2つほど実装して個別のアセンブリを作っておきます。

imageimage

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になるというヒドイ目を見ました。ふぅ。

最後にロードしたいアセンブリの情報をサービス設定ファイルに書いておきましょう。

image

で、サービス設定ファイルが更新されたらアセンブリをロードしなおすようにします。

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

view raw

gistfile1.cs

hosted with ❤ by GitHub

ADSIで利用方法によってはリークするケースがある

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さんに感謝感謝~