ちょっと先取り Routing 4:その5 RouteParameter
この記事は その1 その2 その3 その4 の記事を見ていることを前提にしています。
RouteParameterを用意すると、DataSource等のコントロールにそのままパラメータとしてRouteDataを渡すことができるようになります。
ここでもweb.configの修正はあるのですが、その前にRouteParameter.csファイルを追加します。
このファイルに以下のプログラムを記述します。
namespace Samples.Routing
{
using System;
using System.Web;
using System.Web.Routing;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
public class RouteParameter : Parameter
{
public RouteParameter()
{
}
public RouteParameter(string name, string routekey)
: base(name)
{
this.RouteKey = routekey;
}
public RouteParameter(string name, DbType dbtype, string routekey)
: base(name, dbtype)
{
this.RouteKey = routekey;
}
public RouteParameter(string name, TypeCode type, string routekey)
: base(name, type)
{
this.RouteKey = routekey;
}
protected RouteParameter(RouteParameter original)
: base(original)
{
this.RouteKey = original.RouteKey;
}
protected override Parameter Clone()
{
return new RouteParameter(this);
}
public string RouteKey
{
get
{
object o = base.ViewState["RouteKey"];
if (o == null)
return String.Empty;
return (string)o;
}
set
{
base.ViewState["RouteKey"] = value;
base.OnParameterChanged();
}
}
protected override object Evaluate(HttpContext context, Control control)
{
if ((context != null) && (context.Request != null))
{
var r = RouteTable.Routes.GetRouteData(new HttpContextWrapper(HttpContext.Current));
return r != null ? r.Values[this.RouteKey].ToString() : string.Empty;
}
return null;
}
}
}
このプログラムを記述するにあたっては、QueryStringParameterクラスの実装を参考にしました。
次にweb.configのsystem.web/pages/controls要素に以下の通りに追加します。
<add tagPrefix="asp" namespace="Samples.Routing" />
実はRouteParameter.csのプログラム中でnamespaceを設定しているのが重要な点です。
web.conifgにtagPrefixの設定をする場合、Webサイトプロジェクトに追加したクラスならnamespaceさえ記述してあればこんなに簡単にtagPrefixの設定ができます。
この設定を行うことで、まるで元から用意されているコントロールのように、<asp: というタグを使えるようになります。
さて、ここまでで準備は終了。
適当なデータを用意し、Default2.aspxに以下のようにGridViewとSqlDataSourceを追加して動作を確認してみましょう。
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" />
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionString %>" SelectCommand="SELECT * FROM [Table1] WHERE ([ID] = @ID)">
<SelectParameters>
<asp:RouteParameter Name="ID" RouteKey="id" />
</SelectParameters>
</asp:SqlDataSource>
これでURLに対応したIDのデータのみが選択されて表示されればテストは終了です。
実はRouteParameterのかわりにRouteValue expressionを使っても同じことはできます。
<asp:Parameter Name="ID" DefaultValue="<%$ RouteValue : id %>" />
こんな感じで使えばいいですね。
まぁ、せっかく用意したRouteParameterなので、使えるところはこっちを使うほうがいい、、、のかな(w
ちょっと先取り Routing 4:その4 RouteValue expression
この記事は その1 その2 その3 の記事を見ていることを前提にしています。
その3の記事とほとんど同じように見えるので、どこが違うのか注意して読んでください。
RouteValue expressionはRouteDataをaspxページ内で定義されたコントロールに渡す手段を提供します。
web.configのsystem.web/compilation/expressionBuilders要素に以下を追加します。
<add expressionPrefix="RouteValue" type="RouteValueExpressionBuilder"/>
そして、新しいクラスとしてRouteValueExpressionBuilder.csファイルを追加します。
このファイルには以下のプログラムを記述します。
using System;
using System.CodeDom;
using System.ComponentModel;
using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
using System.Web.UI;
[ExpressionPrefix("RouteValue")]
public class RouteValueExpressionBuilder : ExpressionBuilder
{
public static object GetEvalData(string expression, Type target, string entry)
{
var r = RouteTable.Routes.GetRouteData(new HttpContextWrapper(HttpContext.Current));
return r != null ? r.Values[expression].ToString() : string.Empty;
}
public override object EvaluateExpression(object target, BoundPropertyEntry entry,
object parsedData, ExpressionBuilderContext context)
{
return GetEvalData(entry.Expression, target.GetType(), entry.Name);
}
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
Type type1 = entry.DeclaringType;
PropertyDescriptor descriptor1 = TypeDescriptor.GetProperties(type1)[entry.PropertyInfo.Name];
CodeExpression[] expressionArray1 = new CodeExpression[3];
expressionArray1[0] = new CodePrimitiveExpression(entry.Expression.Trim());
expressionArray1[1] = new CodeTypeOfExpression(type1);
expressionArray1[2] = new CodePrimitiveExpression(entry.Name);
return new CodeCastExpression(descriptor1.PropertyType, new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(base.GetType()), "GetEvalData", expressionArray1));
}
public override bool SupportsEvaluate
{
get { return true; }
}
}
RouteUrlExpressionBuilderと同じく、GetEvalDataメソッドの中身以外はExpressionBuilderクラスのサンプルプログラムそのものです。
さて、ここまでで準備は終了。
Default2.aspxに以下の記述を追加して、デバッグを開始してみます。
<asp:Label ID="Label2" runat="server" Text="<%$ RouteValue : id %>" />
Default.aspxにはその3で試したリンクが表示されるので、そのリンクをクリックして以下のURLに遷移します。
http://localhost:(port)/RoutingTest/test/3
このとき、上で追加したLabel2に"3"が表示されればテストは終了です。
RouteValue expressionを使うと、このようにaspxページ内のコントロールのプロパティにRouteDataを直接設定できます。
DataSourceコントロールとともにRouteValue expressionを利用すると、コードを記述することなくURLに合わせたデータを表示することができるようになります。
ちょっと先取り Routing 4:その3 RouteUrl expression
この記事は その1 その2 の記事を見ていることを前提にしています。
RouteUrl expressionはRouting用のURL設定をaspxページ内で定義されたコントロールに渡す手段を提供します。
やはりweb.configに設定が必要になってきますので、system.web/compilation要素に以下を追加します。
<expressionBuilders>
<add expressionPrefix="RouteUrl" type="RouteUrlExpressionBuilder"/>
</expressionBuilders>
そして、新しいクラスとしてRouteUrlExpressionBuilder.csファイルを追加します。
このファイルには以下のプログラムを記述します。
using System;
using System.CodeDom;
using System.Web.UI;
using System.ComponentModel;
using System.Web.Compilation;
using System.Web.Routing;
[ExpressionPrefix("RouteUrl")]
public class RouteUrlExpressionBuilder : ExpressionBuilder
{
public static object GetEvalData(string expression, Type target, string entry)
{
string[] expressionArray = expression.Split(‘=’);
return RouteTable.Routes.GetVirtualPath(null, new RouteValueDictionary { { expressionArray[0], expressionArray[1] } }).VirtualPath;
}
public override object EvaluateExpression(object target, BoundPropertyEntry entry,
object parsedData, ExpressionBuilderContext context)
{
return GetEvalData(entry.Expression, target.GetType(), entry.Name);
}
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
Type type1 = entry.DeclaringType;
PropertyDescriptor descriptor1 = TypeDescriptor.GetProperties(type1)[entry.PropertyInfo.Name];
CodeExpression[] expressionArray1 = new CodeExpression[3];
expressionArray1[0] = new CodePrimitiveExpression(entry.Expression.Trim());
expressionArray1[1] = new CodeTypeOfExpression(type1);
expressionArray1[2] = new CodePrimitiveExpression(entry.Name);
return new CodeCastExpression(descriptor1.PropertyType, new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(base.GetType()), "GetEvalData", expressionArray1));
}
public override bool SupportsEvaluate
{
get { return true; }
}
}
GetEvalDataメソッドの中身以外はExpressionBuilderクラスのサンプルプログラムそのものです。
さて、ここまでで準備は終了。
Default.aspxに以下の記述を追加して、デバッグを開始してみます。
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="<%$ RouteUrl: id=3 %>">RouteUrlテスト</asp:HyperLink>
正しくリンクが表示され、リンクをクリックすることで以下のURLに遷移できればテストも完了です。
http://localhost:(port)/RoutingTest/test/3
RouteUrl expressionを使うと、このようにaspxページ内のコントロールのプロパティにRouting用のURLを設定できます。
ただこれでできるのは静的な設定だけなので、実は直接URLを書いてしまってもいいような気がしています。
ちょっと先取り Routing 4:その2 Page.RouteData じゃなくて Page.GetRouteData()
この記事は その1 の記事を前提にしています。
まずはそちらを見てください。
ASP.NET 4 で予定されている Page.RouteData という形でPageのプロパティとしてRoutingのデータを取り出すには、HttpContextクラスやPageクラス自体に変更を加える必要があります。
そこで、ここでは拡張メソッドを利用してPageクラスにRoutingデータを取り出すGetRouteDataメソッドを追加することにしました。
web.configファイルの system.web/compilation/assemblies 要素に以下の記述を追加します。
<add assembly=”System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35″/>
このdll参照設定がないと、コンパイル時にアセンブリが足りない、といったエラーになります。
さて、PageExtentionクラスを追加し、以下のとおりに記述します。
using System.Web;
using System.Web.UI;
using System.Web.Routing;
public static class PageExtention
{
public static string GetRouteData(this Page p, string term)
{
var r = RouteTable.Routes.GetRouteData(new HttpContextWrapper(HttpContext.Current));
return r != null ? r.Values[term].ToString() : string.Empty;
}
}
これで準備完了。
Default2.aspxにLabelを貼り付けて、コードビハインド側に以下のように記述しましょう。
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = “ID: ” + this.GetRouteData(“id”);
}
デバッグを実行して、次のURLにアクセスします。
http://localhost:(port)/RoutingTest/test/3
間違えたところがなければ、ブラウザ上には「ID:3」と表示されるはずです。
では、次のURLだとどうなるでしょう。
http://localhost:(port)/RoutingTest/test/
実はこの場合、「ID:1」と表示されます。
その1 の記事の中でRoutingの設定を行っていますが、そこでidのデフォルト値として1を設定していますので、こんな結果がでることになります。
ちょっと先取り Routing 4:その1 PageRouteHandler
ASP.NET 4 のホワイトペーパーを見ると、Routingに関して以下の機能が追加されています。
・MapPageRoute
特定のRoutingパターンに対して特定のページを設定する。
・Page.RouteData
RouteDataをページ内のプログラムで利用するために取得する手段を提供する。
・RouteUrl expression
Routing用のURL設定をaspxページ内で定義されたコントロールに渡す手段を提供する。
・RouteValue expression
RouteDataをaspxページ内で定義されたコントロールに渡す手段を提供する。
・RouteParameter
RouteDataをDataSourceコントロールにParameterとして渡す手段を提供する。
これらの機能を用いることでRoutingを気軽に利用できるようになると考えられます。
しかし、こんな便利そうな機能セット、なんとか3.5 SP1の環境で利用できないものでしょうか。
ということで、上記の機能そのものではないにしても、同じような機能を用意し、利用する方法を探ってみます。
第1回目としてはMapPageRouteのベースと考えられるPageRouteHandlerを作成します。
まず最初にRoutingを利用するための環境を整えましょう。
新しいWebサイト(とりあえずRoutingTestという名前をつけます)を作成します。
web.configファイルの system.web/compilation/assemblies 要素に以下の記述を追加します。
<add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
これは System.Web.Routing.dllを利用する、という設定になります。
それから同じくweb.configファイルの system.web/httpmodules 要素に以下の記述を追加します。
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
これは Routing用のModule(UrlRoutingModule)を利用する、という設定です。
とりあえずVisualStudio や Visual Web Developterのテスト環境では、以上の設定でRoutingが利用できるようになります。
※IIS7以降の環境で動作させるためには こちらのMSDNのページを参考にしてください。
次に、PageRouteHandlerクラスを作成します。
新しいクラスとしてPageRouteHandler.csファイルを追加しましょう。
このファイルには以下のプログラムを記述します。
using System.Web;
using System.Web.Routing;
using System.Web.Compilation;
using System.Web.UI;
public class PageRouteHandler : IRouteHandler
{
string virtualPath = string.Empty;
public PageRouteHandler(string vPath)
{
virtualPath = vPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page)) as IHttpHandler;
}
}
さて、このPageRouteHandlerクラスが本当に動作するかどうか確認してみましょう。
グローバルアプリケーションクラス(Global.asax)を追加して、2行めに以下を記述します。
<%@ Import Namespace="System.Web.Routing" %>
これでGlobal.asaxの中でSystem.Web.Routingネームスペースが利用できるようになります。
Application_Startメソッドに以下を記述します。
RouteTable.Routes.Add("testRoute",
new Route("test/{id}",
new RouteValueDictionary(new { id = 1 }),
new PageRouteHandler("~/Default2.aspx")));
プロジェクト内にDefault2.aspxを追加して何か目印になる文字列を書いておき、デバッグを実行して以下のようなURLにアクセスしてみましょう。
http://localhost:(port)/RoutingTest/test/
ここで(port)の部分は実行している環境によって異なります。
このURL、つまりWebアプリケーションのルートに test を加えたURLでDefault2.aspxの内容が表示されればPageRouteHandlerクラスが正しく動作しているということになります。
PageRouteHandlerを使う場合、1つのページ毎にRoutingの設定を追加して利用することになります。
Routingの設定数は増えますが、そのほうがぱっと見てわかりやすい、ということがあるかもしれません。