CRUD server controller

namespace: MvcControlsToolkit.Controllers

The ServerCrudController<VMD, VMS, D> assists server controls in their ajax edit/detail-edit/detail-show/add/add-detail/delete operations. Operations with the "detail prefix" takes place in a detail modal, while all other operations takes place in-line, in the control itself.

ServerCrudController operates both on server controls based on IEnumerables of ViewModels of type VMS and on single item controls. VMD is the ViewModel type to be used in all detail operations, while VMS isthe shorter in-line operations ViewModel. Finally, D is the type of the principal key of both VMS and VMD.

All ajax operations are taken into account automatically by ServerCrudController, so the developer typically inherits from it and adds all other needed action methods, such as, for instance, the action method that populates the initial page with all controls. All html is generated with the same templates used by the controls handled by the controller. For more details on how to associate a controller to a control, and how a controller is able to select the right template for all controls it takes care of, please refer to the documentation on RowType.

The inheriting control must have a constructor with some DI injected parameters that it will pass to the base controller constructor, and with a Repository that will be used by all ajax operations. This repository must be an implementation of the ICRUDRepository interface, such as, any subclass of the interface default implementation:

public class CGridsController : 
    ServerCrudController<ProductViewModelDetailProductViewModelint?>
{
    public CGridsController(ProductRepository repository,
        IStringLocalizerFactory factory, IHttpContextAccessor accessor)
        : base(factory, accessor)
    {
        Repository = repository;
    }

As shown in the example above the repository once injected in the constructor must be saved in the Repository property inherited by the ServerCrudController. IStringLocalizerFactory is needed for various localization stuffs, while IHttpContextAccessor might be needed to instantiate in-line templates. Both ServerCrudController and IHttpContextAccessor are obligatory.

Another setting that might be provided is the name of VMD key property. If a property is named Id as a default it is assumed to be the key, if there is no Id property or if you want to override this setting you must provide explicitely the key name by overriding:


public virtual string DeatailKeyName { get { return null; } }
        

There is no need to provide VMS key since this information is automatically extracted by RowTypes of the controls handled by the controller.

Below a complete controller for a View containing a paged immediate update grid:

public class ProductsController : ServerCrudController<SimpleProductViewModelDetailSimpleProductViewModelint?>
{
    public ProductsController(ProductRepository repository, 
        IStringLocalizerFactory factory, IHttpContextAccessor accessor) 
        : base(factory, accessor)
    {
        Repository = repository;
    }
 
    public async Task<IActionResult> Index(int? page)
    {
        int pg = page.HasValue ? page.Value : 1;
        if (pg < 1) pg = 1;
 
        var model = new SimpleProductlistViewModel
        {
            Products = await Repository
                .GetPage<SimpleProductViewModel>(
                    null,
                    q => q.OrderBy(m => m.Name),
                    pg, 5)
        };
        return View(model);
    }
}

As you can see, just the Index method that displays a page of data has been added: all grid operations are automatically handled by ServerCrudController. For more examples see the Grid section of the live examples.

While the operations shown in the examples above are all what is needed to have all controls associated to the controller work properly, the behavior of the ServerCrudController may be customized in several ways.

Controlling user permissions

No authorize attribute may be placed on all inherited ajax calls/handling action methods, so user authorization may be handled either with an unique controller level Authorize attribute that allows/disallows all controller operations, or, if one needs a more fine grained control, by overriding the function: protected virtual Functionalities Permissions(IPrincipal user) Default implementation always returns Functionalities.All where Functionalities ias a Flag enumeration.

Customizing error messages

The ServerCrudController may return one of the messages below to the client side (all controls take care of dispatching these messages properly):
  • error 0: "wrong call", when the ajax call whose done with wrong parameters(which may happens only when it has been created by an human, instead of a control)
  • error 1: "internal server error", an unspecified error took place during business processing
  • error 2: "item not found, item to be shown in a detail window was not found
  • error 3: "unauthorized", current logged user is not authorized to perform the operation. One should never have this error since UI elements needed to start the operation are not shown to unhauthorized users.
  • error 4: "unable to delete item (maybe already deleted)", self-explanatory
  • error 5: "unable to update item (maybe it has been deleted)", self-explanatory
  • error 6: "operation aborted", this error takes place when the operation was aborted by a custom OnOperation...ing, event handling function (see below).

The above messages may be customized in two ways:

  • overriding protected virtual string ErrorMessage(int i)
  • localizing the the default messages by simply creating a resource file associated to the type ServerCrudController. See Asp.net core Mvc localization for more infos.

Customizing detail modal

The easiest way to customize the modal detail is by furnishing a custom title, by overriding: public virtual string DetailTitle { get { return "Item detail"; } }

Each detail modal is created by using default settings and default templates. If this is not acceptable one may provide the name of a partial View containig custom RowType definitions as in the example below:

<export-settings>
    <column asp-for="TypeId">
        <external-key-remote display-property="TypeName"
                             items-value-property="Value"
                             items-display-property="Display"
                             items-url="@(Url.Action("GetTypes", 
                                        "DetailTest"new { search = "_zzz_" }))"
                             dataset-name="product-types"
                             url-token="_zzz_"
                             max-results="20" />
    </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>
</export-settings>

The View must have a model of type VMD. The export-settings TagHelper allows the content of the view be imported in a calling template.

The name of the View containing all RoType definitions may be specified by overriding: public virtual string DetailColumnAdjustView { get { return null; } }

Finally, one may provide a completely custom modal by overriding: public virtual string DetailView { get { return "DefaultServerItemDetail"; } } Advice: before trying a completely custom inplementation please read about detail forms

Events

The ServerCrudController behaviour may be customized also by "attaching" custom code to the various ajax operations. This can be done thanks to ...ed and ...ing function called respectively after and before each single operation. If a ...ing function returns CrudOperationSwitch.Abort the operation is aborted. Below the list of all such operations the inheriting controller may override:

        public virtual async Task 
            OnOperationExecuted(CrudOperation op, VMS item)
        {
            return;
        }
        public virtual async Task 
            OnDetailOperationExecuted(CrudOperation op, VMD item)
        {
            return;
        }
        public virtual async Task OnDeleteExecuted(D key)
        {
            return;
        }
        public virtual async Task<CrudOperationSwitch> 
                OnOperationExecuting(CrudOperation op, VMS item)
        {
            return CrudOperationSwitch.Go;
        }
        public virtual async Task<CrudOperationSwitch> 
                OnDetailOperationExecuting(CrudOperation op, VMD item)
        {
            return CrudOperationSwitch.Go;
        }
        public virtual async Task<CrudOperationSwitch> 
                OnDeleteExecuting(D key)
        {
            return CrudOperationSwitch.Go;
        }
    

Fork me on GitHub