だるろぐ

明日できることは、今日しない。

WebMatrix:IHttpModule で定期実行を実装する

WebMatrix 3: Twitter Bot (+リアルタイムログ表示付き)でも作ってみる。 - だるろぐ で System.Timers.Timer を使って ASP.NET の定期処理を実装したのだけど、のいえ先生の neue cc - ASP.NETでの定期的なモニタリング手法 でもう一度実装しなおしてみた。

f:id:daruyanagi:20140720222501p:plain

Web.config

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.webServer>
    <modules>
      <add name="SchedulerModule" type="SchedulerModule"/>
    </modules>
  </system.webServer>
</configuration>

Web.config でモジュールを登録する。

~/App_Code/ScheduleModule.cs

using System;
using System.Threading;
using System.Web;

public class SchedulerModule : IHttpModule
{
    static int initializedModuleCount = 0;
    static Timer timer;
 
    public void Init(HttpApplication context)
    {
        var count = Interlocked.Increment(ref initializedModuleCount);

        if (count != 1) return;
 
        timer = new Timer(_ =>
        {
            try
            {
                System.Diagnostics.Debug.WriteLine("だん!");
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
            }
        }, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
    }
 
    public void Dispose()
    {
        var count = Interlocked.Decrement(ref initializedModuleCount);

        if (count != 0) return;

        var target = Interlocked.Exchange(ref timer, null);

        if (target != null) target.Dispose();
    }
}

SchedulerModule はこんな感じ。IHttpModule インターフェイス(Init、Dispose)を実装する。

f:id:daruyanagi:20140720222920p:plain

(ExpressWeb で動かして initializedModuleCount をインクリメント・デクリメントをメールで通知するようにしてみたらこんな感じ)

SchedulerModule はいくつも作成(&いくつも殺される)ので、ちゃんと数をカウントして、タイマーが大量生産されないように管理する必要があるそうな(← ASP.NET のライフサイクルをちゃんとわかってない。確かライフサイクルを書いたポスターがあったはずなので、今度確認しておこう)。

WebMatrix 3: @ でハマる(解決編

f:id:daruyanagi:20130929122557p:plain

WebMatrix 3: @ でハマる - だるろぐ の続き。

というアドバイスをもらった。あ、たぶんそれだ。というわけで書き直した。

旧バージョン(Logger.cshtml)

#App_Code/Logger.cshtml

@helper Write(string message)
{
    System.IO.File.AppendAllText(
        Server.MapPath("~/log.txt"),
        string.Format("{0}:\t{1}\r\n", DateTime.Now, message)
    );
}

これって実は public static HelperResult Write(string message) になるんだよね。それにしてもなぜ AppendAllText() が実行されないのかは謎だけど、HTML が含まれていない(出力がない)ならば処理を飛ばしてしまう最適化なんかがあるのかもしれない。とりあえず“Page に対してなんら出力のないヘルパー”という使い方は NG ってことかな。

新バージョン(Logger.cs)

f:id:daruyanagi:20131001075835p:plain

#App_Code/Logger.cs

using System;

public static class Logger
{
    public static void Write(string message)
    {
        System.IO.File.AppendAllText(
            System.Web.Hosting.HostingEnvironment.MapPath("~/log.txt"),
            string.Format("{0}:\t{1}\r\n", DateTime.Now, message)
        );
    }
}

CSHTML → CS にして、コードを Razor ではなく C# で書き直す。“HostingEnvironment.MapPath(String) Method (System.Web.Hosting) | Microsoft Docs”は最近覚えたのだけど、Server.MapPath() が使えない場面でも動く(のだと期待している)。

f:id:daruyanagi:20131001080145p:plain

結果は――期待通りログが出力される。けれど、Default.cshtml も少し書き直さなければならない。

旧バージョン(Default.cshtml)

@{
     @Logger.Write("冒頭のコードブロック内で記述");
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @Logger.Write("Body 内で記述")
    </body>
</html>

新バージョン(Default.cshtml)

@{
     Logger.Write("冒頭のコードブロック内で記述");
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @{ Logger.Write("Body 内で記述"); }
    </body>
</html>

Logger.Write が HelperResult を返さなくなったことにより(ヘルパーメソッドとしてシグネチャーが合わなくなったので)、@Logger.Write() という書き方はできなくなり、コードブロックで囲まなければならなくなった。けど、これが正しい。

そういえば

をあんまり理解していないことを思い出したので、これも今度解決しておくことにする。

追記

この功を表して、しばやんにはもう一枚 CD を贈ることにした。

WebMatrix 3: @ でハマる

ちょいとログを外部ファイルに吐きたくなって、テキトーにこんなコードを書いてみた。

f:id:daruyanagi:20130929121802p:plain

#App_Code/Logger.cshtml

@helper Write(string message)
{
    System.IO.File.AppendAllText(
        Server.MapPath("~/log.txt"),
        string.Format("{0}:\t{1}\r\n", DateTime.Now, message)
    );
}

これを Default.cshtml でテストしてみた。

f:id:daruyanagi:20130929122015p:plain

@{
     Logger.Write("冒頭のコードブロック内で記述");
}

<!DOCTYPE html>

<html lang="ja">
    <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    </head>
    <body>
        @Logger.Write("Body 内で記述")
    </body>
</html>

結果はというと――

f:id:daruyanagi:20130929122031p:plain

――冒頭のコードブロック内で記述したログは記録されない。「あれ、なんでだろう?」と思って、あちこちごちゃごちゃ弄ってみたのだけど、正解はコレだった。

@{
     @Logger.Write("冒頭のコードブロック内で記述");
}

f:id:daruyanagi:20130929122557p:plain

Logger.Write() のまえに @ を足せば実行される。

@{
     @(
         Logger.Write("冒頭のコードブロック内で記述")
     )
}

でもいいのだけど。

しっかし、これ、なぜなんだろう。自分はまだまだ Razor がわかってないな。

ASP.NET and Web Tools 2012.2

(208日前に書いた下書きをそのまま公開してみるなど)

f:id:daruyanagi:20130221101959p:plain

Last year the ASP.NET team started talking about something we're calling "One ASP.NET." I showed some mockups of our ideas last summer at the aspConf Keynote that you can watch online.

We also announced then that we would add new features to ASP.NET as out of band releases (OOB) without breaking existing functionality. This means that developers don't have to wait for the next version of Visual Studio for great web development features today. We're aiming to add to ASP.NET and Web Tools every 6 months.

For those that don't want to wait, Mads and the team also started a feature playground called Web Essentials. This is an extension to Visual Studio that updates all the time with ideas and brainstorms about how VS can be an even better editor for the web. As features "graduate" from Web Essentials, they move into ASP.NET and Web Tools proper. A bunch of features graduate with today's release.

Today we announced ASP.NET and Web Tools 2012.2. You can use the Web Platform Installer to get ASP.NET and Web Tools 2012.2 now.

Released: ASP.NET and Web Tools 2012.2 in Context - Scott Hanselman

なんか立て続けにいろいろでたけど、本業のほうで扱えなかったこともあり、ブログを追っかけているだけで、個人的には消化しきれてなかった。

ASP.NET and Web Toolsについては、

  • Visual Studio 2012 Update(4半期ごと)の Web 開発部分を先取りリリース
  • ASP.NET のコアに変更を加えるものではなく、その周辺機能や開発支援機能を拡張するもの
  • 6ヶ月おきのリリース

という理解でよいのだろうか。変更点は以下の通り。

  • Enhancements to Web Publishing
  • New Web API functionality
  • New templates for Facebook Application and Single Page Application
  • Real-time communication via ASP.NET SignalR
  • Extensionless Web Forms via ASP.NET Friendly URLs
  • Support for the new Windows Azure Authentication

日本語による包括的なまとめは、@chack411 さんがオススメ。

WebMatrix 3: Twitter でログインしてアクセストークン(秘)を取得する

WebMatrix 3: Twitter でログインする - だるろぐ でめでたく Twitter でのログインが実現できたのだけど、実はひとつ問題があった。

f:id:daruyanagi:20130905064941p:plain

AccessTokenSecret が取れない。

自分もあんまりよくわかっていないのだけど、Twitter の API を利用するには以下の情報が必要であるみたい。

f:id:daruyanagi:20130911233354p:plain

まず、これ。アプリが Twitter へアクセスするために必要。

  • ConsumerKey
  • ConsumerSecret

次に、これ。ユーザーに成り代わって Twitter の API を使うために必要。

  • AccessKey
  • AccessKeySecret

アプリの登録画面で取得できる AccessKey/AccessKeySecret はアプリを登録したユーザーのアクセスキー。このアプリにログインしたユーザーとして API を利用するには、そのユーザーに対して発行される AccessKey/AccessKeySecret が必要だ。

でも、OAuthWebSecurity では ExtraData から AccessKey をもらうことはできても、AccessKeySecret まではくれないみたい。

f:id:daruyanagi:20130912000225p:plain

通信を Fiddler でみてみた。ちゃんと authorize したあとに access_token している(ここで AccessKey がもらえる)から、ついでに AccessKeySecret もとってきてくれてもよさそうなのだけど。なにか理由があるのかもしれないが、これではちょっと困る。

これを解決するには、Twitter プロバイダーを自分で実装すればよいようだ。

// ~/App_Code/TwitterClient.cs

using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
using System.Collections.Generic;

// http://stackoverflow.com/questions/12198734/getting-twitter-access-secret-using-dotnetopenauth-in-mvc4

public class TwitterClient : OAuthClient
{
    /// <summary>
    /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
    /// </summary>
    public static readonly ServiceProviderDescription TwitterServiceDescription = new ServiceProviderDescription
    {
        RequestTokenEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/request_token",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        UserAuthorizationEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/authenticate",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        AccessTokenEndpoint =
            new MessageReceivingEndpoint(
                "https://api.twitter.com/oauth/access_token",
                HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
    };

    public TwitterClient(string consumerKey, string consumerSecret) :
        base("twitter", TwitterServiceDescription, consumerKey, consumerSecret) { }
    protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
    {
        string accessToken = response.AccessToken;
        string accessSecret = (response as ITokenSecretContainingMessage).TokenSecret;
        string userId = response.ExtraData["user_id"];
        string userName = response.ExtraData["screen_name"];

        var extraData = new Dictionary<string, string>()
                            {
                                {"accesstoken", accessToken},
                                {"accesssecret", accessSecret}
                            };
        return new AuthenticationResult(
            isSuccessful: true,
            provider: ProviderName,
            providerUserId: userId,
            userName: userName,
            extraData: extraData);
    }
}

結果はこんな感じ。いつもどおり ObjectInfo.Print() で中身を見てみたよ。

f:id:daruyanagi:20130912115257p:plain

実装としてどうするのが理想的なのかはよくわからないけれど、とりあえずユーザーを管理するテーブルを拡張して、アクセスキーを保管しておくのとかどうでしょうか。

@{
    var returnUrl = Request["returnUrl"];

    // ログインの検証
    var result = OAuthWebSecurity.VerifyAuthentication(  
         Href("LogonCallBack", new { ReturnUrl = returnUrl })
    );

    if (result.IsSuccessful)
    {
        // ログインが成功すると、
        // - provider: twitter
        // - ProviderUserId: twitter の ID
        // - UserName: twitter のスクリーンネーム
        // の3つが得られる。自動補完が効かないので変数に入れとく
        var provider = result.Provider;
        var providerUserId = result.ProviderUserId;
        var userName = result.UserName;
        var accessToken = result.ExtraData["accesstoken"];
        var accessTokenSecret = result.ExtraData["accesssecret"];

        <p>@ObjectInfo.Print(result.ExtraData)</p>

        // ユーザー名が Users テーブルに存在しない場合、
        // あらかじめユーザー名を追加しておく。
        // でないと CreateOrUpdateAccount() でコケる
        using (var db = Database.Open("kenzou-memo"))
        {
            const string SELECT = "SELECT * FROM USERS WHERE Name=@0";
            const string INSERT = "INSERT INTO Users (Name, AccessToken, AccessTokenSecret) VALUES (@0, @1, @2)";
            const string UPDATE = "UPDATE Users SET AccessToken=@1, AccessTokenSecret=@2 WHERE Name=@0";

            if (db.QuerySingle(SELECT, userName) == null) // この処理を追加してみました
            {
                db.Execute(INSERT, userName, accessToken, accessTokenSecret);
            }
            else
            {
                db.Execute(UPDATE, userName, accessToken, accessTokenSecret);
            }
        }

        // CreateOrUpdate とか言ってるけど、
        // やってることは Users テーブルと内部管理テーブルの紐づけ
        OAuthWebSecurity.CreateOrUpdateAccount(
            provider,
            providerUserId,
            userName);

        // ログインチケットの発行
        OAuthWebSecurity.Login(
            provider,
            providerUserId,
            createPersistentCookie: true);

        Response.Redirect(returnUrl);        
    }
    else
    {
        // ログインに失敗したときの処理
    }
}

自分でプロバイダーを実装するのはそこはかとなくめんどくさいけれど、丸コピで動くのでまぁ、よし。プロバイダーをどうやって実装するのかも少し分かったし。練習として、ほかのサービスを実装してみるのもよいかもしれない。最近なんかだと GitHub なんかが需要ありそうだ。


Microsoft のダウンロードセンターがリニューアル

f:id:daruyanagi:20130516071452p:plain

まぁまぁいいかも。全体的にメトロ(死語)っぽくまとまっている。お前らのお目当てであろうダウンロードボタンがわかりやすくデカデカと置いてあって、俺のような人間にしか用のないオプションの情報は折りたたんでシンプルに表示してあるのがイイ。ここ半年間デザインが二転三転し、米国と日本でデザインがチグハグだったりしてややこしかったけれど、これが一番いいんじゃないかなぁ。

f:id:daruyanagi:20130516071737p:plain

どんなデザインか確認するついでに、ASP.NET Web API HTTP Message Lifecycle ポスター(http://www.microsoft.com/ja-jp/download/details.aspx?id=36476)でもダウンロードしてみよう ☆(ゝω・)vキャピ

日本語化&実寸印刷配布したいなぁ・・・(チラッ

ASP.NET Web API HTTP Message Lifecycle ポスター - THE TRUTH IS OUT THERE - Site Home - MSDN Blogs

日本語化はされている。実寸印刷のものが貰えたら、便所に貼りたいと思う。

あと、これの WebMatrix (ASP.NET Web Pages)版ないかしら!

WebMatrix 2:ASP.NET と PHP(2)

WebMatrix 2:ASP.NET と PHP - だるろぐ あたりを書いた時に Twitter で「WebMatrix 2(というより、Razor 記法)は Visual Basic に対応していないのか?」という疑問を見かけたのですが、ちゃんと対応しています。

たとえば前回の例の場合、こんなふうに書きます。

@Code
    Dim s = "Hello! World" ' 追加
End Code

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        @s <!-- 追加 -->
    </body>
</html>

C# の @{ … } が @Code … End Code に置き換わったぐらいかな。For 文の書き方なんかも VB 風に書けます。詳しいことは VB Razor構文 基礎文法 総まとめ - @IT でも参照してください。個人的にはこれから始める人が VB で Razor を書く意味はあまりないのではないかなと思います。XML の処理などは簡単なんだそうですけど。

あと、前回は言わなかったけれど <% … %> という ASP.NET の古い記法(ASPX、C# または Visual Basic)を利用することもできます。

まぁ、それはともかく。

f:id:daruyanagi:20121227064529p:plain

こんなにいっぱいあるんだったら、どれが一番いいのかって気になりますよね。前にも言った通り、ぶっちゃけ好きなものを選べばいいと思うんですけど(Razor/C# を推しますけどね)、今回は簡単なプログラム(0から10,000までの2乗をリスト表記)で速さを計測してみました。

PHP

f:id:daruyanagi:20121227064356p:plain

PHP はあんまり知らないのでこれでいいのか多少不安ですけど。

  • 0.007682秒
  • 0.004367秒
  • 0.003787秒
  • 0.003856秒
  • 0.003891秒

最初だけ時間を食いましたが、こんな感じ。

以下、初回計測時にはサーバーを一度再起動させています。PHP の結果を見ると、初回起動時はちょっと時間がかかるのかなと感じたので。念のためもう一度計測したら、サーバー再起動後で 0.0035609999999999秒 でした。0.007秒というのは、異常値として処理しちゃってもいいレベルかな。

•ASP.NET (ASPX/C#)

f:id:daruyanagi:20121227064720p:plain

  • 0.0029692秒
  • 0.0025189秒
  • 0.0024252秒
  • 0.0024489秒
  • 0.0024761秒

安定して速かったです。ただ、これで書こうかという気は起こらないのですけど。

•ASP.NET (Razor/C#)

f:id:daruyanagi:20121227065042p:plain

  • 0.0028398秒
  • 0.0024506秒
  • 0.0024667秒
  • 0.0025609秒
  • 0.0026207秒

ASPX よりはわずかながら遅いような(ほとんど誤差レベルですが)。PHP よりは速かった。

•ASP.NET (Razor/Visual Basic)

f:id:daruyanagi:20121227065439p:plain

  • 0.0046345秒
  • 0.0039607秒
  • 0.0036215秒
  • 0.0036577秒
  • 0.0038143秒

ついでに Razor だけ VB でも試してみました。PHP よりはマシという感じの速さですね。

まぁ、簡単すぎてあんまり参考にならないレベルですけどちょっと興味がわいたので試してみました。著しく不公平なところがあれば、ぜひ教えてください。正直あんまり自信ない。

WebMatrix でファイルのアップロード

f:id:daruyanagi:20120819110408p:plain

今日は「WebMatrix 2」でファイルのアップロードを試してみた。なお、このサンプルは「Empty Sites」テンプレートを元に作成している。

Delault.cshtml

<!DOCTYPE html>

<html lang="ja">
    <head>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
    </head>
    <body>
        <form action="~/Upload" method="post"
              enctype="multipart/form-data">
            <input type="file" name="upload" /><br />
            <input type="submit" name="submit" />
        </form>
    </body>
</html>

拡張子を html にすれば、ただの HTML ドキュメントだね! ファイルのアップロードを行うので、 multipart/form-data をつけるのを忘れないように。

f:id:daruyanagi:20120819110559p:plain

Upload.cshtml

アップロード処理を行う cshtml はこんな感じにしてみた。

ほんとは path が存在しなければ例外、 file のサイズが 0 ならば例外、 file が image/*** でなければ例外、といったチェックを入れるのだけれど、ソースが長くなるので割愛している。あと、最初から複数ファイルのアップデートに対応できるように記述している。

@using System.IO

@functions {
    enum Result { Success, Error };
}

@{
    var result = Result.Error;
    var message = "You can use only POST method.";
    var link = string.Empty;

    if (IsPost)
    {
        foreach (var key in Request.Files.AllKeys)
        {
            var file = Request.Files[key];

            try
            {
                const string OUTPUT = "~/Files/";
                var path = Server.MapPath(OUTPUT);

                var src = Path.GetFileName(file.FileName);
                var dst = string.Format(
                    "{0:yyyyMMdd-HHmmssfff}{1}",
                    DateTime.Now, Path.GetExtension(src).ToLower()
                );

                file.SaveAs(Path.Combine(path, dst));

                result = Result.Success;
                message = string.Format(
                    "{0} is uploaded as {1}.", 
                    src, dst
                );
                link = VirtualPathUtility.ToAbsolute(OUTPUT + dst);
            }
            catch (Exception e)
            {
                result = Result.Error;
                message = e.Message;
            }
        }
    }
}

<h1>@result</h1>
<p>@message</p>
if (!string.IsNullOrEmpty(link))
{
    <p><img src="@link" /></p>
}
<p>&raquo; Back to <a href="~/">home</a></p>

基本的には、 Request.Files でファイルを取得し、 SaveAs() で保存するだけ。そのほかはファイル名の決定だのエラー処理だのといったことをしているに過ぎない。

Default.cshtml から画像ファイルを POST すると、

f:id:daruyanagi:20120819111843p:plain

エラーが出たらこんな感じで……

f:id:daruyanagi:20120819111723p:plain

成功したらこんな感じになる。

f:id:daruyanagi:20120819112043p:plain

"~/Files/"フォルダが夢のようになっておるな!

ステップアップ

ヘルパーで楽をしよ……ぅ?

f:id:daruyanagi:20120819114311p:plain

ASP.NET Web Helpers Library という NuGet をインストールすると、複数ファイルのアップロードに対応した Form タグを簡単に生成できる。

@FileUpload.GetHtml()

でも、個人的にはあんまり好きじゃなかったので今回は使わなかった。

<!DOCTYPE html>

@{
    if (IsPost)
    {
        foreach (var key in Request.Files.AllKeys)
        {
            var file = Request.Files[key];

            try
            {
                file.SaveAs(
                   System.IO.Path.Combine(
                       Server.MapPath("~/Files/"),
                       file.FileName)
                );
            }
            catch (Exception e)
            {

            }
        }
    }
}

<html lang="ja">
    <head>
        <meta charset="utf-8" />
        <title>マイ サイトのタイトル</title>
    </head>
    <body>
        @FileUpload.GetHtml()
    </body>
</html>

f:id:daruyanagi:20120819115634p:plain

なんか動的に生成されるノードの名前がカブってるし*1、あんまりよくわかんなかった。

ビューでつかう変数をまとめる

Upload.cshtml のソースコードがなんだか冗長なのは、HTML の出力に使う result、message、link という3つの変数を処理するためだけど、こいつらって匿名クラスでまとめてもいいよね。

@using System.IO

@functions { enum Result { Success, Error }; }

@{
    const string OUTPUT = "~/Files/";
    dynamic model = null;

    if (IsPost)
    {
        foreach (var key in Request.Files.AllKeys)
        {
            var file = Request.Files[key];

            try
            {
                var path = Server.MapPath(OUTPUT);

                var src = Path.GetFileName(file.FileName);
                var dst = string.Format(
                    "{0:yyyyMMdd-HHmmssfff}{1}",
                    DateTime.Now, Path.GetExtension(src).ToLower()
                );

                file.SaveAs(Path.Combine(path, dst));

                model = new {
                    Result = Result.Success,
                    Message = string
                        .Format("{0}'s uploaded as {1}", src, dst),
                    Link = VirtualPathUtility
                        .ToAbsolute(OUTPUT + dst),
                };
            }
            catch (Exception e)
            {
                model = new {
                    Result = Result.Error,
                    Message = e.Message,
                    Link = string.Empty,
                };
            }
        }
    }
    else
    {
        model = new {
            Result = Result.Error,
            Message = "You can use only POST method",
            Link = string.Empty,
        };
    }
}

<h1>@model.Result</h1>
<p>@model.Message</p>
@if (!string.IsNullOrEmpty(model.Link))
{
    <p><img src="@model.Link" /></p>
}
<p>&raquo; Back to <a href="~/">home</a></p>

記述量はかえって多くなったけど、「何かの処理 → 結果(モデル)の生成」という流れが明確になった気がする。この @model っていうのが MVVM の ViewModel じゃない ViewModel という理解でいいんでしょうか。

Ajax には Json で応える

ViewModel を返すことの利点は、可読性だけじゃない。たとえばこんなこともできる。

@if (IsAjax)
{
    // Response.ContentType = "application/json";
    Response.Write(Json.Encode(model));
}
else
{
    <h1>@model.Result</h1>
    <p>@model.Message</p>
    if (!string.IsNullOrEmpty(model.Link))
    {
        <p><img src="@model.Link" /></p>
    }
    <p>&raquo; Back to <a href="~/">home</a></p>
}

f:id:daruyanagi:20120819125626p:plain

Ajax リクエストに Json で応えるなんてことも簡単にできる!

拡張メソッドのお時間です

あとさ、これダサいよね。

foreach (var key in Request.Files.AllKeys)
{
    var file = Request.Files[key];
    :
    :

拡張メソッドを書いて、シンプルにしましょう。

foreach (var file in Request.Files.ToEnumerable())
{
    :
    :

~/App_Code/HttpFileCollectionBaseExtension.cs を作成してこのように書いてみました。

using System.Collections.Generic;
using System.Web;

public static class HttpFileCollectionBaseExtension
{
    public static IEnumerable<HttpPostedFileBase> ToEnumerable(
        this HttpFileCollectionBase target)
    {
        foreach (var key in target.AllKeys)
        {
            yield return target[key];
        }
    } 
}

*1:JavaScriptの不具合かなぁ

SignalR のデモがちょっとかっこいい


Video streaming by Ustream

イベントの方向とタイトル的に ASP.NET 全体を少しずつ喋る形にしたのですが、SignalR だけで 45 分話してもいけたかなという印象を持ちました。

Go Azure お疲れ様でした - まめしば雑記

あんまりまだよくわかってないけど、あのデモは結構興味惹かれたかも。

SignalR は、このあと見て行くように、単なる WebSocket 開発のためだけのライブラリーではなく、さらに多くの付加価値を含んだ上位のフレームワークです。抽象度も高く、日本語のエンコードなど含めプリミティブな処理を開発者が記述する必要はありません。

WebSocket サーバー開発 : SignalR とクロス ブラウザーへの対応 - 松崎 剛 Blog - Site Home - MSDN Blogs

SignalR という非同期でリアルタイムな双方向通信を実現するライブラリが面白そうなので試してみました。作者は ASP.NET チームの Damian Edwards 氏と David Fowler 氏です。

SignalR を簡単に使ってみた - まめしば雑記

二日目はオンライン参加だったので、セッションが終わってすぐ手元のPCで実際にサンプルを試してみた。

f:id:daruyanagi:20120704080210p:plain

まず、 NuGet で SignalR のサンプルをインストール。依存関係のあるものも自動ででろでろっとダウンロード・インストールされる。

f:id:daruyanagi:20120704080345p:plain

サンプルは株価の上がり下がりを表示するもので、サーバー側からデータが送られてきてクライアント側(Webブラウザー)の表示が更新されるという、要はプッシュ通知のデモになっている。見てるだけでもちょっと楽しい。

SignalR のもう一つのキモは、ブラウザーが対応する技術に応じて Web Socket やフレームを使い分けてくれるところ。これを確かめるために、今回は「IE Tester」というツールを利用してみた。

My DebugBar | IETester / Browser Compatibility Check for Internet Explorer Versions from 5.5 to 10

本当は仮想マシン環境でも用意すべきなんだろうけど、さすがにそれは面倒だった。ちなみに、 Microsoft では IE の旧バージョンをテストするための仮想環境を用意しているので、空き時間とディスク空き容量が豊富な人は試してみるとよいかもしれない。

窓の杜 - 【NEWS】Microsoft、互換性検証を目的としたIE6/7/8/9環境の仮想マシンイメージを更新

f:id:daruyanagi:20120704080652p:plain

残念ながら、 IE7 以下の環境では失敗したものの、

f:id:daruyanagi:20120704080658p:plain

Web Socket に対応していない IE8 でも動作した。「IE Tester」が完全に旧バージョンのIEをエミュレートしているとは限らないので、これをもって IE7 非対応とは言えないと思うが、まぁ、参考として。

f:id:daruyanagi:20120704081526p:plain

株式会社だるだる を追加して遊んでみた。ふふふ……