Table of Contents

Creating custom screens

How to create custom screens for the administration UI

The DynamicWeb 10 interface consists of screens - views where you can see and interact with all the stuff in the solution database. When you're extending a DynamicWeb 10 solution you therefore sometimes need to create custom screens to enable your backend users to see and work with non-standard data.

There are two main approaches to creating custom screens you can take:

  • Using an existing ScreenType - you get a lot of stuff for free; functionality, overall look, and so on is handled by us
  • Using a custom ScreenType - if our built-in ScreenTypes don't work for you, can create custom ScreenTypes to supplement them - with the added caveat that this means you have to handle everything; functionality, looks, rendering, etc.

For both of these you need to figure out where the data for the Screen should come from, and whether the screen needs to be able to manipulate data and if so - how. In DynamicWeb:

  • Data on a screen comes from a query
  • Data is manipulated using commands

If your custom screen works with standard DynamicWeb 10 data you can use our built-in queries and commands - known as the Management API - you can explore them in detail via the API explorer tool or by appending /admin/api/docs/ to your solution URL and playing around with the Swagger documentation: Management API

However, if your screen needs to work with custom data you need to implement custom queries and possibly custom commands.

Custom queries

Queries are used for fetching data from the domain based on a specific condition, and then transforming that domain model into a DataViewModel.

If you need to implement your own Query, there's two different types of Queries:

A DataViewModel is a representation of a domain model, where the data is optimized for display on Screens. Properties intended for display are marked with the [ConfigurableProperty()] attribute; those without it will not appear. This means the Model is created specifically for use in Screens and Commands, containing only the information that we want to show on these Screens.

The PageDataModel has many properties, but this is a small example of two that have been decorated with the [ConfigurableProperty()] so that they can be displayed on the Screen. The ColorSchemeId property has also been given a label so it will be displayed as "Color scheme" on the Screen, rather than "ColorSchemeId". This is very useful for making your Screen more user-friendly and readable:

public sealed class PageDataModel : DataViewModelBase
{
    [ConfigurableProperty]
    public bool Published { get; }

    [ConfigurableProperty("Color scheme")]
    public string ColorSchemeId { get; set; } = "";
    
    ...
}

There are two ways to define the mapping between the domain model and the DataViewModel:

  • Global mapping - In this case we define the mapping based on the source type (domain model) and the target type (DataViewModel). So as soon as we have defined a mapping from a specific domain model to a specific DataViewModel, we can reuse it throughout the entire system every time we work with these types. This makes it a lot easier to maintain the mapping functionality, because it's the same that is being used everywhere.
  • Custom mapping - In some scenarios you need to map some properties on the DataViewModel in a special way when you're working on a specific Query. When that's needed you can just do the mapping manually in the GetModel function on the Query.

Single Model

When you want to implement a Query that only returns a single Model, you need to inherit from the DataQueryModelBase<TModel> class to indicate that this is a Query. When inheriting, you need to define which Model this Query will be returning.

public sealed class PageByNavigationTagQuery : DataQueryModelBase<PageDataModel>
{
    public string NavigationTag { get; set; } = "";

    public override PageDataModel GetModel()
    {
        //Find the page using the domain API
        var page = Dynamicweb.Content.Services.Pages.GetPageByNavigationTag(NavigationTag)

        //Map the page into a PageDataModel
        return MappingService.Map<PageDataModel>(page);
    }
}

List of Models

When you want your Query to return a list of Models, there are two different ways to do it.

The first option is to create a simple Query which just returns an IEnumerable<TModel>. This one doesn't require much code and it's easy to understand. The downside of this option is that you have no context information on the Model about which conditions the Query has used to find the information, which sometimes is needed to show the information on the Screen.

The simple Query is implemented by inheriting from the DataQueryListBase<TModel, TDomainModel>:

public sealed class PagesByParentIdQuery : DataQueryListBase<PageDataModel, Page>
{
    public int ParentId { get; set; }
    protected override IEnumerable<Page> GetListItems()
    {
        return Dynamicweb.Content.Services.Pages.GetPagesByParentID(ParentId);
    }
}

The other option is to define your own specific ListDataModel, and then your Query will return that ListDataModel:

public sealed class SubPagesListDataModel : DataListViewModel<PageDataModel>
{
    public int ParentId { get; set; }
}

Now that we have our ListDataModel, we can implement the Query, which needs to inherit from DataQueryListBase<TModel, TDomainModel, TListModel>:

public sealed class PagesByParentIdQuery : DataQueryListBase<PageDataModel, Page, SubPagesListDataModel>
{
    protected override SubPagesListDataModel MakeListModel() => new()
    {
        ParentId = ParentId,
    };

    protected override IEnumerable<Page> GetListItems()
    {
        return Dynamicweb.Content.Services.Pages.GetPagesByParentID(ParentId);
    }
}

Custom commands

Commands are actions that are handled on the server. These can be used to manipulate data e.g. save or delete a Page. A Command will typically receive a Model or some other information that can be used to identify the entity we want to manipulate. The Command will then interact with the domain to make sure that the data is persisted in the database.

There are two kinds of Commands that can be implemented:

  • Command with a Model
  • Command without a Model

Command with Model

A Command with a Model is only used for saving data on an EditScreen. You set the Model type when you implement the Command.

When the Command is being called, the Model will contain the information that has been posted to it by the EditScreen. That way you will have access to all the information which can then be transformed into a domain model and persisted in the database.

To implement a Command with a Model you need to inherit from CommandBase<TModel>, where TModel defines which Model this Command will be working on.

public sealed class FolderSaveCommand : CommandBase<FolderModel>
{
    public override CommandResult Handle()
    {
        //Getting the Model from the Command
        var model = GetModel();

        //Fetching the model from the domain, if we have a valid ID, otherwise we will create a new Page
        var folder = model.Id == 0 ? new Page(model.AreaId, model.ParentId) : Services.Pages.GetPage(model.Id);

        //Check permission level
        var level = model.Id == 0 ? PermissionLevel.Create : PermissionLevel.Edit;
        folder.ValidatePermission(level);

        //Mapping the values from the Model to the domain model
        folder.IsFolder = true;
        folder.MenuText = model.Name;
        folder.ActiveFrom = DateTime.Now;
        folder.Active = true;
        folder.Allowsearch = false;
        folder.Allowclick = false;
        folder.ShowInSitemap = false;
        folder.ShowInLegend = false;
        folder.ShowUpdateDate = false;
        folder.UrlIgnoreForChildren = true;
        folder.HideForPhones = true;
        folder.HideForTablets = true;
        folder.HideForDesktops = true;

        //Saving the domain model
        Services.Pages.SavePage(folder);

        //Return a result for indicating that the Command executed successfully
        return new CommandResult
        {
            Model = model,
            Status = CommandResult.ResultType.Ok
        };
    }
}

Command without Model

For all other Commands you just need to inherit from CommandBase without any generic arguments. When we are working with a Command without a Model, we have no information about the entity we are working on. So this kind of Command would need to have some properties that can contain an identifier for the entity we want to manipulate.

public sealed class AreaDeleteCommand : CommandBase
{
    public int Id { get; set; }

    public override CommandResult Handle()
    {
        //Find the domain model matching the given Id
        var area = Services.Areas.GetArea(Id);

        //Check if the user has permissions to do the action
        area.ValidatePermission(PermissionLevel.Delete);

        //Delete the area
        var response = Services.Areas.DeleteArea(Id);

        //Return a result for indicating that the Command executed successfully
        var result = new CommandResult
        {
            Status = response.Succeeded ? CommandResult.ResultType.Ok : CommandResult.ResultType.Error,
            Message = response.Succeeded ? "" : response.Message
        };

        return result;
    }
}
To top