Detail form edit mode/custom columns
This example contains 2 custom column definitions: 1) to declare TypeId is an external key whose value is TypeName,
and whose values must be provided by an autocomplete. 2) A custom column edit template to edit the Description
property in a text area. Custom edit templates may be provided in-line, with partial views and with ViewComponents.
One may provide also row custom templates. Custom templates may be installed also globally by re-defining the partial
views with the default templates either in a controller specific folder or in the shared folder.
This examples handles also the possibility that the actual model be a subclass containing futher properties.
For this reason a second row-type is defined whose type matches the subclass type, and whose column
definitions are inherited from the 0 index main row-type. Then, the new subclass specific MaintenanceYearlyRate column
is added and the Price column layout settings are overriden to make room for the new column.
<detail-form asp-for="@Model.SubInfo<ProductViewModelDetail>().Model" all-properties="true" edit-mode-default="true" form-method="POST" form-action="@Url.Action("EditCustom", "DetailForm")"> <column asp-for="TypeId"> <external-key-remote display-property="TypeName" items-value-property="Value" items-display-property="Display" items-url="@(Url.Action("GetTypes", "DetailForm", new { search = "_zzz_" }))" dataset-name="product-types" url-token="_zzz_" max-results="20" /> </column> <column asp-for="Description"> <asp-template type="Edit"> @{ { var Model = Html.Item<string>(); <div class="form-group col-xs-12"> <label asp-for="@Model"></label> <textarea class="form-control" asp-for="@Model"></textarea> <span asp-validation-for="@Model" class="text-danger"></span> </div> } } </asp-template> </column> <row-type asp-for="@Model.SubInfo<ProductMaintenanceViewModelDetail>().Model" from-row="0"> <column asp-for="Price" detail-widths="new decimal[] {30, 15 }" /> <column asp-for="@((Model as ProductMaintenanceViewModelDetail).MaintenanceYearlyRate)" /> </row-type> </detail-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")] [Display(Name = "Name", Order = 400)] [Required] public string Name { get; set; } [ColumnLayout(DetailWidthsAsString = "60 30")] [Display(Name = "Price", Order = 300)] public decimal Price { get; set; } [Display(Name = "Cur", Order = 280)] [ColumnLayout(WidthsAsString = "10", DetailWidthsAsString = "40 20")] public Currency ChosenCurrency { get; set; } [ColumnLayout(DetailWidthsAsString = "30")] [Display(Name = "Av", Order = 230)] public bool Available { get; set; } [Display(Name = "Type", Order = 250)] public string TypeName { get; set; } [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 async Task<IActionResult> EditCustom(int? id) { if (!id.HasValue) id = 4; var model = await repository .GetById<ProductViewModelDetail, int>(id.Value); return View(model); } [HttpPost] public async Task<ActionResult> EditCustom(ProductViewModelDetail model) { if (ModelState.IsValid) { repository.Update(false, model); await repository.SaveChanges(); return RedirectToAction("EditCustom", new { id = model.Id }); } return View(model); }