Представляю вашему вниманию ещё одну реализацию AjaxGrid на ASP.Net MVC 3.
В статье рассказывается как создать табличную форму с inline редактированием, ajax сортировкой и ajax пейджером.
Данная реализация — компиляция из нескольких доступных компонентов.
Как это обычно бывает, по рабочей необходимости мне понадобилось отображать табличные данные с возможностью их редактирования. Примерно как на скриншоте:
Перед тем как приступим установим AjaxGridScaffolder
Сущность.
Интерфейс.
Контекст.
Реализация интерфейса.
ObjectsView:
1)Для этого щёлкаем правой кнопкой в Object Explorer'e(Обозреватель объектов) на папке Controllers, выбираем Add->Controller… (Добавить->Контроллер...).
2)Пишем название ObjectsViewController.
3)Шаблон — Ajax Grid Controller
4)Класс модели — RealtyObject (HabraHabrMVC3.Domain.Entities)
5)Класс контекста данных — EFContext (HabraHabrMVC3.Domain.Concrete)
6)Добавить.
После того как мастер сгенерировал нам код, добавляем конструктор в контроллёр, для того чтобы, Ninject корректно инжектировал наш слой данных.
Теперь надо в сгенерированном коде поменять источники данных:
db.RealtyObjects на db.GetAll()
db.RealtyObjects.Find(id) на db.GetAll().SingleOrDefault(z=>z.Id==id)
Сейчас мне не нужны экшены по созданию, редактированию и удалению данных, я их удалю.
Также в view GridData.cshtml удалил кнопки редактирования и удаления.
Сейчас не заработает строчка:
т.к. db не поддерживает IObjectContextAdapter.
Поэтому добавим метод раширения Linq.
Я создал отдельный статический класс OrderByHelper.
И тогда новый вид action GridData:
Сейчас у нас получилась прекрасная Ajax таблица с сортировкой и постраничным просмотром.
Добавляем ещё один метод расширения, только уже для HTML хелпера:
Заменим view GridData на
А view Edit на:
В view Index добавим:
Добавляем action Save:
Здесь важно, чтобы совпадали имя коллекции в @using (Html.BeginCollectionItem("objects")) и название параметра в методе action Save(ICollection objects).
Всё, получилась табличная форма с inline редактированием, ajax сортировкой и ajax пейджером.
Список используемой литературы в интернетах:
1)Ajax Grid Scaffolder
2)Editing a variable length list, ASP.NET MVC 2-style
3)Dynamic SQL-like Linq OrderBy Extension
Скачать полученный результат.
В статье рассказывается как создать табличную форму с inline редактированием, ajax сортировкой и ajax пейджером.
Данная реализация — компиляция из нескольких доступных компонентов.
Как это обычно бывает, по рабочей необходимости мне понадобилось отображать табличные данные с возможностью их редактирования. Примерно как на скриншоте:
Перед тем как приступим установим AjaxGridScaffolder
PM> Install-Package AjaxGridScaffolder
Создаём слой данных
Сущность.
namespace HabrahabrMVC.Domain.Entities
{
public class RealtyObject
{
public int Id { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string House { get; set; }
public double Cost { get; set; }
public bool Visible { get; set; }
}
}
Интерфейс.
namespace HabrahabrMVC.Domain.Abstract
{
public interface IRepository
{
IQueryable<RealtyObject> GetAll();
void Save(RealtyObject objectToSave);
}
}
Контекст.
namespace HabrahabrMVC.Domain.Concrete
{
public class EFContext : DbContext
{
public DbSet<RealtyObject> RealtyObjects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
Реализация интерфейса.
namespace HabrahabrMVC.Domain.Concrete
{
public class EFRepository :IRepository
{
EFContext _db = new EFContext();
public void Save(Entities.RealtyObject objectToSave)
{
_db.Entry(_db.RealtyObjects.SingleOrDefault(z=>z.Id==objectToSave.Id)).CurrentValues.SetValues(objectToSave);
_db.Entry<RealtyObject>(_db.RealtyObjects.SingleOrDefault(z => z.Id == objectToSave.Id)).State = System.Data.EntityState.Modified;
_db.SaveChanges();
}
public IQueryable<RealtyObject> GetAll()
{
return _db.RealtyObjects.AsQueryable();
}
}
}
Создаём контроллер
ObjectsView:
1)Для этого щёлкаем правой кнопкой в Object Explorer'e(Обозреватель объектов) на папке Controllers, выбираем Add->Controller… (Добавить->Контроллер...).
2)Пишем название ObjectsViewController.
3)Шаблон — Ajax Grid Controller
4)Класс модели — RealtyObject (HabraHabrMVC3.Domain.Entities)
5)Класс контекста данных — EFContext (HabraHabrMVC3.Domain.Concrete)
6)Добавить.
После того как мастер сгенерировал нам код, добавляем конструктор в контроллёр, для того чтобы, Ninject корректно инжектировал наш слой данных.
//private EFContext db = new EFContext();
private IRepository db;
public ObjectsViewController(IRepository dbparam)
{
db = dbparam;
}
Теперь надо в сгенерированном коде поменять источники данных:
db.RealtyObjects на db.GetAll()
db.RealtyObjects.Find(id) на db.GetAll().SingleOrDefault(z=>z.Id==id)
Сейчас мне не нужны экшены по созданию, редактированию и удалению данных, я их удалю.
Также в view GridData.cshtml удалил кнопки редактирования и удаления.
Сейчас не заработает строчка:
ObjectQuery<RealtyObject> realtyobjects = (db as IObjectContextAdapter).ObjectContext.CreateObjectSet<RealtyObject>();
т.к. db не поддерживает IObjectContextAdapter.
Поэтому добавим метод раширения Linq.
Я создал отдельный статический класс OrderByHelper.
namespace HabraHabrMVC3.Infrastructure
{
public static class OrderByHelper
{
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> enumerable, string orderBy)
{
return enumerable.AsQueryable().OrderBy(orderBy).AsEnumerable();
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, string orderBy)
{
foreach (OrderByInfo orderByInfo in ParseOrderBy(orderBy))
collection = ApplyOrderBy<T>(collection, orderByInfo);
return collection;
}
private static IQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, OrderByInfo orderByInfo)
{
string[] props = orderByInfo.PropertyName.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
string methodName = String.Empty;
if (!orderByInfo.Initial && collection is IOrderedQueryable<T>)
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "ThenBy";
else
methodName = "ThenByDescending";
}
else
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "OrderBy";
else
methodName = "OrderByDescending";
}
//TODO: apply caching to the generic methodsinfos?
return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { collection, lambda });
}
private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy)
{
if (String.IsNullOrEmpty(orderBy))
yield break;
string[] items = orderBy.Split(',');
bool initial = true;
foreach (string item in items)
{
string[] pair = item.Trim().Split(' ');
if (pair.Length > 2)
throw new ArgumentException(String.Format("Invalid OrderBy string '{0}'. Order By Format: Property, Property2 ASC, Property2 DESC", item));
string prop = pair[0].Trim();
if (String.IsNullOrEmpty(prop))
throw new ArgumentException("Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
SortDirection dir = SortDirection.Ascending;
if (pair.Length == 2)
dir = ("desc".Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);
yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };
initial = false;
}
}
private class OrderByInfo
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
public bool Initial { get; set; }
}
private enum SortDirection
{
Ascending = 0,
Descending = 1
}
}
}
И тогда новый вид action GridData:
public ActionResult GridData(int start = 0, int itemsPerPage = 20, string orderBy = "Id", bool desc = false)
{
Response.AppendHeader("X-Total-Row-Count", db.GetAll().Count().ToString());
var realtyobjects = db.GetAll().OrderBy(orderBy + (desc ? " desc" : ""));
return PartialView(realtyobjects.Skip(start).Take(itemsPerPage));
}
Сейчас у нас получилась прекрасная Ajax таблица с сортировкой и постраничным просмотром.
Добавление inline редактирования
Добавляем ещё один метод расширения, только уже для HTML хелпера:
namespace System.Web
{
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
}
Заменим view GridData на
@model IEnumerable<HabraHabrMVC3.Domain.Entities.RealtyObject>
@if (Model.Count() > 0)
{
foreach (var item in Model)
{
<text>@Html.Partial("Edit", item)</text>
}
}
А view Edit на:
@model HabraHabrMVC3.Domain.Entities.RealtyObject
@using (Html.BeginCollectionItem("objects"))
{
<tr>
<td>
@Html.EditorFor(model => model.City)
@Html.ValidationMessageFor(model => model.City)
</td>
<td>
@Html.EditorFor(model => model.Street)
@Html.ValidationMessageFor(model => model.Street)
</td>
<td>
@Html.EditorFor(model => model.House)
@Html.ValidationMessageFor(model => model.House)
</td>
<td>
@Html.EditorFor(model => model.Cost)
@Html.ValidationMessageFor(model => model.Cost)
</td>
<td>
@Html.EditorFor(model => model.Visible)
@Html.ValidationMessageFor(model => model.Visible)
</td>
</tr>
}
В view Index добавим:
@using (Html.BeginForm("Save", "ObjectsView", FormMethod.Post))
{
<table id="AjaxGrid">
...code...
</table>
<input type="submit" id="save" value="Сохранить" />
}
Добавляем action Save:
[HttpPost]
public ActionResult Save(ICollection<RealtyObject> objects)
{
foreach(var item in objects)
{
db.Save(item);
}
return RedirectToAction("Index");
}
Здесь важно, чтобы совпадали имя коллекции в @using (Html.BeginCollectionItem("objects")) и название параметра в методе action Save(ICollection objects).
Всё, получилась табличная форма с inline редактированием, ajax сортировкой и ajax пейджером.
Список используемой литературы в интернетах:
1)Ajax Grid Scaffolder
2)Editing a variable length list, ASP.NET MVC 2-style
3)Dynamic SQL-like Linq OrderBy Extension
Скачать полученный результат.