ASP.NET 5 (CTP5) のプロジェクトを Azure Websites で動かす

タイトルの通り。前回せっかくDocker on Ubuntu用に作ったのでそのままWebsitesで動かしたいと思います。

やり方は2通り。

  • Visual Studio 2015 (CTP5)から直接発行する
  • ASP.NET5 (CTP5)なソースがあるGit(GitHub)からソース連携して発行する

※Docker on Ubuntu用のproject.jsonなどは前回のPost参照で

結論から言うとVSからの直接発行は何の問題もなし。すんなり動作します。

でGit連携のほうはちょっとだけコツが必要。

How to

Websitesの構成でアプリケーション設定にSCM_KRE_VERSIONを追加し、CTP5用のバージョン(1.0.0-beta2)を指定します。

image

これだけ。

x64にしたりランタイム全部持ってくる場合とか用(?)にSCM_KRE_ARCHやSCM_KRE_CLRとかの引数もあります。VS2015でプロジェクトのプロパティみたときのTarget KRE Versionで指定するあれを分解した感じですね。

※SCM_KRE_VERSIONを指定しないとbeta1が使われてCTP5だと500エラーになります。

※デプロイは一応成功する

まぁそんなわけで、設定してあげてあとはGit連携すればちゃんと動作するようになります。

一応これでASP.NET5 CTP5をWindowsでもLinuxでもAzure Websitesでも動作させられるようになりましたね。理屈的にはMac OS上でも動作すると思いますが環境ないのでわかりません。

細かい話はきっとしばやん先生が解決してくれると思います。あとこちら参照。

ASP.NETとTypeScript

なんとなくJavaScriptもちゃんと触りたいと思ってTypeScript本読んだりしてました。

ほんとはUI周りでFlagrateを使いたいからという理由で、じゃぁどうせJavaScriptするならTypeScriptかなーと。でも鯖側はまだ慣れてるC#で書きたいし…ということでASP.NETなプロジェクトとどう共存しようというのを見てみました。たぶん何そんなの基本だろ的な感じだと思われますが。

続きを読む

ASP.NETでCacheSeviceのOutputCache有効にすると超遅くなる現象

ASP.NET MVC4でAzure CacheServiceを使ったOutputCacheを有効にすると超遅くなる、という現象があって困ったのでメモ。

  1. 普通にASP.NET MVC4なアプリを作ります
  2. Install-Package Microsoft.WindowsAzure.Caching します
  3. Web.configを編集してOutputCacheプロバイダを設定します。
  4. 実行してみます

わかりやすいようにMiniprofilerも入れてみました。

image

ファッ!?

ローカルでこの遅さ。パーシャルViewとか使うともりもり+100msとかかかります。ありえない。

結論から言うとテンプレートに含まれる既定のMicrosoft.AspNet.Web.OptimizationがASP.NET既定のOutputCacheProvider以外はサポートしてないから処理に時間がかかる、ということでした。

NuGetでMicrosoft.AspNet.Web.Optimizationを1.1.0に更新すれば解決。

image

 

image

まともになりました。ふぅ。

教訓: 更新忘れずに。

Windows Azure AppFabric ACS でカスタムのサインイン画面を作る

Windows Azure AppFabric ACS使ってフェデレーション認証するのはいいけど、味気ないサインイン画面で辟易してる方も多いんじゃないでしょうか。

↓味気ないサインイン画面

というわけで今日はこいつのカスタマイズをしたいと思います。

ゴールとして今回はASP.NET MVC3のログイン画面内で上記のサインイン画面(というかセレクタ画面)を埋め込んでアプリケーションでちゃんと(?)制御できるようにしたいと思います。

続きを読む

Windows AzureのWeb Roleでセッションを共有する

紆余曲折ありましたが(?)、Windows Azure上のWeb Roleでセッションを共有する方法を簡単に(いい加減に)まとめたいと思います。

  ASP.NET標準 ASP.NET標準 MSDN Code Gallaryのセッションプロバイダ ASP.NET Univarsal Providers High Performance Session State Provider Windows Azure AppFabric Caching
ストア メモリ(InProc) SQL Azure Azure Storage Table SQL Azure SQL Azure Windows Azure AppFabric Caching
作成者 MS MS MS? MS @takepara
@kazuk
MS
SLA 無(ストアはある) 無(ストアはある) 無(ストアはある) 無(ストアはある)
特徴 冗長化・負荷分散しない漢向け 削除をなんとかしたら使えないことはない Table使うので安い 新入り。そつなく利用可。 自作上等。ASP.NET Universal Providerの残念なところを解消 大規模で本気出すなら商用サービスだよね
メリット お手軽高速 お手軽 お手軽で安い? お手軽。RoleやMembershipも使える お手軽 高速。一番早い。
デメリット 冗長化されない・負荷分散されない セッションが削除されない セッションが削除されない。遅い。 セッション削除に難あり 自家製 お金。
追加コスト 無し DB分必要 低(SQL Azureよりは安い) DB分必要 DB分必要 高め(128MB/月で3,933.45円)
パフォーマンス 中の上
その他 パフォーマンスカウンタが付く セッション数の把握はDB見れば可 よくわからない セッション数の把握はDB見れば可 セッション数の把握はDB見れば可 ざっくりした合計セッションサイズがある程度わかる(ただしリアルタイムでは把握できない?)

 

という感じで役に立つかどうかわかりませんがまとめました。いろいろ要件にあわせて選んでくださいませ。

冗長化いらないならInProcとか。
最低限負荷分散したいけど小規模だしコストかけたくないなぁとかならAzure Storage Tableとか。
それなりにちゃんと使いたいとかならHighPerformanceSessionStateProviderとか。
いやー自分で面倒みるの大変だよとかならASP.NET Univarsal Providersとか。
大規模で性能いるんだよお金はあるぜとかならAzure AppFabric Cachingとか。

まぁ他にもMemcached使ったり、自前で負荷分散も対応したInMemoryなプロバイダ作ってもいいでしょうし。

お好みでどうぞ!

ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (4)

性懲りもなく4回目。

@kazuk さんが「無駄な処理をしない事と、重い処理をバックグラウンドにする事」ということで、
削除処理を毎回するんじゃなくてある程度間隔あけて+別スレッドでやるべきだろJKとおっしゃられました。
というわけで @takepara さんのHighPerformanceSessionStateProviderに追加してみました。

↓結果

少しパフォーマンスよくなりました。ただMAXも2秒とかなので、平均して落ち着いた感。エラーもなくなってますね。
クライアントは↓

多少ばらつきありますがネットワーク的に遠いところ(日本)なのでまぁこんな感じですかね。ゴミセッションの削除がはしったと思われるタイミングでもそんなに重くなってないのが特徴です。

とまぁこんな感じでかなりまともに使えるセッションプロバイダになった気がします。
ロールやメンバシップの部分はASP.NET Univarsal Providersのが良しなに利用できると思うので組み合わせるといいんではないでしょうか。

最終的なソースはこちら↓

// create: 2011.08.23
// author: @takepara (http://tinyurl.com/3kebwlu) , @kazuk (http://tinyurl.com/3r5vjkq)

using System;
using System.Configuration;
using System.Data.Common;
using System.Reflection;
using System.Web;
using System.Web.Providers;
using System.Web.Providers.Entities;
using System.Threading;

namespace UniversalProviders
{
	public class HighPerformanceSessionStateProvider : DefaultSessionStateProvider
	{
		private string _connectionStringName;
		private bool _initialized = false;
		private DateTime _lastSessionExpireInvoked = DateTime.MinValue;
		private object _lockSessionExpire = new object();
		private bool _working = false; 

		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			_connectionStringName = config["connectionStringName"];

			base.Initialize(name, config);
		}

		private void ExecuteSql(ConnectionStringSettings connectionStringSettings, Action<DbProviderFactory, DbCommand> functor)
		{
			var providerName = connectionStringSettings.ProviderName;
			var factory = DbProviderFactories.GetFactory(providerName);
			using (var connection = factory.CreateConnection())
			{
				connection.ConnectionString = connectionStringSettings.ConnectionString;
				connection.Open();

				var command = connection.CreateCommand();

				functor(factory, command);
				command.ExecuteNonQuery();
				connection.Close();
			}
		}

		private void CreateSessionIndex(ConnectionStringSettings connectionStringSettings)
		{
			ExecuteSql(connectionStringSettings, (factory, command) =>
			{
				command.CommandText = @"IF not EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Sessions]') AND name = N'IX_Sessions')
begin
CREATE NONCLUSTERED INDEX [IX_Sessions] ON [dbo].[Sessions] 
(
	[Expires] ASC
)
end
";
			});
		}

		private void RemoveExpiredSessions( 
			ConnectionStringSettings connectionStringSettings) 
		{ 
			if( _working ) return; // やってる人が居るならやらんでいいでしょ 
			_working = true; 
			try { 
				ThreadPool.QueueUserWorkItem( (object state)=> 
				{  
					try 
					{ 
						ExecuteSql(connectionStringSettings, (factory, command) => 
						{  
							command.CommandText = "delete Sessions where Expires < @0"; 
							var parameter = factory.CreateParameter(); 
							parameter.ParameterName = "@0"; 
							parameter.Value = DateTime.UtcNow; 
							command.Parameters.Add(parameter); 
						}); 
					} 
					finally { _working = false; } 
				}); 
			} catch { _working = false; throw; } 
		}

		private MethodInfo GetCreateSessionEntities()
		{
			var modelHelper =
				Assembly.GetAssembly(typeof(Session)).GetType("System.Web.Providers.Entities.ModelHelper");
			return modelHelper.GetMethod("CreateSessionEntities", 
				BindingFlags.NonPublic | BindingFlags.Static);
		}

		public override void InitializeRequest(HttpContext context)
		{
			var connectionStringSettings = ConfigurationManager.ConnectionStrings[_connectionStringName];
			if (!_initialized)
			{
				var initializer = GetCreateSessionEntities();
				initializer.Invoke(null, new object[] { connectionStringSettings });
				CreateSessionIndex(connectionStringSettings);
				_initialized = true;
			}

			if( _lastSessionExpireInvoked < DateTime.UtcNow - TimeSpan.FromSeconds(30) ) 
			{ 
				if( Monitor.TryEnter( _lockSessionExpire ) ) { 
					// ロックに入れたスレッドでだけ処理する、入れなかったスレッドでは処理しない 
					DateTime old = _lastSessionExpireInvoked;
					_lastSessionExpireInvoked = DateTime.UtcNow; 
					try 
					{ 
						try 
						{ 
							RemoveExpiredSessions(connectionStringSettings); 
						} 
						catch 
						{ 
							// 失敗した時はタイマ値を戻すことで別のスレッドにやってもらう 
							_lastSessionExpireInvoked = old; 
							throw; 
						} 
					} 
					finally 
					{ 
						// 絶対にMonitorを出る 
						Monitor.Exit( _lockSessionExpire ); 
					} 
			   } 
			}
		}
	}
}