ASP.NET MVC中的多步骤注册过程问题(拆分视图模型,单个模型) [英] multi-step registration process issues in asp.net mvc (split viewmodels, single model)

查看:63
本文介绍了ASP.NET MVC中的多步骤注册过程问题(拆分视图模型,单个模型)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个多步骤注册过程,由域层中的单个对象作为后盾,该属性具有在属性上定义的验证规则.

I have a multi-step registration process, backed by a single object in domain layer, which have validation rules defined on properties.

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

How should I validate the domain object when the domain is split across many views, and I have to save the object partially in the first view when posted?

我考虑过使用会话,但这是不可能的,因为该过程很长且数据量很大,所以我不想使用会话.

I thought about using Sessions but that's not possible cause the process is lengthy and amount of data is high, So I don't want to use session.

我考虑过将所有数据保存在关系内存数据库中(与主数据库具有相同的架构),然后将数据刷新到主数据库,但是出现了问题,原因是我应该在服务之间(在视图中请求)之间路由使用主数据库和内存数据库.

I thought about saving all the data in an relational in-memory db (with the same schema as main db) and then flushing that data to main db but issues arisen cause I should route between services (requested in the views) who work with the main db and in-memory db.

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

I'm looking for an elegant and clean solution (more precisely a best practice).

更新和说明:

@Darin谢谢您的深思熟虑的回复, 这就是我到目前为止所做的. 但是顺便说一句,我有一个包含许多附件的请求,我设计了一个Step2View例如哪些用户可以异步上传文件, 但是这些附件应该保存在一个表中,该表与之前已经保存在Step1View中的另一个表具有引用关系.

@Darin Thank you for your thoughtful reply, That was exactly what I've done till now. But incidentally I've got a request which have many attachments in it, I design a Step2View e.g. which user can upload documents in it asynchronously , but those attachments should be saved in a table with referential relation to another table that should have been saved before in Step1View.

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

Thus I should save the domain object in Step1 (partially), But I can't, cause the backed Core Domain object which is mapped partially to a Step1's ViewModel can't be saved without props that come from converted Step2ViewModel.

推荐答案

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

First you shouldn't be using any domain objects in your views. You should be using view models. Each view model will contain only the properties that are required by the given view as well as the validation attributes specific to this given view. So if you have 3 steps wizard this means that you will have 3 view models, one for each step:

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

    ...
}

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

    ...
}

,依此类推.所有这些视图模型都可以由主向导视图模型支持:

and so on. All those view models could be backed by a main wizard view model:

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

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

then you could have controller actions rendering each step of the wizard process and passing the main WizardViewModel to the view. When you are on the first step inside the controller action you could initialize the Step1 property. Then inside the view you would generate the form allowing the user to fill the properties about step 1. When the form is submitted the controller action will apply the validation rules for step 1 only:

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

    if (!ModelState.IsValid)
    {
        return View(model);
    }
    return View("Step2", model);
}

现在在第2步视图中,您可以使用

Now inside the step 2 view you could use the Html.Serialize helper from MVC futures in order to serialize step 1 into a hidden field inside the form (sort of a ViewState if you wish):

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

,并在步骤2的POST操作中:

and inside the POST action of step2:

[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中将填充所有数据.然后,您将视图模型映射到您的域模型,并将其传递到服务层进行处理.服务层本身可以执行任何验证规则,等等...

And so on until you get to the last step where you will have the WizardViewModel filled with all the data. Then you will map the view model to your domain model and pass it to the service layer for processing. The service layer might perform any validation rules itself and so on ...

还有另一种选择:使用javascript并将所有内容放到同一页面上.

There is also another alternative: using javascript and putting all on the same page. There are many jquery plugins out there that provide wizard functionality (Stepy is a nice one). It's basically a matter of showing and hiding divs on the client in which case you no longer need to worry about persisting state between the steps.

但是,无论您选择哪种解决方案,都始终使用视图模型并对这些视图模型执行验证.只要您将数据注释验证属性粘贴到域模型上,由于域模型不适合视图,您将很难解决.

But no matter what solution you choose always use view models and perform the validation on those view models. As long you are sticking data annotation validation attributes on your domain models you will struggle very hard as domain models are not adapted to views.

更新:

好的,由于评论过多,我得出的结论是我的答案不清楚.我必须同意.因此,让我尝试进一步阐述我的示例.

OK, due to the numerous comments I draw the conclusion that my answer was not clear. And I must agree. So let me try to further elaborate my example.

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

We could define an interface which all step view models should implement (it's just a marker interface):

public interface IStepViewModel
{
}

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

then we would define 3 steps for the wizard where each step would of course contain only the properties that it requires as well as the relevant validation attributes:

[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; }
}

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

next we define the main wizard view model which consists of a list of steps and a current step index:

[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]属性
  • 装饰的原因
  • Index POST操作将IStepViewModel接口作为参数,因此要使其有意义,我们需要一个自定义模型绑定器.
  • The Index POST action uses the [Deserialize] attributes from the Microsoft Futures library so make sure you have installed the MvcContrib NuGet. That's the reason why view models should be decorated with the [Serializable] attribute
  • The Index POST action takes as argument an IStepViewModel interface so for this to make sense we need a custom model binder.

以下是相关的模型资料夹:

Here's the associated model binder:

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的特殊隐藏字段,该字段将包含每个步骤的具体类型,并在每次请求时发送.

This binder uses a special hidden field called StepType which will contain the concrete type of each step and which we will send on each request.

此型号资料夹将在Application_Start中注册:

This model binder will be registered in Application_Start:

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

难题的最后一个遗漏之处是视图.这是主要的~/Views/Wizard/Index.cshtml视图:

The last missing bit of the puzzle are the views. Here's the main ~/Views/Wizard/Index.cshtml view:

@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部分:

And that's all you need to make this working. Of course if you wanted you could personalize the look and feel of some or all steps of the wizard by defining a custom editor template. For example let's do it for step 2. So we define a ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml partial:

@model Step2ViewModel

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

结构如下:

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

Of course there is room for improvement. The Index POST action looks like s..t. There's too much code in it. A further simplification would involve into moving all the infrastructure stuff like index, current index management, copying of the current step into the wizard, ... into another model binder. So that finally we end up with:

[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操作的外观.我将在下一次离开此改进:-)

which is more how POST actions should look like. I am leaving this improvement for the next time :-)

这篇关于ASP.NET MVC中的多步骤注册过程问题(拆分视图模型,单个模型)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆