1

I found this great answer to how I can do a wizard in ASP MVC.
multi-step registration process issues in asp.net mvc (splitted viewmodels, single model)

I have just one question related to this. What would be the best practice to populate data into the view models?

Lets say in step 2 I have the need to display a list of data to the user. The list data comes from the DB. Would I then go ahead and create a constructor to the view model, or should I populate it in the controller?

This is how my code looks right now.

Model

[Serializable]
public class Step1ViewModel : IStepViewModel
{
    public bool MyProperty { get; set; }
}

[Serializable]
public class Step2ViewModel : IStepViewModel
{
    // This needs to be populated with data, I need to display it in a list
    public List<string> MyList { get; set; }
}

[Serializable]
public class Step3ViewModel : IStepViewModel
{
    public bool MyProperty { get; set; }
}

[Serializable]
public class PublishViewModel
{
    public int CurrentStepIndex { get; set; }
    public IList<IStepViewModel> Steps { get; set; }

    public void Initialize()
    {
        Steps = typeof(IStepViewModel)
            .Assembly
            .GetTypes()
            .Where(t => !t.IsAbstract && typeof(IStepViewModel).IsAssignableFrom(t))
            .Select(t => (IStepViewModel)Activator.CreateInstance(t))
            .ToList();
}

public class PublishViewModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var stepTypeValue = bindingContext.ValueProvider.GetValue("StepType");
        var stepType = Type.GetType((string)stepTypeValue.ConvertTo(typeof(string)), true);
        var step = Activator.CreateInstance(stepType);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => step, stepType);
        return step;
    }
}

public interface IStepViewModel
{
}

Controller

public ActionResult Publish(int? id)
{
    var publish = new PublishViewModel();
    publish.Initialize();
    return View(publish);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Publish([Deserialize] PublishViewModel publish, IStepViewModel step)
{
    publish.Steps[publish.CurrentStepIndex] = step;

    if (ModelState.IsValid)
    {

        if (!string.IsNullOrEmpty(Request["next"]))
        {
            publish.CurrentStepIndex++;
        }
        else if (!string.IsNullOrEmpty(Request["prev"]))
        {
            publish.CurrentStepIndex--;
        }
        else
        {
            // TODO: we have finished: all the step partial
            // view models have passed validation => map them
            // back to the domain model and do some processing with
            // the results

            return Content("thanks for filling this form", "text/plain");
        }
    }
    else if (!string.IsNullOrEmpty(Request["prev"]))
    {
        // Even if validation failed we allow the user to
        // navigate to previous steps
        publish.CurrentStepIndex--;
    }

    return View(publish);
}

So my question is, where would I populate my list for Step2? My first thought would be to have a constructor in the Step2 view model. Second thought would be to have some logic in the controller find out which step im at, and populate it from there. But it all sounds a bit bad.

Community
  • 1
  • 1
Martin at Mennt
  • 5,677
  • 13
  • 61
  • 89

2 Answers2

2

Populate from the controller. Always. You should never be interacting with your context inside of a view model or, worse, an entity. If you want to abstract the database work, move it to a repository or service and then just have your controller call a method on that.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    Thanks, I will add it to the controller then. Next question would be where. I have one controller that initialize the wizard, and one that handles the POST requests. If I add it to the initializer the data will follow through on all steps, is that good practice? Or should I try to add some check in the POST controller, to find out which step im at and inject the data to the correct view model? – Martin at Mennt Jul 31 '15 at 19:16
  • If by "initializer" you mean "constructor", then no, you don't want to do that there. Controllers are instantiated and disposed with each request. If you need to share data between requests, you either need to post everything each time or add it to something like `TempData`. – Chris Pratt Aug 03 '15 at 13:31
0

I ended up with this at the end. But I would really love to get some feedback on this approach. For instance, how could I move this controller mess into the custom model binder?

Snippet

    if (publish.Steps[publish.CurrentStepIndex].GetType() == typeof(Step1ViewModel))
    {
        var model = publish.Steps[publish.CurrentStepIndex] as Step1ViewModel;
        // Do some magic
    }

Full code

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Publish([Deserialize] PublishViewModel publish, IStepViewModel step)
    {
        publish.Steps[publish.CurrentStepIndex] = step;

        if (ModelState.IsValid)
        {
            if (!string.IsNullOrEmpty(Request["next"]))
                publish.CurrentStepIndex++;
            else if (!string.IsNullOrEmpty(Request["prev"]))
                publish.CurrentStepIndex--;
        }
        else if (!string.IsNullOrEmpty(Request["prev"]))
        {
            publish.CurrentStepIndex--;
        }

        if (publish.Steps[publish.CurrentStepIndex].GetType() == typeof(Step1ViewModel))
        {
            var model = publish.Steps[publish.CurrentStepIndex] as Step1ViewModel;
            // Do some magic
        }
        else if (publish.Steps[publish.CurrentStepIndex].GetType() == typeof(Step2ViewModel))
        {
            var model = publish.Steps[publish.CurrentStepIndex] as Step2ViewModel;
            // Do some magic
        }

        return View(publish);
    }
Martin at Mennt
  • 5,677
  • 13
  • 61
  • 89