asp.net mvc中的多步注册流程问题(split viewmodels,single model)

Jah*_*ine 115 asp.net-mvc

我有一个多步注册过程,由域层中单个对象支持,其中包含在属性上定义的验证规则.

当域被分割为多个视图时,我应该如何验证域对象?我必须在发布时将对象部分保存在第一个视图中?

我想过使用Sessions但是这不可能因为这个过程很漫长而且数据量很高,所以我不想使用session.

我考虑将所有数据保存在关系内存数据库中(使用与主数据库相同的模式),然后将该数据刷新到主数据库,但出现问题因为我应该在服务(在视图中请求)中使用主数据库和内存数据库.

我正在寻找一个优雅而干净的解决方案(更准确地说是一种最佳实践).

更新和澄清:

@Darin感谢您的深思熟虑的答复,这正是我迄今为止所做的.但顺便说一句,我有一个请求,里面有很多附件,我设计了一个Step2View例如哪个用户可以异步上传文件,但是这些附件应该保存在一个表中,该表与之前保存的另一个表有参考关系.Step1View.

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

Dar*_*rov 227

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

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

    ...
}

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

    ...
}
Run Code Online (Sandbox Code Playgroud)

等等.所有这些视图模型都可以由主向导视图模型支持:

public class WizardViewModel
{
    public Step1ViewModel Step1 { get; set; }
    public Step2ViewModel Step2 { get; set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以让控制器操作呈现向导过程的每个步骤,并将主要文件传递WizardViewModel给视图.当您进入控制器操作的第一步时,您可以初始化该Step1属性.然后在视图内部生成表单,允许用户填写有关步骤1的属性.提交表单时,控制器操作将仅应用步骤1的验证规则:

[HttpPost]
public ActionResult Step1(Step1ViewModel step1)
{
    var model = new WizardViewModel 
    {
        Step1 = step1
    };

    if (!ModelState.IsValid)
    {
        return View(model);
    }
    return View("Step2", model);
}
Run Code Online (Sandbox Code Playgroud)

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

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

并且在step2的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);
}
Run Code Online (Sandbox Code Playgroud)

依此类推,直到你完成最后一步,你将获得WizardViewModel所有数据.然后,您将视图模型映射到域模型,并将其传递到服务层进行处理.服务层可以自己执行任何验证规则等等......

还有另一种选择:使用javascript并将所有内容放在同一页面上.有许多jquery插件提供向导功能(Stepy是一个很好的插件).这基本上是在客户端显示和隐藏div的问题,在这种情况下,您不再需要担心步骤之间的持久状态.

但无论您选择何种解决方案,始终使用视图模型并对这些视图模型执行验证.只要您在域模型上粘贴数据注释验证属性,您就会非常努力,因为域模型不适合视图.


更新:

好的,由于众多评论,我得出的结论是我的答案不明确.我必须同意.所以让我试着进一步阐述我的例子.

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

public interface IStepViewModel
{
}
Run Code Online (Sandbox Code Playgroud)

然后我们将为向导定义3个步骤,其中每个步骤当然只包含它所需的属性以及相关的验证属性:

[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; }
}
Run Code Online (Sandbox Code Playgroud)

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

[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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们转到控制器:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • Index POST操作使用[Deserialize]Microsoft Futures库中的属性,因此请确保已安装MvcContribNuGet.这就是为什么应该使用[Serializable]属性修饰视图模型的原因
  • Index 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

该绑定器使用一个名为StepType的特殊隐藏字段,该字段将包含每个步骤的具体类型,并将在每个请求中发送.

此型号活页夹将注册于Application_Start:

ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());
Run Code Online (Sandbox Code Playgroud)

拼图的最后一点是观点.这是主要~/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" />
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是你需要做的所有工作.当然,如果您希望通过定义自定义编辑器模板,可以个性化向导的部分或全部步骤的外观.例如,让我们为第2步执行此操作.因此我们定义了一个~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml部分:

@model Step2ViewModel

Special Step 2
@Html.TextBoxFor(x => x.Bar)
Run Code Online (Sandbox Code Playgroud)

这是结构的样子:

在此输入图像描述

当然还有改进的余地.Index POST操作看起来像s..t.它中的代码太多了.进一步的简化将涉及移动所有基础设施的东西,如索引,当前索引管理,将当前步骤复制到向导,...到另一个模型绑定器.最后我们最终得到:

[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);
}
Run Code Online (Sandbox Code Playgroud)

这更像是POST动作的样子.我将在下一次离开这个改进:-)

  • +1 @Jani:你真的需要给达林50分这个答案.它非常全面.他设法重申需要使用ViewModel而不是Domain模型;-) (20认同)
  • 我无法在任何地方找到Deserialize属性...同样在mvccontrib的codeplex页面中我发现这个94fa6078a115由Jeremy Skinner 2010年8月1日下午5:55 0删除已弃用的Deserialize binder你建议我做什么? (3认同)
  • 我发现了一个问题,而我没有将我的观点命名为Step1,Step2等......我的名字更有意义,但不是按字母顺序排列.所以,我最终得到了错误的模型.我在IStepViewModel接口中添加了StepNumber属性.现在我可以在WizardViewModel的Initialize方法中对此进行排序. (2认同)

Arn*_*501 13

为了补充Amit Bagga的答案,你会在下面找到我所做的.即使不那么优雅,我发现这种方式比达林的答案更简单.

控制器:

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);
}
Run Code Online (Sandbox Code Playgroud)

楷模 :

 [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; }
    }
Run Code Online (Sandbox Code Playgroud)


Ami*_*gga 10

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

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

  1. 用户出现了Step1,其上有一个标签"Next"
  2. 单击下一步我们创建一个Ajax请求并创建一个名为Step2的DIV并将HTML加载到该DIV中.
  3. 在Step3上,我们有一个标记为"已完成"的按钮,点击按钮,使用$ .post调用发布数据.

这样,您可以直接从表单发布数据轻松构建域对象,如果数据有错误,则返回包含所有错误消息的有效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;
 }
}
Run Code Online (Sandbox Code Playgroud)

以上只是一个演示,它将帮助您实现最终结果.在最后一步,您必须创建域对象并从向导对象和存储到数据库中填充正确的值.


Dar*_*oll 5

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

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

上面的男女同校是愚蠢的,所以在那里替换你的领域.接下来,我们从启动向导的简单操作开始.

    public ActionResult WizardStep1()
    {
        return View(new MyModel());
    }
Run Code Online (Sandbox Code Playgroud)

这会调用视图"WizardStep1.cshtml(如果使用razor).如果需要,可以使用创建模板向导.我们只是将帖子重定向到不同的操作.

<WizardStep1.cshtml>
@using (Html.BeginForm("WizardStep2", "MyWizard")) {
Run Code Online (Sandbox Code Playgroud)

值得注意的是,我们将把它发布到另一个动作; WizardStep2动作

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

在此操作中,我们检查我们的模型是否有效,如果是,我们将其发送到WizardStep2.cshtml视图,否则我们将其发送回第一步并验证错误.在每个步骤中,我们将其发送到下一步,验证该步骤并继续前进.现在一些精明的开发人员可能会说,如果我们在步骤之间使用[Required]属性或其他数据注释,我们就无法在这样的步骤之间移动.你会是对的,所以删除尚未检查的项目上的错误.如下.

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

最后,我们将模型保存一次到数据存储.这也可以防止启动向导但未完成向导的用户不将不完整的数据保存到数据库.

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

谢谢阅读.


Arc*_*ade 5

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

相反,我建立了一个HTML Helper,它将遍历模型的所有属性并为每个属性生成一个自定义的隐藏元素。如果它是一个复杂的属性,那么它将在其上递归运行。

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

我是为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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

如果需要,您甚至可以在每个步骤上都有一个后退按钮。只需在表单中有一个后退按钮,即可使用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; }
}
Run Code Online (Sandbox Code Playgroud)

这是您的控制器

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是您的意见

步骤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>
}
Run Code Online (Sandbox Code Playgroud)

第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>
}
Run Code Online (Sandbox Code Playgroud)

第三步

@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>
}
Run Code Online (Sandbox Code Playgroud)


小智 -9

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

另一种是为每个步骤创建Value Objects然后存储在Cache或中Session。然后,如果一切顺利,您可以从中创建 Domain 对象并保存它