Tuesday 1 May 2018

Smart asp.net mvc wizard part 3

Smart asp.net mvc wizard part 3

Apply to asp.net core MVC



Here we will apply the object model to an asp.net MVC core web application to create dynamic wizard that read steps from JSON configuration file and store the state in MS SQL Database.

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:
  1. Please keep in your mind the main trick of the wizard is redirect from step to another (it’s all about redirection).
  2. 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 .
  1. 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.
  1. BaseWizard<WizardStep>:Property for the wizard itself (BaseWizard<WizardStep>) 
  1. stepCompleted: Property to track the state of the step (stepCompleted) … default is false 
  1. Navigator: property for the wizard … details info in part 2 
  1. WizardView: view model property for BaseWizard<WizardStep>  ... details later in this article
  1. WizardName: abstract property to be implemented with each step controller so wizard builder will load the proper wizard steps  from JSON configuration file. 
  1. 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 
  1. Bookmark: each time bookmark button clicked in the wizard I will call this method from base controller and call Go method from the bookmark.
  1. 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) 
  1. 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:

  1. 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.
  1. 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 

  1. 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:
  1. Load steps from JSON configuration file based on wizard name
  2. Create steps (WizardStepCollection) for each wizard
  3. Create Wizard Navigator … check part 2 for more in
  4. Create LinaerWizard based on (1,2 and 3)
  5. Start wizard
  6. 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:
  1. Wizard … it contain the LinearWizard generated from LinearWizardBuilder 
  1. ControllerName : loaded from dictionary property from step (this.Wizard.ActiveWizardStep["controllername"]) 
  1. Action : loaded from dictionary property from step return (string)this.Wizard.ActiveWizardStep["action"]; 
  1. WizardTitle 
  1. Commands : list of commands view 
  1. Bookmarks : list of Bookmarks view 

Views:
  1. _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). 
  2.  In _LayoutWizard will load WizardViewModel from View Bag (if you remember we filled view Bag in StepResult method in WizarBaseController).
   
_LayoutWizard.cshtml
_LayoutWizard.cshtml      


  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 

  1. _Bookmarks.cshtml      
_Bookmarks.cshtml
Bookmarks


  1. _Commands.cshtml:

_Commands
Commands View





Your questions is more than welcome, wish you all luck.

4 comments:

  1. A made a very good use of it... Thank you very much!!!!

    ReplyDelete
    Replies
    1. HI, can you help me to use this asp.net mvc

      Delete
  2. Looks good, just working on keeping history when going forward and back, No luck yet. If anyone wants to know how to create the db, try this link https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/migrations?view=aspnetcore-3.0

    ReplyDelete
  3. Can you use it with aspnet core razor pages

    ReplyDelete