Creating a Multi-Step Form in ASP.NET Core MVC with Separate ViewModels for Each Step
Multi-step forms are a great way to simplify lengthy forms by breaking them into smaller, manageable parts. This approach improves user experience and helps reduce form abandonment. In this guide, we’ll show you how to create a multi-step form in ASP.NET Core MVC using separate ViewModels for each step, while also showing how to use a MultiStepFormModel class to manage data across steps.
Why Use Separate ViewModels?
Using a single model for an entire form may seem convenient, but it can create challenges:
- Fields for future steps can trigger validation errors when submitting the current step.
- Managing validation logic becomes more complex and error-prone.
By using separate ViewModels:
- Validation logic is isolated for each step, reducing complexity.
- Code is easier to maintain and debug.
- Users encounter fewer errors during the process.
Additionally, the MultiStepFormModel class allows us to consolidate data across steps for easier processing at the end.
Step-by-Step Guide
Define the MultiStepFormModel Class
This class acts as the overarching model, consolidating all the data from individual steps. It does not handle validation but is used to collect and store form data.
public class MultiStepFormModel { // Step 1 fields public string FirstName { get; set; } public string LastName { get; set; } // Step 2 fields public string Email { get; set; } public string Phone { get; set; } // Step 3 fields public string Address { get; set; } public string City { get; set; } public string County { get; set; } public string Postcode { get; set; } }
This class will be used to consolidate all data from the separate ViewModels.
Define ViewModels for Each Step
Each step is represented by its own ViewModel, which includes only the fields and validation logic for that step.
public class Step1ViewModel { [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } } public class Step2ViewModel { [Required, EmailAddress] public string Email { get; set; } [Required, Phone] public string Phone { get; set; } } public class Step3ViewModel { [Required] public string Address { get; set; } [Required] public string City { get; set; } [Required] public string County { get; set; } [Required] public string Postcode { get; set; } }
Create a Controller to Manage Steps
The controller handles navigation and manages the state of the form data. Data from each step is saved into the MultiStepFormModel using session storage.
Example Controller
public class MultiStepFormController : Controller { private const string SessionKey = "MultiStepForm"; [HttpGet] public IActionResult Step1() { var model = new Step1ViewModel(); return View(model); } [HttpPost] public IActionResult Step1(Step1ViewModel model) { if (!ModelState.IsValid) { return View(model); } var sessionModel = HttpContext.Session.Get<MultiStepFormModel>(SessionKey) ?? new MultiStepFormModel(); sessionModel.FirstName = model.FirstName; sessionModel.LastName = model.LastName; HttpContext.Session.Set(SessionKey, sessionModel); return RedirectToAction("Step2"); } [HttpGet] public IActionResult Step2() { var model = new Step2ViewModel(); return View(model); } [HttpPost] public IActionResult Step2(Step2ViewModel model) { if (!ModelState.IsValid) { return View(model); } var sessionModel = HttpContext.Session.Get<MultiStepFormModel>(SessionKey); sessionModel.Email = model.Email; sessionModel.Phone = model.Phone; HttpContext.Session.Set(SessionKey, sessionModel); return RedirectToAction("Step3"); } [HttpGet] public IActionResult Step3() { var model = new Step3ViewModel(); return View(model); } [HttpPost] public IActionResult Step3(Step3ViewModel model) { if (!ModelState.IsValid) { return View(model); } var sessionModel = HttpContext.Session.Get<MultiStepFormModel>(SessionKey); sessionModel.Address = model.Address; sessionModel.City = model.City; sessionModel.County = model.County; sessionModel.Postcode = model.Postcode; // Process the form data (e.g., save to database) HttpContext.Session.Remove(SessionKey); return RedirectToAction("Confirmation"); } public IActionResult Confirmation() { return View(); } }
Create Views for Each Step
Each step has a separate Razor View that maps to the corresponding ViewModel.
Step 1 View
@model Step1ViewModel <form asp-action="Step1" method="post"> <label for="FirstName">First Name:</label> <input asp-for="FirstName" /> <span asp-validation-for="FirstName"></span> <label for="LastName">Last Name:</label> <input asp-for="LastName" /> <span asp-validation-for="LastName"></span> <button type="submit">Next</button> </form>
Step 2 View
@model Step2ViewModel <form asp-action="Step2" method="post"> <label for="Email">Email:</label> <input asp-for="Email" /> <span asp-validation-for="Email"></span> <label for="Phone">Phone:</label> <input asp-for="Phone" /> <span asp-validation-for="Phone"></span> <button type="submit">Next</button> </form>
Step 3 View
@model Step3ViewModel <form asp-action="Step3" method="post"> <label for="Address">Address:</label> <input asp-for="Address" /> <span asp-validation-for="Address"></span> <label for="City">City:</label> <input asp-for="City" /> <span asp-validation-for="City"></span> <label for="County">County:</label> <input asp-for="County" /> <span asp-validation-for="County"></span> <label for="Postcode">Postcode:</label> <input asp-for="Postcode" /> <span asp-validation-for="Postcode"></span> <button type="submit">Submit</button> </form>
Confirmation View
<h2>Thank You</h2> <p>Your submission has been successfully received.</p>
Add Session Handling Extensions
Use helper extensions to manage session storage:
public static class SessionExtensions { public static void Set<T>(this ISession session, string key, T value) { session.SetString(key, JsonSerializer.Serialize(value)); } public static T Get<T>(this ISession session, string key) { var value = session.GetString(key); return value == null ? default : JsonSerializer.Deserialize<T>(value); } }
Configure Session Services
Enable session storage in the Program.cs file:
builder.Services.AddSession(); builder.Services.AddDistributedMemoryCache(); var app = builder.Build(); app.UseSession(); app.MapControllers(); app.Run();
Benefits of This Approach
- Simplified Validation: Each step’s validation is handled independently.
- Maintainability: Using ViewModels for individual steps and a centralised MultiStepFormModel keeps the code clean and manageable.
- User-Friendly Design: The form is broken into logical steps, improving the user experience.
- Separation of Concerns: Each step operates independently, making debugging and updates straightforward.
Conclusion
By using separate ViewModels for each step and consolidating data into a MultiStepFormModel, you can create a robust, user-friendly multi-step form in ASP.NET Core MVC. This approach simplifies validation, enhances maintainability, and delivers a seamless experience for users. Follow this guide to streamline your form development and provide a polished, professional solution. Happy coding!