0

I'm building a MVC application and came across a question i cant seem to get a real good answer too. The user that will use this application will go thru a series of steps filling in forms. The first step is a basic simple form with only a choice between categories and a name. The next step will be a little more information. To get this to work i have a ViewModel that i use in step 1 and 2. The problem is in the ViewModel i want to use annotation for required fields and other validation. There are fields in step 2 that i want to make required but not visible in step 1. What would be the best approach? Should i make 2 seperate ViewModels. Is there a way to specify when the field is required?

Update1: So i found this article: multi-step registration process issues in asp.net mvc (splitted viewmodels, single model) and this looks pretty much what i want. The only problem i see here is that based on user input im presenting different ViewModels. For example if the user chose a category Airplanes i want to use different ViewModels then when the user choses Cars. This will mean ill get 3 ViewModels per category. Will that make sense?

Community
  • 1
  • 1
blaataap
  • 219
  • 2
  • 12
  • If you wanting to perform validation on step 1 before moving to step 2, [this answer](http://stackoverflow.com/questions/25643394/mvc-force-jquery-validation-on-group-of-elements/25645097#25645097) may help –  Aug 11 '15 at 11:43
  • Check this http://stackoverflow.com/questions/31626652/how-to-ignore-a-specific-property-when-editing-an-entity-via-an-mvc-edit-page – Ahmed Aug 11 '15 at 11:58
  • @Ahmed thanks, thats actually what im doing right now. Im building ViewModels that ill automap to the Business model. I think ill go for a "Container" Viewmodel that has Step1ViewModel and Step2ViewModel as childeren. I've found this article to be helpful: http://stackoverflow.com/questions/6402628/multi-step-registration-process-issues-in-asp-net-mvc-splitted-viewmodels-sing/6403485#6403485. – blaataap Aug 11 '15 at 12:18
  • That's a very good answer. Regarding your update check this link http://www.codeproject.com/Articles/613610/Dynamic-View-Model – Ahmed Aug 11 '15 at 12:36

1 Answers1

0

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.

  1. StepNumber : Determines the step we are currently on
  2. Step1Validation : Simple string we validate as not empty for all steps
  3. 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.

  1. Create a rule for the Expression Property (Linq Expression)
  2. As Not Empty (pretty much a required field)
  3. 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.

Nico
  • 12,493
  • 5
  • 42
  • 62