Creating custom screens
How to create custom screens for the administration UI
When you want to create your own screens, there are a few things you need to consider. First we need to figure out where the data to the screen should come from. In DynamicWeb data on a Screen comes from a Query, so we need to see if any of the built-in Queries can delivery the data that's needed or if you need to implement your own.
Next we need to identify if our screen need to manipulate data, because if that's needed, then you need to implement your own commands
Query
If you need to implement your own Query, there's two different types of Queries
- One that return a single model
- One the return a list of models
Single model
So when you want to implement a query that only return 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, but the downside of this option is that you have no context information on the Model about, which conditions the Query have used find the information, which can sometimes be needed when you need 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, then we can implement the Query, which now need 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);
}
}
Commands
There's two different 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, then 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 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 commands 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 this 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 commands executed successfully
var result = new CommandResult
{
Status = response.Succeeded ? CommandResult.ResultType.Ok : CommandResult.ResultType.Error,
Message = response.Succeeded ? "" : response.Message
};
return result;
}
}
Customized Screens
Now that we have created the Queries and Commands needed for our Screens, we can start implementing the actual screens. DynamicWeb provides three built-in ScreenTypes, which can be used for creating a custom screen:
- ListScreen
- EditScreen
- OverviewScreen
When you use one of the standard ScreenTypes, you don’t have to worry about the look and feel of your screen, as we’ve taken care of that part – you just need to concentrate about your data.
All of the standard ScreenTypes are located in Dynamicweb.CoreUI.Screens, and contain methods that you need to override to put something on the screen.
In the example below we inherit from ListScreenBase – in this case for the HealthProviderCheckDataModel – and use two overrides to add Name and Columns to the screen:
public sealed class HealthProviderCheckListScreen : ListScreenBase<HealthProviderCheckDataModel>
{
// Override default values
protected override string GetScreenName() => "Health checks";
protected override IEnumerable<ListViewMapping> GetViewMappings()
{
var rowMapping = new RowViewMapping
{
Columns = new System.Collections.Generic.List<ModelMapping>
{
CreateMapping(p => p.State),
CreateMapping(p => p.Name),
CreateMapping(p => p.Description),
CreateMapping(p => p.Count)
}
};
return new ListViewMapping[] { rowMapping };
}
}