Grid with custom rows and query
Important. This is a complex example. Before reading it, please study allgrid and all previousquery examples.
Also complex grids that list different item types in edit mode may use query facilities. However, some cautions must be taken. Namely:
- All item types have a custom ancesctor, called main item ViewModel whose rendering is usually defined in the first RowType. Query, behavior is defined in the main item ViewModel, since all query may involve just properties common to all item types.
-
In the repository together with the instruction that specifies how to project db data in
the various item ViewModels :
DeclareProjection( m => m.Maintenance == null ? new ProductViewModel{} : new ProductMaintenanceViewModel { MaintenanceYearlyRate = (decimal)m.Maintenance.YearlyRate });
We must add also an instruction that specifies how to project DB data on the main item ViewModel since it is the one used for querying data:DeclareQueryProjection(m => new ProductViewModel { }, m => m.Id);
Where the second argument is an expression that specifies the key property. In fact, LinQ is not able to project queries through a select operation that contains conditions.
@model LiveExamples.Viemodels.ProductlistViewModel @using LiveExamples.Controllers @{ ViewData["Title"] = "grid with custom rows and query"; if (Model.Query.AttachedTo == null) { Model.Query.AttachEndpoint(Url.Action("CustomServerQuery", "CQGrids")); } } ... ... ... @*antiforgery is compulsory *@ <form asp-antiforgery="true"> <div asp-validation-summary="All" class="text-danger"></div> <grid asp-for="Products.Data" type="Immediate" all-properties="true" mvc-controller="typeof(CQGridsController)" row-id="immediate-custom-query" operations="user => Functionalities.FullInLine | Functionalities.GroupDetail" query-for="Query" sorting-clauses="2" enable-query="true" query-grouping-type="typeof(ProductViewModel)" class="table table-condensed table-bordered"> <column asp-for="Products.Data.Element().TypeId"> <external-key-remote display-property="Products.Data.Element().TypeName" items-value-property="Value" items-display-property="Display" items-url="@(Url.Action("GetTypes", "CGrids", new { search = "_zzz_" }))" dataset-name="product-types" url-token="_zzz_" max-results="20" /> </column> /> <column asp-for="Products.Data.Element().Price" colspan="2" /> <row-type asp-for="Products.Data.SubInfo<ProductMaintenanceViewModel>().Model" row-id="immediate-custom-query-maintenance" from-row="0"> <column asp-for="Products.Data.Element().Price" colspan="1" /> <column asp-for="Products.Data.SubElement<ProductMaintenanceViewModel>() .MaintenanceYearlyRate" /> </row-type> <toolbar zone-name="@LayoutStandardPlaces.Header"> <pager class="pagination pagination-sm" max-pages="4" page-size-default="5" total-pages="Products.TotalPages" /> <query type="Filtering" /> <query type="Sorting" /> <query type="Grouping" /> <verify-permission required-permissions="@(Functionalities.Append)"> <button type="button" data-operation="add append 0" class="btn btn-sm btn-primary"> new product </button> <button type="button" data-operation="add append 1" class="btn btn-sm btn-primary"> new product with maintenance </button> </verify-permission> </toolbar> </grid> </form>
/* DetailWidthsAsString in ColumnLayout specifies percentage width for different device screen widths, while WidthsAsString percentage width when shown in-line within a collection control. Oder specify the order columns are rendered. Higher Order come first. All setting, included Display/Name may be overriden by providing a column definition TagHelper within the control definition. */ public class ProductViewModel { public int? Id { get; set; } [MaxLength(128)] [ColumnLayout(DetailWidthsAsString = "100 50")] [Query] [Display(Name = "Name", Order = 400)] [Required] public string Name { get; set; } [ColumnLayout(DetailWidthsAsString = "60 30")] [Query] [Display(Name = "Price", Order = 300)] public decimal Price { get; set; } [Query] [Display(Name = "Cur", Order = 280)] [ColumnLayout(WidthsAsString = "10", DetailWidthsAsString = "40 20")] public Currency ChosenCurrency { get; set; } [Query] [ColumnLayout(DetailWidthsAsString = "30")] [Display(Name = "Av", Order = 230)] public bool Available { get; set; } [Query] [Display(Name = "Type", Order = 250)] public string TypeName { get; set; } [Query] [Display(Name = "Type", Order = 250)] [ColumnLayout(DetailWidthsAsString = "70")] public int? TypeId { get; set; } } /* All subclasses whose exact type is inferred at run-time by model binders/JSON deserializer must be decorated with the RunTimeType to prevent attacks consisting in the forced creation of dangerous types */ [RunTimeType] public class ProductMaintenanceViewModel: ProductViewModel, IUpdateConnections { [Display(Name = "Price/Year", Order = 299)] public decimal MaintenanceYearlyRate { get; set; } // IUpdateConnections specifies when to //update a connected entity contained in a property //with name prefix in the DB model public bool MayUpdate(string prefix) { return prefix == "Maintenance"; } } public class ProductViewModelDetail: ProductViewModel { [MaxLength(256)] [Display(Name = "Description", Order = 100)] [DisplayFormat(NullDisplayText = "no description available")] public string Description { get; set; } } /* All subclasses whose exact type is inferred at run-time by model binders/JSON deserializer must be decorated with the RunTimeType to prevent attacks consisting in the forced creation of dangerous types */ [RunTimeType] public class ProductMaintenanceViewModelDetail : ProductViewModelDetail, IUpdateConnections { [Display(Name = "Price/Year", Order = 299)] [ColumnLayout(DetailWidthsAsString = "30 15")] public decimal MaintenanceYearlyRate { get; set; } // IUpdateConnections specifies when to //update a connected entity contained in a property //with name prefix in the DB model public bool MayUpdate(string prefix) { return prefix == "Maintenance"; } }
public class CQGridsController : ServerCrudController<ProductViewModelDetail, ProductViewModel, int?> { public override string DetailColumnAdjustView { get { return "_DetailRows"; } } public IWebQueryProvider queryProvider { get; private set; } public CQGridsController(ProductRepository repository, IStringLocalizerFactory factory, IHttpContextAccessor accessor, IWebQueryProvider queryProvider) : base(factory, accessor) { Repository = repository; this.queryProvider = queryProvider; } [ResponseCache(Duration = 0, NoStore = true)] public async Task<IActionResult> CustomServerQuery() { var query = queryProvider.Parse<ProductViewModel>(); int pg = (int)query.Page; if (pg < 1) pg = 1; var model = new ProductlistViewModel { Products = await Repository.GetPage( query.GetFilterExpression(), query.GetSorting() ?? (q => q.OrderBy(m => m.Name)), pg, 5, query.GetGrouping()), Query = query }; return View(model); } [HttpGet] public async Task<ActionResult> GetTypes(string search) { var res = search == null || search.Length < 3 ? new List<AutoCompleteItem>() : await (Repository as ProductRepository).GetTypes(search, 10); return Json(res); } }
tfoot .pagination, thead .pagination { margin: 0; display: inline !important; } .full-cell { width: 100%; } table .input-validation-error { border: 1px solid red; background-color: mistyrose; }
public class ProductRepository : DefaultCRUDRepository<ApplicationDbContext, Product> { private ApplicationDbContext db; public ProductRepository(ApplicationDbContext db, IHostingEnvironment hosting) : base(db, db.Products){this.db = db; } public async Task<IEnumerable<AutoCompleteItem>> GetTypes(string search, int maxpages) { return (await DefaultCRUDRepository.Create(db, db.ProductTypes) .GetPage<AutoCompleteItem>(m => m.Display.StartsWith(search), m => m.OrderBy(n => n.Display), 1, maxpages)) .Data; } static ProductRepository() { DeclareProjection( m => m.Maintenance == null ? new ProductViewModel{} : new ProductMaintenanceViewModel { MaintenanceYearlyRate = (decimal)m.Maintenance.YearlyRate }); DeclareQueryProjection(m => new ProductViewModel { }, m => m.Id); DefaultCRUDRepository<ApplicationDbContext, ProductType> .DeclareProjection(m => new AutoCompleteItem { Display=m.Name, Value=m.Id }); } }