Smart asp.net mvc wizard part 3
Apply to asp.net core MVC
Again we will build a wizard with server side code only (no client side at all).
For online demo click here for source code click here.
Steps to create the wizard in asp.net core MVC
Note:
- Please keep in your mind the main trick of the wizard is redirect from step to another (it’s all about redirection).
- Each Step is an action with Get mode and Post Mode
Configuration File (appsettings.json):
Below is the configuration file for two wizards steps where each step is an action in controller , step has id (must be unique) , action name , controller name and step title to be used in bookmark.
"Wizards": {
"Sample1": [
{
"id": "1",
"action": "Step1",
"controllername": "Sample1",
"title": "Step 1"
},
{
"id": "2",
"action": "Step2",
"controllername": "Sample1",
"title": "Step 2"
},
{
"id": "3",
"action": "Step3",
"controllername": "Sample1",
"title": "Step 3"
},
{
"id": "4",
"action": "Step4",
"controllername": "Sample1",
"title": "Step 4"
},
{
"id": "5",
"action": "Step4",
"controllername": "Sample1",
"title": "Step 5"
}
],
"Sample2": [
{
"id": "1",
"action": "Step1",
"controllername": "Sample2",
"title": "Step 1"
},
{
"id": "2",
"action": "Step2",
"controllername": "Sample2",
"title": "Step 2"
},
{
"id": "3",
"action": "Index",
"controllername": "Login",
"title": "Step 3"
},
{
"id": "4",
"action": "Index",
"controllername": "Membership",
"title": "Step 4"
}
]
}
WizardBaseController:
This is the base controller (heart) for any controller that we want to use as a step in the wizard .
- Constructor : has two parameters:
- IConfiguration: the .net core configuration to load steps from JSON file.
- IWizardNavigatorService<WizardStep>: The interface for the service that save wizard state I have created my own implementation NavigatorPersistenceService to save wizard states in MS SQL database then register it in Startup file (as you know in .net core) , you can create your own implementation.
- BaseWizard<WizardStep>:Property for the wizard itself (BaseWizard<WizardStep>)
- stepCompleted: Property to track the state of the step (stepCompleted) … default is false
- Navigator: property for the wizard … details info in part 2
- WizardView: view model property for BaseWizard<WizardStep> ... details later in this article
- WizardName: abstract property to be implemented with each step controller so wizard builder will load the proper wizard steps from JSON configuration file.
- StepResult: method return IActionResult : as you know the wizard step will be an action method in a controller so each action method must return StepResult and based on calling StepResult wizard will move for example :
- if current step (Action) is completed (based on logic in the view) and everything is fine the Action will return StepResult with stepCompleted parameter set to true then base controller will updated wizard state in database and move to next step …later more details.
- if current step (Action) is NOT completed the Action will return StepResult with stepCompleted parameter set to FALSE which mean stay in current step and don’t move next
- Bookmark: each time bookmark button clicked in the wizard I will call this method from base controller and call Go method from the bookmark.
- OnActionExecuted: this method will be executed after calling any action which mean it will be called after calling (StepResult),in this method I will do the following:
- Read the command Id from query string when someone click on any command Next ,Finish or previous (I have 3 commands in my sample)
- Fetch command based on its Id from Wizard property
- Call Execute method for the command with parameters( ActionExecutedContext && stepCompleted)
- OnActionExecuting: I use this method only to prevent user from playing with URL and move from uncompleted step to another one(all wizard bout redirections from step to another)
LinearWizard:
I have created a linear wizard by inheriting from BaseWizard
you can inherit from base class and create as much you want based on your need for example you can create vertical wizard.
You must implement CreateNavigationCommands (more details in part 2).
I will create three commands:
- NextNavigationCommand : command with text value = Next , will be shown only if active step is not last step and has an OnExecute method which do the following:
- If isStepCompleted parameter true will change current step state to be completed , save wizard state and reload the page (it’s all about redirection)
- If not completed OnExecute will do nothing
- OnExecute method take ActionExecutedContext from controller WizardBaseController from method OnActionExecuted.
- PreviousNavigationCommand : command with text value = Previous , will be shown if active step is not first step and has an OnExecute method which do the following:
- isStepCompleted has no meaning here (bad design :( )
- When Navigator fire MoveToPreviousStep the active step index will be changed to previous step and save state in data then redirect to active step controller and action.
- OnExecute method take ActionExecutedContext from controller WizardBaseController from method OnActionExecuted
- FinishNavigationCommand : I will show this command only if user in last step and when he click on finish command I will check if stepCompledted == true then will redirect user to thank full page /Home/Completed , otherwise I will do nothing.
I suggest that you add SkipCommand and allow user to skip the step in case the step index id or title meet some business requirements.
Bookmark:
View model for Bookmark class in object model, it will be used to display the bookmark bar in top of wizard and each time user click on bookmark button (if enabled) a method in wizard base controller (public IActionResult Bookmark(string stepId)) will be executed and redirect the user to target step.
LinearWizardBuilder:
Builder factory class based on builder pattern to build a LinearWizard .
object it will do the following:
- Load steps from JSON configuration file based on wizard name
- Create steps (WizardStepCollection) for each wizard
- Create Wizard Navigator … check part 2 for more in
- Create LinaerWizard based on (1,2 and 3)
- Start wizard
- Return wizard … LinearWizard
Note: in Step class there a dictionary where I used it to store Controller & Action for any step loaded from JSON file.
WizardViewModel:
This the view model of the wizard that used in ASP.NET MVC Core Views.
in wizardBaseController each time StepResult called I store WizardViewModel in ViewBag to use it in Views (this.ViewBag.WizardView = this.WizardView;) … check code wizardBaseController
Wizard View model contains the following properties:
- Wizard … it contain the LinearWizard generated from LinearWizardBuilder
- ControllerName : loaded from dictionary property from step (this.Wizard.ActiveWizardStep["controllername"])
- Action : loaded from dictionary property from step return (string)this.Wizard.ActiveWizardStep["action"];
- WizardTitle
- Commands : list of commands view
- Bookmarks : list of Bookmarks view
Views:
- _LayoutWizard.cshtml (Not Partial View): master layout for any action that want be a step in wizard, I try to use partial view but the problem happened when you want load or execute logic when loading the step (you want inject some services in controller … I know there is components in .net core but it will not help when Posting an action).
- In _LayoutWizard will load WizardViewModel from View Bag (if you remember we filled view Bag in StepResult method in WizarBaseController).
Details:
- @RenderBody() will render the view and inject any services in your view.
- @Action loaded from WizardViewModel … active step index
- @ControllerName load from WizardViewModel … active step action
- @Partial("_Bookmarks", wizard.Bookmarks) .. execute partial view _Bookmarks.cshtml
- @Partial("_Commands", wizard.Commands) … execute atrial view _Commands.cshtml
Commands View |
Your questions is more than welcome, wish you all luck.