2011年8月28日日曜日

IIS7でnode.js

Installing and Running node.js applications within IIS on Windows - Are you mad? - Scott Hanselman

node.jsをIISで動かす!ファイル更新で自動リサイクルは楽チンでいいですね!受け口がIISだからIISのその他の便利モジュールがそのまま使えるのもいい。

install.batでやってることも紹介してくれてるので、同じ手順でIIS Expressでも大丈夫!なはず。

iisnode.dllっていうネイティブモジュールが橋渡し。よさそうな雰囲気。プロセス数の設定とか、いろいろ調整できるのも素晴らしす。

と、いうわけで、試しに動かしてみました。

インストール手順。

  1. node.jsのバイナリダウンロードしてc:\nodeにコピー
  2. iisnodeのバイナリをc:\inetpub/iisnodeにコピー
    ※ソースから自分でビルドしても可
  3. iisnode内のinstall.batを実行

以上。簡単ですね!node.exeのパスを変えたい場合は、install.batの確認場所の変更と、web.configでnodeProcessCommandLine指定でどーぞー。

node2

node

なんかいいね。6000req/sec。たいしたもんだ。ただ、iisnode.dllがちょいちょい落ちる(アプリケーションプールごと道連れ)。まだまだ発展途上。でもネイティブモジュールのスゴさを垣間見れるし、手軽にnode.jsをWindows環境で走らせることが出来ていいです!

ちなみにハンセルマンさんのエントリでも書いてる通り、AppPoolのアカウントをLocalSystemにしとかないと動きません。あと、wcatのsettings.ubrはちゃんとsettings{}で囲んでおきましょう。wcatについてはよく知りませんが(すません)、wcclientが動いてくれなくて、ちゃんと走りませんでした(手動起動で動かした)。

こんなに簡単にできるなら、何か面白そうなことを考えて試してみたり、node.jsでできることいろいろ追いかけて見たくなりますね!

2011年8月19日金曜日

HighPerformanceSessionStateProvider

ASP.NET Univarsal Providers のセッションプロバイダを使ってみる (2) « ブチザッキ

カメさんのこのエントリーに対してこんなコメントをしてみたわけですが、具体的なコードを提示しないで案だけ出すって、そんな失礼なことがあっちゃいけねー。江戸っ子なら宵越しの銭はもってちゃいけねー。江戸っ子じゃないんですけどね。

まずは現状確認。カメさんのコードをいただきつつ。ASP.NET MVC3のサイトをデフォルトで作成。ASP.NET Universal ProvidersをNuGetでインストール。web.configでSessionStateの設定をCookieless="UseUri"とTimeout="1"に変更(検証が簡単だからね)。

using System;
using System.Net;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

      GetPage();

      int count = int.Parse(args[0]);

      for (int i = 0; i < count; i++)
      {
        sw.Reset();
        sw.Start();
        var success = GetPage();
        sw.Stop();
        Console.ForegroundColor = success ? ConsoleColor.White : ConsoleColor.Red;
        Console.WriteLine("{0}\t{1}\t{2}\t{3}", 
            DateTime.UtcNow.ToString("yyyy/MM/dd hh:mm:ss.fff"), 
            sw.Elapsed, i, success ? "OK" : "NG");
      }
    }

    static private bool GetPage()
    {
      var result = true;
      try
      {
        var request = WebRequest.Create("http://localhost:52855/");
        {
          using (var response = request.GetResponse())
          {

          }
        }
      }
      catch
      {
        result = false;
      }

      return result;
    }
  }
}

ずるくないもん!オマージュだもん!

コレとMVCサイトで実行した結果(クリックでズーム)。

up1

セッションを生成するコンソールは3個でCPUはMAX。白行はエラーなく進んだところで、赤行はASP.NET側でエラーになったところ。

up2

1分でセッションは切れるので、1分後からドカドカ古いセッションの削除が始まります。

概ね680セッションあたりで飽和。エラーと正常実行を繰り返す感じです。切ないですね。セッションの削除が同時実行されててんやわんやな例外。

これを解消するために、InitializeRequestをoverrideしたSessionStateProviderクラスを定義します。

やることはSessionsテーブルにExpires列のインデックスがなければ作成と、セッション初期化時の有効期限切れセッションの削除をEFのDeleteObjectじゃなくSQLのDelete文を実行するように。

ちょっと長いですけど。

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

namespace UniversalProviders
{
  public class HighPerformanceSessionStateProvider : DefaultSessionStateProvider
  {
    private string _connectionStringName;
    private bool _initialized = 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
) ON [PRIMARY]
end
";
      });
    }

    private void RemoveExpiredSessions(ConnectionStringSettings connectionStringSettings)
    {
      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);
      });
    }

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

      RemoveExpiredSessions(connectionStringSettings);
    }
  }
}

クラス名はもちろんHighPerformanceSessionStateProviderデス!

up3

<sessionState mode="Custom" customProvider="HighPerformanceSessionProvider" cookieless="UseUri" timeout="1">
  <providers>
    <add name="DefaultSessionProvider"
         type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         connectionStringName="DefaultConnection" applicationName="/" />
    <add name="HighPerformanceSessionProvider"
         type="UniversalProviders.HighPerformanceSessionStateProvider, UniversalProviders"
         connectionStringName="DefaultConnection" applicationName="/" />
  </providers>
</sessionState>

Web.configも上記のように変更しちゃいましょう!customProviderの指定がDefaultSessionProviderでオリジナルに戻ります。

セッションが飽和するのが、940件くらいのところです。もちろん例外なんて起きませんよ。有効期限切れのレコード削除なんて何回実行したって、無いものはない!潔し!ちなみに先程の数値もそうですけど、コレが同時実行の限界値なわけではないです。マイノートPCの同時実行の限界くらいです。Webの受付のね。DbProviderFactoriesを利用することでEFと同じだけのポータビリティ(言い過ぎ)。

名前ほど早くないけど、エラーが起きないから実用的なんじゃん?読み込み性能は気にしてないでーす。あくまで新規セッション作成時のパフォーマンス向上委員会です。言い訳がましい...。

ご利用は計画的に!

Azureでは動かしてないので、そちらの検証はたぶんカメさんがその3でやってくれるんじゃないでしょーか。

2011年8月14日日曜日

Controllerを名前から生成するしHTMLを書き換えたりもしてみる

Developer @ ADJUST : ASP.NET MVC のコントローラクラスを、コントローラ名から取得したいのだけれど...結局、ぜんぶ列挙!?

前回のエントリーからはや1ヶ月以上経過したこのブログ。エアコンが壊れてて、バリバリ節電に貢献できてる気はするけど、辛い毎日でブログどころではないですよ!

で、坂本さん、上記の件ですが以下のような方法はいかがでしょうか。単純に型が欲しいだけならインスタンスは不要なので無駄な感じはしなくもないですが。必要なのがインスタンスであれば一番いい方法だと思います。

var factory = ControllerBuilder.Current.GetControllerFactory();
var controller = factory.CreateController(Request.RequestContext, "Home");
var type = controller.GetType();
factory.ReleaseController(controller);

コレと言って、自信で発明したものはなく、やってることはMvcHandlerでやってることと同じです。MvcHandlerってあれです、MvcRouteHandlerが返してくるIHttpHandlerのことです。

内部でタイプキャッシュとかしてくれてるので、パフォーマンスには自信があります!あると思います!

ここから別件。

もう暑さで脳みそが働かない...。けど、これだけじゃ坂本さんも納得しないと思うので、全然関係ない面白い機能紹介。といっても、URLRewrite 2.0の機能です。ハイパフォーマンスWebサイトの構築には必須のOutputCacheなんですが、クローラーなんかはUser AgentがCookieに対応してないと言ってくるじゃないですか。browserファイルで対応するのが王道ですが、それだと追従していくのが大変面倒くさい。

Creating Outbound Rules for URL Rewrite Module : URL Rewrite Module 2 : URL Rewrite Module : The Official Microsoft IIS Site

想定している状況を簡単にいうと、SessionのcookielessはuseDeviceProfile。PCサイトとケータイサイトを同一アプリケーションで実行。PCサイトは積極的にOutputCacheを利用し、ケータイサイトでは消極的(Viewでは使わない)に利用。この場合、PCサイトにクローラが来るとそのままではCookielessとしてセッションが生成されるので、OutputCacheにSessionIDを保持したHTMLが出力されてしまって都合悪い!

ということなんですが、伝わるでしょうか...。

OutputCacheをVaryByUserAgentにするのも手ですね。いろいろ方法はあると思うんですけど、こないだ試したのがPCサイトの場合、出力されるHTMLにSession IDが含まれることが正常系ではありえないので、PCサイトの場合、Response.FilterでURLにSession IDが含まれてたら消してしまうという方法。Response.Filterでやるよりももっと簡単なのがURL Rewrite 2.0の出力書き換えを利用するっていう方法。どうですか、面白そうじゃないですか?

ちなみにOutbound Rule用に独自Provider書いたりもできるけど、そこまでやることはあんまりないかなー、と思いますがどうでしょう。

<rewrite>
  <outboundRules>
      <rule name="Sessionless" preCondition="html" enabled="true">
          <match filterByTags="A, Area, Base, Form, Frame, Head, IFrame, Img, Input, Link, Script, CustomTags" customTags="All" pattern="(.*)/\(S\([0-9a-z]+\)\)(.*)" />
          <action type="Rewrite" value="{R:1}{R:2}" />
          <conditions>
              <add input="{REQUEST_URI}" matchType="Pattern" pattern="\)/mobile" ignoreCase="true" negate="true" />
          </conditions>
      </rule>
      <customTags>
          <tags name="All" />
      </customTags>
      <preConditions>
          <preCondition name="html">
              <add input="{RESPONSE_CONTENT_TYPE}" pattern="text/html" />
              <add input="{REQUEST_URI}" pattern="/mobile" negate="true" />
          </preCondition>
      </preConditions>
  </outboundRules>
</rewrite>
<urlCompression doDynamicCompression="false" />

だいたいこんな感じの設定です。これをWeb.configに書いておきましょう。system.webServer配下です。ケータイサイトが/mobile配下という前提です。

実験。MVCサイトをデフォルトのまま生成し、Web.configに上記設定を追加しないで動作を確認。

この状態で出力されるのは↓。

cookieless1

そりゃそうですね。中身は↓。

cookieless2

なんてことないですよね。

今度はsessionStateのcookielessをUseUriにしてみましょう。見た目は同じなのでソースだけ。

cookieless3

画像をクリックして大きくしてみるとわかりますが、Session IDを含んだURLですよね。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Home Page</title>
    <link href="/(S(f4grns0hiatkwquk55qulbmd))/Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="/(S(f4grns0hiatkwquk55qulbmd))/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="/(S(f4grns0hiatkwquk55qulbmd))/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay">
                    [ <a href="/(S(f4grns0hiatkwquk55qulbmd))/Account/LogOn">Log On</a> ]
            </div>
...以下省略

今度は先のURLRewrite設定をOnにした場合。

cookieless4

あら素敵。ウソじゃないよ!URLにはちゃんとSession ID入ってるでしょ?

これでクローラが来てもOutputCacheにへんてこなものが仕込まれることないですね!あと、動的圧縮をオフにしてるのは既知です。設定いいんですけど今回はこれで。

役に立つやら立たないやらな情報でした!

2011年6月30日木曜日

全角数値を半角数値に変換するModelBinder

拝啓、まゆきっつぁん

いつも、The Shodoにて筆の練習をさせていただいてます。ただ、いつまで経っても筆ぺんでは上手にかけるようになれません。本物の習字道具を使わないと練習の成果が出ないのでしょうか?The Fudepenで練習すると効果が期待できるかもしれないですね。是非、考慮いただければと思います。

先日のmvcConf @:Japan懇親会での一件について。ふと、思い出したので書いてみました。あの時はValueProviderがどうのこうのという話になったような気がしないでもないですが、ValueProviderではレイヤ低すぎて型は意識されてないのダメですね。

こんな感じでいかがでしょうか?整数に限定してますが、応用すると他にもいろいろできると思います。

public class IntegralModelBinder : DefaultModelBinder
{
  private readonly List<Type> _integralTypes = new List<Type>
  {
    typeof (sbyte),
    typeof (byte),
    typeof (char),
    typeof (short),
    typeof (ushort),
    typeof (int),
    typeof (uint),
    typeof (long),
    typeof (ulong)
  };
  private const string WideIntegrals = "1234567890一二三四五六七八九零壱弐参肆伍陸柒捌玖零";
  private const string NarrowIntegrals = "123456789012345678901234567890";

  protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
  {
    if (!_integralTypes.Contains(propertyDescriptor.PropertyType) || value != null)
    {
      base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
      return;
    }

    var providerResult = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
    value = providerResult.AttemptedValue;

    var narrow = string.Join("",(value + "").Select(c => WideIntegrals.Contains(c) ? NarrowIntegrals[WideIntegrals.IndexOf(c)] : c));
    var converter = TypeDescriptor.GetConverter(propertyDescriptor.PropertyType);
    try
    {
      value = converter.ConvertFrom(narrow);
      bindingContext.ModelState.Remove(propertyDescriptor.Name);
    }
    catch{}

    base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
  }
}

試しに以下のようなモデルを定義してみました。

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime? Birthday { get; set; }
        public byte Rank { get; set; }
    }

AgeとRankが整数型なので処理対象となります。Global.asaxでDefaultBinder(ModelBinders.Binders.DefaultBinder = new IntegralModelBinder();)を差し替えて実行した結果は↓こんな感じになります。

mb1

@model ZenBinder.Models.Person
@{
    ViewBag.Title = "ホーム ページ";
}

<h2>@ViewBag.Message</h2>

@using (Html.BeginForm())
{
    @Html.EditorForModel()

    <button type="submit">送信</button>
}

まずはフォームを出すでしょう。簡単にEditorForModelを使います。

mb2

普通に半角だけで試して送信してみると、ちゃんと動きます。同じものを全角にしてみても結果は同じになります。

mb3

変換出来ない場合はDefaultModelBinderの挙動になります。十とか百とか千も変換するためのマッピングを用意すれば、もう少しオシャレさが増すかもしれないです。

mb4

いかがでしょう。ケータイでの入力に是非応用してみてください。

2011年6月18日土曜日

mvcPhotosの出来るまで

ちょっと書いてみます。あんまり面白く無いですよ。

ASP.NET MVCをメインにした企画をやってみたいとチャックから連絡があって、喋る人が決まったところで各々にテーマがふられました。んで、たけはら担当として「クライアントサイドのテクノロジをメインに扱うセッションを」というリクエストから始まるんですが、ぶっちゃけ「それMVC関係ないじゃない...」と愕然としたものです。

とはいえ、テーマを無視するのもあれだからと、まずはセッション概要を伝え(もちろんこの時にはまだ何をするのか決めてないので、ぼかしまくった感じで)募集開始。そろそろ真面目に考えないとね~、と思いつつ仕事も忙しかったしで、ほっといたらあっという間に5月中旬。そろそろスライドだけでも書かねばと書き始めるものの、何を作るかは全く決まらず...。とりあえずの方向性としては

  1. 作ったものに参加者もその場でアクセスできる(だけど会場内のネットワークでは自マシンに参加者がアクセス出来ないはず)
  2. アーキテクチャを意識する
  3. MVC使いつつクライアントサイドモリモリ
  4. コードは参加者へのプレゼント

の4点。

※ネットワークについては、昨年のTechEDでダメだったのを経験してたので。

はてさて、どうしたものか。Twitterは他の人が絶対利用するだろうからパス。単純に外部サーバーにWebアクセスしてみんなでワイワイする感じで何がいいかな~、って考えるとどうしてもMvcGraffiti(みんなでお絵かき)とかぶる。となると、手堅い方法はメールか。ケータイなら絶対繋がるはず。メールということはPOP3の実装は必要だな~。メールなら写メがいいかも。という流れでとりあえずPOP3の実装と画像のリサイズ部分だけ先行コーディング。

いろいろ悩んだ挙句、普通に写真共有っていうところに落ち着くんだけど、どうやってクライアントサイドモリモリを達成するか。そこは後回しにしてアーキテクチャ。この時点でAppHarborをプラットフォームにして、ストレージはGoogle Storage使ってみようかと実験。でもGoogle Storageが思ったようにいかないからS3にチェンジ。モデルはCodeFirstで、ストレージは置き換えられるようにProviderとして実装。MVCでのサーバーサイドはシンプルにAPI的なものと、UA切り替え出来るような仕組みをどうするか考えつつ、ワーカーによるバックグラウンド処理でメールとストレージのつなぎをやろうと決める。UAに合わせてViewを切り替えるのはいろんなやり方があるけど、出回ってるやり方を実装してもつまらないので、随分悩んだ末にRoutingとViewEngineのコンビネーションで行う方法を思いつく。さすがオレ。

クライアントサイドはこのころすごく気になってたknockout.jsとModernizrを使うことで、うまいことやろうと思いつつ具体的なことは決めずにサーバーサイドをゴリゴリ作る。あと、それっぽくテストコードも用意することで、スタックというかレイヤというか、その辺を意識しやすいようにしておこう。

ちょっと横道にそれますが、ControllerのテストをしやすくするためにFormCollectionをパラメータに使う例をよく見ますね。でも意味ないですよね。そこは普通に入力モデルを渡せばいいじゃないっていうふうに思うわけですよ。だって、FormなりのValueProviderからModelBinder経由した入力モデルへのマッピングって、Controllerのテストじゃ無いからですよ。通常のFormを想定した場合、FormCollectionからUpdateModelをするなら、それはもうMVCが提供してくれるModelBinderのテストをするようなもんでしょう。意味無いじゃん!なので、ModelBinderのテストはMVCの開発チームに任せて、自分たちの書いたコードに対するテストを書きましょう。ただ、今回Controllerのテストは書いてないですけどね!てへ。

ここまで全然コード解説じゃないですけど、セッションで話したいのはそもそも製品の紹介じゃなくて「アプリケーションの作り方」。何をどういう設計で作るかを決めることで詰みです。「拳(コード)で語る」のがプログラマーですよ。だって、ASP.NET MVCも1~3へと進み、4以降になったら製品の使い方、コードの書き方なんて変わるわけですよ。Razorなんてものも出てくるし。だけど、考え方とか適用の仕方ってMVCっていうアーキテクチャスタイルとか、デザインパターンとかって基礎として使い続けるじゃん?そこ意識すること大事だと思うんす。

ただでも、このやり方は諸刃で、聞きに来てくれる層によってはドン引きされるんです(経験あり)。だって「お前の作ったアプリケーションを見に来たんじゃないんだよ!」とか「そのアプリでオレのプロジェクトは解決できない!」という意見もあるからね。いろいろです。どんな意見も、それぞれの人のコンテキストでは正論っす。ただ、自分にとっては、目の前の問題の答えを提示する局所最適じゃなく、全体最適を目指すほうが楽しい。なので、これからもこのやり方は変わらないでしょう。

はっ!熱くなってた!しっけいしっけい。

サーバーサイドが概ね出来たところで、クライアントサイドの実装に入るわけですが、PC,iPad,iPhone、そして日本が誇る超精密パーソナル通信機器、通称ガラケー対応も無視できない。モダンブラウザ向けにはjQuery Mobile使おうと思ってたんだけど、Azure担当大臣のだいちゃんが「jQuery Mobileでモテモテになるっす!」とか言い出しやがって、かぶるじゃねーかよ、的なね。まぁ、いいか。んじゃ、オレ適当に実装する、ということになりました。なので、見た目かなりしょぼくなったけど、オレのせいじゃないから!エロ大臣のせいだから!!Azureチーム金持ってるからって可愛いキャラ多すぎなんだよ。ASP.NET界隈では緑のキグルミしかいないっつーの。羨ましくなんか..うっぐ。泣かない。

はいはい。クライアントサイドでどういう感じに動かすのか、図に書いて最初に実装したのが、↓こんなやつ。

sample

受信画像をタイル状にランダム表示。緑も意識してみた。だけどコレがまたダサいのなんのって。イメージ通りに作ったはずなのに。自分のセンスに絶望。

sample2

ランダムがだめなのかと思って順番に表示するようにしたりしたけど...。根本的におかしい。マジやべー。

sample3

色がだめなのか!?と思って黒くしてみた。

sample4

で、最終的には↑こうなるんだけど、みんな知ってた?ウィンドウサイズに合わせてサムネイルの画像サイズは100→50→25と収まりよくなるようにリサイズするんだよ?

http://mvcphotos.takepara.com/

試してみてね。

ソースはこちら。

http://mvcphotos.codeplex.com/

Source Codeタブをクリックして右端のLatest VersionにあるBrowseで見たり、Downloadで取得してね。

source

書き疲れた...。もういい?仕様書もマニュアルもなしで、コードを追いかけるのも大変だと思うので、今回作成したmvcPhotosの実装をザックリ書きだしておきます。

  • POP3でメール受信
  • クラウドストレージとローカルストレージを切り替えやすくするためのストレージプロバイダ化
  • 画像のリサイズ
  • EF CodeFirstによるDAL
  • データベースアクセスをRepositoryにより抽象化
  • サーバーサイドでもDAL用のモデルと、入出力用のモデルを分けることでレイヤ分離と検証ルールの明確化
  • 動的画像リサイズを行うためのコンカレント制御と非同期Controller
  • 複数のUserAgentを同一Controllerで処理するためのViewEngineとRouting制御
  • モダンブラウザの判定と、動的スクリプト読み込みにModernizr
  • knockout.jsによるクライアントサイドでのMVVM実装
  • 自作Service Locatorと、DependencyResolver実装
  • DIを3パターン実装(探してみてね!)

こんな感じです。

ちなみにパネルディスカッションの最後でゴニョゴニョ言ってたことなんですけど「大事なのはMVCの心を理解しようとし、SoC - Separation of Concerns - 関心の分離を意識すること」。つまりきちんと役割を分離して、実装も可能なかぎり分離して参照関係を単純にしましょうね、と言いたかったですが言葉足らずのドヤ顔で失礼しました。

あと、一色さん、変なやりとりでスマセンした。失礼ぶっこいてスマセンした。ホントはすごいシャイボーイなんです。自分で言うのもなんですが、草食系男子なんです。型は古くて時化には強いタイプですけどシャバいやつなんす。

今後ともご贔屓に~。

2011年6月13日月曜日

メモ帳ですいません

mvcConf @:Japanでしゃべりましたね。あまりにも大雑把な説明っぷりに自分でもビックリです。オレ、こんなに雑だったんだ...もっと繊細だと思ってた。なんつっ亭たけはらです。

雨の中、足を運んでくれたたくさんの参加者の方々に感謝の気持ちでいっぱいです。

mvcConfといえば、知る人ぞ知るマニアにはたまらないイベントです。それを日本で日本人だけで、ASP.NETバカ集合させてしゃべらせようと企んだチャックの度量には度肝を抜かれました。初めての試みだし、人が集まるのか、どのような背景の人が参加してくれるのか全く未知数で、ランチも交通費もでないという低予算のなか、よくもまぁなんとかなったもんですね。

個人的には全然言いたいことがいえずに、あっという間に終わってしまったので、ブログを通して言い足りなかった部分、特にサンプルに盛り込んでいるテクノロジや設計方針なんかを、ちょびっとだけ書き残しておこうと思います。

まず当日、スライドの内容が思い出せるきがしなかったので、メモを書きました。それがコレ↓です。

mvcPhotos

  http://mvcphotos.takepara.com
  http://mvcphotos.takepara.com/mobile

  mvcphotos@takepara.com

サイトURL設計

  Home
    Index
  Photos
    Index - Jsonable
    Tags – Jsonable
    Create (GET/POST)
    Image
  Tags
    Index – Jsonable
※/mobile配下も同じ

アーキテクチャとしてみるWeb Stack

  • ViewEngineでのView切り替え
    同一コントローラを利用する
  • knockoutjsを使ってMVVMなクライアントサイド実装
  • Modenizrでのブクライアントサイド機能判定
    Modernizr.load活用
  • クラウド利用によるスケール、可用性の確保
    AppHarborとAWS
  • メールを利用したデータ入力
  • サーバーサイドを極力API化
    ODataを出力しdatajsで取得する。
  • テスト
    少しずつでいいからテストも書いていこうね
    Mockじゃなくても、StubやFakeを用意する。
  • razordo.it / guttokita.ccもよろしく!

参照ページ

ココまで。

Jsonableは渾身の仕込みだったけど、響かなくて残念でした。

リンク多いけど、参考になるサイトばかりで紹介しておきたかったのでメモに書いてました。気が向いたらのぞきに行ってみてはどうでしょう。

パネルディスカッションで「Page Controller」がどうのこうの言ってたんですけど、なんとなくPageとControlと聞き間違いされてる気がしたので改めて「Page Controller」デス!

実装サンプルを用意して適用方法を解説しようというセッションなので、最初からセッション資料なんてこれだけでよかったかも。今度からそうしようかな。技術的な解説よりもライブコーディングをいれたほうがピンと来やすいかもしれないしねー。「御託はいいよ・・・拳で語ってくれ」と誰もが思ったことでしょう。

次のエントリはコード解説を書こうと思います。が、途中でギャー!ってなってポイってしても、そこは笑って許してね!

2011年6月5日日曜日

ClayFactory

Clay面白いかも!

NuGet Package of the Week #6 - Dynamic, Malleable, Enjoyable Expando Objects with Clay - Scott Hanselman

Clay - Home

ORM使ったリポジトリを使った場合、戻りの型って結構融通効きにくいじゃないですか。select対象のクラスを全部手書きするのかよ、って。だからといって無名クラスは戻せないじゃない。objectでリフレクションとか本末転倒。

なのでdynamicを使うことにするんですけど、そのためにLINQ selectの射影をdynamicにする簡単なヘルパーを用意しとくと便利ですね。

public static class ObjectToDynamicExtensions
{
  public static dynamic ToDynamic(this object obj)
  {
    dynamic model = new ExpandoObject();
    var properties = obj.GetType().GetProperties();
    foreach (var property in properties)
    {
      ((IDictionary<string, object>)model)[property.Name] = property.GetValue(obj, null);
    }
    return model;
}
}

使用例

return from person in db.People
       select new {
         FullName = person.FirstName + 
                    " " + 
                    person.LastName,
         Age = ((DateTime.Today.Year*10000 + 
               DateTime.Today.Month*100 + 
               DateTime.Today.Day) - 
               (person.Birthday.Year*10000 + 
               person.Birthday.Month*100 + 
               person.Birthday.Day))/10000
       }.ToList().ToDynamic()
       

 

使い道はこんな感じのマッピングなんですけど、ここでちょっと残念なのが、ASP.NET MVCのViewにdynamicを渡すと、ほとんどのヘルパーが機能してくれないこと。残念ですね。凹みますね。

ちゃんと型付けしとけ、っていうのはもちろんそうなんですけどね。だからScaffoldingもあるわけでして。

とは言え、柔軟な実装にしたい。単純にマッピングをしたいようなときにはClayいいかもという流れです。

dynamicをインターフェース実装のプロキシに変換してくれるみたい。そうなればもう強い型付け完了。

インターフェースは規約で判定するみたいだけど、namespaceどうするんだろ。気になる。あ、でも、Entity ModelとOutput Modelを両方共クラスにするんじゃなくて、Output Modelはインターフェースにしちゃう感じなのかな。

dynamicから特定のインターフェースに変換したくなるときって、よくあるきがしてきた!ワクワクするね!

2011年5月6日金曜日

@RenderPageとHtml.RenderPartial

いや~、連休ももうすぐ終わってしまうと思うと、ちょっぴりアンニュイな気持ちになってしまいます。アンニュイってララバイくらい意味がよくわからない。

Razor使ってますか?使ってますよね。お腹すいたらRazorだし、テレビ見ながらRazorですよ!

疲れてるのかな...。

~/Views/Shared/_Partial.cshtml

@{
    var message = Model ?? (PageData.Any() ? PageData.First().Value : null);
}
ゴールデン @message

↑こんな部分ビューを用意しました。

~/Views/Home/Index.cshtml

@{Layout = null;}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <title>連休</title>
</head>
<body>
<p>@@RenderPage - 
  @RenderPage("~/Views/Shared/_Partial.cshtml", "ウィーク")</p>
<p>Html.RenderPartial - 
  @{Html.RenderPartial("~/Views/Shared/_Partial.cshtml", "ウィーク");}</p>
<p>Html.Partial - 
  @Html.Partial("~/Views/Shared/_Partial.cshtml", "ウィーク")</p>
</body>
</html>

↑そしてそれを利用するビューも用意しました。

ここで問題です!このページ(Index.cshtml)を表示するとどういう表示になるでしょーか?

答え

partial1

ビックリした?ねーねー、ビックリした?オレ、すげービックリした。

なんでHtml.RenderPartialだと”ゴールデン ウィーク”と表示されないんでしょうね。ソース追っかけたりしてスゴイ悩んだんですよ。そもそもRazorではストリームに直接出力うするHtml.RenderPartial使えないのか!?とか仕様を疑ったり(RenderPartialの実装がMVC3になってTextWriter渡す実装に変わってるから関係ないのにね)。

うーん、煮詰まった。ふと、順番入れ替えたらどうなるのか試してみたんす。

<p>Html.RenderPartial - 
  @{Html.RenderPartial("~/Views/Shared/_Partial.cshtml", "ウィーク");}</p>
<p>Html.Partial - 
  @Html.Partial("~/Views/Shared/_Partial.cshtml", "ウィーク")</p>
<p>@@RenderPage - 
  @RenderPage("~/Views/Shared/_Partial.cshtml", "ウィーク")</p>

@RenderPageを最後に移動。

partial2

おや~?ちゃんと出た。あれ~。この際Html.Partialは無視して試しに親戚のHtml.RenderActionはどうなるのか試してみたっす。

HomeControllerに以下を追加。

[ChildActionOnly]
public ActionResult Week()
{
    return Content("ウィーク");
}

Index.cshtmlを以下のように変更(@RenderPageを最初に持って来てHtml.RenderAction追加)。

<p>@@RenderPage - 
  @RenderPage("~/Views/Shared/_Partial.cshtml", "ウィーク")</p>
<p>Html.RenderPartial - 
  @{Html.RenderPartial("~/Views/Shared/_Partial.cshtml", "ウィーク");}</p>
<p>Html.RenderAction - 
  ゴールデン @{Html.RenderAction("Week");}</p>

partial3

Html.RenderActionは平気みたい。このまま@RenderPageを最後に移動。

<p>Html.RenderPartial - 
  @{Html.RenderPartial("~/Views/Shared/_Partial.cshtml", "ウィーク");}</p>
<p>Html.RenderAction - 
  ゴールデン @{Html.RenderAction("Week");}</p>
<p>@@RenderPage - 
  @RenderPage("~/Views/Shared/_Partial.cshtml", "ウィーク")</p>

 

partial4

ちゃんと出ますね。ということはですよ、同じWebPage中では@RenderPageした後はHtml.RenderPartialが正しく動かないということですよ。これってもしかして...。あ、いや、仕様かもしれないし。どういう事?教えてWebMatrixMan~!

※@RenderPage自体がRazor構文なのでWeb Formsでは関係ない問題です。

2011年5月4日水曜日

ぷっすぷすにしてやんよ、Glimpseで。

Glimpse - A client side look at whats going on in your server

なんと素敵なツールでしょうね!

glimpse1

↑ChromeでGlimpseをオンにしたうえに、デベロッパーツールもオンにした状態。かぶせまくり。

これ何?っていうのは上記Glimpseサイトのビデオか、MIX11でのHanselmanさんのセッションビデオ見れば一発でわかります。と言ってしまうとブログに書く必要が無くなってしまうので、少し説明すると、Trace.axdやelmah.axd、RouteDebuggerを全部ひっくるめて組み合わせてさらにサーバーサイドの情報をひと通り参照できるようにするツールデス。分かりにくいっすね。

とにかく、NuGetでInstall-Package Glimpseと入力してプロジェクトに入れて見ましょう。

その状態でデバッグを開始すると、おや?ナニも変化ないですね。なので、URLに/Glimpse/ConfigといれてGlimpse機能をOnにしまいしょう。Onにした後はURLをもとのページに自分で戻すことを忘れずに。

glimpse2

そうすると画面右下に変な目玉アイコン(Glimpseアイコンね)が表示されるのでそこをクリック!

glimpse3

そうすると出てきます。

glimpse4

すごいねー。ちなみにブラウザに表示されてるのはプラグインとかじゃなくて100%JavaScriptだけで作られたものです。

glimpse5 glimpse6

ソース表示すると</html>の後にデータがレンダリングされて、最後にglimpseClinet.jsがロードされてるのが確認できます。

あとは、見たまんまです。各タブにそれぞれ情報が表示されます。

  • Binding
    まだ開発中~。Binderの情報がでる予定(だと思います)
  • Config
    Web.Configの設定内容をセクション別に表示
    config
  • Environment
    サーバーの実行環境の情報
    Application Assemblies/System Assembliesが見れるのは嬉しいですね
    emvironment
  • Execution
    ActionInvokderの実行内容を表示(だと思う)
    Filterの実行も見れるよ!
    execution
  • Glimpse Warnings
    Glimpseの何か
  • MetaData
    どこぞのMetadata
  • Remote
    リクエスト履歴っぽい
  • Request
    HttpRequestBaseの中身からCookie,Form,QueryStringを表示
    request
  • Routes
    表示内容(アクション)に到達するルーティングの解決順とマッチしたルート
    route
  • Server
    ServerValiablesの内容
  • Session
    Sessionの内容
  • Trace
    System.Diagnostics.Traceに出力した内容
  • Views
    表示内容(View)の解決に至る過程
    view
  • XHRequest
    まだっぽい

全部スクリーンショットをとるのが面倒だったから動かして確認してみてね!

んじゃ、そもそもGlimpseの仕組みはどうなってるの?って気になるよね。NuGetからインストールすると使うのは楽チンだけどソースが確認できないから、改めてソースをダウンロードしましょう。

Glimpse/Glimpse - GitHub

真っ先に気になるのが、PreApplicationStartCode。当たり前のようにスタートアップコードが書かれてる。んで、その中身はMicrosoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModuleを使ったカスタムIHttpModuleの動的登録。その中で各種プラグイン(タブで表示する情報を取得したりするもの)をMEFで登録。

Moduleでハンドリングするタイミングは以下の4つ。

context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
context.PostRequestHandlerExecute += PostRequestHandlerExecute;
context.PreSendRequestHeaders += PreSendRequestHeaders;

ここから結構気になってるExecutionで表示してるのがホントにActionInvokerに介入した結果なのかチェックしてみよう。

ExecutionプラグインのSetupInitでいろいろ仕込んでますね。ControllerFactoryとDependencyResolverをGlimpseのものに置き換えてます。GlimpseControllerFactory.CreateControllerでIControllerExtentions.TrySetActionInvokerを呼んでCastleのInterceptorを利用して、プロキシを生成しそれをActionInvokerにセットしてました。これでプロキシが必ず呼び出されるから、実行をインターセプトして実行情報を取得できるということですね。うほ。

DependencyResolverを利用した場合にも大丈夫なようにGlimpseDependencyResolverが用意されてて、こっちでも同じくProxyを生成するように抜かりなし。

他のプラグインもIGlimpsePlugin実装になってて、SetupInitしてGetDataを呼ぶことで各種データをHttpContext.Itemsに入れることで、最終的にGlimpseRespondersがJsonにシリアライズ。まるっとApplication編集に保存してGlimpseResponders.StandardResponseがResponse.Writeしてる。

Glimpseの仕組み

MEFを使ったプラグイン構造でContext.ItemsからApplication変数にデータを保持(configのsaveRequestCountはこの履歴保持数ということみたい)し、それをレスポンス(リクエスト毎にGuidを発行してコンテキストがきちんと判定できるよう保持)。各種情報はプラグインが自立して取得するけど、実行状態のインターセプトはCastleがProxyを生成して介入。

イロイロなプラグインを作っていけば、データの取得と表示はベースの機能でまかなえる素敵設計であるのと、.NET4、MVC3の機能もめいいっぱい利用したお洒落実装。これは萌える。萌え萌え。ぷっすぷすにされちまったぜ!

2011年5月3日火曜日

Dapper.NET

dapper-dot-net - Simple SQL object mapper for SQL Server - Google Project Hosting

これ。Massiveと同じくらい短いコードのORM。WebMatrix.Data.Databaseみたいなものですね。

オープンソースとしてたくさん存在する軽量ORMのなかでもDapperがスゴイところがパフォーマンス。どのくらい早いのかは上記サイトを確認してみると各種ORMとの速度比較が出てます。

A day in the life of a slow page at Stack Overflow

ASP.NET MVC & SQLServer & LINQ to SQLのスタックで有名な大規模サイトといえばStackoverflow.comですね。なんとそのStackoverflow.comでパフォーマンスに問題が出てきたので解決するために、このDapperを利用したというじゃないですか。

そもそもORMでN+1問題が出やすいので、LINQならjoin使ってたくさんのSQLを発行しないようにするとか、DBとのあいだのやりとりもちゃんと確認する(パフォーマンスに問題があると認識したならの話です。問題になってないなら気にしなくていいですよ)必要がありましょう。その辺はちゃんと実装したとしてもDapperが早いということです。Emitしちゃってるし。

使い方も簡単でIDbConnectionの拡張メソッドとして実装してるので、DBコネクションさえあれば簡単に導入できます。もちろんモデルクラスをPOCOで定義しておくことが最速を維持する秘訣。

AdventureWorksLT2008R2をDBに利用するためのサンプル。

public class Product
{
  public int ProductId { get; set; }
  public string Name { get; set; }
  public string ProductNumber { get; set; }
}

using (var connection = connectionFactory())
{
  var result = connection.Query<int>("select count(*) from Product").Single();
  Console.WriteLine(result + " products.");
}

// 1テーブルを1クラスにマッピング
using (var connection = connectionFactory())
{
  var result =
      connection.Query<Product>(
          "select * from Product where ListPrice between @low and @high",
          new { low = 10.0, high = 100.0 });
  Console.WriteLine("-- simple mapping:" + result.Count());
  foreach (var p in result)
  {
      Console.WriteLine(string.Format("ID:{0},Name:{1}", p.ProductId, p.Name));
  }
}

// ジェネリック指定しないで1テーブルをマッピング
using (var connection = connectionFactory())
{
  var result =
      connection.Query(
          "select * from Product where ListPrice between @low and @high",
          new { low = 10.0, high = 100.0 });
  Console.WriteLine("-- dynamic mapping:" + result.Count());
  foreach (var p in result)
  {
      Console.WriteLine(string.Format("ID:{0},Name:{1},Price:{2}", p.ProductID, p.Name, p.ListPrice));
  }
}

dapper2

ここで、実際にテーブルにはたくさん項目あるけど、モデルには省略しちゃってるんですけど、このままSQLにjoin含めた場合、ちゃんとモデルにはマッピングできませんでした。dynamicだとうまくいくんだけど、その辺の整合性はちゃんととっておかないとダメなわけですね。でもdynamicな戻り値なら早いわけではない。

dapper1

var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < 100; i++)
{
  using (var connection = connectionFactory())
  {
    var result =
        connection.Query(
            @"select p.*, c.ProductCategoryId as CategoryId, c.Name as CategoryName
          from Product as p 
            inner join ProductCategory as c on 
              p.ProductCategoryID = c.ProductCategoryID 
          where p.ListPrice between @low and @high",
            new { low = 10.0, high = 100.0 });
    foreach (var p in result){}
  }
}
stopwatch.Stop();
Console.WriteLine("dapper time:" + stopwatch.ElapsedMilliseconds + " ms");

stopwatch.Reset();
stopwatch.Start();
for (var i = 0; i < 100; i++)
{
  using (var l2s = new AwDataContext(connectionFactory()))
  {
    var result = from p in l2s.Product
                 join c in l2s.ProductCategory on p.ProductCategoryID equals c.ProductCategoryID
                 select
                     new
                         {
                             ProductId = p.ProductID,
                             p.Name,
                             Category = c.Name
                         };
    foreach (var p in result) {}
  }
}
stopwatch.Stop();
Console.WriteLine("linq2sql time:" + stopwatch.ElapsedMilliseconds + " ms");

大きなサイトへ導入されてるというのは、興味深いじゃないですか。それだけの負荷で問題が起きないように丁寧な実装がされてるわけで(どっちもだけど)。

今後、より使いやすくなることを期待しつつ見守っていきたいプロジェクトです。

2011年4月24日日曜日

IIS ExpressでSSLを有効にしてデバッグ

Working with SSL at Development Time is easier with IISExpress - Scott Hanselman

Hanselmanさんのエントリに書かれてる通りデス!

IIS ExpressでSSLを有効にしたデバッグだと無効な証明書だと警告はでるけど、HTTPSでのテストはコレでできますよね。

だけど、名前はlocalhostに限定です。これはまぁ、そういうバインドしかapplicationHost.configに登録してないからなのと、アクセス許可をしてなからなんですが(詳しくはmvcConf 2 - Vaidy Gopalakrishnan: IIS Express | mvcConf | Channel 9)、その辺をちゃんと登録して気持ちよく使いましょうという内容ですね。途中URLRewriteでAccountControllerへのアクセスをHTTPSに書き換えるというのが挟まってるけど、コードで対応するならここは飛ばしてもOKです。

netshへのcerthashの登録、信頼されたルート証明機関の証明書として証明書を登録、最終的にIisExpressAdminCmdでACLの許可登録で気持よく、マシン名でもSSL有効な状態でアクセスできます。

というのを、自分の環境でも確認してみましょう。

1.ASP.NET MVCサイトを作成

もちろんVS2010で行うのが簡単ぽんなんですが、VS2008でも同じようにできるので(applicationHost.configとnetshなので)、そっちしかない場合はそっちで。プロジェクトの設定で、外部プログラムを起動するを利用しましょう(IIS ExpressのVS2008/VS2010利用)。

今回は簡単なVS2010で新しくなったMVC3 Toolsで作ります。

ssl1

いい感じにHTML5ですね!

2.プロジェクトのプロパティでSSL有効化

ssl2

trueにするだけの簡単な作業です。これでSSL URLが入ります。この例だと https://localhost:44301/ ですね。

この状態でまずは実行してみましょう。

ssl3

文句は言われるけど、普通に問題なくアクセスできますね。楽チンデス。IIS Express最高!

3.localhostじゃなく好きな名前でアクセス

マシン名でもいいんですが、そこはほら、恥ずかしい名前付けてる場合もあるじゃないですか。そんな状態で表示するのもどうかと思うわけですよ。なので、今回は完全カスタムな名前にしてしまいます。

takepara-local

という名前にします。これが恥ずかしい名前じゃないかと言われると、まぁ微妙ですが。あ、ちなみに自分のマシン名はそんなに恥ずかしい名前じゃないですよ。

IisExpressAdminCmdでアクセスできるようにしましょう。

※”.”でドメインな感じのマシン名だとsetupFriendlyHostnameUrlに失敗します。

ssl6

これでブラウザからアクセスしてみましょう!

ssl7-2

残念。エラーです。何か忘れてますね。そうですね、applocationHost.configにバインド登録してなかったですね。

<site name="SslTest" id="15">
    <application path="/" applicationPool="Clr4IntegratedAppPool">
        <virtualDirectory path="/" physicalPath="..." />
    </application>
    <bindings>
        <binding protocol="http" bindingInformation="*:51578:localhost" />
        <binding protocol="https" bindingInformation="*:44301:localhost" />
        <binding protocol="http" bindingInformation="*:80:takepara-local" />
        <binding protocol="https" bindingInformation="*:443:takepara-local" />
    </bindings>
</site>

ssl7

グレートですね。ちなみにhostsにtakepara-local登録して、netshでurlacl登録しても一緒です。

このまま、調子にのってHTTPSにしてみる。

ssl8

残念。ダメでした。

4.証明書を作成

takepara-localの証明書を作成してませんでしたね。hanselmanさんのエントリに書かれてるとおりです。makecertしましょう。CNだけ変えて後はそのままで。

ssl9

makecert -r -pe -n "CN=TAKEPARA-LOCAL" -b 01/01/2000 -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12

VSコマンドプロンプトから実行しましょう。パス通ってて確実。

ssl10

証明書をnetshで登録。

netsh http add sslcert ipport=0.0.0.0:443 appid={214124cd-d05b-4309-9af9-9caa44b2b74a} certhash=864759f75a04d030e243ecf1cfe19ce336653374

ssl11ssl12

警告でるけど、気にせず表示。

5.証明書を信頼

このままでもいいんですが、やっぱり気持よく。

ssl13

信頼しちゃいましょう。自分のマシンで、自分で作った証明書。信頼しちゃえばいいじゃないか!

ssl14

Chrome厳しす。まぁ、いいか。警告でなくなったしね!

6.Accountコントローラを厳しく

URLRewriteで同じようになってもいいんですが、あえてコードで対応しましょう。と、言っても難しいことはなく、AccountControllerにRequireHttps属性を付けるだけ。

ssl15

これで、AccountControllerへのアクセスがHTTPSになってなければ、リダイレクトされます。リダイレクトされるということはHTTPでのPOSTは無視される(リダイレクトはGETデス)ので、気をつけましょう。

ssl16

この動きが気持ち悪いときはViewでURLを埋め込むときにHTTPSにしてあげる必要があるので、修正箇所が随分増えちゃいますが、通常の開発であればViewでもちゃんと書いて、ControllerへのRequireHttpsも付ける2段構えになるのが王道でしょう。抜け道なし。ViewのURLなんて嘘つくからね。

例えば、_LogOnPartial.cshtmlを以下のようにする。

@if(Request.IsAuthenticated) {
    <text>Welcome <strong>@User.Identity.Name</strong>!
    [ @Html.ActionLink("Log Off", "LogOff", "Account", "https", "takepara-local", "", null, null) ]</text>
}
else {
    @:[ @Html.ActionLink("Log On", "LogOn", "Account", "https", "takepara-local", "", null, null) ]
}

これで、必ずHTTPSのURLが生成されます。

画像てんこ盛りですが、手順通りやればうまくいくので、HTTPSでのテストをやりたい場合は是非どうぞ。

なんか、うまくできない!という場合はタスクトレイのIIS Expressを終了させてやり直してみるといいかも。applicationHost.config書き換えしたときなんかは特に。

2026年06月16日の記事一覧

(全 23 件) そうきたか。マイクロソフトが新しく提案するAI搭載の「社員証」 「WSL」のバージョン3が開発者にもたらす大きなメリット Stack Overflow、AIエージェント同士が掲示板で技術情報を共有する「Stack Overflow fo...