problemi di processo di registrazione multi-step in asp.net mvc (split viewmodels, single model)

Ho un processo di registrazione in più fasi , supportato da un singolo object nel livello dominio , che ha regole di convalida definite sulle proprietà.

Come dovrei convalidare l’object dominio quando il dominio è diviso su più viste e devo salvare parzialmente l’object nella prima vista quando inviato?

Ho pensato di usare Sessions ma non è ansible perché il processo è lungo e la quantità di dati è alta, quindi non voglio usare la sessione.

Ho pensato di salvare tutti i dati in un db relazionale in memoria (con lo stesso schema del db principale) e poi di svuotare quei dati nel db principale ma i problemi sono sorti perché dovrei instradare tra i servizi (richiesti nelle viste) che lavorano con il db principale e db in memoria.

Sto cercando una soluzione elegante e pulita (più precisamente una best practice).

AGGIORNAMENTO E Chiarimento:

@Darin Grazie per la tua risposta riflessiva, è stato esattamente quello che ho fatto fino ad ora. Ma, per inciso, ho una richiesta che Step2View molti allegati, progetto un Step2View esempio quale utente può caricare documenti in modo asincrono, ma quegli allegati dovrebbero essere salvati in una tabella con relazione referenziale ad un’altra tabella che avrebbe dovuto essere salvata prima in Step1View .

Quindi dovrei salvare l’object dominio in Step1 (parzialmente), ma non posso, perché l’object Dominio Core supportato che è mappato parzialmente a ViewModel di Step1 non può essere salvato senza oggetti di scena che provengono da Step2ViewModel convertito.

Per prima cosa non dovresti usare alcun object di dominio nelle tue viste. Dovresti usare i modelli di vista. Ogni modello di vista conterrà solo le proprietà richieste dalla vista specificata e gli attributi di convalida specifici per questa vista. Quindi se hai una procedura guidata a 3 passaggi ciò significa che avrai 3 modelli di visualizzazione, uno per ogni passaggio:

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

e così via. Tutti i modelli di visualizzazione possono essere supportati da un modello di visualizzazione guidata principale:

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

quindi è ansible che le azioni del controller WizardViewModel il rendering di ogni fase del processo della procedura guidata e passino alla WizardViewModel principale WizardViewModel . Quando si è sul primo passaggio all’interno dell’azione del controller, è ansible inizializzare la proprietà Step1 . Quindi all’interno della vista si genera il modulo che consente all’utente di riempire le proprietà del passaggio 1. Quando il modulo viene inviato, l’azione del controller applicherà le regole di convalida solo per il passaggio 1:

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

Ora, nella visualizzazione del passaggio 2, è ansible utilizzare l’ helper Html.Serialize dai futures MVC per serializzare il passaggio 1 in un campo nascosto all’interno del modulo (una specie di ViewState se lo si desidera):

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

e all’interno dell’azione POST di 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); } 

E così via fino all’ultima fase in cui il WizardViewModel riempito con tutti i dati. Quindi mappare il modello di visualizzazione al modello di dominio e passarlo al livello di servizio per l’elaborazione. Il livello di servizio potrebbe eseguire tutte le regole di convalida e così via …

C’è anche un’altra alternativa: usare javascript e mettere tutti sulla stessa pagina. Ci sono molti plug-in jquery che forniscono funzionalità di wizard ( Stepy ne ha uno bello). Si tratta fondamentalmente di mostrare e hide i div sul client, nel qual caso non è più necessario preoccuparsi dello stato di persistenza tra i passaggi.

Tuttavia, indipendentemente dalla soluzione scelta, utilizzare sempre i modelli di visualizzazione ed eseguire la convalida su tali modelli di viste. Fino a quando continuerai ad applicare gli attributi di convalida dell’annotazione dei dati sui tuoi modelli di dominio, ti troverai molto difficile poiché i modelli di dominio non sono adattati alle visualizzazioni.


AGGIORNARE:

OK, a causa dei numerosi commenti traggo la conclusione che la mia risposta non è stata chiara. E devo essere d’accordo. Quindi lascia che provi a elaborare ulteriormente il mio esempio.

Potremmo definire un’interfaccia che tutti i modelli di visualizzazione passo dovrebbero implementare (è solo un’interfaccia marcatore):

 public interface IStepViewModel { } 

quindi definiremmo 3 passaggi per la procedura guidata in cui ogni passaggio ovviamente contiene solo le proprietà che richiede e gli attributi di convalida rilevanti:

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

successivamente definiamo il modello di vista wizard principale che consiste in un elenco di passaggi e un indice di passo corrente:

 [Serializable] public class WizardViewModel { public int CurrentStepIndex { get; set; } public IList 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(); } } 

Quindi passiamo al controller:

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

Un paio di osservazioni su questo controller:

  • L’azione Index POST utilizza gli attributi [Deserialize] dalla libreria Microsoft Futures, quindi assicurati di aver installato MvcContrib NuGet. Questo è il motivo per cui i modelli di visualizzazione dovrebbero essere decorati con l’attributo [Serializable]
  • L’azione Index POST accetta come argomento un’interfaccia IStepViewModel , quindi per questo ha senso avere bisogno di un modello personalizzato.

Ecco il modello associato:

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

Questo raccoglitore utilizza uno speciale campo nascosto chiamato StepType che conterrà il tipo concreto di ogni passaggio e che invieremo su ogni richiesta.

Questo modello di raccoglitore sarà registrato in Application_Start :

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

L’ultimo pezzo mancante del puzzle sono i punti di vista. Ecco la vista principale ~/Views/Wizard/Index.cshtml :

 @using Microsoft.Web.Mvc @model WizardViewModel @{ var currentStep = Model.Steps[Model.CurrentStepIndex]; } 

Step @(Model.CurrentStepIndex + 1) out of @Model.Steps.Count

@using (Html.BeginForm()) { @Html.Serialize("wizard", Model) @Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType()) @Html.EditorFor(x => currentStep, null, "") if (Model.CurrentStepIndex > 0) { } if (Model.CurrentStepIndex < Model.Steps.Count - 1) { } else { } }

E questo è tutto ciò che serve per farlo funzionare. Ovviamente, se lo si desidera, è ansible personalizzare l’aspetto di alcuni o tutti i passaggi della procedura guidata definendo un modello di editor personalizzato. Ad esempio, eseguiamolo per il passaggio 2. Quindi definiamo ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml partial:

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

Ecco come appare la struttura:

inserisci la descrizione dell'immagine qui

Certo, c’è spazio per migliorare. L’azione Index POST ha l’aspetto di s..t. C’è troppo codice in esso. Un’ulteriore semplificazione comporterebbe il trasferimento di tutte le infrastrutture come l’indice, la gestione dell’indice corrente, la copia del passo corrente nella procedura guidata, … in un altro modello di raccoglitore. Quindi alla fine ci ritroviamo con:

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

che è più come dovrebbero essere le azioni POST. Lascio questo miglioramento per la prossima volta 🙂

Per completare la risposta di Amit Bagga, troverai di seguito quello che ho fatto. Anche se meno elegante, trovo questo modo più semplice della risposta di Darin.

Controller:

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

Modelli :

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

Ti suggerirei di mantenere lo stato di Complete Process sul client usando Jquery.

Ad esempio, abbiamo un processo di procedura guidata in tre fasi.

  1. L’utente presentato con il passaggio 1 su cui è presente un pulsante con l’etichetta “Avanti”
  2. Facendo clic su Avanti Facciamo una richiesta Ajax e creiamo un DIV chiamato Step2 e carichiamo l’HTML in quel DIV.
  3. Sul Passaggio 3 abbiamo un pulsante con l’etichetta “Finito” al clic sul pulsante inserisci i dati usando $ .post.

In questo modo puoi facilmente creare il tuo object dominio direttamente dai dati del modulo e nel caso in cui i dati abbiano errori restituire JSON valido contenente tutti i messaggi di errore e visualizzarli in un div.

Si prega di dividere i passaggi

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

The Above è solo una dimostrazione che ti aiuterà a raggiungere il risultato finale. Nella fase finale è necessario creare l’object dominio e inserire nel database i valori corretti dall’object e dall’archivio della procedura guidata.

I wizard sono semplici passaggi per l’elaborazione di un modello semplice. Non c’è motivo di creare più modelli per un wizard. Tutto quello che devi fare è creare un singolo modello e passarlo tra le azioni in un singolo controller.

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

Il sopra scritto è stupido, quindi sostituisci i tuoi campi. Quindi iniziamo con una semplice azione che avvia il nostro mago.

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

Questo chiama la vista “WizardStep1.cshtml (se si usa il razor) .Si può usare la procedura guidata di creazione del modello se lo si desidera. Stiamo semplicemente reindirizzando il post a un’altra azione.

  @using (Html.BeginForm("WizardStep2", "MyWizard")) { 

La cosa importante è che lo pubblicheremo in un’azione diversa; l’azione WizardStep2

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

In questa azione controlliamo se il nostro modello è valido, e in tal caso lo inviamo alla nostra vista WizardStep2.cshtml altrimenti lo rimandiamo al primo passo con gli errori di convalida. In ogni passaggio lo inviamo al passaggio successivo, convalidare quel passaggio e andare avanti. Ora alcuni sviluppatori esperti potrebbero dire bene che non possiamo muoverci tra un passo e l’altro se usiamo gli attributi [Required] o altre annotazioni di dati tra i passaggi. E avresti ragione, quindi rimuovi gli errori sugli articoli che devono ancora essere controllati. come sotto

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

Infine, salveremmo il modello una volta nell’archivio dati. Ciò impedisce anche a un utente che avvia una procedura guidata ma non la completa per non salvare dati incompleti nel database.

Spero che questo metodo di implementazione di una procedura guidata sia molto più semplice da utilizzare e gestire rispetto a qualsiasi metodo menzionato in precedenza.

Grazie per aver letto.

Volevo condividere il mio modo di gestire questi requisiti. Non volevo usare SessionState, né volevo che fosse gestito dal lato client, e il metodo serialize richiede MVC Futures che non volevo includere nel mio progetto.

Invece ho creato un helper HTML che eseguirà l’iterazione su tutte le proprietà del modello e genererà un elemento nascosto personalizzato per ognuno. Se è una proprietà complessa, verrà eseguita in modo ricorsivo su di essa.

Nel tuo modulo verranno postati sul controller insieme ai nuovi dati del modello in ogni fase della “procedura guidata”.

Ho scritto questo per 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(this HtmlHelper html, Expression> expression) { return HiddenClassFor(html, expression, null); } public static MvcHtmlString HiddenClassFor(this HtmlHelper html, Expression> 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(HtmlHelper html, LambdaExpression expression, ModelMetadata metaData, IDictionary 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(this HtmlHelper html, Expression> expression) { return HiddenArrayFor(html, expression, null); } public static MvcHtmlString HiddenArrayFor(this HtmlHelper html, Expression> 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(HtmlHelper html, LambdaExpression expression, ModelMetadata metaData, IDictionary 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(this HtmlHelper html, Expression> expression) { return MinHiddenFor(html, expression, null); } public static MvcHtmlString MinHiddenFor(this HtmlHelper html, Expression> 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 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 _pair in htmlAttributes) { _input.MergeAttribute(_pair.Key, _pair.Value.ToString(), true); } } return new MvcHtmlString(_input.ToString(TagRenderMode.SelfClosing)); } } } 

Ora per tutti i passaggi del “wizard” è ansible utilizzare lo stesso modello di base e passare le proprietà del modello “Step 1,2,3” nell’helper @ Html.HiddenClassFor utilizzando un’espressione lambda.

Puoi anche avere un pulsante indietro ad ogni passaggio se lo desideri. Basta avere un pulsante indietro nel modulo che lo invierà a un’azione StepNBack sul controller usando l’attributo formaction. Non incluso nell’esempio qui sotto, ma solo un’idea per te.

Comunque qui è un esempio di base:

Ecco il tuo MODELLO

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

Ecco il tuo CONTROLLER

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

Ecco le tue VISIONI

Passo 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)  } 

Passo 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)  } 

Passaggio 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" }, });  } 

Aggiunta di ulteriori informazioni dalla risposta di @ Darin.

Che cosa succede se si dispone di uno stile di progettazione separato per ogni passaggio e si desidera mantenerne ciascuno in una vista parziale separata o se si dispone di più proprietà per ogni passaggio?

Durante l’utilizzo di Html.EditorFor abbiamo la limitazione per usare la vista parziale.

Creare 3 viste parziali nella cartella Shared denominata: Step1ViewModel.cshtml , Step3ViewModel.cshtml , Step3ViewModel.cshtml

Per brevità sto solo postando la prima vista patial, altri passi sono gli stessi della risposta di Darin.

Step1ViewModel.cs

 [Serializable] public class Step1ViewModel : IStepViewModel { [Required] public string FirstName { get; set; } public string LastName { get; set; } public string PhoneNo { get; set; } public string EmailId { get; set; } public int Age { get; set; } } 

Step1ViewModel.cshtml

  @model WizardPages.ViewModels.Step1ViewModel 

Personal Details

@Html.TextBoxFor(x => x.FirstName)
@Html.TextBoxFor(x => x.LastName)
@Html.TextBoxFor(x => x.PhoneNo)
@Html.TextBoxFor(x => x.EmailId)

Index.cshtml

 @using Microsoft.Web.Mvc @model WizardPages.ViewModels.WizardViewModel @{ var currentStep = Model.Steps[Model.CurrentStepIndex]; string viewName = currentStep.ToString().Substring(currentStep.ToString().LastIndexOf('.') + 1); } 

Step @(Model.CurrentStepIndex + 1) out of @Model.Steps.Count

@using (Html.BeginForm()) { @Html.Serialize("wizard", Model) @Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType()) @Html.Partial(""+ viewName + "", currentStep); if (Model.CurrentStepIndex > 0) { } if (Model.CurrentStepIndex < Model.Steps.Count - 1) { } else { } }

Se c’è qualche soluzione migliore, si prega di commentare per far sapere agli altri.

Un’opzione è creare un insieme di tabelle identiche che archivieranno i dati raccolti in ogni passaggio. Quindi, nell’ultimo passaggio, se tutto va bene, è ansible creare l’ quadro reale copiando i dati temporanei e memorizzarli.

Altro è creare Value Objects per ogni passaggio e memorizzarlo in Cache o Session . Quindi se tutto va bene puoi creare il tuo object Dominio da loro e salvarlo