Show / Hide Table of Contents

Deviser Admin

Deviser Admin is a core feature of Deviser Platform to build admin user interfaces with just few lines of code. Admin UIs are generated based on an UI model that can be built using fluent API. In addition to the Admin UI, web service (Web APIs) are also generated for the UI. An UI model can be created either directly from an Entity Framework (EF) Core data context or from an implementation of admin service. EF Core Data context method is a basic approach where CRUD operations for an Admin UI can be built. On other hand, customized UI model can be built using an admin service. These two approaches are explained in the following sections.

Database Context

In this approach, Deviser Admin uses an EF Core dabase context to build Admin UIs. The below diagram illustrates the datbase context approach.

image/svg+xml Database EF CoreDbContext UI Model REST API Admin UI

Follow the steps to build Admin UIs with database context approach:

Create Deviser Module

Create a deviser module project with database context as explained in this section.

Implement IAdminConfigurator

Implement a class with the interface IAdminConfigurator<MyDbContext>. The ConfigureAdmin() method is used to build Admin UI model.

public class AdminConfigurator : IAdminConfigurator<BlogDbContext>
{
    public void ConfigureAdmin(IAdminBuilder adminBuilder)
    {
        adminBuilder.MapperConfiguration = BlogMapper.MapperConfiguration;

        adminBuilder.Register<DTO.Post>(modelBuilder =>
        {
            modelBuilder.GridBuilder.Title = "Posts";
            modelBuilder.FormBuilder.Title = "Post";

            modelBuilder.GridBuilder
                .AddField(p => p.Title)
                .AddField(p => p.Category)
                .AddField(p => p.Tags)
                .AddField(p => p.CreatedOn, option => option.Format = "dd.MM.yyyy")
                .AddField(p => p.CreatedBy, option => option.DisplayName = "Author");

            modelBuilder.FormBuilder
                .AddKeyField(p => p.Id)
                .AddField(p => p.Title)
                .AddField(s => s.Summary)
                .AddField(s => s.Thumbnail)
                .AddField(s => s.Content)
                .AddSelectField(s => s.Category, expr => expr.Name)
                .AddInlineMultiSelectField<DTO.Tag>(s => s.Tags, expr => expr.Name)
                .AddField(p => p.CreatedOn, option => option.Format = "dd.MM.yyyy");
            
            modelBuilder.FormBuilder
                .Property(p => p.Tags)
                .AddItemBy(t => t.Name);
            
            modelBuilder.AddChildConfig(s => s.Comments, (childForm) =>
            {
                childForm.FormBuilder
                .AddKeyField(c => c.Id)
                .AddField(c => c.UserName)
                .AddField(c => c.Comment)
                .AddField(c => c.CreatedOn)
                .AddField(c => c.IsApproved);
            });
        });

        adminBuilder.Register<DTO.Category>(modelBuilder =>
        {
            modelBuilder.GridBuilder
                .AddField(p => p.Name);

            modelBuilder.FormBuilder
                .AddKeyField(p => p.Id)
                .AddField(p => p.Name);
        });

        adminBuilder.Register<DTO.Tag>(modelBuilder =>
        {
            modelBuilder.GridBuilder
                .AddField(p => p.Name);

            modelBuilder.FormBuilder
                .AddKeyField(p => p.Id)
                .AddField(p => p.Name);
        });

    }
}
Note

A deviser module can have only one implementation of IAdminConfigurator, but it can have one or more admin pages. Each admin page is registered by a class type (usually a DTO) and it should be unique in a IAdminConfigurator implementation.

IAdminBuilder.Register<TModel>() method registers a admin page for the model TModel. ModelBuilder API on this method has GridBuilder and FormBuilder API to build Grid and Form respectively. In addition, parent-child relationship can be built using ModelBuilder.AddChildConfig() method.

Note

A KeyField is must for an admin page. It can be either declared in GridBuilder or FormBuilder.

Configure Mapping using AutoMapper

Create a IMapper using Automapper. For example:

public class BlogMapper
{
    public static MapperConfiguration MapperConfiguration;
    public static IMapper Mapper;
    static BlogMapper()
    {
        MapperConfiguration = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<Post, DTO.Post>().ReverseMap();
            cfg.CreateMap<Tag, DTO.Tag>().ReverseMap();
            cfg.CreateMap<Comments, DTO.Comments>().ReverseMap();
            cfg.CreateMap<Category, DTO.Category>().ReverseMap();
        });
        Mapper = MapperConfiguration.CreateMapper();
    }
}
Note

Why mapping is required? Exposing all fields (in particular audit and secure fields) from data layer to UI layer or Web API layer is not secure as well not a best practice. Therefore, mapping from data models to DTOs (Data Transfer Objects) allows to hide audit/secure fields. In addition, this approach decouples the data layer and UI layer.

Create AdminController

Finally, create a AdminController class inherits AdminController<TAdminConfigurator>, TAdminConfigurator is the class that has been created in previous step. TAdminConfigurator should implement IAdminConfigurator interface.

[Module("DemoAdminBlog")]
public class AdminController : AdminController<AdminConfigurator>
{
    public AdminController(IServiceProvider serviceProvider)
        : base(serviceProvider)
    {

    }
}

Create a Page

Once the IAdminConfigurator has been implemetned, each UI model should be hosted on a page. To create a page type Admin, navigate to Admin (click the Deviser logo on top left corner) -> Page Management.

[Page Management image goes here....]

  1. Click Add button to create a page.
  2. Select Page Type as Admin.
  3. Enter Name and Title for the the Page.
  4. Select the Module Name. It should match the module name configured in the Module Managment and in the AdminController. In this example, select DemoAdminBlog.
  5. Enter the Model Name of UI. It should match the configured UI Model. For example, DTO.Post to display Post UI model in this page.
  6. Save the page

Page configuration to display Post UI model would look like this

[Page Management to display Post UI model image goes here....]

Result

The generated UI would look like this

  • Post
  • Category
  • Tag

Admin Services

This approach allows to build more customized admin UI from an existing .NET API. Following illustration shows the admin service.

image/svg+xml AdminServices UI Model REST API Admin UI

Create Deviser Module

Create a deviser module project as explained in the module section.

Implement IAdminConfigurator

Implement a class with interface IAdminConfigurator. This interface has method ConfigureAdmin() which is used to build an UI model.

Interface IAdminBuilder have following methods to build customized UI:

  1. IAdminBuilder.Register<TModel, TAdminService>() method registers a admin page for the model TModel which has a grid and a form. The admin page can be customized by a class TAdminService which implements interface IAdminService<TModel>. This class has methods to perform CRUD operations on the specified UI model.
  2. IAdminBuilder.RegisterGrid<TModel, TAdminGridService>() registers a admin page for model TModel which has only grid. The grid can be customized by class TAdminGridService which implements interface IAdminGridService<TModel>. This class has methods to perform operations on the grid.
  3. IAdminBuilder.RegisterForm<TModel, TAdminFormService>() registers a admin page for model TModel which has only form. This form can be customized by class TAdminFormService which implements the interface IAdminFormService<TModel>.
  4. IAdminBuilder.RegisterTreeAndForm<TModel, TAdminGridService>() registers a admin page for model TModel which has a tree and a form. Both the tree and form can be customized by class TAdminTreeService which implements the interface IAdminFormService<TModel>

The Deviser Admin provides following interfaces to build an admin service:

  • IAdminService<TModel> is used to build an admin UI with customized CRUD. It includes:
    • A grid view to list all records
    • A Form to create a new record and to edit an existing record
  • IAdminGridService<TModel> is used to build an admin UI where only a grid view is required.
  • IAdminFormService<TModel> is used to build an admin UI only with a Form
  • IAdminTreeService<TModel> is used to build an admin UI with customized CRUD. It includes:
    • A tree view to list all records in a tree view
    • A form to create a new record and to edit an existing record.
public class AdminConfigurator : IAdminConfigurator
{
    public void ConfigureAdmin(IAdminBuilder adminBuilder)
    {
        adminBuilder.Register<Employee, EmployeeAdminService>(modelBuilder =>
        {
            modelBuilder.GridBuilder.Title = "Employee";
            modelBuilder.FormBuilder.Title = "Employee Details";

            modelBuilder.GridBuilder
                .AddField(c => c.Name)
                .AddField(c => c.Designation)
                .AddField(c => c.Email)
                .AddField(c => c.Nationality)
                .AddField(c => c.IsActive, option =>
                {
                    option.DisplayName = "Is Active";
                    option.IsTrue = "Active";
                    option.IsFalse = "In Active";
                });

            modelBuilder.GridBuilder.DisplayFieldAs(c => c.IsActive, LabelType.Badge, c => c.IsActiveBadgeClass);

            modelBuilder.FormBuilder
                .AddKeyField(c => c.Id)
                .AddField(c => c.Name, option => { option.EnableIn = FormMode.Create; })
                .AddField(c => c.Designation)
                .AddField(c => c.Email, option => option.ValidationType = ValidationType.Email)
                .AddField(c => c.IsActive)
                .AddSelectField(c => c.Nationality);

            modelBuilder.FormBuilder.Property(c => c.Nationality).HasLookup(sp => sp.GetService<EmployeeAdminService>().GetCountries(),
                ke => ke.Code,
                de => de.Name);
        });

        adminBuilder.RegisterGrid<Customer, CustomerAdminGridService>(builder =>
        {
            builder.Title = "Customers";

            builder
                .AddKeyField(c => c.Id)
                .AddField(c => c.OrderId)
                .AddField(c => c.Name)
                .AddField(c => c.Email)
                .AddField(c => c.OrderDate)
                .AddField(c => c.OrderStatus)
                .AddField(c => c.ShipDate)
                .AddField(c => c.ShipCountry);

            builder.Property(c => c.ShipCountry).HasLookup(sp => sp.GetService<EmployeeAdminService>().GetCountries(),
                ke => ke.Code,
                de => de.Name);

            builder.Property(c => c.OrderStatus).HasLookup(sp => OrderStatus.OrderStatuses,
                ke => ke.Id,
                de => de.Name);

            builder.DisplayFieldAs(c => c.OrderStatus, LabelType.Badge, c => c.OrderStatusClass);

            builder.AddRowAction("MarkAsDelivered", "Mark As Delivered",
                (provider, item) => provider.GetService<CustomerAdminGridService>().MarkDelivered(item));

            builder.HideEditButton();
        });

        adminBuilder.RegisterForm<Guest, EventFormService>(builder =>
        {
            builder
                    .AddFieldSet("General", fieldBuilder =>
                    {
                        fieldBuilder
                            .AddKeyField(c => c.Id)
                            .AddField(p => p.Name)
                            .AddSelectField(p => p.Gender)
                            .AddField(p => p.Email, option => option.ValidationType = ValidationType.Email);
                    })

                    .AddFieldSet("Dietary requirements", fieldBuilder =>
                    {
                        fieldBuilder
                            .AddField(p => p.IsTakePartInDinner)
                            .AddSelectField(p => p.FoodType);
                    });

            builder.Property(f => f.FoodType)
                .ShowOn(f => f.IsTakePartInDinner)
                .ValidateOn(f => f.IsTakePartInDinner);

            builder.Property(f => f.Gender).HasLookup(
                sp => Gender.Genders,
                ke => ke.Id,
                de => de.Name);

            builder.Property(f => f.FoodType).HasLookup(
                sp => FoodType.FoodTypes,
                ke => ke.Id,
                de => de.Name);
        });

        adminBuilder.RegisterTreeAndForm<Folder, FolderAdminService>(builder =>
        {
            builder.TreeBuilder.Title = "File Manager";
            builder.FormBuilder.Title = "File Manager";
            builder.TreeBuilder.ConfigureTree(p => p.Id,
                p => p.Name,
                p => p.Parent,
                p => p.SubFolders,
                p => p.SortOrder);

            var formBuilder = builder.FormBuilder;
            var adminId = Guid.Parse("5308b86c-a2fc-4220-8ba2-47e7bec1938d");
            var urlId = Guid.Parse("bfefa535-7af1-4ddc-82c0-c906c948367a");
            var standardId = Guid.Parse("4c06dcfd-214f-45af-8404-ff84b412ab01");

            formBuilder
                    .AddFieldSet("General", fieldBuilder =>
                    {
                        fieldBuilder
                            .AddField(p => p.Name);
                    })

                    .AddFieldSet("Permissions", fieldBuilder =>
                    {
                        fieldBuilder.AddCheckBoxMatrix(p => p.PagePermissions,
                            p => p.RoleId,
                            p => p.PermissionId,
                            p => p.Id,
                            p => p.FolderId, typeof(Role), typeof(Permission),
                            option => option.IsRequired = false);
                    });

            formBuilder.Property(f => f.PagePermissions).HasMatrixLookup<Role, Permission, Guid>(
                sp => sp.GetService<IRoleRepository>().GetRoles(),
                ke => ke.Id,
                de => de.Name,
                sp => sp.GetService<IPermissionRepository>().GetPagePermissions(),
                ke => ke.Id,
                de => de.Name);
        });
    }
}

Create AdminController

Finally, create a class AdminController inherits from class AdminController<TAdminConfigurator>, TAdminConfigurator is the class that has been created in the previous step. The class TAdminConfigurator should implement IAdminConfigurator interface.

[Module("DemoAdmin")]
public class AdminController : AdminController<AdminConfigurator>
{
    public AdminController(IServiceProvider serviceProvider)
        : base(serviceProvider)
    {

    }
}

Grid

GridBuilder API under ModelBuilder API is used to build an admin grid. The admin grid can be customized API methods.

Fields

In a grid, fields can be added by AddField() method. Similarly, key field can be added by AddKeyField() method. Both methods accept a lambda expression to select a field.

modelBuilder.GridBuilder.AddField(c => c.Name)
modelBuilder.AddKeyField(c => c.Id)

LookUp

A grid field may have a select or multi-select field. Adding a lookup for select or multi-select filed is required to display the values. Lookup can be added either in a GridBuilder API or a FormBuilder API in a admin page.

modelBuilder.FormBuilder.Property(c => c.Nationality).HasLookup(sp => sp.GetService<EmployeeAdminService>().GetCountries(),
                   ke => ke.Code,
                   de => de.Name);

Above method specifies field Nationality has a lookup that can be provided by EmployeeAdminService.GetCountries() method. In addition, key and display fields of the lookup are specified as Code and Name respectively.

Note

In case of database context approach, lookups are automatically identified by TDatabaseContext. No need to define it explicitly

Field Customization

Appearance of a field in a grid can be customized by DisplayFieldAs() method. For example, a filed can be displayed as badge or an icon.

modelBuilder.GridBuilder.DisplayFieldAs(c => c.IsActive, LabelType.Badge, c => c.IsActiveBadgeClass);

Grid Customization

Grid behaviors can be customized further. For example, Edit Button of a grid ha be hidden by GridBuilder.HideEditButton() method. In addition, one or more row actions can be added to a grid using builder.AddRowAction() method.

modelBuilder.GridBuilder.AddRowAction("MarkAsDelivered", "Mark As Delivered",
                   (provider, item) => provider.GetService<CustomerAdminGridService>().MarkDelivered(item));

Form

Deviser admin provides a FormBuilder API to build an admin form. An admin form can be built for an admin grid or fit can be build independent of an admin grid i.e standalone admin form.

Fields

In a form, fields can be added in a new line by AddField(e => e.FieldName) method. Similarly, the method AddInlineField(e => e.FieldName) is used to add a filed in the same line. A key field can be added by AddKeyField(e => e.Id) method. All those methods accept a lambda expression to select a field.

For example, following code snipet will added a key field Id and a text field name.

modelBuilder.FormBuilder
    .AddKeyField(c => c.Id)
    .AddField(c => c.Name)
Note

Key Field A key field is mandatory for an admin model and it can be either specified in a GridBuilder or in a FormBuilder API.

Similarly, a single select filed can be added using AddSelectField() and a multiple select filed can be added using AddMultiSelectField() methods. For each single or multiple select field a lookup must be specified using .Property(f=>f.fieldName).HasLookup() method.

Admin form supports following field types:

Field Type Methods Property Types Option
Text AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string None
Number AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
int, float, double, long and decimal None
Static Text AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
bool option.FieldType = FieldType.Static
CheckBox AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
bool None
Email AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string option.FieldType = FieldType.EmailAddress
Password AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string option.FieldType = FieldType.Password
Phone AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string option.FieldType = FieldType.Phone
TextArea AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string option.FieldType = FieldType.TextArea
RichText AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string option.FieldType = FieldType.RichText
Date AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
DateTime None
Select AddSelectField(e => e.FieldName)
AddInlineSelectField(e => e.FieldName)
Custom type T None
MultiSelect AddMultiSelectField(e => e.FieldName)
AddInlineMultiSelectField(e => e.FieldName)
ICollection<T> None
RadioButton AddSelectField(e => e.FieldName)
AddInlineSelectField(e => e.FieldName)
Custom type T option => option.FieldType = FieldType.RadioButton
Image AddField(e => e.FieldName)
AddInlineField(e => e.FieldName)
string option.FieldType = FieldType.Image
CheckBox Matrix AddCheckBoxMatrix(e => e.FieldName, e => e.RowKey, e => e.ColumnKey, e => e.matrixKey, e => e.ContextKey, typeof(TRow), typeof(TColumn)) ICollection<TRowColumn> None

Lookup

Select and Multiselect fileds require a lookup configuration. For example select filed Nationality requires a HasLookup() method as shown below.

modelBuilder.FormBuilder
    .AddKeyField(c => c.Id)    
    .AddSelectField(c => c.Nationality);

    modelBuilder.FormBuilder.Property(c => c.Nationality).HasLookup(sp => sp.GetService<EmployeeAdminService>().GetCountries(),
        ke => ke.Code,
        de => de.Name);

HasLookup() method accepts three parameters. First parameter expression expects the list of objects of type TProperty, second parameter expression is a key selector for the lookup and the third parameter expression is the display filed for the lookup.

Field Grouping

Fields can be grouped by using formBuilder.AddFieldSet() method.

formBuilder
    .AddFieldSet("General", fieldBuilder =>
    {
        fieldBuilder
            .AddField(f => f.SiteName)
            .AddField(f => f.SiteDescription, option => option.FieldType = FieldType.TextArea)
            .AddField(f => f.SiteAdminEmail, option => option.FieldType = FieldType.EmailAddress)
            .AddField(f => f.SiteRoot)
            .AddField(f => f.SiteHeaderTags)
            .AddSelectField(f => f.SiteLanguage);

    });

Field Options

  • Description
  • DisplayName
  • LabelOption
  • Format (DateTime)
  • IsRequired
  • MaxLength
  • ShowIn - Show/Hide Fields in both create and update or create only or update only
  • EnableIn - Enable/Disable in both create and update or create only or update only
  • ShowOn

Validation

  • Basic Validation
  • Condition Validation (ValidateOn)

LookUp – LookUp can be defined either in Grid/Form

  • Basic lookup
  • CheckBoxMatrix / MatrixLookUp

Tree

Display Property

Parent

Child

Sort Property

  • Improve this Doc
Back to top © Copyright 2022 Karthick Sundararajan