I am attempting to write a generic framework to assist with developing wizard-style forms.
I have a model with properties representing each step in the wizard, e.g.
public class ExampleWizardTransaction : WizardTransaction
{
public override TransactionType TransactionType { get; set; } = TransactionType.ExampleWizard;
public override string ControllerName { get; set; } = "WizardExample";
[DisplayName("Client Details")]
public ClientDetails ClientDetails { get; set; } = new ClientDetails();
[DisplayName("Client Questions")]
public ClientQuestions ClientQuestions { get; set; } = new ClientQuestions();
[DisplayName("Client Preferences")]
public ClientPreferences ClientPreferences { get; set; } = new ClientPreferences();
}
[Step(1)]
public class ClientDetails : IStep
{
[Display(Description = "Please enter your first name")]
public string FirstName { get; set; }
[Display(Description = "Please enter your last name")]
public string LastName { get; set; }
}
[Step(2)]
public class ClientQuestions : IStep
{
[DisplayName("What is your favourite car?")]
public string FavouriteCar { get; set; }
[DisplayName("What is your favourite holiday destination?")]
public string FavouriteDestination { get; set; }
}
[Step(3)]
public class ClientPreferences : IStep
{
[DisplayName("Red or Blue?")]
public Colours Colour { get; set; }
[DisplayName("Do you like Indian food")]
public bool LikeFood { get; set; }
}
Initially, I had a partial view for each wizard step which looked like this:
@model Web.Models.ExampleWizard.Create
<div class="row">
<div class="col-md-6">
@Html.EditorFor(x => x.ExampleWizardTransaction.ClientDetails)
</div>
</div>
Using this, the values of my form bind up correctly since when I post it up MVC knows the binding context.
On my form, I render the partial view by passing in the step number, e.g.
Html.RenderPartial($"_CreateWizard_{Model.ExampleWizardTransaction.CurrentStep}", Model);
I'm attempting to generalise the code, so that I don't need to include a partial view for every step in the wizard.
To do that, I'm rendering an action which determines which type is associated with the wizard step and I return a partial view:
Html.RenderAction("GetStep", "ExampleWizard", Model.ExampleWizardTransaction);
My partial view specifies an interface that each wizard step implements:
_WizardStep.cshtml
@model Services.ViewModels.Wizard.IStep
<div class="row">
<div class="col-md-6">
@Html.EditorFor(x => x)
</div>
</div>
When I use the above, the form renders correctly, but the values no longer bind up on the POST, which I assume is because it doesn't have the binding context for the properties (e.g. the id and name of the input types aren't fully qualified).
I have an EditorFor template for the string properties on my wizard steps which renders a textbox:
@model string
<div class="col-md-12">
<div class="form-group">
<label class="m-b-none" for="@ViewData.Model">
@ViewData.ModelMetadata.DisplayName
</label>
<span class="help-block m-b-none small m-t-none">@ViewData.ModelMetadata.Description</span>
<div class="input-group">
@Html.TextBox("", Model, new {@class = "form-control"})
<div class="input-group-addon">
<i class="fa validation"></i>
</div>
</div>
</div>
</div>
Is it possible to use my generic "_WizardStep.cshtml" partial view and still bind up the properties for the current step to my model?
My controller looks like this:
[HttpPost]
public virtual ActionResult CreateWizard(Create model, string action)
{
var createModel = CreateModel<Create>();
switch (createModel.Save(action))
{
case WizardState.Finished:
return RedirectToActionWithMessage("List", "Transaction", "Completed", ToastNotificationStatus.Success);
case WizardState.Ongoing:
return RedirectToAction(MVC.ExampleWizard.CreateWizard(
model.ExampleWizardTransaction.Id,
model.ExampleWizardTransaction.GetNextStep(action)));
default:
model.MapExistingTransactions<ExampleWizardTransaction>();
return View(model);
}
}
My 'Create' model contains my 'ExampleWizardTransaction' property which contains each of the wizard steps which implement the IStep interface.