Its quite possible to do this but I am actually going to plug a library here. I have moved entirely to FluentValidation for my choice of validation frameworks. Mostly as it fits with most other library's that we are now used to.
Basically this framework replaces the attributes you would normally use on your model classes.
There are two nuget packages you need:
The first package is obvious, its the core libraries for FluentValidation, the second is an MVC5 package (there are other frameworks available) that plugs into the MVC validation pipline and handles your model validation. What I really like about this framework is it supports constructor injection (and property injection, only tested with AutoFac).
FYI: You need to register the validator into the MVC pipeline done in you Application_Start
method in your global.asax
or in your OWIN startup class.
Simple as:
FluentValidationModelValidatorProvider.Configure();
Ok.. Enough plugs lets look at a basic model.
public class SampleModel
{
public int StepNumber { get; set; }
public string Step1Validation { get; set; }
public string Step2Validation { get; set; }
}
Very simple model. Basically we have three properties.
StepNumber
: Determines the step we are currently on
Step1Validation
: Simple string we validate as not empty for all steps
Step2Validation
: Simple string again for step 2 onwards.
Now in this model we maintain the step based on the StepNumber
property. This must be present in all request. Next we have our two string properties Step1Validation
will be an input as the first step and validated on all steps. Step2Validation
will be an input just on the second step that is only validated on step 2 or above.
Now a validator.. This is some copy\paste code but is straight forward. Basically we have a class that inherits from AbstractValidator<T>
where T
is the ViewModel class to validate. Next we write some fluent validator methods.
public class SampleModelValidator : AbstractValidator<SampleModel>
{
public SampleModelValidator()
{
this.RuleFor(x => x.Step1Validation)
.NotEmpty()
.When(x => x.StepNumber >= 1)
.WithMessage("Step1 Validation Required");
this.RuleFor(x => x.Step2Validation)
.NotEmpty()
.When(x => x.StepNumber >= 2)
.WithMessage("Step2 Validation Required.");
}
}
Now looking at the method calls (note in the constructor) basically reads as this.
- Create a rule for the Expression Property (Linq Expression)
- As Not Empty (pretty much a required field)
- When the LinqExpression is a match (note this is optional, without this all rules are run)
So we can read. Validate Step1Validation
as NotEmpty
when StepNumber is greater or equal to 1
.
Now to plug the validator into the class you must decorate the class with the ValidatorAttribute(Type type)
attribute as such..
[Validator(typeof(SampleModelValidator))]
public class SampleModel
{
//omiteed
}
Finally our controller and the view (note these very basic).
Controller:
[HttpGet]
public ActionResult Index()
{
var model = new SampleModel();
model.StepNumber = 1;
return View(model);
}
[HttpPost]
public ActionResult Index(SampleModel model)
{
if (model == null)
{
model = new SampleModel();
return View(model);
}
if (!ModelState.IsValid)
return View(model);
model.StepNumber++;
return View(model);
}
View:
@using (Html.BeginForm())
{
@Html.HiddenFor(x => x.StepNumber)
if (Model.StepNumber == 1)
{
<div class="row">
<div class="col-md-4">
@Html.LabelFor(x => x.Step1Validation)
</div>
<div class="col-md-8">
@Html.EditorFor(x => x.Step1Validation)
@Html.ValidationMessageFor(x => x.Step1Validation)
</div>
</div>
<div class="row">
<button type="submit" class="btn btn-primary">Next</button>
</div>
}
else if (Model.StepNumber == 2)
{
@Html.HiddenFor(x => x.Step1Validation)
<div class="row">
<div class="col-md-4">
@Html.LabelFor(x => x.Step2Validation)
</div>
<div class="col-md-8">
@Html.EditorFor(x => x.Step2Validation)
@Html.ValidationMessageFor(x => x.Step2Validation)
</div>
</div>
<div class="row">
<button type="submit" class="btn btn-primary">Next</button>
</div>
}
else
{
<h4>All Done</h4>
}
}
Now again this does require another library but from my experience this library is worth the package reference.