Detail form display mode/custom columns
This example contains a custom column definition to declare TypeId is an external key whose value is TypeName.
Moreover, it handles 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.
color laser printer
400.00
40.00
$
printer
High quality color laser printer
<detail-form asp-for="@Model.SubInfo<ProductViewModelDetail>().Model" all-properties="true" edit-mode-default="false"> <column asp-for="TypeId"> <external-key-static display-property="TypeName" /> </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> ShowCustom(int? id) { if (!id.HasValue) id = 2; var model = await repository .GetById<ProductViewModelDetail, int>(id.Value); return View(model); }