フィルター作成
自作のフィルター機能を実装する前にざっくりと全体を紹介します。/DynamicData/Filters/フォルダ内にフィルター機能のユーザーコントロールを作成し、その中でLINQでいうWhereメソッドを自作すると言う流れになります。手順は以下のとおりです。
1.Filtersフォルダ内にユーザーコントロールを追加する
追加したファイルの名前はDateと設定する。
2.Date.ascxにサーバーコントロールを設定する
以下のようにサーバーコントロールを設定します。
<asp:TextBox ID="TextBox1" runat="server" CssClass="DDTextBox" Columns="12"></asp:TextBox> <asp:CalendarExtender ID="TextBox1_CalendarExtender" runat="server" Enabled="True" TargetControlID="TextBox1"> </asp:CalendarExtender> <asp:DropDownList runat="server" ID="DropDownList1" CssClass="DDFilter" AutoPostBack="True" onselectedindexchanged="DropDownList1_SelectedIndexChanged" > <asp:ListItem Text="同一日" value="==" /> <asp:ListItem Text="以前" value="<" /> <asp:ListItem Text="以後" value=">" /> </asp:DropDownList>
実行時にこのユーザーコントロールは、テキストボックスにフォーカスがある場合にカレンダーがポップアップし、選択入力できます。その後、ドロップダウンリストで選択することで、入力日当日もしくは以前か以後のデータをフィルターで抽出できます。
3.メタデータクラスを作成し、FilterUIHint属性を付加する
データフィールドをデータと関連付けるためにはメタデータとFilterUIHint属性を使用します。メタデータEDMのパーシャルクラスを作成し、属性クラスを指定します。メタデータにFilterUIHint属性を使用して指定されたデータフィールドは、実行時に指定したデータのカラム名のラベルを自動表示し、指定されたユーザーコントロールをレンダリングします。コードは以下のようになります。
using System; using System.ComponentModel.DataAnnotations; namespace CustomizeDD4 { [MetadataType(typeof(Orders.MetaOrders))] partial class Orders { public class MetaOrders { [FilterUIHint("Date")] public DateTime OrderDate; [FilterUIHint("Date")] public DateTime RequiredDate; [FilterUIHint("Date")] public DateTime ShippedDate; } } }
今回DateTime型のカラムは、上記通り3つあるので、それぞれにFilterUIHint属性を指定します。これで、Orderテーブルのリストを表示する際にフィルターコントロールが表示されるようになりました。続いて、ユーザーコントロールのコードビハインド側の記載を変更します。
4.Date.ascx.csの継承元を変更させる
以下のようにDate.ascx.csファイルの記載を変更します。
public partial class DateFilter : System.Web.DynamicData.QueryableFilterUserControl { protected DateFilter() { } protected void Page_Load(object sender, EventArgs e) { } public override Control FilterControl { get { return this.TextBox1; } } public override IQueryable GetQueryable(IQueryable source) { } }
フィルターを作成する際はQueryableFilterUserControl抽象クラスを継承します。この時、GetQueryableメソッドをオーバーライドで実装します。また、フィルターコントロール自身をテキストボックスにオーバーライドして設定します。続いて実際のフィルター処理の実装です。
5.GetQueryableメソッドの実装
GetQueryableメソッドは、フィルターにより受け取ったクエリを実行するメソッドです。パラメータは使用するデータソースコントロールで、戻り値もIQueryableになります。つまり、GetQueryableメソッドは、LINQならばWhereを実装する場所と捉えて貰えれば間違いありません。
今回は日付フィルターと言っても、Ordersテーブル上で使用することを目的として作成します。ユーザーコントロールは、FilterUIHint属性で指定した分だけ表示され、処理も実施されます。
コードは以下のように記載します。
// ① public override IQueryable GetQueryable(IQueryable source) { // Nullチェック if (string.IsNullOrEmpty(this.TextBox1.Text)) { return source; } // 今回はOrdersテーブル上で使用をするので非ジェネリックの値をパラメータにメソッドを呼び出す if (source.ElementType == typeof(Orders)) return GetQueryableOfDate(source.OfType<Orders>()); throw new NotSupportedException(); } // ② private IQueryable GetQueryableOfDate(IQueryable<Orders> source) { // ユーザーコントロールから入力日付を取得 DateTime inputdate = DateTime.Parse(this.TextBox1.Text); // ユーザーコントロールから列名を取得して処理を分岐 switch(this.Column.Name) { case "OrderDate": // 各列の値と入力値による比較を実施後のラムダ式の値を返す switch (this.DropDownList1.SelectedValue) { case "==": return source.Where(x => x.OrderDate == inputdate); case ">": return source.Where(x => x.OrderDate > inputdate); case "<": return source.Where(x => x.OrderDate < inputdate); default: throw new NotSupportedException(); } case "RequiredDate": switch (this.DropDownList1.SelectedValue) { case "==": return source.Where(x => x.RequiredDate == inputdate); case ">": return source.Where(x => x.RequiredDate > inputdate); case "<": return source.Where(x => x.RequiredDate < inputdate); default: throw new NotSupportedException(); } case "ShippedDate": switch (this.DropDownList1.SelectedValue) { case "==": return source.Where(x => x.ShippedDate == inputdate); case ">": return source.Where(x => x.ShippedDate > inputdate); case "<": return source.Where(x => x.ShippedDate < inputdate); default: throw new NotSupportedException(); } default: break; } throw new NotSupportedException(); }
①の部分について
IQueryableインターフェースは式ツリーを使用して動的にクエリを組み立てます。LINQ to Entitiesなどがこの仕組みを利用しています。今回はパラメータのsourceがOrdersテーブルのデータである前提での記載なので、IQueryableインターフェースのインスタンスが実行された時の型を取得するElementTypeプロパティがOrdersの場合に、OfType<T>メソッドを使用することで、IQueryableインターフェースのインスタンスを非ジェネリックIQueryable<T>のパラメータに変換してメソッドを呼び出すことができます。
自作フィルター最大のキモはこのIQueryableインターフェースのパラメータをIQueryable<T>インターフェースのパラメータに変換する点です。
②の部分について
IQueryable<Orders>のパラメータではWhereなどのクエリ演算子が使用できるようになります。あとは、入力された日付型と入力されたカラムを特定した上で日付比較を実施し、結果を返すだけです。
ここでは、Dynamic Dataにおいて特定のフィルター対象で使用することを前提で作成しました。しかしこのやり方では、Ordersテーブル以外のテーブルでも日付フィルターを使用したい場合に、その数だけデータテーブル型のフィルターを作成することになる点に注意が必要です。なお、式ツリーを駆使することで、ジェネリックなフィルターを作成することもできます。ただし、ジェネリックな式ツリーを組むのは多くのXXXExpressionクラスやExpressionクラス自身について熟知する必要があります。余程の事がない限りジェネリックなフィルターではなく、非ジェネリックなフィルターを実装する方がDynamic Dataを利用する目的に沿うことになるでしょう。
なお、式ツリーについて詳細を知りたい方はこちらを参照ください。
5.フィルターを実行するメソッドを記載
フィルターの実装はできましたが、この状態では実行されません。以下のようにOnFilterChangedメソッドをドロップダウンリストのSelectedIndexChangedイベント内に記載することで、フィルターを利用したクエリの再実行をデータソースに通知します。
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { OnFilterChanged(); }
フィルター実際の処理は記載する内容により大きく変わりますが、冒頭に記載したように/DynamicData/Filters/フォルダ内にフィルター機能のユーザーコントロールを作成し、その中でLINQでいうWhereメソッドを自作すると言う流れを押さえておくことで、より現場で活用できるフィルターを自作できるようになるでしょう。