D'abord, vous ne devriez pas être à l'aide de n'importe quel domaine d'objets dans vos vues. Vous devriez être en utilisant les modèles de vue. Chaque modèle de vue contiendra uniquement les propriétés qui sont requis par la vue ainsi que la validation des attributs spécifiques à ce point de vue. Donc, si vous avez 3 étapes de l'assistant, cela signifie que vous aurez 3 modèles de vue, un pour chaque étape:
public class Step1ViewModel
{
[Required]
public string SomeProperty { get; set; }
...
}
public class Step2ViewModel
{
[Required]
public string SomeOtherProperty { get; set; }
...
}
et ainsi de suite. Tous ces modèles de vue pourrait être soutenu par la principale de l'assistant de modèle de vue:
public class WizardViewModel
{
public Step1ViewModel Step1 { get; set; }
public Step2ViewModel Step2 { get; set; }
...
}
vous pourriez avoir des actions de contrôleur rendu de chaque étape de la procédure de l'assistant et en passant le principal WizardViewModel
à la vue. Lorsque vous êtes sur la première étape à l'intérieur du contrôleur de l'action que vous avez pu initialiser l' Step1
de la propriété. Puis à l'intérieur de la vue qui vous permettrait de générer le formulaire permettant à l'utilisateur de remplir les propriétés de l'étape 1. Lorsque le formulaire est soumis à l'action du contrôleur va appliquer les règles de validation de l'étape 1 uniquement:
[HttpPost]
public ActionResult Step1(Step1ViewModel step1)
{
var model = new WizardViewModel
{
Step1 = step1
};
if (!ModelState.IsValid)
{
return View(model);
}
return View("Step2", model);
}
Maintenant à l'intérieur de l'étape 2 en vue, vous pourriez utiliser le Html.Sérialiser helper de MVC futures afin de sérialiser l'étape 1 dans un champ caché à l'intérieur de la forme (une sorte de ViewState si vous le souhaitez):
@using (Html.BeginForm("Step2", "Wizard"))
{
@Html.Serialize("Step1", Model.Step1)
@Html.EditorFor(x => x.Step2)
...
}
et à l'intérieur de l'action POST de 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);
}
Et ainsi de suite jusqu'à ce que vous arrivez à la dernière étape où vous aurez l' WizardViewModel
rempli avec toutes les données. Ensuite vous aurez la carte de la vue modèle de votre modèle de domaine et de le transmettre à la couche de service pour le traitement. La couche de service peut effectuer toutes les règles de validation de lui-même et ainsi de suite ...
Il y a aussi une autre solution: à l'aide de javascript et de mettre tous sur la même page. Il existe de nombreux plugins jquery là-bas qui fournissent la fonctionnalité de l'assistant (Stepy est une belle). C'est essentiellement une question de montrer et de cacher les divs sur le client, dans ce cas, vous n'avez plus besoin de vous inquiéter de la persistance de l'état entre les étapes.
Mais n'importe quelle solution vous choisissez toujours utiliser des modèles de vue et d'effectuer la validation de ces modèles de vue. Tant que vous êtes collant annotation de données attributs de validation de votre domaine de modèles vous permettra de combat très dur que les modèles de domaine ne sont pas adaptées à la vue.
Mise à JOUR:
OK, en raison des nombreux commentaires que j'ai tirer la conclusion que ma réponse n'était pas claire. Et je dois dire. Donc, laissez-moi essayer de développer mon exemple.
Nous pourrions définir une interface qui tous les modèles de vue devraient mettre en œuvre (c'est juste un marqueur de l'interface):
public interface IStepViewModel
{
}
ensuite, nous définissons les 3 étapes de l'assistant, où chaque étape de cours contiennent uniquement les propriétés qu'il exige ainsi que les attributs de 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; }
}
ensuite, nous définissons le principal assistant de modèle de vue qui consiste en une liste d'étapes et une étape actuelle de l'indice:
[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();
}
}
Ensuite, nous passons sur le contrôleur:
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);
}
}
Quelques remarques à propos de ce contrôleur:
- L'Indice POST action utilise l'
[Deserialize]
attributs de Microsoft à Terme de bibliothèque donc, assurez-vous que vous avez installé l' MvcContrib
NuGet. C'est la raison pour laquelle les modèles de vue doit être décoré avec de la [Serializable]
d'attribut
- L'Indice suivant l'action prend comme argument une
IStepViewModel
d'une interface pour ce qui est logique nous avons besoin d'un modèle de liaison personnalisé.
Voici le modèle associé classeur:
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;
}
}
Ce classeur utilise un champ caché appelé StepType qui contiendra le type concret de chaque étape et à qui nous enverrons à chaque demande.
Ce modèle de classeur sera enregistré dans Application_Start
:
ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());
Le dernier morceau du puzzle sont les points de vue. Voici les principales ~/Views/Wizard/Index.cshtml
vue:
@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" />
}
}
Et c'est tout ce que vous devez faire ce travail. Bien sûr, si vous vouliez vous pourriez personnaliser l'aspect et la convivialité de certaines ou de toutes les étapes de l'assistant par la définition d'un éditeur personnalisé modèle. Pour exemple, nous allons le faire pour l'étape 2. C'est pourquoi nous définissons un ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtml
partielle:
@model Step2ViewModel
Special Step 2
@Html.TextBoxFor(x => x.Bar)
Voici comment la structure ressemble à:
![enter image description here]()
Bien sûr, il y a place à l'amélioration. L'Indice action POST ressemble à s..t. Il y a trop de code. Une autre simplification consisterait en déplacement de toutes les infrastructures des trucs comme indice, indice actuel de la gestion, de la copie de l'étape en cours dans l'assistant, ... dans un autre modèle de classeur. De sorte que, finalement, nous nous retrouvons avec:
[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);
}
qui est de plus comment les actions POST devrait ressembler. Je quitte cette amélioration pour la prochaine fois :-)