CRUD Controller

The ServerCrudController<VMD, VMS, D> contains all action methods for assisting collection rendering server controls in ajax based updates without writing further code. Namely: deletes, in-line additions, in-line modifications, detail show, detail form based additions, and detail form modifications.

It is an abstract class the developer must inherit from. Once inherited typically one adds just the Index ActionMethod that shows the initial data page, since all other operations are handled automatically. In the dase of batch collection controls, since user may submit the whole page with all modifications done on its data, also the HttpPost ActionMethod corresponding to this Index Get ActionMethod must be provided.

Available also and ..ed event handling methods to override for all operations. This way the developer has the possibility to abort any operation( methods) and to add further processing to each of them (..ed operations are executed before the repository SaveChanges).

Crud controllers accept 3 generic parameters: the detail ViewModel type, the short ViewModel type used for in-line display, and the type of their keys. Subclasses of both short ViewModel and detail Viemodel with further fields are handled properly, since both controls on the UI side and CRUD repositories on the business side auto-detect sub-classes and handle them properly with minimal code required by the developer. Please refer to both Detail forms, and Grids examples, and to the CRUD Repository page for more details on this.

Crud controllers handle all operations by using ICRUDRepository implimentations like the one provided by the CRUD Repository class or any other custom subclass of it. The ICRUDRepository is injected in the constructor together with other classes (see example below).

User permissions on each operation are handled by overriding the method Functionalities Permissions(IPrincipal user) that allows all operations to all users, as a default . In case permissions do not depend on the specific operation one may simply decorate the inheriting controller with an Authorize attribute.

The CRUD controller handles the whole communication protocol with the JavaScript classes that manage the collection control on the client side, by returning the right HTML for updated or new items, and both all Validation errors found in the ModelState, and "Internal server errors", "item not found errors", and so on. All generic error messages (the ones whose source is not the ModelState) may be customized by overriding the string ErrorMessage(int i) method. Moreover, they are automatically localized if a resource has been associated with the inheriting controller type.

All HTML is created by using the same templates defined in the collection control handled by the CRUD controller subclass. Each CRUD controller subclass may handle several controls, but in this case the collection control must provide an identifier that is unique among all controls handled by the same CRUD Controller subclass.

All detail views use a standard template, that may be customized in several ways:

  1. By providing a custom "DefaultServerItemDetail" view either in the shared folder or in the controller specific folder
  2. By overriding string DetailView { get { return "DefaultServerItemDetail"; } }
  3. By changing the default detail View title overriding string DetailTitle { get { return "Item detail"; } }
  4. In case one needs just to provide infos on how to render columns that cannot be extracted by ViewModel metadata, it is possible to specify a view containing just the relevant row/column definitions, by overriding the read only property string DetailColumnAdjustView.

Below a Crud controller that handles a batch grid, with customized detail row/column definitions. Please refer to the grid examples to see CRUD controllers runnin.

public class CGridsController : 
    protected override Functionalities Permissions(IPrincipal user)
        return Functionalities.All & ~Functionalities.Delete;
    public override string DetailColumnAdjustView
            return "_DetailRows";
    public CGridsController(ProductRepository repository,
        IStringLocalizerFactory factory, IHttpContextAccessor accessor)
        : base(factory, accessor)
        Repository = repository;
    public async Task<IActionResult> IndexBatch(int? page)
        int pg = page.HasValue ? page.Value : 1;
        if (pg < 1) pg = 1;
        var model = new ProductlistBatchViewModel
            Products = await Repository.GetPage<ProductViewModel>(
            q => q.OrderBy(m => m.Name),
            pg, 3)
        model.ModifiedProducts = model.Products.Data;
        return View(model);
    public async Task<IActionResult> IndexBatch(ProductlistBatchViewModel model)
        if (ModelState.IsValid)
                model.Products.Data, model.ModifiedProducts);
            await Repository.SaveChanges();
            return View(model);
            return View(model);
@model ProductViewModelDetail
@using System.Collections
    <column asp-for="TypeId">
        <external-key-remote display-property="TypeName"
                    new { search = "_zzz_" }))"
            max-results="20" />
    <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>
            } }
    <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)" />

Fork me on GitHub