Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Using composite command in ASP.NET Core

My introduction to Composite Command pattern was more theoretical and focused to high-level details. This blog post focuses on implementation details and introduces how to use composite command in ASP.NET Core to upload and process photos.

Composite command pattern

Composite command is mixed design pattern that is made up of command pattern and composite pattern.

The idea of composite command is to split big commands to smaller reusable steps that are also commands. It leads to more flexible code where logical steps are isolated from each other.

I defined abstract base class for all generic commands (in practice there are always some shared functionalities that base class can provide). Generic parameter is needed for Execute() method to have parameter of conrete type.

public abstract class BaseCommandT> //: ICommand
{
    public abstract void Execute(T model);
}

Here is generic composite command that accepts model of type T as argument of Execute() method.

public abstract class CompositeCommandT> : BaseCommandT>
{
    protected IListBaseCommandT>> Commands = new ListBaseCommandT>>();

    public override void Execute(T model)
    {
        foreach (var command in Commands)
        {
            command.Execute(model);
        }
    }
}

With this construct we can start building real-life composite commands.

Photo upload scenario

Suppose we have composite command that does image processing when user uploads new photo to ASP.NET Core application. Some child commands need database context and there are also those that need other service instances.

Our composite command for processing and saving uploaded photo has seven steps. Each step is child command.

Here are definitions of child commands with body of Execute() method left out for clarity. Notice how some child commands get dependencies through constructor injection.

public class ExtractGpsCoordinatesCommand : BaseCommandPhotoUploadModel>
{
    public override void Execute(PhotoUploadModel model)
    {
        // Use external EXIF library
    }
}

public class RemoveExifDataCommand : BaseCommandPhotoUploadModel>
{
    public override void Execute(PhotoUploadModel model)
    {
        // Use external EXIF library
    }
}

public class CreateThumbnailCommand : BaseCommandPhotoUploadModel>
{
    public override void Execute(PhotoUploadModel model)
    {
        // Use external graphics library
    }
}

public class DescribeAndTagPhotoCommand : BaseCommandPhotoUploadModel>
{
    private readonly IComputerVisionClient _visionClient;

    public DescribeAndTagPhotoCommand(IComputerVisionClient visionClient)
    {
        _visionClient = visionClient;
    }

    public override void Execute(PhotoUploadModel model)
    {
        // Use Azure cognitive services to tag and describe photo
    }
}

public class DetectPeopleCommand : BaseCommandPhotoUploadModel>
{
    private readonly IComputerVisionClient _visionClient;

    public DetectPeopleCommand(IComputerVisionClient visionClient)
    {
        _visionClient = visionClient;
    }

    public override void Execute(PhotoUploadModel model)
    {
        // Use Azure cognitive services to find people on photo
    }
}

public class SavePhotoToDatabaseCommand : BaseCommandPhotoUploadModel>
{
    private readonly ApplicationDbContext _dataContext;

    public SavePhotoToDatabaseCommand(ApplicationDbContext dataContext)
    {
        _dataContext = dataContext;
    }

    public override void Execute(PhotoUploadModel model)
    {
        // Save photo metadata to database
    }
}

public class SavePhotoToFileStoreCommand : BaseCommandPhotoUploadModel>
{
    private readonly IFileClient _fileClient;

    public SavePhotoToFileStoreCommand(IFileClient fileClient)
    {
        _fileClient = fileClient;
    }

    public override void Execute(PhotoUploadModel model)
    {
        // Save photo to file store
        // Save thumbnail to file store
    }
}

NB! IFileClient interface with local file system and Azure storage clients is discussed and demonstrated in my blog post Generalize file access for ASP.NET Core applications using IFileClient implementations.

In primitive world we can build up composite command like shown here.

public class UploadPhotoCommand : CompositeCommandPhotoUploadModel>
{
    public UploadPhotoCommand()
    {
        Commands.Add(new ExtractGpsCoordinatesCommand());
        Commands.Add(new RemoveExifDataCommand());
        Commands.Add(new CreateThumbnailCommand());
        Commands.Add(new DescribeAndTagPhotoCommand());
        Commands.Add(new DetectPeopleCommand());
        Commands.Add(new SavePhotoToDatabaseCommand());
        Commands.Add(new SavePhotoToFileStoreCommand());
    }
}

But well… we don’t live in a primitive world and we don’t get away with such an easy solution.

Using dependency injection with composite command

One may think – why not inject all dependencies required by child commands to UploadPhotoCommand() and create child commands there like shown above? It would be easy task to do and it doesn’t need much code.

I don’t think it’s good idea because it makes it hard to replace child command implementations (I don’t cover it here as it is worth separate blog post). And I also think that if we add new command to our code then it will be the citizen of code having full rights like all other classes.

In my opinion we can go with ASP.NET Core dependency injection in this point and register commands to request scope.

// database context is registered with AddDbContext() of EF Core
services.AddScopedIComputerVisionClient, ComputerVisionClient>();
services.AddScopedIFileClient>(factory =>
{
    var rootPath = "wwwroot";

    return new LocalFileClient(rootPath);
});

// register commands
services.AddScopedUploadPhotoCommand>();
services.AddScopedExtractGpsCoordinatesCommand>();
services.AddScopedRemoveExifDataCommand>();
services.AddScopedDescribeAndTagPhotoCommand>();
services.AddScopedDetectPeopleCommand>();
services.AddScopedCreateThumbnailCommand>();
services.AddScopedSavePhotoToDatabaseCommand>();
services.AddScopedSavePhotoToFileStoreCommand>();

This is good idea as construction of child commands is not the matter of composite command. It just consumes injected instances.

public class UploadPhotoCommand : CompositeCommandPhotoUploadModel>
{
    public UploadPhotoCommand(ExtractGpsCoordinatesCommand extractGpsCoordinatesCommand,
                              RemoveExifDataCommand removeExifDataCommand,
                              CreateThumbnailCommand createThumbnailCommand,
                              DescribeAndTagPhotoCommand describeAndTagPhotoCommand,
                              DetectPeopleCommand detectPeopleCommand,
                              SavePhotoToDatabaseCommand savePhotoToDatabaseCommand,
                              SavePhotoToFileStoreCommand savePhotoToFileStoreCommand)
    {
        Commands.Add(extractGpsCoordinatesCommand);
        Commands.Add(removeExifDataCommand);
        Commands.Add(createThumbnailCommand);
        Commands.Add(describeAndTagPhotoCommand);
        Commands.Add(detectPeopleCommand);
        Commands.Add(savePhotoToDatabaseCommand);
        Commands.Add(savePhotoToFileStoreCommand);
    }
}

Now we can use dependency injection to build up composite command with its child commands.

Injecting composite command to controller action

Usually we have only few controller actions where UploadPhotoCommand is needed. Therefore I don’t see any point why it should be injected to controller as most of actions doesn’t need it. Still construction of UploadPhotoCommand takes time and resources. Instead of constructor injection I go with controller action injection.

[HttpPost]
public IActionResult Upload(PhotoUploadModel model,
                            [FromServices]UploadPhotoCommand command)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    command.Execute(model);

    return RedirectToAction(nameof(this.Index));
}

FromServices attribute tells ASP.NET Core that UploadPhotoCommand is not coming in from browser request but it must be injected using dependency injection.

Wrapping up

Composite command is practical design pattern that helps us split larger commands to smaller ones and host it in composite. Child components isolate their functionalities and avoid repeated code. Also it’s good way to avoid unnecessary dependencies between logical steps forming composite command. Composite commands are easy to use in ASP.NET Core applications as we can use dependency injection. To avoid commands constructed for every request we used controller action injection so command is injected to action only when action is actually called.

The post Using composite command in ASP.NET Core appeared first on Gunnar Peipman - Programming Blog.



This post first appeared on Gunnar Peipman - Programming, please read the originial post: here

Share the post

Using composite command in ASP.NET Core

×

Subscribe to Gunnar Peipman - Programming

Get updates delivered right to your inbox!

Thank you for your subscription

×