Introduction to PostWizard
PostWizard is a set of extensions for ASP.Net MVC3. It allows you to persist the state of your model between different Views and Actions without using the Session object. The model state will be turned into a JSON string and added to your HTML form as a hidden field. The values inside the state object can then be connected to your model and kept in sync between views. I designed it to help me to implement a Wizard style interface if the site will exist behind a load balancer and where using traditional Session State is not desirable.
I'm releasing the code under the GNU GENERAL PUBLIC LICENSE, the NuGet package contains the license details.
Example Code
If you want to take a shortcut and download the example as a Visual Studio 2010 project, just click here.
We assume that you have already created an MVC3 application and used NuGet to add the PostWizard package to the project. Apart from that we'll talk you through everything to get a simple example working.
1.0 Creating the State Object
The first thing we need to do is create an object that contains the model state that we need to save between views. This is pretty easy, just add a class like the one shown below into your /Models folder.
Note that this class has been decorated with the [HiddenState] attribute. This tells the PostWizard framework to JSON serialise the object and save it in a hidden form field. Here is an example of that:
Models/MyModelState.cs
using System;
using PostWizard;
namespace PostWizardBasicExample.Models
{
[HiddenState]
public class MyModelState
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
NOTE: Don't forget that you'll need to add the 'using' statement to be able to add the attribute, like this: using PostWizard;
2.0 Creating Models
The next thing that we need to do is create some model classes (one for each view). They need to inherit from the WizardModelBase class, providing the name of the State object (the one we just created in section 1) as a generic type parameter. This tells the PostWizard framework that we're connecting some of the values in the State object to this model. To make it easy, we're just going to put them all in one file in the /Models folder. Here's an example of that:
Models/MyModels.cs
using System;
using System.ComponentModel.DataAnnotations;
using PostWizard;
namespace PostWizardBasicExample.Models
{
public class MyModelPage1 : WizardModelBase<MyModelState>
{
[ConnectedTo("FirstName")]
[Display(Name = "First Name")]
public string FirstName_UI { get; set; }
}
}
Notice that in this case we're only including fields for one of the values in the State object. In these models we will only need to add fields that are being edited in the UI. Also notice the [ConnectedTo] attribute that we have used. This makes sure that the value in the UI is synchronised with the value in the model.
We need to create a model for each step of the Wizard, each model only needs to contain the fields that will be updated in the UI, so continuing with our example, we can also add this to the same file:
public class MyModelPage2 : WizardModelBase<MyModelState>
{
[ConnectedTo("LastName")]
[Display(Name = "Last Name")]
public string LastName_UI { get; set; }
}
So ... we now have the models in place for a two-stage Wizard. We can input a single value on each page. These values will be automatically saved in the State object which will be available to each of the models (since the State object comes from the base class).
3.0 Creating some views
The next thing that we're going to do is add some views so that we can actually navigate between the stages of the Wizard. We will create a view for each of the Wizard steps. This is pretty normal MVC3 stuff, the only important things are:
- to configure the Back and Next buttons correctly
- we need to use Html.BeginPostWizardForm() instead of Html.BeginForm()
We configure the buttons by giving them a name= attribute. This attribute needs to match an attribute given to an Action in the controller. That's important otherwise the buttons won't work, we'll see more about that when we build the controller.
Using Html.BeginPostWizardForm() is very simple, just use this command to create the html form: Html.BeginPostWizardForm(m=>m.State) - it's that easy. Notice that we're telling the form how to find the state object by providing an expression in the usual way.
So, here's an example view for Step 1 of our example:
Views/Home/Step1.cshtml
@using PostWizard
@model PostWizardBasicExample.Models.MyModelPage1
@{
ViewBag.Title = "Step 1";
}
<h2>Step 1</h2>
@using (Html.BeginPostWizardForm(m => m.State)) {
<div>
<fieldset>
<legend>Enter Your First Name</legend>
<div class="editor-label">
@Html.LabelFor(m => m.FirstName_UI)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.FirstName_UI)
</div>
<p>
<input type="submit" name="Page1_Back" value="Back" />
<input type="submit" name="Page1_to_Page2" value="Next" />
</p>
</fieldset>
</div>
}
...and here's the next view, for Step 2:
Views/Home/Step2.cshtml
@using PostWizard
@model PostWizardBasicExample.Models.MyModelPage2
@{
ViewBag.Title = "Step 2";
}
<h2>Step 2</h2>
@using (Html.BeginPostWizardForm(m => m.State)) {
<div>
<fieldset>
<legend>Enter Your Last Name</legend>
<div class="editor-label">
@Html.LabelFor(m => m.LastName_UI)
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.LastName_UI)
</div>
<p>
<input type="submit" name="Page2_backto_Page1" value="Back" />
<input type="submit" name="Page2_to_End" value="Finish" />
</p>
</fieldset>
</div>
}
So that's all there is to creating the views. It's pretty normal MVC3 stuff, except we're using a slightly different method to generate the html form. Now all we need to do is build the controller, which will make everything hang together. We just need to remember what name= parameters we have given to the input tags.
3.0 Building the Controller
The first thing that we need to do is to create an initial Action that will respond to an HTTP GET command. Without that we won't be able to start the Wizard. What we're going to do is put a link on the homepage of our MVC site with an ActionLink like this: @Html.ActionLink("your link text" , "Wizard") so we will need something that responds to an HTTP GET for the resource called Wizard. This is just normal MVC stuff. So let's add something to /Controllers/HomeController.cs to do that, like this:
public ActionResult Wizard()
{
return View("Step1");
}
As you can see, all we're doing is showing the first view in our Wizard. Now we've got that far, we need to hook up those buttons we added to the first step, let's do that by also adding this to /Controllers/HomeController.cs:
[WizardButton("Page1_Back")]
[HttpPost]
public ActionResult Page1_Back()
{
// just quit
return RedirectToAction("Index");
}
[WizardButton("Page1_to_Page2")]
[HttpPost]
public ActionResult Page1_to_Page2(MyModelPage1 model)
{
// go to the next page
return View("Step2", model.To<MyModelPage2>());
}
These actions will fire when the buttons are pressed on the first step. They both need to have the [HttpPost] attribute, because we've used submit buttons which means an HTTP POST will send the data back to us. You can also see how we've connected the right button to the right Action, this achieved with the [WizardButton] attribute. You'll note that the name given to those attributes matches the name we gave the buttons in the views. That's how the button becomes attached to the right controller action.
The first button just rediects back to the home page (this example will just go off and do an HTTP GET). For the second button, we need to switch from the model of the first view to the model of the second, otherwise the next view won't understand what we're sending to it. This is achieved by calling model.To<MyModelPage2>() the base class behind all our model objects has a To<T>() method to do the work for us.
Well that's it for the first step of the wizard. Everything is done, all we need to do now is follow a similar process for the second step of the Wizard, so let's do that (we're still adding to /Controllers/HomeController.cs):
[WizardButton("Page2_backto_Page1")]
[HttpPost]
public ActionResult Page2_backto_Page1(MyModelPage2 model)
{
// go back to the last page
return View("Step1", model.To<MyModelPage1>());
}
[WizardButton("Page2_to_End")]
[HttpPost]
public ActionResult Page2_to_End(MyModelPage2 model)
{
// we've finished, get the latest data from the State object
ViewBag.Message = string.Format("Hello {0} {1}.",
model.State.FirstName,model.State.LastName);
return View("Index");
}
This actually completes our trivial wizard example. Everything should work OK so try it out! Be sure to go back and forth between the two wizard steps and see how your first name is remembered when you go back from the second wizard step to the first one. Then, when you go farward again your second name will have been remembered too. Cool.
These final couple of Actions are pretty similar to the ones we added for the first step of the wizard. Normally, when the final button is clicked we'd do something with the data. We're simply using it to show a greeting back on the home page. But you can see that when the final button is pressed we're getting the data straight from the State object, this is where everything has been stored for us.
4.0 Extending the demo
I have added some more advanced example code here, it shows how to use PostWizard with more complex features like:
- adding a summary page to the end of the Wizard steps
- using validation attributes
- modifying the model data on the server side between views
- updating collections in the model
- updating model properties which have sub-properties
...all those things can be done with the current version of PostWizard, so feel free to take a look at the examples.
5.0 Comments, ideas, bug reports
If you want to get in touch, you can reach me via my blog page.
6.0 History
List of releases:
- v0.38 Initial release on NuGet
For the latest version of this page, see: http://www.codehosting.net/blog/BlogEngine/page/PostWizard.aspx