I have a multi-step registration process, backed by a single object in domain layer, which have validation rules defined on properties.
How should I validate the domain object when the domain is split across many views, and I have to save the object partially in the first view when posted?
I thought about using Sessions but that's not possible cause the process is lengthy and amount of data is high, So I don't want to use session.
I thought about saving all the data in an relational in-memory db (with the same schema as main db) and then flushing that data to main db but issues arisen cause I should route between services (requested in the views) who work with the main db and in-memory db.
I'm looking for an elegant and clean solution (more precisely a best practice).
UPDATE AND Clarification:
@Darin Thank you for your thoughtful reply,
That was exactly what I've done till now.
But incidentally I've got a request which have many attachments in it, I design a Step2View
e.g. which user can upload documents in it asynchronously ,
but those attachments should be saved in a table with referential relation to another table that should have been saved before in Step1View
.
Thus I should save the domain object in Step1
(partially), But I can't,
cause the backed Core Domain object which is mapped partially to a Step1's ViewModel can't be saved without props that come from converted Step2ViewModel
.
I wanted to share my own way of handling these requirements. I did not want to use SessionState at all, nor did I want it handled client side, and the serialize method requires MVC Futures which I did not want to have to include in my project.
Instead I built an HTML Helper that will iterate through all of the properties of the model and generate a custom hidden element for each one. If it is a complex property then it will run recursively on it.
In your form they will be posted to the controller along with the new model data at each "wizard" step.
I wrote this for MVC 5.
Now for all steps of your "wizard" you can use the same base model and pass the "Step 1,2,3" model properties into the @Html.HiddenClassFor helper using a lambda expression.
You can even have a back button at each step if you want to. Just have a back button in your form that will post it to a StepNBack action on the controller using the formaction attribute. Not included in the below example but just an idea for you.
Anyways here is a basic example:
Here is your MODEL
Here is your CONTROLLER
Here are your VIEWS
Step 1
Step 2
Step 3
Adding more info from @Darin's answer.
What if you have separate design style for each steps and wanted maintain each in separate partial view or what if you have multiple properties for each step ?
While using
Html.EditorFor
we have limitation to use partial view.Create 3 Partial Views under
Shared
folder named :Step1ViewModel.cshtml , Step3ViewModel.cshtml , Step3ViewModel.cshtml
For brevity I just posting 1st patial view, other steps are same as Darin's answer.
Step1ViewModel.cs
Step1ViewModel.cshtml
Index.cshtml
If there some better solution, please comment to let others know.
I would suggest you to maintain the state of Complete Process on the client using Jquery.
This way you can easily build your domain object directly from the form post data and in case the data has errors return valid JSON holding all the error message and display them in a div.
Please split the Steps
First you shouldn't be using any domain objects in your views. You should be using view models. Each view model will contain only the properties that are required by the given view as well as the validation attributes specific to this given view. So if you have 3 steps wizard this means that you will have 3 view models, one for each step:
and so on. All those view models could be backed by a main wizard view model:
then you could have controller actions rendering each step of the wizard process and passing the main
WizardViewModel
to the view. When you are on the first step inside the controller action you could initialize theStep1
property. Then inside the view you would generate the form allowing the user to fill the properties about step 1. When the form is submitted the controller action will apply the validation rules for step 1 only:Now inside the step 2 view you could use the Html.Serialize helper from MVC futures in order to serialize step 1 into a hidden field inside the form (sort of a ViewState if you wish):
and inside the POST action of step2:
And so on until you get to the last step where you will have the
WizardViewModel
filled with all the data. Then you will map the view model to your domain model and pass it to the service layer for processing. The service layer might perform any validation rules itself and so on ...There is also another alternative: using javascript and putting all on the same page. There are many jquery plugins out there that provide wizard functionality (Stepy is a nice one). It's basically a matter of showing and hiding divs on the client in which case you no longer need to worry about persisting state between the steps.
But no matter what solution you choose always use view models and perform the validation on those view models. As long you are sticking data annotation validation attributes on your domain models you will struggle very hard as domain models are not adapted to views.
UPDATE:
OK, due to the numerous comments I draw the conclusion that my answer was not clear. And I must agree. So let me try to further elaborate my example.
We could define an interface which all step view models should implement (it's just a marker interface):
then we would define 3 steps for the wizard where each step would of course contain only the properties that it requires as well as the relevant validation attributes:
next we define the main wizard view model which consists of a list of steps and a current step index:
Then we move on to the controller:
Couple of remarks about this controller:
[Deserialize]
attributes from the Microsoft Futures library so make sure you have installed theMvcContrib
NuGet. That's the reason why view models should be decorated with the[Serializable]
attributeIStepViewModel
interface so for this to make sense we need a custom model binder.Here's the associated model binder:
This binder uses a special hidden field called StepType which will contain the concrete type of each step and which we will send on each request.
This model binder will be registered in
Application_Start
:The last missing bit of the puzzle are the views. Here's the main
~/Views/Wizard/Index.cshtml
view:And that's all you need to make this working. Of course if you wanted you could personalize the look and feel of some or all steps of the wizard by defining a custom editor template. For example let's do it for step 2. So we define a
~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml
partial:Here's how the structure looks like:
Of course there is room for improvement. The Index POST action looks like s..t. There's too much code in it. A further simplification would involve into moving all the infrastructure stuff like index, current index management, copying of the current step into the wizard, ... into another model binder. So that finally we end up with:
which is more how POST actions should look like. I am leaving this improvement for the next time :-)
To supplement on Amit Bagga's answer you will find below what I did. Even if less elegant I find this way simpler than Darin's answer.
Controller :
Models :
Wizards are just simple steps in processing a simple model. There is no reason to create multiple models for a wizard. All you would do is create a single model and pass it between actions in a single controller.
The above coed is stupid simple so replace your fields in there. Next we start with a simple action that initiates our wizard.
This calls the view "WizardStep1.cshtml (if using razor that is). You can use the create template wizard if you want. We will just be redirecting the post to a different action.
The thing of note is that we will be posting this to a different action; the WizardStep2 action
In this action we check if our model is valid, and if so we send it to our WizardStep2.cshtml view else we send it back to step one with the validation errors. In each step we send it to the next step, validate that step and move on. Now some savvy developers might say well we can't move between steps such as this if we use [Required] attributes or other data annotations between steps. And you would be right, so remove the errors on items that are yet to be checked. like below.
Finally we would save the model once to the data store. This also prevents a user that starts a wizard but doesn't finish it not to save incomplete data to the database.
I hope you find this method of implementing a wizard much easier to use and maintain than any of the previously mentioned methods.
Thanks for reading.