この質問は古いですが、この問題と戦う他の誰かを助けるかもしれません. 私は非常に良い解決策を持っています。
ページの読み込み中にテーブル (AjaxTable) を記述するオブジェクトを作成する html ヘルパー (AjaxTable、Column) があります。このオブジェクトはセッションに保存されます。すべての ajax リクエストは、保存されたオブジェクトを使用してレスポンスを作成する共通コントローラーで処理されます。ページング、ソート、フィルタリングをサポートしています。
使用例:
コントローラーで:
public ActionResult Odeslane()
{
return View(db.Dotaz.Where( ... /* select all data which should be displayed in the table */ ));
}
ビューで:
@model IQueryable<Dotaz>
@using (Html.AjaxTable(Model, new { id = "dotazTable" }))
{
Html.Column(Model, @<th>@Html.DisplayNameFor(model => item.Autor.JmenoPrijmeniAUzivatelskeJmeno)</th>,
@<td>
<input type="hidden" name="id" value="@item.DotazId" />
@if (item.AutorId != null) {
@Html.DisplayFor(model => item.Autor.JmenoPrijmeniAUzivatelskeJmeno)
}
else
{
@Html.DisplayFor(model => item.AnonymMail)
}
</td>,
d => d.AutorId != null ? d.Autor.Jmeno + " " + d.Autor.Prijmeni + " (" + d.Autor.UserName + ")" : d.AnonymMail);
Html.Column(Model, @<th>@Html.DisplayNameFor(model => item.Nazev)</th>, @<td>@Html.DisplayFor(model => item.Nazev)</td>, d => d.Nazev );
Html.Column(Model, @<th>@Html.DisplayNameFor(model => item.DatumVzniku)</th>, @<td>@Html.DisplayFor(model => item.DatumVzniku)</td>, d => d.DatumVzniku );
}
実装:
ヘルパー:
public static IDisposable AjaxTable<T>(this HtmlHelper htmlHelper, IQueryable<T> items, object htmlAttributes) where T : new()
{
var attrs = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
// TODO: replace CommonAjaxActionUrl with url of the common ajax action
return new AjaxTableDisposable<T>(htmlHelper, items, CommonAjaxActionUrl, attrs);
}
public static MvcHtmlString Column<T, TKey>(this HtmlHelper htmlHelper, IEnumerable<T> items, Func<T, HelperResult> th, Func<T, HelperResult> td, Expression<Func<T, TKey>> keySelector) where T : new()
{
var obj = htmlHelper.ViewContext.TempData["AjaxTable"];
AjaxTableDisposable<T> table = obj as AjaxTableDisposable<T>;
table.Column(th, td, keySelector);
return MvcHtmlString.Empty;
}
AjaxTableDisposable:
public class AjaxTableDisposable<T> : IDisposable where T : new()
{
private HtmlHelper htmlHelper;
private ViewContext viewContext;
private AjaxTable<T> table;
public AjaxTableDisposable(HtmlHelper htmlHelper, IQueryable<T> items, string ajaxUrl, RouteValueDictionary attrs)
{
// akce na zacatku
this.htmlHelper = htmlHelper;
viewContext = htmlHelper.ViewContext;
viewContext.TempData["AjaxTable"] = this;
if (!attrs.ContainsKey("id")) attrs["id"] = "AjaxTable" + Guid.NewGuid().ToString("N");
table = new AjaxTable<T>() { AjaxUrl = ajaxUrl, Attrs = attrs, Items = items };
}
public void Column<TKey>(Func<T, HelperResult> th, Func<T, HelperResult> td, Expression<Func<T, TKey>> keySelector)
{
AjaxTableColumn<T> col = new AjaxTableColumn<T>() { Th = th, Td = td, KeySelector = keySelector, KeyType = typeof(TKey) };
col.OrderData = (IQueryable<T> data, bool asc) => asc ? data.OrderBy(keySelector) : data.OrderByDescending(keySelector);
table.Columns.Add(col);
}
// When the object is disposed (end of using block), write "end" function
public void Dispose()
{
// akce na konci
viewContext.TempData.Remove("AjaxTable");
viewContext.Writer.Write(htmlHelper.Partial("DisplayTemplates/AjaxTable", table));
string tableId = table.Attrs["id"].ToString();
StoreInSession(table); // TODO: you have to implement the StoreInSession method
}
}
AjaxTable テンプレート:
@model IAjaxTable
@{
RouteValueDictionary attrs = Model.GetAttrs();
string tableId = attrs["id"].ToString();
string cls = attrs.ContainsKey("class") ? " " + attrs["class"] : "";
}
<table id="@tableId" class="@{ ViewContext.Writer.Write(cls); }"@{
foreach (var attr in attrs)
{
if (attr.Key != "id" && attr.Key != "class")
{
ViewContext.Writer.Write(" "+attr.Key+"="+"\""+attr.Value+"\"");
}
}
}>
<thead>
<tr>
@for (int i = 0; i < Model.GetColumnsCount(); i++ )
{
Model.GetTh(i).WriteTo(ViewContext.Writer);
}
</tr>
</thead>
<tbody></tbody>
</table>
<script type="text/javascript">
$(document).ready(function () {
var dt = $('#@tableId').DataTable({
serverSide: true,
ajax: function (data, callback, settings) {
data["tableId"] = "@tableId";
$.ajax({
dataType: 'json',
url: "@Model.GetAjaxUrl()",
type: "POST",
success: callback,
data: data
});
},
...
});
});
</script>
AjaxTable クラス:
public class AjaxTable<T> : IAjaxTable where T : new()
{
public List<AjaxTableColumn<T>> Columns { get; set; }
public T Row { get; set; }
public String AjaxUrl { get; set; }
public RouteValueDictionary Attrs { get; set; }
public IQueryable<T> Items { get; set; }
public AjaxTable()
{
Columns = new List<AjaxTableColumn<T>>();
Row = new T();
}
public HelperResult GetTh(int column)
{
return Columns[column].Th.Invoke(Row);
}
public string GetAjaxUrl()
{
return AjaxUrl;
}
public RouteValueDictionary GetAttrs()
{
return Attrs;
}
public int GetColumnsCount()
{
return Columns.Count;
}
public object DefaultAjaxAction()
{
var total = Items.Count();
IQueryable<T> data = Search(Items, ParamSearchValue, ParamSearchRegex);
var filtered = data.Count();
data = Columns[ParamOrderColumn].OrderData(data, ParamOrderDirAscending);
data = data.Skip(ParamStart).Take(ParamLength);
return CreateAjaxResponse(data, total, filtered);
}
public IQueryable<T> Search(IQueryable<T> data, string search, bool regex)
{
if (search == null || search == "") return data;
Expression orExpression = null;
IReadOnlyCollection<ParameterExpression> parameters = null;
foreach (var col in Columns)
{
if (col.KeyType == typeof(string))
{
Expression<Func<T, string>> keySelector = (Expression<Func<T, string>>) col.KeySelector;
Expression compare = Expression.Call(keySelector.Body, typeof(String).GetMethod("Contains"), Expression.Constant(search));
if (orExpression == null)
{
orExpression = compare;
parameters = keySelector.Parameters;
}
else
{
orExpression = Expression.OrElse(compare, orExpression);
}
}
}
if (orExpression != null)
{
Expression<Func<T, bool>> whereExpr = Expression.Lambda<Func<T, bool>>(orExpression, parameters);
UnifyParametersVisitor visitor = new UnifyParametersVisitor();
whereExpr = visitor.UnifyParameters(whereExpr);
data = data.Where(whereExpr);
}
return data;
}
public object CreateAjaxResponse(IQueryable<T> data, int recordsTotal, int recordsFiltered)
{
Dictionary<string,object> obj = new Dictionary<string,object>();
obj.Add("draw", HttpContext.Current.Request.Params["draw"]);
obj.Add("recordsTotal", recordsTotal);
obj.Add("recordsFiltered", recordsFiltered);
List<T> dataList = data.ToList();
String[][] cell = new String[dataList.Count()][];
int rowIndex = 0;
foreach (T row in dataList)
{
cell[rowIndex] = new String[Columns.Count];
int colIndex = 0;
foreach (var column in Columns)
{
StringWriter sw = new StringWriter();
column.Td.Invoke(row).WriteTo(sw);
cell[rowIndex][colIndex++] = sw.ToString();
sw.Dispose();
}
rowIndex++;
}
obj.Add("data", cell);
return obj;
}
public int ParamStart { get { return Int32.Parse(HttpContext.Current.Request.Params["start"]); } }
public int ParamLength { get { return Int32.Parse(HttpContext.Current.Request.Params["length"]); } }
public int ParamOrderColumn { get { return Int32.Parse(HttpContext.Current.Request.Params["order[0][column]"]); } }
public bool ParamOrderDirAscending { get { return HttpContext.Current.Request.Params["order[0][dir]"] == "asc"; } }
public string ParamSearchValue { get { return HttpContext.Current.Request.Params["search[value]"]; } }
public bool ParamSearchRegex { get { return HttpContext.Current.Request.Params["search[regex]"] == "true"; } }
}
AjaxTableColumn クラス:
public class AjaxTableColumn<T>
{
public Func<T, HelperResult> Th { get; set; }
public Func<T, HelperResult> Td { get; set; }
public Expression KeySelector { get; set; } // typ: Expression<Func<T,?>>, typicky neco jako: (T t) => t.Key
public Type KeyType { get; set; }
public OrderDataDelegate OrderData { get; set; }
public delegate IQueryable<T> OrderDataDelegate(IQueryable<T> data, bool asc);
}
IAjaxTable インターフェース:
public interface IAjaxTable
{
HelperResult GetTh(int column);
string GetAjaxUrl();
RouteValueDictionary GetAttrs();
int GetColumnsCount();
object DefaultAjaxAction();
}
UnifyParametersVisitor:
public class UnifyParametersVisitor : ExpressionVisitor
{
private IEnumerable<ParameterExpression> parameters;
public Expression<TDel> UnifyParameters<TDel>(Expression<TDel> expression)
{
parameters = expression.Parameters;
return (Expression<TDel>) Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression node)
{
foreach (var param in parameters)
{
if (param.Name == node.Name)
{
return param;
}
}
return base.VisitParameter(node);
}
}
ajax 応答を作成するためのコントローラー - アイデア: tableId でセッション内の AjaxTable インスタンスを見つけてから、Json(ajaxTable.DefaultAjaxAction()) を返す必要があります。ユーザーがログアウトしたときにセッションがクリアされていることを確認します。
更新: 今日、私は私のアプローチの制限に遭遇しました: テーブル セル内でいくつかの html ヘルパーを使用することはできません (テンプレートの操作、部分ビューのレンダリング...)、しかしそれは解決できます! http リクエストごとに htmlHelper の新しいインスタンスを作成し、標準の「Html」の代わりに使用する必要があります。たとえば、次の html ヘルパーを使用します。
public static HtmlHelper<object> CurrentHtml(this HtmlHelper html)
{
var ch = (HtmlHelper<object>)HttpContext.Current.Items["currentHtml"];
if (ch == null)
{
var context = new HttpContextWrapper(HttpContext.Current);
var routeData = new RouteData();
routeData.Values["controller"] = "Home"; // you can use any controller here
var controllerContext = new ControllerContext(new RequestContext(context, routeData), new HomeController());
var view = ViewEngines.Engines.FindView(controllerContext, "Index", null);
ch = new HtmlHelper<object>(new ViewContext(controllerContext, view.View, new ViewDataDictionary(), new TempDataDictionary(), TextWriter.Null), new ViewPage());
HttpContext.Current.Items["currentHtml"] = ch;
}
return ch;
}