0

I have an MVC controller holding several abstractions (components). I want to reduce the amount of params to max 4 params.

To do this, I have several strategies that do not satisfy:

  • Bundling a set of params into a new class where the params will be properties: that just masks the complexity.
  • Resolve dependencies not by constructor injection but resolve by using factories: that also masks complexity.

Is there a better alternative approach ?

This the code of the controller:

public abstract class BaseVerificatieController : ExtendedController
    {
        private readonly IReferentieTabellenSysteemSettings referentieTabellenSysteemSettings;
        private readonly IProcessManager processManager;
        private readonly INavigationManager navigationManager;
        private readonly ITransactionFacade transactionFacade;
        private readonly IUISupportFacade uiSupportFacade;
        private readonly ProcessIndex process;

        protected BaseVerificatieController(
            IBackHandler backHandler,
            IReferentieTabellenSysteemSettings referentieTabellenSysteemSettings,
            IProcessManager processManager,
            INavigationManager navigationManager,
            ITransactionFacade transactionFacade,
            IUISupportFacade uiSupportFacade,
            ProcessIndex process)
            : base(backHandler)
        {
            this.referentieTabellenSysteemSettings = referentieTabellenSysteemSettings;
            this.processManager = processManager;
            this.navigationManager = navigationManager;
            this.transactionFacade = transactionFacade;
            this.uiSupportFacade = uiSupportFacade;
            this.process = process;
        }

        [HttpGet]
        public ActionResult Index()
        {
            var processStep = processManager.StartProcess(process);

            return navigationManager.RedirectFromProcessStep(process, processStep);
        }

        [HttpGet]
        public ActionResult Oe()
        {
            var model = new OeViewModel();

            var transactionResult = transactionFacade.VerifyIdentityStart();

            model.SetTransactionResult(transactionResult);

            return View(model);
        }

        [HttpPost]
        public ActionResult Oe(OeViewModel viewModel)
        {
            if (viewModel == null)
            {
                throw new ArgumentNullException("viewModel");
            }

            var transactionResult = transactionFacade.VerifyIdentityCheckRegisters(viewModel.SKN, null);

            if (transactionResult.MoveToStep != Business.Models.ProcessStepIndex.NoStep)
            {
                return navigationManager.RedirectFromTransactionResult(process, transactionResult);
            }

            var model = new OeViewModel();

            model.SetTransactionResult(transactionResult);

            return View(model);
        }

        [HttpGet]
        public ActionResult Oz()
        {
            var model = new OzViewModel(uiSupportFacade, referentieTabellenSysteemSettings);

            var idStaatResult = transactionFacade.IdStaatStart();

            model.SetIdStaatResult(idStaatResult);

            return View("Oz_SKDB", model);
        }

        [HttpPost]
        public ActionResult Oz(OzViewModel viewModel)
        {
            return RedirectToAction("Index", "Home");
        }
Patrick Peters
  • 9,103
  • 7
  • 53
  • 99
  • You inject an object `process`, but only use it when you pass it along to other injected objects. Can't you remove this injection in this controller, and inject it in the other injected objects? – Maarten Jun 24 '15 at 08:23
  • 1
    You are also newing up view models with the use of injected objects. This strikes me as odd since it means that your viewmodels are not just data holders but are also containing logic. They will also have multiple constructors since they are also used as an action-method-parameter. I would suggest to remove the logic from the viewmodel, and define a separate service class to do the logic. Using this you can probably reduce the injections in your controller since you do not have to 'pass along' the injected objects to the service (instead of the viewmodels), the DI component will do that. – Maarten Jun 24 '15 at 08:27
  • @Maarten: this is a base controller class. There are 2 other classes that derive from the BaseVerificatieController class. The DI injection is done at these 2 derived classes and the params are chained with a base(...), where the process enum is set explicitly. – Patrick Peters Jun 24 '15 at 08:34
  • @Maarten: about the viewmodels: you are right there, but focused on my question this doesn't fix the dependency issue. – Patrick Peters Jun 24 '15 at 08:36
  • So you're having two levels of inheritance. You might want to work on that. – Steven Jun 24 '15 at 11:12
  • @Mark: you are right, this question can be closed. I followed the Facade Services pattern you provided and I sliced the param list to less than 5 which is the target for us because of code audit. – Patrick Peters Jun 24 '15 at 13:27

1 Answers1

1

As @Maarten says in comments, I would get rid of the injection of the process and inject it where its needed (not passing it around).

I would further move all the view model logic into "view model handlers" and use a mediator for executing the view model handlers.

The dependencies such as the transactionFacade and uiSupportFacade would then be injected in the view model handlers. This can be achieved with the following:

 /// <summary>
/// Specifices that the target class is a view model. This is a marker interface and has no methods.
/// </summary>
public interface IViewModel
{
    // Marker interface
}

/// <summary>
/// Handles the <typeparamref name="TViewModel"/>.
/// </summary>
/// <typeparam name="TViewModel">The view model which should be handled.</typeparam>
public interface IHandleViewModel<out TViewModel> where TViewModel : IViewModel
{
    /// <summary>
    /// Creates a <typeparamref name="TViewModel"/>.
    /// </summary>
    /// <returns>An instance of the <typeparamref name="TViewModel"/>.</returns>
    TViewModel Handle();
}

/// <summary>
/// Handles the <typeparamref name="TViewModel"/> with the argument of <typeparamref name="TInput"/>
/// </summary>
/// <typeparam name="TInput">The argument for the view model</typeparam>
/// <typeparam name="TViewModel">The view model which should be handled.</typeparam>
public interface IHandleViewModel<out TViewModel, in TInput> where TViewModel : IViewModel
{
    /// <summary>
    /// Creates a <typeparamref name="TViewModel"/>.
    /// </summary>
    /// <returns>An instance of the <typeparamref name="TViewModel"/>.</returns>
    TViewModel Handle(TInput input);
}

/// <summary>
/// Processes and creates view models.
/// </summary>
public interface IProcessViewModels
{
    /// <summary>
    /// Creates the <typeparamref name="TViewModel"/>.
    /// </summary>
    /// <returns>The view model</returns>
    TViewModel Create<TViewModel>() where TViewModel : IViewModel;

    /// <summary>
    /// Create the <typeparamref name="TViewModel"/> with an argument of type <typeparamref name="TInput"/> 
    /// </summary>
    /// <typeparam name="TViewModel">The view model which should be constructed</typeparam>
    /// <typeparam name="TInput">The type of argument for the view model</typeparam>
    /// <param name="input">The argument for the view model</param>
    /// <returns>The view model</returns>
    TViewModel Create<TViewModel, TInput>(TInput input) where TViewModel : IViewModel;
}

This means you can inject a single IProcessViewModels into your controller and execute the handlers. E.g. by filling the dependency container (here Simple Injector):

/// <summary>
/// Registers the view models in the Simple Injector container
/// </summary>
/// <param name="container">The Simple Injector container</param>
/// <param name="viewModelAssemblies">The assembly location of the view models</param>
public static void RegisterViewModels(this Container container, Assembly[] viewModelAssemblies)
{
    if (container == null)
        throw new ArgumentNullException("container");

    if (viewModelAssemblies == null) 
        throw new ArgumentNullException("viewModelAssemblies");

    container.RegisterSingle<IProcessViewModels, ViewModelProcessor>();
    container.RegisterManyForOpenGeneric(typeof(IHandleViewModel<>), viewModelAssemblies);
    container.RegisterManyForOpenGeneric(typeof(IHandleViewModel<,>), viewModelAssemblies);
}

The above code is from my "Nerve Framework" here: https://github.com/janhartmann/nerve-framework/tree/master/NerveFramework.Web.Mvc

janhartmann
  • 13,440
  • 13
  • 78
  • 130
  • 1
    In addition to Jan's answer, you might want to read [this article](https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91). – Steven Jun 24 '15 at 11:34
  • Ow wait, you also use `IHandleViewModel` abstractions. That's the equivalent of [this](https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92). – Steven Jun 24 '15 at 12:46
  • Hmm, more or less. I actually use `public interface IHandleQuery where TQuery : IQuery` on my query side – janhartmann Jun 24 '15 at 12:51
  • 1
    Heavily inspired by your blog :-) – janhartmann Jun 24 '15 at 12:53