ASP.NET Core MVC Simple Wizard

THE wizard control, I love those. The first wizard I remember was a Windows95 setup wizard for all software installers. On Windows Forms platform the wizard UI design pattern is a very nice way to break long processes into step by step.

I’m not a Windows Forms developer converted to a web developer, I started with the web, but touched Forms as well and on ASP.NET WebForms (back then just ASP.NET) the abstraction of UI Web Controls lacked THE wizard control and it was eventually introduced in later versions of ASP.NET.

When I started using MVC immediately mimicked the way of creating a wizard. The wizard in the web had some constraints:

  • Dependable steps by steps.
  • Previous, next, finish, cancel buttons.
  • Same URL, the user see the same URL all the time, there is no `?step=1` and then validate the step is right.

This is a very simple wizard I had to create for a checkout-like process in ASP.NET Core with a single URL all the time, and depending on a persisted state, it will enable or disable the actions to handle that request.

Every wizard step is defined by one or many MVC actions. They are decorated with WizardStep attribute that will decide if the action is enabled or not.

[WizardStep(0)] [ActionName("Checkout")] public IActionResult CheckoutStep0Get(Guid checkoutId) { //... return View("Checkout0"); } [WizardStep(0)] [ActionName("Checkout")] [HttpPost, ValidateAntiForgeryToken] public IActionResult CheckoutStep0Post(Guid checkoutId, string go) { if (!ModelState.IsValid) { return View("Checkout0"); } //...

if (go == "Next") { _checkoutRepository.MoveNext(checkout); } else { _checkoutRepository.MovePrevious(checkout); } return RedirectToAction("Checkout", new { checkoutId = checkoutId}); }

In this sample, there is a wizard step 0, two actions handle that step, CheckoutStep0Get and CheckoutStep0Post. MVC will use the HTTP verb to to enable or disable the actions and the wizard attribute on top of that will decide based on the current step which one to enable or disable as well.

Here’s the WizardStep attribute:

      [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]     public class WizardStepAttribute : ActionMethodSelectorAttribute     {         ///          /// Indicates the step          ///          public int Step { get; private set; }          ///         /// A provider to get steps based on current request data         ///          public Type WizardStepProviderType { get; set; }          public WizardStepAttribute(int step)         {             Step = step;         }          public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)         {             IWizardStepProvider stepsProvider;              if (WizardStepProviderType != null && typeof(IWizardStepProvider).IsAssignableFrom(WizardStepProviderType))             {                 stepsProvider = routeContext.HttpContext.RequestServices                     .GetService(WizardStepProviderType) as IWizardStepProvider;             }             else             {                 stepsProvider = routeContext.HttpContext.RequestServices                     .GetService(typeof(IWizardStepProvider)) as IWizardStepProvider;             }              if (stepsProvider == null)             {                 throw new Exception($"Can't create an instance of type '{WizardStepProviderType}'");             }              int currentStep = stepsProvider.GetCurrentStep(routeContext.HttpContext);              return Step == currentStep;         }     }

The ICheckoutStepsProvider is requested from the DI container or the type specified in the member is used. It will provide the current step we are in, I used a QueryString to describe the current checkout id, then use it to retrieve from storage that checkout object.

Download this sample project from Github to play around with the simple wizard. Download