在asp.net mvc(分割视图模型,单一模型)中的多步注册过程问题

我有一个多步注册过程 ,由域层中单个对象支持,它们具有在属性上定义的validation规则。

当域被拆分成多个视图时,我应该如何validation域对象,并且我必须在发布时将对象部分保存在第一个视图中?

我想过使用会话,但这是不可能的,因为过程是漫长的,数据量很高,所以我不想使用会话。

我想将所有的数据保存在关系型内存数据库(与主数据库具有相同的模式),然后将数据刷新到主数据库,但问题出现,因为我应该在服务之间路由(请求在视图中)主数据库和内存数据库。

我正在寻找一个优雅和干净的解决scheme(更确切地说是一个最佳实践)。

更新和澄清:

@Darin谢谢你深思熟虑的回答,这正是我迄今为止所做的。 但顺便说一下,我有一个请求,其中有很多的附件,我devise了一个Step2View例如哪个用户可以asynchronous上传文件,但这些附件应该保存在一个表中,与另一个应该保存的表在Step1View

因此,我应该保存在Step1 (部分)的域对象,但我不能,导致部分映射到Step1的ViewModel的支持Core Domain对象无法保存没有来自转换Step2ViewModel道具。

首先,你不应该在视图中使用任何域对象。 你应该使用视图模型。 每个视图模型将只包含给定视图所需的属性以及特定于此给定视图的validation属性。 所以如果你有3个步骤的向导,这意味着你将有3个视图模型,每个步骤一个:

 public class Step1ViewModel { [Required] public string SomeProperty { get; set; } ... } public class Step2ViewModel { [Required] public string SomeOtherProperty { get; set; } ... } 

等等。 所有这些视图模型都可以由一个主向导视图模型来支持:

 public class WizardViewModel { public Step1ViewModel Step1 { get; set; } public Step2ViewModel Step2 { get; set; } ... } 

那么你可以让控制器动作呈现向导过程的每一步,并将主要的WizardViewModel传递给视图。 当您处于控制器操作的Step1您可以初始化Step1属性。 然后在视图中,您将生成允许用户填写关于步骤1的属性的表单。当表单被提交时,控制器操作将仅应用步骤1的validation规则:

 [HttpPost] public ActionResult Step1(Step1ViewModel step1) { var model = new WizardViewModel { Step1 = step1 }; if (!ModelState.IsValid) { return View(model); } return View("Step2", model); } 

现在在步骤2的视图中,您可以使用MVC futures的Html.Serialize助手将步骤1序列化为表单内的隐藏字段(如果您愿意,可以使用ViewState):

 @using (Html.BeginForm("Step2", "Wizard")) { @Html.Serialize("Step1", Model.Step1) @Html.EditorFor(x => x.Step2) ... } 

并在步骤2的POST操作中:

 [HttpPost] public ActionResult Step2(Step2ViewModel step2, [Deserialize] Step1ViewModel step1) { var model = new WizardViewModel { Step1 = step1, Step2 = step2 } if (!ModelState.IsValid) { return View(model); } return View("Step3", model); } 

依此类推,直到您到达最后一步,您将拥有填充所有数据的WizardViewModel 。 然后,您将视图模型映射到您的域模型,并将其传递到服务层进行处理。 服务层本身可能执行任何validation规则,等等…

还有另一种select:使用JavaScript并把所有在同一页面上。 有很多jQuery插件提供向导function( Stepy是一个很好的)。 这基本上是在客户端显示和隐藏div的问题,在这种情况下,您不再需要担心步骤之间的持续状态。

但无论您select哪种解决scheme,都始终使用视图模型并对这些视图模型执行validation。 只要您将数据注释validation属性粘贴到您的域模型上,您将非常困难,因为域模型不适用于视图。


更新:

好的,由于无数的评论,我得出的结论是我的回答不清楚。 我必须同意。 所以让我试着进一步阐述我的例子。

我们可以定义一个所有步骤视图模型应该实现的接口(它只是一个标记接口):

 public interface IStepViewModel { } 

那么我们将为向导定义3个步骤,每个步骤当然只包含它需要的属性以及相关的validation属性:

 [Serializable] public class Step1ViewModel: IStepViewModel { [Required] public string Foo { get; set; } } [Serializable] public class Step2ViewModel : IStepViewModel { public string Bar { get; set; } } [Serializable] public class Step3ViewModel : IStepViewModel { [Required] public string Baz { get; set; } } 

接下来我们定义主向导视图模型,它由一系列步骤和一个当前步骤索引组成:

 [Serializable] public class WizardViewModel { 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 WizardController : Controller { public ActionResult Index() { var wizard = new WizardViewModel(); wizard.Initialize(); return View(wizard); } [HttpPost] public ActionResult Index( [Deserialize] WizardViewModel wizard, IStepViewModel step ) { wizard.Steps[wizard.CurrentStepIndex] = step; if (ModelState.IsValid) { if (!string.IsNullOrEmpty(Request["next"])) { wizard.CurrentStepIndex++; } else if (!string.IsNullOrEmpty(Request["prev"])) { wizard.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 wizard.CurrentStepIndex--; } return View(wizard); } } 

几个关于这个控制器的评论:

  • Index POST操作使用Microsoft Futures库中的[Deserialize]属性,因此请确保已经安装了MvcContrib NuGet。 这就是为什么视图模型应该用[Serializable]属性来装饰的原因
  • 索引POST动作作为IStepViewModel接口的参数,所以为了使这个有意义,我们需要一个自定义的模型绑定器。

以下是关联的模型联编程序:

 public class StepViewModelBinder : 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; } } 

这个联编程序使用一个名为StepType的特殊隐藏字段,它将包含每个步骤的具体types以及我们将在每个请求中发送的内容。

该模型联编Application_Start将在Application_Start注册:

 ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder()); 

这个难题的最后一个缺点是观点。 以下是主要的~/Views/Wizard/Index.cshtml视图:

 @using Microsoft.Web.Mvc @model WizardViewModel @{ var currentStep = Model.Steps[Model.CurrentStepIndex]; } <h3>Step @(Model.CurrentStepIndex + 1) out of @Model.Steps.Count</h3> @using (Html.BeginForm()) { @Html.Serialize("wizard", Model) @Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType()) @Html.EditorFor(x => currentStep, null, "") if (Model.CurrentStepIndex > 0) { <input type="submit" value="Previous" name="prev" /> } if (Model.CurrentStepIndex < Model.Steps.Count - 1) { <input type="submit" value="Next" name="next" /> } else { <input type="submit" value="Finish" name="finish" /> } } 

这就是所有你需要使这个工作。 当然,如果你想要的话,你可以通过定义一个自定义的编辑器模板来个性化向导的一些或所有步骤的外观和感觉。 例如,让我们来做第2步。所以我们定义一个~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml部分:

 @model Step2ViewModel Special Step 2 @Html.TextBoxFor(x => x.Bar) 

结构如下所示:

在这里输入图像说明

当然还有改进的余地。 索引POST动作看起来像s..t。 代码太多了 进一步的简化将涉及到所有的基础设施的东西,如索引,当前索引pipe理,将当前步骤复制到向导中…到另一个模型联编程序中。 所以最后我们结束了:

 [HttpPost] public ActionResult Index(WizardViewModel wizard) { if (ModelState.IsValid) { // 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"); } return View(wizard); } 

这更像POST动作的样子。 我下次要离开这个改进:-)

为了补充Amit Bagga的回答,你会在下面find我所做的。 即使不那么优雅,我觉得这比达林的答案简单。

控制器:

 public ActionResult Step1() { if (Session["wizard"] != null) { WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"]; return View(wiz.Step1); } return View(); } [HttpPost] public ActionResult Step1(Step1ViewModel step1) { if (ModelState.IsValid) { WizardProductViewModel wiz = new WizardProductViewModel(); wiz.Step1 = step1; //Store the wizard in session Session["wizard"] = wiz; return RedirectToAction("Step2"); } return View(step1); } public ActionResult Step2() { if (Session["wizard"] != null) { WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"]; return View(wiz.Step2); } return View(); } [HttpPost] public ActionResult Step2(Step2ViewModel step2) { if (ModelState.IsValid) { //Pull the wizard from session WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"]; wiz.Step2 = step2; //Store the wizard in session Session["wizard"] = wiz; //return View("Step3"); return RedirectToAction("Step3"); } return View(step2); } public ActionResult Step3() { WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"]; return View(wiz.Step3); } [HttpPost] public ActionResult Step3(Step3ViewModel step3) { if (ModelState.IsValid) { //Pull the wizard from session WizardProductViewModel wiz = (WizardProductViewModel)Session["wizard"]; wiz.Step3 = step3; //Save the data Product product = new Product { //Binding with view models Name = wiz.Step1.Name, ListPrice = wiz.Step2.ListPrice, DiscontinuedDate = wiz.Step3.DiscontinuedDate }; db.Products.Add(product); db.SaveChanges(); return RedirectToAction("Index", "Product"); } return View(step3); } 

楷模 :

  [Serializable] public class Step1ViewModel { [Required] [MaxLength(20, ErrorMessage="Longueur max de 20 caractères")] public string Name { get; set; } } [Serializable] public class Step2ViewModel { public Decimal ListPrice { get; set; } } [Serializable] public class Step3ViewModel { public DateTime? DiscontinuedDate { get; set; } } [Serializable] public class WizardProductViewModel { public Step1ViewModel Step1 { get; set; } public Step2ViewModel Step2 { get; set; } public Step3ViewModel Step3 { get; set; } } 

我build议你在使用Jquery的客户端上维护Complete Process的状态。

例如,我们有一个三步向导过程。

  1. 用户在Step1上显示的button上有一个标有“Next”
  2. 点击下一步我们创build一个Ajax请求并创build一个名为Step2的DIV,并将HTML加载到该DIV中。
  3. 在Step3上,我们有一个标记为“完成”的button点击button发布数据使用$ .post调用。

这样,您可以直接从表单发布数据直接构build您的域对象,并在数据有错误的情况下返回有效的JSON保存所有的错误信息,并显示在一个div中。

请分割步骤

 public class Wizard { public Step1 Step1 {get;set;} public Step2 Step2 {get;set;} public Step3 Step3 {get;set;} } public ActionResult Step1(Step1 step) { if(Model.IsValid) { Wizard wiz = new Wizard(); wiz.Step1 = step; //Store the Wizard in Session; //Return the action } } public ActionResult Step2(Step2 step) { if(Model.IsValid) { //Pull the Wizard From Session wiz.Step2=step; } } 

以上只是一个示例,可以帮助您达到最终结果。 在最后一步中,您必须创build域对象并从向导对象填充正确的值并存储到数据库中。

向导只是处理简单模型的简单步骤。 没有理由为一个向导创build多个模型。 你所要做的就是创build一个单一的模型,并在单个控制器中的动作之间传递它。

 public class MyModel { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set }; public string StepOneData { get; set; } public string StepTwoData { get; set; } } 

以上coed是愚蠢的简单,所以在那里replace你的领域。 接下来,我们开始一个简单的动作,启动我们的向导。

  public ActionResult WizardStep1() { return View(new MyModel()); } 

这将调用视图“WizardStep1.cshtml(如果使用的是razor)。如果需要,可以使用创build模板向导。我们只是将该postredirect到不同的操作。

 <WizardStep1.cshtml> @using (Html.BeginForm("WizardStep2", "MyWizard")) { 

值得注意的是,我们将发布这个不同的行动, WizardStep2操作

  [HttpPost] public ActionResult WizardStep2(MyModel myModel) { return ModelState.IsValid ? View(myModel) : View("WizardStep1", myModel); } 

在这个动作中,我们检查我们的模型是否有效,如果是这样的话,我们把它发送到我们的WizardStep2.cshtml视图,否则我们把它发回到第一步,validation错误。 在每一步中,我们将其发送到下一步,validation该步骤并继续前进。 现在一些精明的开发者可能会说,如果我们在步骤之间使用[Required]属性或其他数据注释,我们就不能在这个步骤之间移动。 你会是对的,所以删除那些还没有被检查的项目上的错误。 如下所示。

  [HttpPost] public ActionResult WizardStep3(MyModel myModel) { foreach (var error in ModelState["StepTwoData"].Errors) { ModelState["StepTwoData"].Errors.Remove(error); } 

最后,我们将模型保存一次到数据存储。 这也阻止了一个用户启动一个向导,但没有完成它不保存不完整的数据到数据库。

我希望你发现这个实现向导的方法比任何前面提到的方法更容易使用和维护。

谢谢阅读。

我想分享我自己的方式来处理这些要求。 我根本不想使用SessionState,也不想处理客户端,而序列化方法需要我不想在我的项目中包含的MVC Futures。

相反,我构build了一个HTML Helper,它将遍历模型的所有属性,并为每个属性生成一个自定义隐藏元素。 如果它是一个复杂的属性,那么它将recursion地运行它。

在您的表单中,他们将在每个“向导”步骤中与新模型数据一起发布到控制器。

我为MVC 5写了这个。

 using System; using System.Text; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Web; using System.Web.Routing; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Reflection; namespace YourNamespace { public static class CHTML { public static MvcHtmlString HiddenClassFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) { return HiddenClassFor(html, expression, null); } public static MvcHtmlString HiddenClassFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) { ModelMetadata _metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData); if (_metaData.Model == null) return MvcHtmlString.Empty; RouteValueDictionary _dict = htmlAttributes != null ? new RouteValueDictionary(htmlAttributes) : null; return MvcHtmlString.Create(HiddenClassFor(html, expression, _metaData, _dict).ToString()); } private static StringBuilder HiddenClassFor<TModel>(HtmlHelper<TModel> html, LambdaExpression expression, ModelMetadata metaData, IDictionary<string, object> htmlAttributes) { StringBuilder _sb = new StringBuilder(); foreach (ModelMetadata _prop in metaData.Properties) { Type _type = typeof(Func<,>).MakeGenericType(typeof(TModel), _prop.ModelType); var _body = Expression.Property(expression.Body, _prop.PropertyName); LambdaExpression _propExp = Expression.Lambda(_type, _body, expression.Parameters); if (!_prop.IsComplexType) { string _id = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(_propExp)); string _name = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(_propExp)); object _value = _prop.Model; _sb.Append(MinHiddenFor(_id, _name, _value, htmlAttributes)); } else { if (_prop.ModelType.IsArray) _sb.Append(HiddenArrayFor(html, _propExp, _prop, htmlAttributes)); else if (_prop.ModelType.IsClass) _sb.Append(HiddenClassFor(html, _propExp, _prop, htmlAttributes)); else throw new Exception(string.Format("Cannot handle complex property, {0}, of type, {1}.", _prop.PropertyName, _prop.ModelType)); } } return _sb; } public static MvcHtmlString HiddenArrayFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) { return HiddenArrayFor(html, expression, null); } public static MvcHtmlString HiddenArrayFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) { ModelMetadata _metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData); if (_metaData.Model == null) return MvcHtmlString.Empty; RouteValueDictionary _dict = htmlAttributes != null ? new RouteValueDictionary(htmlAttributes) : null; return MvcHtmlString.Create(HiddenArrayFor(html, expression, _metaData, _dict).ToString()); } private static StringBuilder HiddenArrayFor<TModel>(HtmlHelper<TModel> html, LambdaExpression expression, ModelMetadata metaData, IDictionary<string, object> htmlAttributes) { Type _eleType = metaData.ModelType.GetElementType(); Type _type = typeof(Func<,>).MakeGenericType(typeof(TModel), _eleType); object[] _array = (object[])metaData.Model; StringBuilder _sb = new StringBuilder(); for (int i = 0; i < _array.Length; i++) { var _body = Expression.ArrayIndex(expression.Body, Expression.Constant(i)); LambdaExpression _arrayExp = Expression.Lambda(_type, _body, expression.Parameters); ModelMetadata _valueMeta = ModelMetadata.FromLambdaExpression((dynamic)_arrayExp, html.ViewData); if (_eleType.IsClass) { _sb.Append(HiddenClassFor(html, _arrayExp, _valueMeta, htmlAttributes)); } else { string _id = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(_arrayExp)); string _name = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(_arrayExp)); object _value = _valueMeta.Model; _sb.Append(MinHiddenFor(_id, _name, _value, htmlAttributes)); } } return _sb; } public static MvcHtmlString MinHiddenFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) { return MinHiddenFor(html, expression, null); } public static MvcHtmlString MinHiddenFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) { string _id = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression)); string _name = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression)); object _value = ModelMetadata.FromLambdaExpression(expression, html.ViewData).Model; RouteValueDictionary _dict = htmlAttributes != null ? new RouteValueDictionary(htmlAttributes) : null; return MinHiddenFor(_id, _name, _value, _dict); } public static MvcHtmlString MinHiddenFor(string id, string name, object value, IDictionary<string, object> htmlAttributes) { TagBuilder _input = new TagBuilder("input"); _input.Attributes.Add("id", id); _input.Attributes.Add("name", name); _input.Attributes.Add("type", "hidden"); if (value != null) { _input.Attributes.Add("value", value.ToString()); } if (htmlAttributes != null) { foreach (KeyValuePair<string, object> _pair in htmlAttributes) { _input.MergeAttribute(_pair.Key, _pair.Value.ToString(), true); } } return new MvcHtmlString(_input.ToString(TagRenderMode.SelfClosing)); } } } 

现在,对于“向导”的所有步骤,可以使用相同的基本模型,并使用lambdaexpression式将“Step 1,2,3”模型属性传递到@ Html.HiddenClassFor助手。

如果你愿意,你甚至可以在每一步都有一个后退button。 只需在表单中有一个后退button,使用formaction属性将其发送到控制器上的StepNBack操作。 不包括在下面的例子,但只是一个想法给你。

无论如何这里是一个基本的例子:

这是你的模型

 public class WizardModel { // you can store additional properties for your "wizard" / parent model here // these properties can be saved between pages by storing them in the form using @Html.MinHiddenFor(m => m.WizardID) public int? WizardID { get; set; } public string WizardType { get; set; } [Required] public Step1 Step1 { get; set; } [Required] public Step2 Step2 { get; set; } [Required] public Step3 Step3 { get; set; } // if you want to use the same model / view / controller for EDITING existing data as well as submitting NEW data here is an example of how to handle it public bool IsNew { get { return WizardID.HasValue; } } } public class Step1 { [Required] [MaxLength(32)] [Display(Name = "First Name")] public string FirstName { get; set; } [Required] [MaxLength(32)] [Display(Name = "Last Name")] public string LastName { get; set; } } public class Step2 { [Required] [MaxLength(512)] [Display(Name = "Biography")] public string Biography { get; set; } } public class Step3 { // lets have an array of strings here to shake things up [Required] [Display(Name = "Your Favorite Foods")] public string[] FavoriteFoods { get; set; } } 

这是你的控制器

 public class WizardController : Controller { [HttpGet] [Route("wizard/new")] public ActionResult New() { WizardModel _model = new WizardModel() { WizardID = null, WizardType = "UserInfo" }; return View("Step1", _model); } [HttpGet] [Route("wizard/edit/{wizardID:int}")] public ActionResult Edit(int wizardID) { WizardModel _model = database.GetData(wizardID); return View("Step1", _model); } [HttpPost] [Route("wizard/step1")] public ActionResult Step1(WizardModel model) { // just check if the values in the step1 model are valid // shouldn't use ModelState.IsValid here because that would check step2 & step3. // which isn't entered yet if (ModelState.IsValidField("Step1")) { return View("Step2", model); } return View("Step1", model); } [HttpPost] [Route("wizard/step2")] public ActionResult Step2(WizardModel model) { if (ModelState.IsValidField("Step2")) { return View("Step3", model); } return View("Step2", model); } [HttpPost] [Route("wizard/step3")] public ActionResult Step3(WizardModel model) { // all of the data for the wizard model is complete. // so now we check the entire model state if (ModelState.IsValid) { // validation succeeded. save the data from the model. // the model.IsNew is just if you want users to be able to // edit their existing data. if (model.IsNew) database.NewData(model); else database.EditData(model); return RedirectToAction("Success"); } return View("Step3", model); } } 

这是你的观点

步骤1

 @model WizardModel @{ ViewBag.Title = "Step 1"; } @using (Html.BeginForm("Step1", "Wizard", FormMethod.Post)) { @Html.MinHiddenFor(m => m.WizardID) @Html.MinHiddenFor(m => m.WizardType) @Html.LabelFor(m => m.Step1.FirstName) @Html.TextBoxFor(m => m.Step1.FirstName) @Html.LabelFor(m => m.Step1.LastName) @Html.TextBoxFor(m => m.Step1.LastName) <button type="submit">Submit</button> } 

第2步

 @model WizardModel @{ ViewBag.Title = "Step 2"; } @using (Html.BeginForm("Step2", "Wizard", FormMethod.Post)) { @Html.MinHiddenFor(m => m.WizardID) @Html.MinHiddenFor(m => m.WizardType) @Html.HiddenClassFor(m => m.Step1) @Html.LabelFor(m => m.Step2.Biography) @Html.TextAreaFor(m => m.Step2.Biography) <button type="submit">Submit</button> } 

第3步

 @model WizardModel @{ ViewBag.Title = "Step 3"; } @using (Html.BeginForm("Step3", "Wizard", FormMethod.Post)) { @Html.MinHiddenFor(m => m.WizardID) @Html.MinHiddenFor(m => m.WizardType) @Html.HiddenClassFor(m => m.Step1) @Html.HiddenClassFor(m => m.Step2) @Html.LabelFor(m => m.Step3.FavoriteFoods) @Html.ListBoxFor(m => m.Step3.FavoriteFoods, new SelectListItem[] { new SelectListItem() { Value = "Pizza", Text = "Pizza" }, new SelectListItem() { Value = "Sandwiches", Text = "Sandwiches" }, new SelectListItem() { Value = "Burgers", Text = "Burgers" }, }); <button type="submit">Submit</button> } 

一种select是创build一组相同的表格来存储在每个步骤中收集的数据。 然后在最后一步,如果一切顺利,您可以通过复制临时数据并存储它来创build实体。

其他的是为每个步骤创buildValue Objects ,然后存储在CacheSession 。 然后,如果一切顺利,您可以从中创build您的Domain对象并保存