2011年4月9日土曜日

Heroku for .NET

昨年末でしたね。SalesforceがHerokuを買収しました。

セールスフォース・ドットコム、Heroku社買収の最終合意に署名 - salesforce.com 日本

Heroku自体興味はあったけど、Rubyistじゃないからな~。AppEngineもな~。とかとか。あ、Herokuってあれっす。あれあれ。Rubyのホスティング。PaaS。いいよね~。

来週からMIX11が始まるっていうのに、まだmvcConfのビデオを見てるのんびり屋さんですが、面白いのありました。

mvcConf 2 - Troels Thomsen: Deploy ASP.NET MVC with No Effort | mvcConf | Channel 9

EC2をプラットフォームにした.NET向けのPaaS「AppHarbor」。スゴイんすよ。リポジトリにgit使うんですけど(初めてのgit読まねば)、commitすると向こうでビルドが走って、テスト実行して、デプロイまでやってくれるんすよ。とにかく無料で1アプリ公開できるから試してみた。あ、アプリは作ってないです。MVCのテンプレートそのままです。

AppHarbor - AppHarbor

手順はナニも難しいことはなくてですね、AppHarborにアカウント作って、アプリ作ると最初の画面に「これで出来るぜ」っていうコマンドが一覧で表示されるから、そのまま入力するだけです。

準備としてはGit使えるようにmsysGitいれとくだけです!

Help.GitHub - Set Up Git (Windows)

それも、ダウンロードして後はデフォルトのままクリッククリック。Git Bashってなんかカッコイイ。ハッカーな雰囲気を味わえる。そんな雰囲気を味わいたいからTortoiseGitを使わないオレ。まさに海賊王にふさわしい男気を感じる。ちっちぇ。

ah

git push(知ったかぶりだぜ!)するとAppHarborの管理画面に状況が表示されます。.gitignoreはつくっといたほうがいいです。

ah2

プロジェクトじゃなくてソリューションを追加して、標準のテストプロジェクトも入ってるんだけど、そのテストも実行されて、OKだったらデプロイ。試しにテスト失敗させるためにテストに以下のコードを追加。

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AppHarbor.Takepara.Tests
{
    [TestClass]
    public class SomeTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            Assert.AreEqual(0, 1);
        }
    }
}

ah4

身も蓋もない...。

んで、push(そのまえにadd,commitも忘れずに)!

ah5

ビルド中~。

ah6

テスト失敗!

ah7

詳細見てみると確かにテスト失敗。コレを直して、再度push。

ah8

ちゃんと成功すればデプロイまで行ってアプリケーションが動作します。

動いてるのも見たいよね。どうぞ、こちらです。

ah9

http://takepara.apphb.com/

dyno(Herokuのコンピューティングパワーの単位)の追加とかどうするのかよくわかってないけど、そもそもそういう概念がないのかも?

Pricing – AppHarbor

Load barancingもサービス範囲内。今回DB使ってないけど、せっかくなので後で無料の20MB枠を使ってAccountコントローラも動作するようにしてみようかな。どうしようかな。環境設定を書き換えてくれる特殊なconfig変換があるので、connectionStringなんかはデプロイ時によしなになるようです。

パッと見ですよ、アプリケーション領域としての容量制限とか特に書かれてないわけですよ。DBは無料20MBだとしても。

ということはですね、あれです、単純にサイトを公開したいだけなら無料ですよ!gitとかVS2010でのプロジェクト形式というハードルがあるとはいえ、無料ですよ。後はAppHarborさんたちが頑張ってくれるんですよ。このエントリは中身が薄いけど、AppHarborは凄まじく濃くて、魅力的なサービスです。

AppHarbor(Azure以外の.NET PaaS、AppEngine for .NETでもよろし)がどこまで行けるのか目が離せないですね!

調子にのってvimで.gitignoreやろうとして全くコマンドが分からなかったのは恥ずかしくて言えね。

2011年4月7日木曜日

VS2010のCSSエディタでCSS3スキーマ対応

ASP.NET Wiki: Adding CSS 3.0 support to Visual Studio 2010

  1. スキーマをダウンロードしてインストール
    CSS 3 Intellisense Schema
    ↑ここからCSS3Setup.msiをダウンロードしてインストールしましょう。
  2. レジストリをいじる
    レジストリエディタをひらいて以下に移動してキーと文字列値を追加。
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Packages\{4A0C6509-BF90-43DA-ABEE-0ABA3A8527F1}
    Schemasキーを作成。その中に文字列値の”File”を作って”css30.xml”と入れる。さらに文字列値として”Friendly Name”を作って”CSS 3.0”と入れる。
    場所が9.0なのは間違いじゃないので気にしないでね!
  3. ファイルをコピーする
    1でインストールしたファイルが
    C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Packages\1033\schemas\CSS
    に作成されてるので、日本語版で使うためにコピーして1041のフォルダにも作っておきましょう。
    C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Packages\1041\schemas\CSS

    file

こうしておけば、VS2010でCSS3のスキーマが有効になるよ!スゴイね!

css21

CSS 2.1を指定したままだと”box-shadow”はウニョウニョが出るよね。

css3

でも、CSS 3.0を選んでおくとValidですよ!

is

Intellisenceもほらー。transitionとか~。ね~。

ブチザッキさんへ

お元気ですか。ボクは元気です。

OData and Authentication – Part 7 – Forms Authentication - WCF Data Services Team Blog - Site Home - MSDN Blogs

WCF Data Servicesをプロキシクラス経由で呼び出すときにForm認証してやんよ、の件についてなんですけども。

WCF Data Services で フォーム認証を利用する « ブチザッキ

ナニをナニする系で、ナニしてみたのでご覧いただけたら幸いです。

Custom Membership Providers - Task Manager - CodeProject

プロキシからの呼び出しに介入し、別リクエストでAuthentication_JSON_AppService.axdを呼び出すことでForm認証し、レスポンスからCookieを横取りすることで、認証チケット再利用する部分を、FormsAuthenticationクラスを使って認証チケットを発行してそちらを使うことで実現してみました。

とはいえ、まったく楽チン度と便利さが上がらなかったので、ボツにしようかと思ったのですが、それはそれで少し寂しくなっちゃったので、公開レターの形式にしてみました。

WCF DataServicesをホストするサーバーサイドプロジェクトをASP.NET MVCにして、HomeControllerに以下のアクションメソッドを追加します。

[HttpPost]
public ContentResult FormAuth(string userName, string password)
{
  if (Membership.ValidateUser(userName, password))
  {
    var authTicket = new FormsAuthenticationTicket(
       1,
       userName,
       DateTime.Now,
       DateTime.Now.AddMinutes(30),
       false,
       userName);
    var cookie = 
       FormsAuthentication.FormsCookieName + "=" +
       FormsAuthentication.Encrypt(authTicket) + "; path=" +
       FormsAuthentication.FormsCookiePath + "; " +
       (!FormsAuthentication.RequireSSL
         ? "HttpOnly" : "");
    return Content(cookie);
  }

  return null;
}

続いて、クライアントサイドのプロキシクラス用に実装しているGetCookieの代わりとなる部分を以下のように。

string GetTicket(string userName, string password)
{
  string loginUri = string.Format("{0}/{1}/{2}",
      ServiceUri,
      "Home",
      "FormAuth");
  WebRequest request = HttpWebRequest.Create(loginUri);
  request.ContentType = "application/x-www-form-urlencoded";
  request.Method = "POST";

  string authBody = String.Format(
      "userName={0}&password={1}",
      userName,
      password);
  request.ContentLength = authBody.Length;

  using (StreamWriter w = new StreamWriter(request.GetRequestStream()))
  {
      w.Write(authBody);
      w.Close();

      WebResponse res = request.GetResponse();
      var body = new StreamReader(res.GetResponseStream()).ReadToEnd();
      if (!string.IsNullOrEmpty(body))
      {
          Cookie = body;
      }
      else
      {
          throw new Exception("Invalid username and password");
      }
  }

  return Cookie;
}

GetCookie(userName,password)の部分を上記のGetTicket(userName,password)にすることで、Cookie横取りではなくなりますね。

formauth

だから言ったじゃないですか!ボツにしたかったって...。

2011年4月2日土曜日

万kw

東京電力 電力使用量グラフの数値データAPI

素晴らしいですね。データさえあれば自分の好きなように加工して、使い放題ですもんね。ピーク値も含まれてれば使用量じゃなく使用率にできるからより見やすいでしょうか。

会社の監視モニターにも電力使用率を表示するようにして、日頃から意識しておこうと思う今日この頃です。

マッシュアップです!

tepco

東京電力 電力使用量グラフ

みんな大好きjqPlotも1.0が間近です。

jqPlot Charts and Graphs for jQuery

使おうじゃないですか。どんどんプロットしちゃおうじゃないですか。クライアントサイドで。IE9ならCanvasも動くし。IE8以前でもexcanvas.jsで問題なしですよ。

これといって特別なことをする必要もなく。JSONPで取得して、jqPlotでレンダリング。サーバーサイドのコードは一切なし。

あと、MVPもらっちゃった。

@onosさん、@naoki0311さん、@kazukさん、@shibukiさん、@ailightさん、@jsakamotoさん、@chack411さん。たくさんの方に感謝です。

2011年3月19日土曜日

EF4.1 CodeFirstでenumを使いたい

最近、いろんなお誘いメールがケータイに舞い込んできて、いやもうマジクリックしちゃうぞコノヤロー。そんなに誘惑するんじゃないよ!

なんか迷惑メールが地震以降強烈に増えましたね。

結局enumはサポートされないことが決定してしまったCodeFirst。しょうがないですね。RCからRTMまでの間で機能追加はないので、ここは潔く諦めましょう。

でも、やっぱりenumを使いたいですよね。プロパティをパースするときにenumかenumのジェネリックは完全にスルーされてデータベーステーブルがScaffoldingされます。なのでComplex Typeとしてenumをラップしたクラスを用意し「オレenumじゃないよ、全然関係ないから!」とEFを騙す必要があります。

Tip 23 – How to fake Enums in EF 4 - Meta-Me - Site Home - MSDN Blogs

古のテクニックですね。2009年です。今もこれしか方法がないみたいです。

コンソールアプリを作りながら同じようにやってみよー!VS2010SP1を前提にしますよ?いいですか?だってSQLCE4使ってみたいでしょ。なのでSQLCE4も入ってる前提で行きます。なきゃないで問題ないので、SQLCE4関連の部分は読み替えてください。

ソリューション作ったらまずは、NuGet!NuGetで必要なパッケージを入れてしまいましょう。以下の2つ。

install-package EFCodeFirst
install-package EFCodeFirst.SqlServerCompact

↑こうです。

efenum1 efenum2

WebActivatorは不要なので、参照設定から削除しましょう。WebActivatorについては先日書いたので、気になる人はチェックしてみてね。

無聊を託つ: WebActivatorでお手軽Bootstrapper

App_Start/SQLCEEntityFramework.csもEFCodeFirst 0.8ベースなので少しいじっちゃいます。といっても、DbDatabaseをDatabaseにするのとWebActivatorの属性を削除。あと、SqlCe用のnamespace(System.Data.Entity.Infrastructure)が変わってるので、そこも変更。

efenum3

これで準備完了。ワクワクするね!ワクワクの強要。

最初にモデルクラスをつくって、次にDbContext派生。行きます。

using System.Data.Entity;

namespace EFEnum
{
    public enum Role
    {
        Unknown,
        Sniper,
        Captain
    }

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Role Role { get; set; }
    }

    public class EFEnumContext : DbContext
    {
        public DbSet<Person> People { get; set; }
    }
}

↑EFEnumContext.cs

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="SampleContext" connectionString="Data Source=|DataDirectory|db.sdf;" providerName="System.Data.SqlServerCE.4.0"/>
  </connectionStrings>
  <system.data>
    <DbProviderFactories>
      <remove invariant="System.Data.SqlServerCe.4.0" />
      <add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
    </DbProviderFactories>
  </system.data>
</configuration>

↑App.config。

とりあえず、enumのまま作ってどういう動作になるのかを見てみます。

using System;
using System.Linq;
using EFEnum.App_Start;

namespace EFEnum
{
    class Program
    {
        static void Main(string[] args)
        {
            SQLCEEntityFramework.Start();
            var db = new EFEnumContext();

            var query = from p in db.People
                        select p;
            var count = query.Count();
            Console.WriteLine("{0}人いるよ!", count);

            if (count == 0)
            {
                var person1 = new Person
                                  {
                                      Name = "ルフィー",
                                      Role = Role.Captain
                                  };
                var person2 = new Person
                                  {
                                      Name = "ウソップ",
                                      Role = Role.Sniper
                                  };
                db.People.Add(person1);
                db.People.Add(person2);
                db.SaveChanges();
            }

            count = query.Count();
            Console.WriteLine("{0}人いるよ!", count);
            foreach (var person in query)
            {
                Console.WriteLine(person.Name + string.Format("({0})", person.Role));
            }


            Console.ReadLine();
        }
    }
}

↑main.cs。

    public static class SQLCEEntityFramework {
        public static void Start() {
            Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");

            // Sets the default database initialization code for working with Sql Server Compact databases
            // Uncomment this line and replace CONTEXT_NAME with the name of your DbContext if you are 
            // using your DbContext to create and manage your database
            Database.SetInitializer(new DropCreateCeDatabaseIfModelChanges<EFEnumContext>());
        }
    }

SQLCEEntityFrameworkのStartメソッドにDbContextのクラスを指定するのも忘れずに。

ちょっと、コード長いけどドンマイ。行くぜ!

efenum4

クリックして拡大してみてね。ちゃんと名前の横にロールが表示されました。これはAddするインスタンスに設定したものが、そのまま表示されただけです。次に一度終了して、もう一度実行してみます。

efenum5

そうすると、今度は"Unknown"と出ました。なんでかというと、なんとテーブルには保存されてないからです!そういう仕様なので驚かないでね。VSから確認してみます。

efenum6

efenum7

カラム無いですね。ちょっと切ないですね。

さぁ本題です!ちょこちょこっとクラス用意しましょう。

    public abstract class EnumWrapper<T>
    {
        private T _value;
        public string Value
        {
            get
            {
                return _value.ToString();
            }
            set
            {
                _value = (T)Enum.Parse(typeof(T), value, true);
            }
        }

        public static implicit operator string (EnumWrapper<T> value)
        {
            return value.Value;
        }
    }

これを基底クラスとしてenumの型毎にクラスを用意していきます。

    public class RoleWrapper : EnumWrapper<Role>
    {
        public static implicit operator RoleWrapper(Role value)
        {
            return new RoleWrapper { Value = value.ToString() };
        }
    }

enum型1個しかないので、1個つくりましょう。ホントはimplicit operatorも書きたくないんだけど、こればっかりはしょうがない。何かいい方法ありますかね~?最終的にはT4で自動生成(プロジェクト内のenumを全部列挙してしまえばいいかな)させちゃえばいいでしょう。

そして、モデルクラスをこのクラスを使うように変更します。

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public RoleWrapper Role { get; set; }
    }

このままだと、mainのConsole.WriteLineでクラス名がでちゃうのでそこはRole.Valueに変えておきましょう。そうして実行してみると...。

efenum9 efenum8

タターン!左が1回目。0件で追加して2件。右が2回目。2件あるから2件表示。ちゃんとロールも表示されますね。テーブル見てみましょう。

efenum10

カラムも追加されてるし、値も入ってる~。文字列にしてるのはパースしやすいのと、パッと見データ見ただけでわかるからっていうだけの理由です。intがよければそのように。

いいんじゃないでしょうか。こんな感じで。

packages含んじゃってるのでちょっと大きいですがご容赦を。

2011年3月12日土曜日

ASP.NET MVCでDataAnnotationsのエラーメッセージをカスタム

ちょっとこの質問見てみてくださいよ!

Asp.Net MVC 2 - Changing the PropertyValueRequired string. - Stack Overflow

ASP.NET MVCのDefaultModelBinderにはResourceClassKeyっていうプロパティがあって、そこに自作リソースを指定して、文字列リソースのキー名にPropertyValueInvalid/PropertyValueRequiredっていうのを作っておくと、アプリケーション全体にその文字列が適用される。でもInvalidは動作するけどRequiredがぜんぜん適用されません!っていう内容なんですね。

これ質問もなかなか面白いからか、特別ポイントが付与される質問だったんです。なので、張り切って思ってソース追っかけたりしながら動作を見てみたわけですよ。そしたら確かにInvalidはメッセージの置き換えができるのにRequiredは置き換えがおきない。

public class Person
{
  [Required]
  public string Name { get; set; }

  [Required]
  [Range(0,100)]
  public int? Age { get; set; }

  [DataType(DataType.Date)]
  public DateTime Birthday { get; set; }

  [DataType(DataType.EmailAddress)]
  public string Email { get; set; }
}

msg1

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();

  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);

  DefaultModelBinder.ResourceClassKey = "Messages";
}

msg2

なんでかな~、と調べてみたらDataAnnotationsのRequiredAttributeなんかは内部で参照するリソース名を固定で保持してて、DefaultModelBinderのほうの設定を参照しないんですね。分かってしまえば、そりゃそうなんですけど(日本語のエラーちゃんと出るし、GACに入ってるリソースを参照するのが正しい挙動な感じするしね)、それでもASP.NET MVCならできる方法がありそうな気がするんですよね。これだけ拡張ポイントたくさんあるんだから。

いろいろ見ていくとちゃんと用意されてるのを発見しました。

Reusable Validation Error Message Resource Strings for DataAnnotations

DataAnnotationsModelValidatorProvider.RegisterAdapterでValidationAttributeの型ごとにAdapterを指定できるんです。このAdapterのコンストラクタにはValidationAttributeクラスのインスタンスがわたってくるんですが、このAdapterを経由させてから、MVCはエラーメッセージを生成したりするので、AdapterのコンストラクタでインスタンスプロパティのErrorMessageResourceTypeとErrorMessageResourceNameを書き換えてあげれば、アプリケーション全体のメッセージを変更できるっていう仕組みです。

分かりやすいやり方としてはね、カスタムValidationAttributeを作って、ErrorMessage~の設定を上書いておくか、Modelに指定するときに属性プロパティに指定する方法なんだと思うけど、それだと属性を指定する箇所がすごい数になっちゃうじゃないですか。それでもいいけど、そうじゃなく一括して変更したいっていうのが質問の内容じゃないですか。

んで、ちゃんとサンプル書いて返信したんですよ。

public class MyRequiredAttributeAdapter : RequiredAttributeAdapter
{
  public MyRequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredAttribute attribute) : base(metadata, context, attribute)
  {
    attribute.ErrorMessageResourceType = typeof (Messages);
    attribute.ErrorMessageResourceName = "PropertyValueRequired";
  }
}

アダプター用意して、Global.asaxに

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();

  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);

  DefaultModelBinder.ResourceClassKey = "Messages";
  DataAnnotationsModelValidatorProvider.RegisterAdapter(
    typeof(RequiredAttribute),
    typeof(MyRequiredAttributeAdapter));
}

↑こう書けばカスタムできるよ!って。

msg3

なのに!なのになのに!カスタムValidationAttributeを作るほうに特別ポイントが!!ガッカリデス。ガッカリ過ぎてスネ毛が少し抜けたよ...。

こういう一括していの方法があることを知らなかったので、勉強になりました。

WebActivatorでお手軽Bootstrapper

いろんなセッションビデオやサンプルコードによく登場してるWebActivator。属性指定のしかたから、.NET4で登場したPreApplicationStartMethod(AssemblyInfo.csに指定するやつ)に関する何かだろうと勝手に解釈して、ちゃんと見てなかったんだけど、なんか急に見てみたくなった。

Light up your NuGets with startup code and WebActivator - Angle Bracket Percent - Site Home - MSDN Blogs

解釈はおおむね間違ってないんだけど、PreAppricationStartMethodだと1個しか指定できない(よね?)のと、フックポイントが起動直後しかない(そりゃそうだ)のを拡張してしまおうというものでした。なるほど。

Pre/Post/Shutdowの3箇所に仕込めるんですね。

  • PreApplicationStartMethod
  • PostApplicationStartMethod
  • ApplicationShutdownMethod

この3種類。うまいこと考えてますね。PreとPostはAOPっぽい感じです。タイミングはPreはそのままPreなんだけど、PostはMicrosoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule を使って動的にロードする初期化HttpModule(これ自体ちゃんとWebActivatorに含まれてるので気にすることは無いですね)のInitイベントで実行。ShutodownはそのHttpModuleのDispose時に実行。

David Ebbo: Register your HTTP modules at runtime without config

面白いですね。賢いですね~。アセンブリ属性にしてるのは使い勝手を考慮してのことでしょう。属性宣言とクラス実装が近くにあるはずだから、クラス属性にしてもいいような気もするけど、使い勝手は大事でしょう!

[assembly: WebActivator.PreApplicationStartMethod(typeof(クラス), "実行メソッド名")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(クラス), "実行メソッド名")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(クラス), "実行メソッド名")]

こんな感じでアセンブリ内に宣言します。いくつ宣言してもいいです。ActivationManager(これが標準のPreApplicationStartMethodで指定されてるよ)が実行時にbinフォルダのアセンブリから全部抽出してくれます。

davidebbo / WebActivator / overview – Bitbucket

オレオレBootstrapperを卒業するときがきましたね!

注意点として、HttpModuleを利用するPostとShutdownはAppDomainがロードされなおすたびに実行されるので、何度も実行されるのでその辺気をつけましょう。

とにかくNuGetで Install-Package webactivator とタイプしてインストールしてみて以下のコードで動かしてみました。

[assembly: WebActivator.PreApplicationStartMethod(typeof(Bootstrapper1), "Pre")]
[assembly: WebActivator.PreApplicationStartMethod(typeof(Bootstrapper2), "Pre")]
[assembly: WebActivator.PreApplicationStartMethod(typeof(Bootstrapper3), "Pre")]

[assembly: WebActivator.PostApplicationStartMethod(typeof(Bootstrapper1), "Post")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(Bootstrapper2), "Post")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(Bootstrapper3), "Post")]

[assembly: WebActivator.ApplicationShutdownMethod(typeof(Bootstrapper1), "Shutdown")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(Bootstrapper2), "Shutdown")]
[assembly: WebActivator.ApplicationShutdownMethod(typeof(Bootstrapper3), "Shutdown")]

namespace Mvc3
{
    public class Bootstrapper
    {
        public static void Pre(string name)
        {
            Console.WriteLine("Pre : "+name + " " + DateTime.Now);
        }

        public static void Post(string name)
        {
            Console.WriteLine("Post : " + name + " " + DateTime.Now);
        }

        public static void Shutdown(string name)
        {
            Console.WriteLine("Shutdown : " + name + " " + DateTime.Now);
        }
    }

    public class Bootstrapper1
    {
        private static string Name = "1番";
        public static void Pre()
        {
            Bootstrapper.Pre(Name);
        }

        public static void Post()
        {
            Bootstrapper.Post(Name);
        }

        public static void Shutdown()
        {
            Bootstrapper.Shutdown(Name);
        }
    }

    public class Bootstrapper2
    {
        private static string Name = "2番";
        public static void Pre()
        {
            Bootstrapper.Pre(Name);
        }

        public static void Post()
        {
            Bootstrapper.Post(Name);
        }

        public static void Shutdown()
        {
            Bootstrapper.Shutdown(Name);
        }
    }

    public class Bootstrapper3
    {
        private static string Name = "3番";
        public static void Pre()
        {
            Bootstrapper.Pre(Name);
        }

        public static void Post()
        {
            Bootstrapper.Post(Name);
        }

        public static void Shutdown()
        {
            Bootstrapper.Shutdown(Name);
        }
    }

}

VS2010SP1だとIIS Expressで実行ができるけど、あえて外部コマンドでIIS Expressを指定して実行してみます。

wa

コンソールのスクリーンショットを取りたかったからデス!これって統合されたらどこで見ればいいんだろか。出力ウィンドウだとちょっと違うじゃん?ん~。まぁ、いいか。おいおい分かるでしょう。

残念ながらShutdownは表示するまで待つのも設定変えるのも面倒(IIS ExpressだとidleTimeoueでシャットダウンしない?)なので未確認!雰囲気が伝わればいいかな、なんて。

dotnetConf2015 Japan

https://github.com/takepara/MvcVpl ↑こちらにいろいろ置いときました。 参加してくださった方々の温かい対応に感謝感謝です。