120 votes

questions relatives au processus d’enregistrement de plusieurs étapes dans asp.net mvc (coupée en deux viewmodels, modèle unique)

J'ai un multi-étape processus d'inscription, soutenu par un objet unique dans le domaine de la couche, qui ont des règles de validation défini sur propriétés.

Comment dois-je valider le domaine de l'objet lorsque le domaine est divisé en de nombreux points de vue, et je dois économiser de l'objet partiellement dans le premier moment de leur publication?

J'ai pensé à utiliser les Sessions, mais ce n'est pas cause possible le processus est long et la quantité de données est élevé, Donc je ne veux pas utiliser la session.

J'ai pensé à propos de l'enregistrement de toutes les données dans un relationnel-mémoire db (avec le même schéma que principal db)puis rinçage des données db principaux mais les questions surgi parce que je devrait la route entre les services (demandé dans les points de vue) qui travaillent avec les principaux db et en mémoire db.

Je suis à la recherche d'un élégant et propre solution (plus précisément, une meilleure pratique).

Mise à JOUR ET Précisions:

@Darin Merci pour votre aimable réponse, C'est exactement ce que j'ai fait jusqu'à maintenant. Mais d'ailleurs j'ai une demande qui ont beaucoup de pièces jointes, j'ai de la conception d'un Step2View par exemple, l'utilisateur qui peut télécharger des documents de manière asynchrone , mais ces pièces jointes doivent être enregistrées dans une table avec référentielle rapport à une autre table qui doit avoir été enregistré avant en Step1View.

Donc je devrais sauver le domaine de l'objet en Step1 (en partie), Mais je ne peux pas, cause la sauvegarde de Domaine de Base de l'objet qui est lié en partie à une Étape 1 du ViewModel peut pas être sauvé sans les accessoires qui viennent de convertis Step2ViewModel.

235voto

Darin Dimitrov Points 528142

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 :-)

13voto

Arno 2501 Points 1582

Pour compléter sur la réponse de Amit Bagga, vous trouverez ci-dessous ce que j’ai fait. Même si moins élégant je trouve cela moyen plus simple que la réponse de Darin.

Contrôleur :

Modèles :

11voto

Amit Bagga Points 442

Je vous suggère de maintenir l'état des Processus Complet sur le client à l'aide de Jquery.

Par Exemple, nous avons une des Trois étapes du processus de l'Assistant.

  1. L'utilisateur est présenté avec l'Étape 1 sur qui a un bouton "Suivant"
  2. En Cliquant sur Suivant Nous faire une Requête Ajax et de Créer un DIV appelée l'étape 2, et de charger le code HTML sur DIV.
  3. Sur l'Étape 3, nous avons un Bouton marqué "Terminé" en Cliquant sur le bouton afficher les données à l'aide de $.appel post.

De cette façon, vous pouvez facilement construire votre domaine d'objet directement à partir de la forme de données de postes et dans le cas où les données des erreurs de retour JSON valide la tenue de tout le message d'erreur et de les afficher dans un div.

Veuillez diviser les Étapes

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

Ce qui Précède est juste une démonstration qui vous aidera à atteindre le résultat final. Sur la dernière Étape, vous devez créer l'Objet de Domaine et de le remplir avec les valeurs correctes à partir de l'Assistant de l'Objet et de les Stocker dans la base de données.

5voto

Darroll Points 121

Les assistants sont de simples étapes dans le traitement d'un modèle simple. Il n'y a pas de raison de créer plusieurs modèles d'un assistant. Tout ce que vous avez à faire est de créer un modèle unique et de passer entre les actions d'un contrôleur unique.

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

Le ci-dessus étudiante est stupide simples afin de remplacer vos champs. Ensuite, nous commençons par une simple action qui lance notre assistant.

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

Cela appelle le point de vue "WizardStep1.cshtml (si vous utilisez un rasoir). Vous pouvez utiliser la créer un modèle de l'assistant si vous le souhaitez. Il nous suffit de rediriger le poste à une action différente.

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

La chose à noter est que nous publierons cette action; l'action WizardStep2

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

Dans cette action, nous vérifions si notre modèle est valide, et si oui, nous l'envoyer à notre WizardStep2.cshtml vue d'autre nous le renvoyer à l'étape de l'un avec les erreurs de validation. Dans chaque étape, nous l'envoyons à l'étape suivante, de valider cette étape et de passer. Maintenant, certains futés, les développeurs pourraient bien dire que nous ne pouvons pas déplacer entre les étapes comme si nous utilisons les attributs [Obligatoire] ou d'autres annotations de données entre les étapes. Et vous auriez raison, afin de supprimer les erreurs sur les points qui doivent encore être vérifiées. comme ci-dessous.

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

Enfin, nous permettrait de sauver le modèle, une fois à la banque de données. Cela empêche également d'un utilisateur qui démarre un assistant, mais n'a pas fini de ne pas enregistrer les données incomplètes à la base de données.

J'espère que vous trouverez cette méthode de la mise en œuvre d'un assistant beaucoup plus facile à utiliser et à entretenir que tout le mentionné précédemment méthodes.

Merci pour la lecture.

-9voto

Amila Silva Points 20

Une option consiste à créer des tables identiques qui stockera les données recueillies lors de chaque étape. Puis dans la dernière étape si tout va bien vous pouvez créer l’entité réelle en copiant les données temporaires et stockez-le.

Autre consiste à créer des pour chaque étape et stocker ensuite dans ou `` . Puis si tout va bien vous pouvez créer votre objet de domaine de leur part et l’enregistrer

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X