Alternate MVC Screen Navigation

Suppose you have a simple sequential screen flow in your application like this:

One typical scenario for this is that each screen has its own associated Controller (for the example we'll say Controller1, Controller2, etc. but obviously they would have meaningful names in the real world).  When the user submits Screen1 then the code Controller 1 will have something like this:

return this.RedirectToAction("Index", "Controller2");

In fact, each controller would have to have code like this that essentially "points to" the next controller in the sequential screen navigation. But if Screen 2 and Screen 4 swapped positions in the order, you'd have to:

  • Change the code in Screen 1 to point to Screen 4 instead of 2
  • Change the code in Screen 4 to point to Screen 3 instead of 5
  • Change the code in Screen 2 to point to Screen 5 instead of 3

So in a case like this, it might make more sense to encapsulate this sequential order in a single place.  The goal would be to be able to change the code in each controller to something like this:

return this.RedirectToNextAction();

It's actually not very much work to implement something like this.  First, you'd want to encapsulate everything in some type of "PageManager" class that has a GetNextStepFrom() method where you give it a controller name and it gives you back the next controller.  I've oversimplified an example in the interests of keeping this example simple:

class PageManager
{
    public string GetNextStepFrom(string controller)
    {
        int index = controllerList.IndexOf(controller);
        return controllerList[index + 1];
    }
 
    private List<string> controllerList = InitializeControllerSequence();
 
    private static List<string> InitializeControllerSequence()
    {
        return new List<string> {
            "Controller1",
            "Controller2",
            "Controller3",
            "Controller4",
            "Controller5"
        };
    }
}

At this point, we're ready to implement our RedirectToNextAction() method which is what makes all this work. The best way is to sub-class all your controllers and put this protected method in the base class:

protected ActionResult RedirectToNextAction()
{
    var controllerName = this.RouteData.Values["Controller"].ToString();
    var nextController = PageManager.GetNextStepFrom(controllerName);
    return this.RedirectToAction("Index", nextController);
}

Notice that to get the name of the current controller, I'm having to get it from the RouteData dictionary.  What would be really nice if I could just do this.Name to get the name of the controller.  If you use the MVC T4 templates, it does give each controller class a Name property (although right now it's actually a public field) but it's not usable for this implementation in its current format because the Name property is not on the base class so can't be used polymorphically.  Note that in C# 4.0, you could rewrite line #3 above to be:

dynamic thisController = this;
var controllerName = thisController.Name;

This would be leveraging a form of duck typing but may be overkill for a scenario like this since you're essentially relying on Reflection at this point.

In any case, scenarios like these are good candidates for encapsulating the concern of screen navigation flow to an independent component outside the controllers.