What happens when Clean Sitecore Controllers have ambitions?

As your code base grows, you might observe certain patterns to emerge and you start repeating very similar pieces of code over and over. This is usually a good indicator that a refactoring might be a good idea.

As your code base grows, you might observe certain patterns to emerge and you start repeating very similar pieces of code over and over. This is usually a good indicator that a refactoring might be a good idea.

If you start feeling lost reading through this blog post, you might want to read my previous posts about Cleaning Up Sitecore Controllers and Nesting Model Builders.

All the controllers look the same

By moving the code to build our view models out of the controllers and into model builders, there isn't much logic left in the controller. The single responsibility left is, to ask for a view model and decide on what to render.

We started seeing almost all controllers looking somewhat like this:

namespace MyProject.Feature.Example.Areas.FeatureExample.Controllers
{
    public class ExampleController : GlassController<IExampleElement>
    {
        private readonly IModelFactory modelFactory;

        public ExampleController(IModelFactory modelFactory)
        {
            this.modelFactory = modelFactory;
        }

        public override ActionResult Index()
        {
            var model = this.modelFactory.CreateModel<IExampleElement, ExampleElementViewModel>(this.DataSource);
            
            if (model == null) return new EmptyResult();

            return this.View("modules/example/example", model);
        }
    }
}

The main difference in the various controllers was about what we pass into the model builders:

  • this.DataSource for the datasource item of a component
  • this.Context for the context item
  • Nothing at all

And the main part that remained different was the view path ("modules/example/example").

New controller base classes

By moving the duplicated code into abstract base classes, we ended up with one for each case mentioned above. To pass in this.Datasource, the implementation could look something like this:

namespace MyProject.Foundation.Presentation.Controller
{
    public abstract class DatasourceControllerBase<TDataModel, TViewModel> : GlassController<TDataModel>
        where TDataModel : class
    {
        private readonly IModelFactory modelFactory;

        protected DatasourceControllerBase(IModelFactory modelFactory)
        {
            this.modelFactory = modelFactory;
        }

        protected abstract string ViewPath { get; set; }

        public override ActionResult Index()
        {
            var model = this.modelFactory.CreateModel<TDataModel, TViewModel>(this.DataSource);

            if (model == null) return new EmptyResult();

            return this.View(this.ViewPath, model);
        }
    }
}

Don't call them clean, they start to look clinical

When applying these refactorings, the controllers get so clean, they start to look ridonculous. But I want to let you be the judge of that:

namespace MyProject.Feature.Example.Areas.FeatureExample.Controllers
{
    public class ExampleController : DatasourceControllerBase<IExampleElement, ExampleElementViewModel>
    {
        public ExampleController(IModelFactory modelFactory) : base(modelFactory) {}

        protected override string ViewPath { get; set; } = "modules/example/example";
    }
}

Share your thoughts

And finally, feel free to join the journey. All your questions, thoughts and inputs are welcome in the comment section.