2009年3月11日水曜日

Futuresに含まれるMvc Controls

RC2のFuturesアセンブリ(ソースでも)には、Mvc Controlsが含まれてます。System.Web.UI.Controlクラスの派生クラスとして作成されてるので、WebFormsで使うサーバーコントロールと同じですね。HTML+CSS+JavaScriptで作成するようなクライアントサイドのコントロールじゃなくてASPXのPage Lifecycleの中でコントロールツリーとして生成されるサーバーコントロールです。

Futuresに含まれるコントロールは以下の7つ。

  • Label
  • TextBox
  • Password
  • Hidden
  • DropDownList
  • ActionLink
  • Repeater

はて、ナゼMVCにこれらサーバーコントロールが含まれてるんだろう(Futuresだけど)。ポストバック(ASP.NETの)もViewStateも無いので、ポストバックされた後のコントロールツリー構築なんてことは発生しない(そもそもポストバック先がPageじゃなくてControllerなんだから)し、かといって、積極的にコントロールツリーを構築してからレンダリングするなんてことは全然見通しのいい完全制御出来るHTMLとは言い難い(オレはサーバーコントロールのレンダリング結果を完全に把握してるぜ!という話じゃなくてデス)。なので、考えられる理由は、<%~%>での埋め込みコードを減らして、見やすくしようという意図なのかな~、と推測してます。理由なんてどーでもいいんですけど。

ASP.NET MVC Release Candidate 2: I declare myself to be declarative! - Eilon Lipton's Blog

↑こちらでしっかりと紹介されてます。

試しに使って見ましょう。MVCのプロジェクトを新規に作成し、プロジェクトの参照設定にMicrosoft.Web.Mvcを追加しましょう。続いて、web.configの設定をしておきます。

<system.web>
 …
 <pages>
   <controls>
     <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
     <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
     <add tagPrefix="mvc" namespace="Microsoft.Web.Mvc.Controls" assembly="Microsoft.Web.Mvc"/>
    </controls>
   <namespaces>
     <add namespace="System.Web.Mvc"/>
     <add namespace="System.Web.Mvc.Ajax"/>
     <add namespace="System.Web.Mvc.Html"/>
     <add namespace="System.Web.Routing"/>
     <add namespace="System.Linq"/>
     <add namespace="System.Collections.Generic"/>
     <add namespace="Microsoft.Web.Mvc"/>
    </namespaces>
 </pages>
 …
</system.web>
上記太字の部分、サーバーコントロールのプレフィックス登録と、ページでのネームスペース登録。これをやっておかないと記入が面倒なことになるので忘れずに。

HomeControllerのIndexアクションで出力用のデータをViewDataにセットしておきます。

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

namespace Mvc.RC2.Controllers
{
 [HandleError]
 public class HomeController : Controller
 {
   public ActionResult Index()
   {
     ViewData["Message"] = "Welcome to ASP.NET MVC!";
      ViewData["TextBox"] = "テキストボックスに表示するメッセージ";
     return View(new { Label = "ラベルに表示するメッセージ" });
    }

   public ActionResult About()
   {
     return View();
   }
 }
}

ViewDataにテキストを入れて渡す方法と、Viewへ匿名クラスを直接渡す方法を書いておきます。匿名クラスも簡単に取り出せるのを覚えておくとちょっと便利です。

これらを表示するタメにViews/Home/Index.aspxにコードを追加します。

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
   Home Page
</asp:Content>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   <h2><%= Html.Encode(ViewData["Message"]) %></h2>
   <p>
       To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
   </p>
  
    <p>
     <mvc:TextBox runat="server" Name="TextBox"></mvc:TextBox>
     <mvc:Label runat="server" Name="Label"></mvc:Label>
   </p>
</asp:Content>

サーバーコントロールなのでrunat="server"を忘れずに。Nameで指定したのがエレメントのName属性にセットされるのと同時にViewDataから同名の値を取得して展開してくれます。 なので、これで出力されるHTMLは↓こうなります。

    <h2>Welcome to ASP.NET MVC!</h2>
   <p>
       To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
   </p>
  
   <p>
     <input name="TextBox" type="text" value="テキストボックスに表示するメッセージ" />
     ラベルに表示するメッセージ
   </p>

匿名クラスの値がそのまま展開されてますね。これは内部でViewData.Eval(データーキー)を呼び出してるからです。なので、 MVC Controls特有の動きというわけではなく、自分でも普通に同じ方法で値を取得出来ます。Viewに以下のようなコードを書いてみます。

    <p>
     <div><%= ViewData.Eval("TextBox") %></div>
     <div><%= ViewData.Eval("Label") %></div>
   </p>

これは以下のようなHTMLとして展開されます。

    <p>
     <div>テキストボックスに表示するメッセージ</div>
     <div>ラベルに表示するメッセージ</div>
   </p>

匿名クラスの場合、Modelのクラスが不明なので、インテリセンスでの取得は出来ないですけど、Evalを使う事で取得は出来ます。例えば、RenderPartialするユーザーコントロール(ascx)に、匿名クラスで値を渡す時(View Modelクラスを書くほどでも無いけどViewDataをそのまま参照するのもコード見にくい、なんて時)でもユーザーコントロール内でViewData.Eval()で取得出来るので、何かと便利だったりします。

ちなみにこのmvc:LabelコントロールにはTruncateLengthとTruncateTextなんていうプロパティがあって、コレを指定することで自動で長い文字列をカットしてくれます。例えば先ほどのLabelの部分を以下のように書き換える。

    <p>
     <mvc:TextBox runat="server" Name="TextBox"></mvc:TextBox>
     <mvc:Label runat="server" Name="Label" TruncateLength="5" TruncateText="~"></mvc:Label>
   </p>

そうすると5文字にカットしてサフィックスをくっつけてくれます。HTMLは↓こうなります。

    <p>
     <input name="TextBox" type="text" value="テキストボックスに表示するメッセージ" />
     ラベルに表~
   </p>

TruncateTextは初期値として"..."がセットされてるので、何も指定しなければ今回の例でいうと「ラベルに表…」となります。

RepeaterはWeb FormsのRepeaterと同じように動くので、繰り返しをforeachで書きたくないなんて場合は利用するのもいいんじゃないかな。DataSource指定やDataBind()はしないのでコードビハインドも不要です。今後いろんなコントロールが出てくるかもしれないね。