2009年3月2日月曜日

ASP.NET MVCの新しいサンプル&チュートリアル

ASP.NET MVC Sample Application at www.ASP.net/MVC

ASP.NET MVCでの新しいサンプル。チュートリアルにそって順に良くなっていく。

これはMVCerはみとくべき。

イテレ-ション1 Entity Frameworkをモデルにして、アドレス帳サンプルを作成。この手順はオーソドックスな最初の作り方。これでMVCでの作成ってこれまでとどう違うのかが分かるはず。

イテレーション2 デザインギャラリーからデザインテンプレートを取得したり、自分でデザインしたものを1で作ったものに適用させて、見た目を綺麗にしましょう。ヘルパーなんかも書いたりしてデザインしやすくしておきましょう。

イテレ-ション3 フォームの入力検証を実装。シンプルな検証方法で、DataAnnotationsなどを使うわけではないです。どこで検証を実施すればいいのか、検証結果がどうViewに反映されるのかをしっかり押さえておきます。

イテレーション4 機能を疎結合にしましょう。モデル層はRepositoryに任せ、ロジック層はServiceに移動させる。それぞれインターフェイスと実装を分けて書きましょう。

イテレーション5 ユニットテスト。これまで書いたコードにたいするテストコードを書きましょう。モックを使うとテストコードを楽にかけるのでお気に入りのモックを使いましょう。ここではMoqを使います。他にもRhino MocksやTypeMockなんかもあるよ。テストの初期化時にインターフェースのインスタンスを生成し、コンストラクタインジェクション。 Service層だけじゃなく、コントローラもテストコードを書きましょう。

イテレーション6 5でテストコードを書いたけど、ここからはテストドリブンな開発にしましょう。Group機能を追加する様子をテストを書くところから始めます。その後でテストをパスするコントローラコードを書き、繰り返して機能を実装していく。一通り出来たら、テストコードもあることだし、リファクタリングしてコードをService層に移動したり、Repository層に移動して、テストをパスしないコードが発生しないようにしましょう。

イテレーション7 最後にアプリケーションをAjax化して、使いやすさも向上させましょう。ページの部分更新をしたりするので、共通部分はコントロール化して、Ajaxでの更新時にPartialViewでページ全体じゃなく、必要な部分だけを返すようなコードにしましょう。

テーブルも2個しかないし、規模としては凄く小さいけど、これがASP.NET MVCの開発スタイルの基本になると思います。しっかり身につけて、テストもはしょったりしないようにしましょう(自分で言っときながら耳が痛い...)。

カスタムViewEngineを試す

ASP.NET MVCに限った話ではないんですが、JavaScriptをページで使う場合、外部ファイル(scriptタグのsrc属性で指定する)にするか、インラインでページに直接書く(scriptタグ内にコードを書く)かどっちかになる。

今時の作り方ならJavaScriptはページの最後で外部ファイルを取り込むのが、パフォーマンス的にもよろし、ということになってますね(ハイパフォーマンスWebサイト)。

ASP.NET MVCでも、もちろん外部JSファイルを使ったアプリケーションを作るわけですが、ここで少し悩みが出てきました。

処理コード(静的)としてのJavaScriptは簡単に外部に出せるから問題にはならないんですが、サーバーサイドで生成したデータを元に処理するコードの場合、そのデータをどうやって外部ファイルで使えばいいでしょうか。分かりにくい説明ですが、例えばサーバー上でデータベースから名前一覧を取得してViewDataに入れておいたとします(こればっかりは動的)。そのViewDataを外部JSファイルではもちろんそのまま参照できません。と、いうのもscriptタグで取り込んだ外部JSファイルとページそのもののリクエスト(MvcHandler)は別物だから、いくらコントローラのアクションでViewDataに値を入れたとしても外部JSファイルを取得したリクエスト(StaticFileHandler)では参照できないというのと、そもそもJSファイルが<%=~%>を解析して処理してくれないから。あたりまえですね~。

じゃぁ、どうすればデータ(サーバーサイドで動的生成)をコード(JavaScriptを静的に取得)に簡単に渡せるんだろう。

ページ専用のJavaScriptコードはどうしても、アクションで生成されるデータを使った動的コードにしたくなってしまう。でも、それだと外部ファイルじゃなくViewにscriptタグを書いてしまうことになって、なんかスッキリしない。

  1. 外部JSファイルを拡張子JSじゃなくASPXで作成し、JavaScript用のコントローラを作成(レスポンスのコンテンツタイプをapplication/x-javascriptに変更)し、動的にJSコードを生成するようにして(ViewファイルにJavaScriptコードを書く)、scriptタグでsrc指定。
  2. 外部JSファイルにはコードだけを書き、Viewに出力されるHTMLにscriptタグを書いておいて適当な変数にデータ(JSON)で入れておいて、外部JSファイルからはこれを参照する。

1の方法をとる場合、凄く分かりにくくなるのがコントローラが違うからViewとJavaScriptそれぞれでデータ取得のロジック(もちろんJavaScript用のコントローラではクライアントで必要なデータのみですが)を書かなきゃいけないからコードが散らばるうえに、VSで開発してるにも関わらずコードハイライトもインテリセンスも効かなくなる。

例えば...

// CharaControllerのViewアクション
public ActionResult Character() {
return View();
}

// JsControllerのJavaScriptアクション
public ActionResult Character() {
ViewData["chara"] = ToJson(new {firstName="ルフィ",lastName="モンキー"});
return View();
}

// ビュー
<body>
<!-- viewの定義 -->
</body>
<script type="text/javascript" src="JsController/Character"></script>

// Js/Character.aspx(JavaScript)
var chara = <% = ViewData["chara"] %>;
// 以降charaを使った処理
これだと2つのコントローラが必要になるし、ViewDataの生成と利用が離れすぎ。

綺麗にコードとデータを分離するなら、2の方法が正解ですよね。その場合Viewページにscriptタグを書く必要がありますが、あくまで処理コードは外部に分離できる。

例えば...

// CharaControllerのViewアクション
public ActionResult Character() {
ViewData["chara"] = ToJson(new {firstName="ルフィ",lastName="モンキー"});
return View();
}

// ビュー
<body>
<!-- viewの定義 -->
</body>
<script type="text/javascript">
var viewData = <% = ViewData["chara"] %>;
</script>
<script type="text/javascript" src="chara.js"></script>

// chara.js
var chara = viewData;
// 以降charaを使った処理

Viewアクション内でJavaScriptで利用するデータを生成しておき、外部スクリプトでのデータ参照はグローバル(この場合ならviewData)を見る。

どうも1の方法に固執しすぎてて、これを解決するためにカスタムのViewEngineを作ればいいんじゃないの?というおかしな路線に走ってしまって...。結局は2の方法にすることでViewEngine作る必要は無かったことに気がついたんだけど、その過程で今ネットで見つかるViewEngineの作り方が少し古いやり方な事に気がついたので、無駄にしないために、ココにメモとして残しておきます。

ASP.NET MVC Tip #25 – Unit Test Your Views without a Web Server Maarten Balliauw {blog} - Creating a custom ViewEngine for the ASP.NET MVC framework SingingEels : Creating a Custom View Engine in ASP.NET MVC Brad Wilson: Partial Rendering View Engines in ASP.NET MVC

ViewEngineを作るといっても、単にViewパスの検索場所を変更するためだけの目的もあれば、テンプレートエンジンを置き換えてしまう目的もあると思います。

パスを変えるだけならIViewEngineの実装はせず、PhilさんのAreasデモソース(Grouping Controllers with ASP.NET MVC)のようにWebFormViewEngineを派生させてViewLocationFormatsとMasterLocationFormatsをセットして、IViewは標準のWebFormViewを使えばいいですね。

テンプレートエンジンを変えてしまいたい場合は、IViewEngineとIViewそれぞれを実装することになりますが、今回は少し楽をしてIViewEngineにはVirtualPathProviderViewEngineを使うことにします。

何を作るかというと、拡張子jsの中に/$Key$/という形でテンプレートを入れとくと、ViewDataCollection内の同名Key値を埋め込むというテンプレート。これならJavaScriptのインテリセンスもコードハイライトも有効。

IViewEngineの実装。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Hosting;

namespace Sample.Libraries
{
public class JavaScriptViewEngine : VirtualPathProviderViewEngine
{
public JavaScriptViewEngine()
{
  MasterLocationFormats = new string[0];

  ViewLocationFormats = new[]{
   "~/ViewScripts/{0}.js",
   "~/ViewScripts/Shared/{0}.js"
  };

  PartialViewLocationFormats = ViewLocationFormats;
}

protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
  return CreateView(controllerContext, partialPath, null);
}

protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
  return new JavaScriptView(viewPath);
}
}
}

IViewの実装。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.IO;
using System.Web;
using System.Text.RegularExpressions;

namespace Sample.Libraries
{
public class JavaScriptView : IView
{
private string _templatePath;

public JavaScriptView(string templatePath)
{
  _templatePath = templatePath;
}

public void Render(ViewContext viewContext, System.IO.TextWriter writer)
{
  var appPath = viewContext.HttpContext.Request.PhysicalApplicationPath;
  var filePath = VirtualPathUtility.ToAbsolute(_templatePath).Substring(1).Replace("/", "\\");
  var fullPath = Path.Combine(appPath, filePath);

  if (!File.Exists(fullPath))
    throw new InvalidOperationException("not exits javascript template file.");

  var template = File.ReadAllText(fullPath);

  writer.Write(Parse(template, viewContext.ViewData));
}

public string Parse(string contents, ViewDataDictionary viewData)
{
  return Regex.Replace(contents, @"\$\/(.+)\/\$", m => GetMatch(m, viewData));
}

protected virtual string GetMatch(Match m, ViewDataDictionary viewData)
{
  if (m.Success)
  {
    string key = m.Result("$1");
    if (viewData.ContainsKey(key))
      return viewData[key].ToString();
  }
  return String.Empty;
}
}
}

置換部分の処理はまるっきりStephenさんのコードです...。

拡張子jsのファイルはViewScriptsフォルダに入れておくようにしたものです。コントローラ名もフォマットに含めようと思ったんですが、そこはルーティングの登録を以下のようにしておくことでとりあえず必要無いな、と。でも、ViewScriptsフォルダ内はコントローラ名フォルダ/スクリプト名.jsでファイルを入れておきます。

      routes.MapRoute(
    "ViewScripts",
    "ViewScripts/{*path}",
    new { controller = "ViewScripts", action = "Index" }
  );

ViewEngineの登録も忘れずに。

ViewEngines.Engines.Add(new JavaScriptViewEngine());

あとはViewScriptsControllerを書くだけ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

namespace Sample.Controllers
{
public class ViewScriptsController : Controller
{
//
// GET: /ViewScripts/

public ActionResult Index(string path)
{
  ViewData["test"] = "{data:'sample'}";

  return View(path);
}

}
}

まずは動くコードを、という簡単なサンプルです。

テスト用にViewScripts/Test.jsを作成。

// test.js
var viewData = /$Test$/;
alert(viewData);

あとは、通常のViewでこれをインクルード。

<script type="text/javascript" src="/ViewScripts/Test.js"></script>

これで一応動くものが出来たわけですが、ここで2の方法でいいじゃん、と思い直してコードを破棄...。しかもこの方法だとViewScriptsControllerでいろんなViewDataを入れるためのコードが必要になって、面倒なことに。もっと早い段階で気がつけば良かったけど、カスタムViewEngineを書いてみる勉強になったから良しとします。

ちなみにカスタムViewEngineで最高のサンプルは今書いたこんな中途半端なコードじゃなくてStringTemplate Template Engineじゃないかと思われます。作ってみたい方は是非そちらを参照してみてください。

string-template-view-engine-mvc - Google Code

2009年3月1日日曜日

Crystalized Intelligence

結晶性知性。 【コラム】IT資本論 (26) 8つのパラドクス - 学習パラドクス(4) 持続学習 | 経営 | マイコミジャーナル これを読んでいて、流動性知性はPCやネットで補完できる(外部記憶や計算能力)が、結晶性知性は「コンピュータによって模倣できない」と書かれてる。 専門知や経験知からしか発達していかないインテリジェンスだから、それもそうだろうとは思うけど(画期的なAIっていうドラえもん級の夢のソフトウェアが出れば別)、外部知性というかたちでPCとネットで有効活用出来るとは思う。 Wikiとかナレッジベースとかっていう形での実装ではなくて、もっとリアルタイムに近い形で。 久しぶりにネガティブレガシーと出会いました。 工業経済至上主義を未だ貫き(それはいい)、統計を完全否定し、ネットを不安視(あえて無視)することで現時点での自分の正当性を閉じた世界で証明しようとするその姿勢が面白かった。

2009年2月28日土曜日

Base64でエンコード

Url encoded slash in URL - Stack Overflow

この投稿から始まって、以下のエントリにたどり着く。

Allowing special characters (forward slash, hash, asterisk etc) in ASP.Net MVC URL parameters

まさに、同じ問題に直面した内容。

Double/incomplete Parameter Url Encoding - Stack Overflow

↑ここへのリンクもあったから覗いてみたら、Uri.EscapeDataString メソッド (System)を使ったエンコーディングがあるということを初めて知る。なにこれ~、と思って試したけど、Server.UrlEncodeと同じだった。ガッカリ。いや、違う方が驚きか。

結局、Philさんの投稿のように"/"か他の文字に置換して区切ってしまうか、Base64エンコードで渡すのがいいのかな。Base64といえばFuturesに入ってるLinqBinaryModelBinderが使えるんだもんね。

というわけで試してみた。

登録するルートは以下の通り。

routes.MapRoute(null,
"Proxy/{*base64url}", 
new { controller = "Images", action = "Proxy" } 
); 

Controllerはシンプルに以下。LinqBinaryModelBinderを使うので、参照設定にFuturesのアセンブリを含めるのを忘れずに(単純に文字列で渡しておいて、Binary型にしなくてもConvert.FromBase64Stringでも可)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Net;
using System.Text;
using System.Data.Linq;

namespace Mvc.RC.Controllers
{
 public class ImagesController : Controller
 {
   public ActionResult Proxy(Binary base64url)
   {
     var web = new WebClient();
     var url = Encoding.UTF8.GetString(base64url.ToArray());

     var bytes = web.DownloadData(url);
     return File(bytes, web.ResponseHeaders["Content-Type"]);
   }
 }
}

Viewでリンクを作る時にBase64エンコードしたパラメータを渡す。
  <% = Html.ActionLink("いぬ", "Proxy", "Images", new {
 base64url = Convert.ToBase64String(Encoding.UTF8.GetBytes(
         @"http://farm1.static.flickr.com/131/353753310_1ed04f694c_m.jpg"
         ))
 }, null)%>

これが出力されると以下のHTML。

<a href="/Proxy/aHR0cDovL2Zhcm0xLnN0YXRpYy5mbGlja3IuY29tLzEzMS8zNTM3NTMzMTBfMWVkMDRmNjk0Y19tLmpwZw==">いぬ</a>

リンクをクリックする。

base64

ちゃんと犬が表示されました。一応、ブレークポイントセットして変数の中身を確認。

base64_2

ちょと見にくいけど、引数のbase64UrlにはBase64エンコードされたBinary型の値が入ってて、変数urlには元のURLがデコードされてる。

問題はぱっと見どこのURLを参照してるのかを判断出来ないところ。フレンドリURLとは言い難し。

ところで、Windows Live Writerから投稿すると、毎回無駄な改行が入るのはなんでなんだろう。

2009年2月26日木曜日

ModelBinderでLINQ to SQLのモデルをそのまま使う

ASP.NET MVC Tip #49 - Use the LinqBinaryModelBinder in your Edit Actions

ステファン君それは無理じゃない?更新以前にバインドした時点で例外でるじゃん。

と、思ってたのは今は昔。RCでは何の問題もなくバインド出来るみたい。試してみたら、普通に出来た。ずいぶん前に試したときはデータベースコンテキストがスタティックじゃないと例外でたり(ModelBinderに気をつけねば)、そもそも直接DBO使うのはうんぬんかんぬん。綺麗な設計どうのじゃなくて、純粋に少ないコード量でどこまで出来るか、っていうのを考えたら直接使う事もあったりするのかもね。

これまでも使ってたサンプルはDBを使ってなかったので、改めてDBを用意して、LINQ to SQLのモデルを作成。

db1

これを利用するためのコントローラも定義。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

using Mvc.RC.Models;

namespace Mvc.RC.Controllers
{
public class OnePeaceController : Controller
{
  OnePeaceDataContext _context = null;

  public OnePeaceController()
  {
    _context = new OnePeaceDataContext();
  }

  public ActionResult Index()
  {
    return List();
  }

  public ActionResult List()
  {
    var people = _context.Persons;

    return View("List", people);
  }

  public ActionResult Details(int id)
  {
    var people = _context.Persons;

    return View(people.First(p => p.Id == id));
  }

  [AcceptVerbs(HttpVerbs.Get)]
  public ActionResult Edit(int id)
  {
    var people = _context.Persons;

    return View(people.First(p => p.Id == id));
  }

  [AcceptVerbs(HttpVerbs.Post), ValidateInput(false)]
  public ActionResult Edit(int id, Person person)
  {
    try
    {
      _context.Persons.Attach(person, true);
      _context.SubmitChanges();

      return RedirectToAction("Index");
    }
    catch (Exception ex)
    {
      return View(person);
    }
  }
}
}

List/Detail/Editの各ViewはRCで追加されたAdd Viewコマンドでサラッと作成。こういう時にとても便利ですね。Attachのところでブレークポイントをセットして、まずはこのまま動かしてみたところ、DB使っててもホントに動く。信じてなかったわけじゃないけど、こうもあっさりと動くとちょっと感動する。

bind

↑Listページ。

bind2

↑Editページ。

bind4

↑普通にバインドされてる様子。

でも、このままステップ実行すると、確かに例外が発生。

bind3

エンティティは、バージョン メンバを宣言するか、または更新チェック ポリシーを 含まない場合にのみ、元の状態なしに変更したものをアタッチできます。

System.Exception {System.InvalidOperationException}

と、言うことで、簡単に更新チェックポリシーをオフにしてしまおうかとも思ったけど、それよりちゃんと競合チェックするようにtimestamp型の列をテーブルに追加して動かしてみます。

db2

↑ChangeStampという名前のtimestamp型の列を追加したモデル。

でも、timestamp型はSystem.Data.Linq.Binary型としてクラスが生成されるので、このままだとちゃんとモデルが復元されません。なによりViewにちゃんと出力してないし。まずはEditのViewにChangeStampをhiddenで展開するコードを追加。

            <p>
              <%= Html.Hidden("ChangeStamp",
                              Convert.ToBase64String(Model.ChangeStamp.ToArray())) %>
              <input type="submit" value="Save" />
          </p>
      </fieldset>

  <% } %>

  <div>
      <%=Html.ActionLink("Back to List", "Index") %>
  </div>

</asp:Content>

submitの手前に入れてます。Base64エンコードしてHiddenフィールドに。このままだとDefaultModelBinderが復元してくれくれないので、Global.asaxのApplication_Start時にASP.NET MVC Futuresに入ってるLinqBinaryModelBinderを登録。

    protected void Application_Start()
  {
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.Binders.Add(typeof(System.Data.Linq.Binary),
                              new LinqBinaryModelBinder());
  }

もう、何もかもステファンさんのいいなりです。

この状態で動かしてみて、Viewが出力するHTMLソースのChangeStamp部分を確認してみたのが↓これ。

<input id="ChangeStamp" name="ChangeStamp" type="hidden" value="AAAAAAAAB9Q=" />

ちゃんと、エンコードされて出力されてます。あたりまえだっちゅーの。

bind5

これを更新するためにsubmitして、ブレークポイントでモデルの中身を確認。ちゃんとLinqBinaryModelBinderでBinary型も復元されてます。そのまま実行を続けても例外は出なくて、テーブルも更新されてました。で、更新された後のViewのCangeStampを確認してみる。

<input id="ChangeStamp" name="ChangeStamp" type="hidden" value="AAAAAAAAB9U=" />

ちゃんと、最初とは違う値が入ってますね~。

と、いうことで、ステファンさんのやったことをそのまま試したみたわけですが、これが出来るって事はLINQ to SQL+スキャッフォールディングで凄く簡単にDBを使ったアプリケーションを作成出来る事になりますね。Repositoryは作るにしても、ViewModelを作らずシンプルなコードで開発することが出来るのでWebForms並の生産性(ViewStateとデータバインディング)を実現できてるんじゃないかと思う次第です。

※ViewStateはModelStateが各inputフィールドの値を保持しつつHTMLにレンダリングしてくれたりするので。

2009年2月25日水曜日

Bloggerをいじる

とりあえずは、コードをエントリーに書くことが多くなるとは思うので、綺麗に見せるためにGoogle Code Prettifyを導入してみる。

google-code-prettify - Google Code

ここからコードをダウンロードできるんだけど、はて、このJSファイルとCSSファイルはどこにアップロードするんでしょうね。困りますね。面倒なのでずるいとは思いつつ、SVN-Tranc(google-code-prettify - Revision 64: /trunk/src)から直接貼り付けちゃった。てへ。

テンプレートを編集する画面でheadタグ内の<title>の下にダイレクト挿入!

blogger1

<script src='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js'
       type='text/javascript'/>
<link href='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css'
     rel='stylesheet' type='text/css'/>

後は、コードを載せたい箇所を<pre class=”prettyprint”></pre>で囲むだけ! とは言ってもですね、毎回ソースを開いてそんなことやってられないじゃないか! 便利な物はないかな~、と思って行き着いたのが「Windows Live Writer用のプラグインを開発する:CodeZine」。

おっと、いきなりプラグイン開発ですか?そうですか。それもやむなしですね。どれどれ、記事を読んでみると大して難しいことしなくてもこの程度のことならサクッと作れそう。だってpreで囲むだけだし。SmartContentSourceじゃなくてContentSourceで作れるじゃんね。んで、作ったDLLをPluginフォルダに入れとけばイイみたいだし。良し作るか。と、思ってちなみに同じ事考えてる人いるんじゃないかと思って、改めて検索し直してみたらいた。Vistaのサイドバーガジェットの時の無駄作業を思い出して良かった。あのときも同じようなの作ってる人いたもんね。ちゃんと調べないで作っちゃって損した気分になったし。

Code Prettify for Windows Live Writer – Home

コード見たらビックリだね。こんなに潔いプラグインも珍しい。いや、だって、自分でもきっとこう書く。

次にデフォルトのコメントシステムはいきなりスパムが来たりするくらいなので、速攻で消してとりあえずFriendConnectにしてみたんだけど、TumblrでTypePad Connectを使ってるの思い出した。

Bloggerもサポートしてるよ!と豪語してるだけあって、設置方法もスクリーンショットでテンプレート編集箇所を書いてあったりして、親切だ。

でも、動かなかった。 だいたい、書いてあるとおりにやってみたけどダメだった。Bloggerのテンプレートがどうなってる(構造が)のか調べるのも面倒だしな~。このブログに適用してるブログにはそもそもインストール手順に書いてるような項目もないし。インストール手順がデフォルトテンプレートを基準にするのは、そりゃあたりまえだしね。違うデザインのテンプレートを使ってるんだから致し方なし。

かといって、諦めるのはダンディじゃない。 無理矢理にでも動かしてやる。大まかな手順は3つ。

1.コメントフォームとコメント一覧を表示する部分のコードを貼り付け。 2.コメント件数を表示するアンカーを貼り付け。 3.コメント件数を表示するスクリプトを貼り付け。

1と3の作業は手順書通りに出来そう。ちゃんと該当箇所も見つかるし。 2の作業は...。今度は手順書の場所が見あたらない。なんか、ちょっとテンプレートが違う。

でも、まぁ、同じようなモンだろう、ってことで、↓こんな感じで貼り付けてみた。

    <b:if cond='data:post.allowComments'>
     <h4>
       <b:if cond='data:post.numComments == 1'>
         1 <data:commentLabel/>:
       <b:else/>
         <data:post.numComments/> <data:commentLabelPlural/>:
       </b:if>
     </h4>
    <b:else/>
     <a expr:href='data:post.url + "#comments"'>Comments</a>

で、動かしてもこれがちゃんと表示してくれないんだよね~。 上のコードのリンクは表示されてるんだけど、入力フォームが出てこない。こりゃどうしたものか。 出てこないってことは、貼り付ける場所を間違えてるんだろう、と。 絶対出てくるところに貼り付ければいいじゃん。 ってことで、上記Commentsリンクの直下に貼り付けてみた。

    <b:if cond='data:post.allowComments'>
     <h4>
       <b:if cond='data:post.numComments == 1'>
         1 <data:commentLabel/>:
       <b:else/>
         <data:post.numComments/> <data:commentLabelPlural/>:
       </b:if>
     </h4>
   <b:else/>
     <a expr:href='data:post.url + "#comments"'>Comments</a>
<!-- START TypePad Connect -->
<b:else/>
<div class="comments-content">
~ここに手順書のコード~
</div>
<!-- END TypePad Connect -->

これで、どうかな~。うぬ。ちゃんと出るね。

ということで、Friend Connectは削除して、コメントシステムはTypePad Connectで一本化です! スッキリした。

LINQで今日以降今年いっぱいの日曜の日付を表示

LINQ練習 #1 「LINQ to Object 基本」 - 悠希 - builder by ZDNet Japan

ここで書かれてるコードが↓これ。

List<DateTime> list;
list = new List<DateTime>();
for(int i=0;i<=365;i++) list.Add(DateTime.Today.AddDays(i));

int nowYear;
nowYear = DateTime.Today.Year;
var sunday = from M in list
            where M.DayOfWeek == DayOfWeek.Sunday &&
                  M.Year = nowYear select M;

foreach (var row in sunday) {
   Console.WriteLine(row.ToShortDateString());
}

※今年かどうか判定する条件の部分が代入になってるよ~。

せっかくLINQを使うっていうお題なんだから日付の生成部分もList<DateTime>に生成して入れておく、なんてことをしないで、そこもLINQに含めちゃった方がオシャレ感でると思うよ~。そう、例えば↓こんな感じでね。

var today = DateTime.Today;
var sunday = from date in
             from day in Enumerable.Range(0, 365)
             select today.AddDays(day)
            where date.DayOfWeek == DayOfWeek.Sunday &&
                  date.Year == today.Year
            select date; 

dotnetConf2015 Japan

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