Operation Separation

Operation Separation is my term for separating commands, think ICommand, that are called in a ViewModel, into a separate Operation class and then having a common service handling the result. This means the operation in a ViewModel does not actually cause the navigation or display of a dialog but merely processes user input and state into a final result to be actioned by the system.

operationseparation

This pattern actually takes quite a bit of setup, which is why it was placed into the Exrin framework. If you want to see a full blown example of this pattern implemented, have a look at the Exrin Sample in LoginViewModel.cs.

Note: This is an advanced setup and requires a solid knowledge of MVVM, including ICommands.

Operation

The command is a common helper of the MVVM Pattern, allowing an executable command to be bound to a View from the ViewModel. Having commands in a ViewModel can become difficult to unit test and clutter a ViewModel. Due to this, I create a separate class called to place the operation in.

First you will see in the ViewModel that a call to the operation involves two elements, the ViewModelExecute and the Operation.

public ICommand LoginCommand
{
    get
    {
         // _model is the reference to the Model from this ViewModel
         return Execution.ViewModelExecute(new LoginOperation(_model));
    }
}

The operation is constructed with the appropriate values it needs to operate, such as the ViewModel state. It will return a result that will determine its final action.

This class implements an interface of IOperation, as all operations will do, and returns a function that returns an IList<IResult>. These are all interfaces manually created. You can define these as your project requires.

public class LoginOperation : IOperation
{
    private readonly IModel _model;

    public LoginOperation(IModel model)
    {
        _model = model;
    }

    public Func<IList<IResult>, Task> Function
    {
        get
        {
             return async (results) =>
             {
                 Result result = null;

                 if (await _model.Login())
                     result = new Result() { ResultAction = ResultType.Navigation, Arguments = new NavigationArgs() { Key = Main.Main, StackType = Stack.Main } };
                 else
                     result = new Result() { ResultAction = ResultType.Display, Arguments = new DisplayArgs() { Message = "Login was unsuccessful" } };
 
                 results.Add(result);
             };
         }
     }
 }

The command and operation are the main components you will need to code for each command you want to implement.

ViewModelExecute

ViewModelExecute is the class that runs the Operation, receives the result and processes it. First we create an Execution object than stores the functions for processing the result.

public class Execution : IExecution
{
    public Func<IList<IResult>, Task> HandleResult { get; set; }
}

In your Base View Model or another common point, you need to create an instance of this Execution object that handles the result.

Execution = new Execution()
{
    HandleResult = (results) =>
    {

    if (results == null)
        return;

    foreach (var result in results)
        switch (result.ResultAction)
        {
             case ResultType.Navigation:
             {
                 // Do your navigation here, etc.
                 break;
             }
             ...

Based on this we create our ViewModelExecute, where you can place all the common wrapper code you normally provide for each command,

public static ICommand ViewModelExecute(this IExecution sender, IViewModelExecute execute)
{
    return new Command(async (parameter) =>
    {
        await ViewModelExecute(sender,execute.Operations);
    }
}
 
private static async Task ViewModelExecute(IExecution sender, IOperation operation)
{
    // Do all your prework here, e.g. Show Activity Indicator
    var results = new List<IResult>();
    await operation.Function(results);
    await sender.HandleResult(results);

    // Do your cleanup here e.g. Stop Activity Indicator
}

This is now complete and you can create as many operations and commands needed to run through here.

Having a single class responsible for each command in your ViewModel allows the following benefits to happen.

  • Single point for handling all navigation
  • Catch certain exceptions in single location
  • Implement a common timeout function and handling
  • Single point for capturing application insights
  • Setting a busy indicator

In summary, it allows you to write a lot of code you would normally have to write for each command and only write it once.


Posted

in

by

Tags: