πŸ•Ί Presenters

back

Presenters model the user interactions. A non-visual blue-print of the user interface. This section describes how they are implemented in this architecture.

Contents

The Role of the Presenter

Unlike the ViewModels, which model the data shown on a screen, the Presenters model the actions a user can perform on the screen.

Each View can get its own Presenter, for instance:

class LoginPresenter { }
class ProductListPresenter { }
class ProductEditPresenter { }
class ProductDetailsPresenter { }
class CategorySelectionPresenter { }

Each user action would be represented by a method, like these:

class ProductEditPresenter
{
    void Show() { }
    void Save() { }
}

Working with ViewModels

Our action methods of the Presenter work by a ViewModel-in, ViewModel-out principle. An action method returns a ViewModel that contains the data to display on screen:

class ProductEditPresenter
{
    ProductEditViewModel Show() { }
}

Action methods can also receive a ViewModel parameter containing the data the user has edited:

class ProductEditPresenter
{
    void Save(ProductEditViewModel userInput) { }
}

Other action method parameters are also things the user has chosen:

class ProductEditPresenter
{
    void Show(string productNumber) { }
}

The Presenter's action methods are not limited to returning a single type of ViewModel. It is possible to return different ViewModel types as well:

class ProductEditPresenter
{
    object Save(ProductEditViewModel userInput)
    {
        if (!successful)
        {
            // Stay in the edit view
            return new ProductEditViewModel();
        }

        // Redirect to list
        return new ProductListViewModel();
    }
}

Following those guidelines, the Presenters form a structured model for what the user can do with the application.

Infrastructure and Configuration

Sometimes you also pass infra and config parameters to an action method:

class ProductEditPresenter
{
    object Save(
        ProductEditViewModel userInput, 
        IAuthenticator authenticator) // Infra-related parameter
    {
        // ...
    }
}

But it is preferred that the main chunk of the infra and settings is passed to the Presenter's constructor:

class ProductEditPresenter
{
    /// <summary>
    /// Passing infra-related parameters to the constructor.
    /// </summary>
    public ProductEditPresenter(
        IRepository repository,
        IAuthenticator authenticator,
        string cultureName)
    {
        // ...
    }
}

Internal Implementation

Internally a Presenter can use business logic and Repositories to access the domain model.

Delegating ViewModel Creation

It is preferred that ViewModel creation is delegated to the ToViewModel layer (instead of creating them directly within the Presenters):

public ProductEditViewModel Show(int id)
{
    // Delegate to ToViewModel layer
    var viewModel = entity.ToEditViewModel();
}

This way, ViewModel creation is in a centralized spot, so that changes only have to be made in one place.

Delegating More Responsibilities

The responsibility of the Presenter is not limited to ViewModel creation alone, as you can see here:

/// <summary>
/// An action method with multiple responsibilities.
/// </summary>
public ProductEditViewModel Save(ProductEditViewModel userInput)
{
    // ToEntity
    Product entity = userInput.ToEntity(_repository);

    // Business Logic
    new SideEffect_SetDateModified(entity).Execute();

    // Save
    _repository.Commit();

    // ToViewModel
    var viewModel = entity.ToEditViewModel();
    return viewModel;
}

This way the Presenter layer is also responsible for retrieving data, calling business logic and converting ViewModels back to Entities. So it isn’t a needless pass-through layer just delegating ViewModel creation. It is a combinator class, in that it combines multiple smaller aspects of the logic, by delegating work to various parts of the system.

A Presenter action method can have the following steps:

Β  Β 
Security checks Executing the necessary security checks to prevent unauthorized access.
ViewModel Validation If possible, Entity Validation is preferred. But sometimes the data can’t be converted to an Entity yet. Validating a ViewModel might be useful in that case.
ToEntity Converting ViewModel data into Entity data.
GetEntities Retrieving Entity data from the database.
Business Logic Executing the necessary logic for data Validation, Calculations, and decisions based on the data.
Commit Saving changes made to the Entities to the database.
ToViewModel Mapping Entity data to the corresponding ViewModel properties, to prepare it for the view.
NonPersisted Data Copying data that does not need to be stored, from the old ViewModel to the new, such as selections or search criteria.
Redirect Returning a different ViewModel, to trigger the UI to go to the appropriate page like a success message or the home screen.

This seems a bit of a throw-together of concepts, but that’s how it is for a combinator class, like our Presenters. Separating these steps is recommended, so that they do not get intermixed or entangled.

Not all of the steps are needed. ToEntity / Business Logic / ToViewModel might be the typical steps. Slight variations in the order of the steps are also possible.

Complete Example

Here is a code sample with most of the discussed steps in it:

public class ProductEditPresenter
{
    public object Save(ProductEditViewModel userInput)
    {
        // Security
        SecurityAsserter.AssertLogIn();

        // ToEntity
        Product entity = userInput.ToEntity(_repository);

        // Business Logic
        IValidator validator = new ProductValidator(entity);
        if (validator.IsValid)
        {
            new SideEffect_SetDateModified(entity).Execute();

            // Save
            _repository.Commit();

            // Redirect
            return new ProductListViewModel();
        }

        // ToViewModel
        var viewModel = entity.ToEditViewModel();

        // Non-Persisted Data  
        viewModel.Validation.Messages = validator.Messages;

        return viewModel;
    }
}

Overhead

Even though the actual call to the business logic might be trivial, it may still be necessary to convert from Entity to ViewModel and back.

One reason might be the stateless nature of the web. It requires restoring state from the View to the Entity model in between requests. This is because the ViewModel sent to the server may be incomplete, only containing the editable parts of the page. Restoration of Entity state is also needed to delegate responsibilities to the right parts of the system, like delegate to the business layer, which expects Entities.

You might save the server some work by doing partial loads instead of full loads or maybe execute client-native code. For more info, see: Full Load – Partial Load – Cient-Native Code.

Using ViewModels Directly

Some actions might also operate onto ViewModels directly instead:

public void ExpandNode(TreeViewModel viewModel, int id)
{
    var node = viewModel.Nodes.Single(x => x.ID == id);
    node.IsExpanded = true;
}

This may not be the first option to consider, but sometimes it makes sense.

Conclusion

The Presenter pattern is a commonly used design pattern for modeling user interactions in an application. By creating a Presenter for each View and working with ViewModels, we can achieve a clear modularization of our presentation logic and we ensure that each component has a specific responsibility. Delegating ViewModel creation to the ToViewModel layer enables separation of concerns and allows the Presenter to focus on its primary responsibility of modeling user interaction and delegating work to the various parts of the system.

The Presenters form a platform-independent layer below the actual front-end technology. All logic is hidden under a shell of ViewModels and user actions. This makes it possible to swap out the front-end while leaving the underlying system intact.

back