Search code examples
c#asp.net-mvcdatabasesessiondata-persistence

ASP.NET MVC persisting data across multiple wizard steps using database tables


What is the is the best approach to implement data persistence using database tables over several wizard steps in ASP.NET MVC?

At the moment we are using a session to persist alot of data across several wizard steps/views. We are experiencing an issue and we suspect that session timeouts are to blame. For this reason we have decided to replace sessions with database tables.

So far we've established that we need the following:

  • When the user hits the first page a unique id/token (possibly database primary key) is generated that will determine where data is stored throughout the workflow. This id/token is persisted using the URL, if at all possible, so that we don't need to re implement sessions

  • Separate tables for each wizard view/step. We have implemented logic in each action that test wizard step objects stored in the session to ensure that the user can't skip steps in the workflow. Implementing similar testing with database data persistence would be easier with data separated into different tables as opposed to a single table

  • Storing an expiration time stamp somewhere in the record associated with the provided id/token as to imitate session timeouts e.g when processing the posted form if the current date time is greater than the stored date time stamp deny the request

  • Use entity framework to push and pull data

We are just having a hard time figuring out how to implement this in code. We came across https://web.archive.org/web/20211020145945/https://www.4guysfromrolla.com/webtech/041600-2.shtml which was somewhat helpful but didn't really explain how to implement it in an ASP.NET MVC controller.

We have provided a snippet of code below to provide some understanding of how we are currently doings things using sessions:

Controller

[HttpGet]
public ActionResult StepOne() {
    StepOneViewModel stepOneModel;
    WizardViewModel wizard = (WizardViewModel)Session["Wizard"];

    if(wizard == null || wizard.StepOne == null)
        stepOneModel = new StepOneViewModel();

    else
        stepOneModel = wizard.StepOne 

    return View();
}

[HttpPost]
public ActionResult StepOne()
{
    //validate and store data into wizard session object
}

public ActionResult StepTwo()
{
    StepTwoViewModel stepTwoModel;
    WizardViewModel wizard = (WizardViewModel)Session["Wizard"];

    if(wizard == null || wizard.StepOne == null)
        return RedirectToAction("StepOne");

    if(wizard.StepTwo == null)
        stepTwoModel = new StepTwoViewModel();

    else
        stepTwoModel = wizard.StepTwo;

    Session["Wizard"] = wizard;

    return View();
}

Wizard Model

public WizardViewModel
{
   public StepOne { get; set; }
   public StepTwo  { get; set;}
}

What is the best way to implement this approach? How do we create/track this unique token that determines where data is pulled from? How do we test that users have completed previous steps and not tried to skip? Any controller code/thoughts on how we can implement this approach are appreciated.


Solution

  • Here is how we have implemented a similar pattern in the past. I am assuming here that your users are not registered in the system (if they are then it's a bit easier)

    1. First step on the wizard is to gather the users email address.

    2. We generate a unique token for this session and embed it into url and email it to the user. This can be used by the user to come back at any time and complete the workflow. You need to make the token sufficiently large so people cant just go guessing random tokens.

    Note: We actually generate a Token Id which we use internally to map to the workflow process. We also produce a hash that is embedded into the url and sent to the user.

    1. Rather than separate tables for each step, we use our normal entity tables and over the top of those, we have a table that links the user token to the entity data and indicates a state, which we use to determine how far through the wizard they are. That means you have a base route, that determines the current step and transfers to the correct route for that step.

    2. The tokens are routinely cleaned up, i.e. tokens older than 2 weeks are removed. The url the user was emailed still works but takes them to a new workflow, and the token is reused for a new session.

    3. Finally if the workflow is completed, we can delete the token from the system.

    If the users are registered into the system, then you don't need to email them, you just need to link your workflow to the user somehow.