Docs > Content Management >

Custom View Model Types

Sometimes you may want to customize the data sent to your views by the dynamic page system. There are four types of view models Cofoundry uses for dynamic views:

  • IPageViewModel: Used for most dynamic pages. The base type is PageViewModel.
  • ICustomEntityPageViewModel: Generic view model type used for custom entity pages. Base type is CustomEntityPageViewModel<TDisplayModel>.
  • IErrorPageViewModel: A simple model used for error pages.
  • INotFoundPageViewModel: A simple model used for the 404 page.

To use a different model you will first need to create a class that implements one of the three view model interfaces or extend one the base classes.

ExamplePageViewModel.cs

In this example we extend the base PageViewModel class and add a simple string property. More commonly you may want to add global data like SEO or custom user data that can be used in your layout pages.

using Cofoundry.Web;

public class ExamplePageViewModel : PageViewModel
{
    public string ExampleMessage { get; set; }
}

To use this in your template views you'll need to override some of the base Cofoundry functionality. There's a couple of ways to do this depending on how much you want to customize the view models:

Overriding the IPageViewModelFactory

If all you want to do is use a different view model type, the simplest thing to do is override the base IPageViewModelFactory implementation:

ExamplePageViewModelFactory.cs

In this example each of the three view model types have been given new implementations. If you only wanted to override one of them then you could instead inherit from PageViewModelFactory to take advantage of the base functionality.

using Cofoundry.Web;

public class ExamplePageViewModelFactory : IPageViewModelFactory
{

    public IPageViewModel CreatePageViewModel()
    {
        return new ExamplePageViewModel();
    }
    
    public ICustomEntityPageViewModel<TDisplayModel> CreateCustomEntityPageViewModel<TDisplayModel>() where TDisplayModel : ICustomEntityPageDisplayModel
    {
        return new ExampleCustomEntityPageViewModel<TDisplayModel>();
    }
    
   public INotFoundPageViewModel CreateNotFoundPageViewModel()
    {
        return new ExampleNotFoundPageViewModel();
    }

    public INotFoundPageViewModel CreateNotFoundPageViewModel()
    {
        return new ExampleNotFoundPageViewModel();
    }
}

ExampleDependencyRegistration.cs

To override the existing IPageViewModelFactory implementation we need to register the factory with the Cofoundry DI framework.

using Cofoundry.Core.DependencyInjection;
using Cofoundry.Web;

public class ExampleDependencyRegistration : IDependencyRegistration
{
    public void Register(IContainerRegister container)
    {
        var overrideOptions = RegistrationOptions.Override();

        container.RegisterType<IPageViewModelFactory, ExamplePageViewModelFactory>(overrideOptions);
    }

Overriding IPageViewModelBuilder

Often you want to do more than just change the view model type for example adding some new data or altering the existing data in the model. To do this you can override IPageViewModelBuilder which gives you total control over the view model that is returned.

ExamplePageViewModelBuilder.cs

Here we're demonstrating implementing the IPageViewModelBuilder fully, but as is the case with IPageViewModelFactory, we could instead inherit from the base PageViewModelBuilder implementation if we didn't need to override every view model.

using Cofoundry.Web;
using Cofoundry.Domain;

public class ExamplePageViewModelBuilder : IPageViewModelBuilder
{
    private readonly IPageViewModelFactory _pageViewModelFactory;
    private readonly IPageViewModelMapper _pageViewModelMapper;

    public ExamplePageViewModelBuilder(
        IPageViewModelFactory pageViewModelFactory,
        IPageViewModelMapper pageViewModelMapper
        )
    {
        // Constructor injection is supported
        // Here we make use of the same helpers used in the base class
        _pageViewModelFactory = pageViewModelFactory;
        _pageViewModelMapper = pageViewModelMapper;
    }


    public Task<IPageViewModel> BuildPageViewModelAsync(PageViewModelBuilderParameters mappingParameters)
    {
        // Create the custom view model instance
        var viewModel = new ExamplePageViewModel();

        // Do the base mapping
        await _pageViewModelMapper.MapPageViewModelAsync(viewModel, mappingParameters);

        // TODO: insert your custom custom mapping
        viewModel.ExampleMessage = "I have a cunning plan.";
        
        // async is supported but not used here
        return Task.FromResult<IPageViewModel>(viewModel);
    }

    public async Task<ICustomEntityPageViewModel<TDisplayModel>> BuildCustomEntityPageViewModelAsync<TDisplayModel>(
        CustomEntityPageViewModelBuilderParameters mappingParameters
        ) where TDisplayModel : ICustomEntityPageDisplayModel
    {
        // Create the custom view model instance
        var viewModel = new ExampleCustomEntityPageViewModel<TDisplayModel>();

        // Do the base mapping
        await _pageViewModelMapper.MapCustomEntityViewModelAsync(viewModel, mappingParameters);

        // Example of calling an async custom mapping function
        await ExampleCustomMappingAsync(viewModel);

        return viewModel;
    }

    public async Task<IErrorPageViewModel> BuildErrorPageViewModelAsync(ErrorPageViewModelBuilderParameters mappingParameters)
    {
        // This example show using the default behaviour without any customization
        // You could alternatively inherit from PageViewModelBuilder and use the base implementation
        var viewModel = _pageViewModelFactory.CreateErrorPageViewModel();

        await _pageViewModelMapper.MapErrorPageViewModelAsync(viewModel, mappingParameters);

        return viewModel;
    }
    
    public Task<INotFoundPageViewModel> BuildNotFoundPageViewModelAsync(NotFoundPageViewModelBuilderParameters mappingParameters)
    {
        // This example show using the default behaviour without any customization
        // You could alternatively inherit from PageViewModelBuilder and use the base implementation
        var viewModel = _pageViewModelFactory.CreateNotFoundPageViewModel();

        await _pageViewModelMapper.MapNotFoundPageViewModelAsync(viewModel, mappingParameters);

        return Task.FromResult(viewModel);
    }

    private Task ExampleCustomMappingAsync<TDisplayModel>(ICustomEntityPageViewModel<TDisplayModel> model)
        where TDisplayModel : ICustomEntityPageDisplayModel
    {
        return Task.CompletedTask;
    }
}