asp.net mvc (स्प्लिट व्यूमॉडल्स, सिंगल मॉडल) में बहु-चरण पंजीकरण प्रक्रिया के मुद्दे


117

मेरे पास एक बहु-चरण पंजीकरण प्रक्रिया है , जो डोमेन परत में एकल ऑब्जेक्ट द्वारा समर्थित है , जिसमें गुणों पर मान्यता नियम हैं।

जब डोमेन को कई दृश्यों में विभाजित किया जाता है, तो मुझे डोमेन ऑब्जेक्ट को कैसे मान्य करना चाहिए, और पोस्ट करते समय मुझे पहले दृश्य में आंशिक रूप से ऑब्जेक्ट को सहेजना होगा?

मैंने सत्रों का उपयोग करने के बारे में सोचा, लेकिन यह संभव नहीं है क्योंकि प्रक्रिया लंबी है और डेटा की मात्रा अधिक है, इसलिए मैं सत्र का उपयोग नहीं करना चाहता।

मैंने एक रिलेशनल-इन-मेमोरी डीबी (मुख्य डीबी के रूप में एक ही स्कीमा) के साथ सभी डेटा को बचाने के बारे में सोचा और फिर उस डेटा को मेन डीबी पर फ्लश कर दिया, लेकिन समस्याएं पैदा हुईं, मुझे सेवाओं (विचारों में अनुरोध) के बीच काम करना चाहिए मुख्य डीबी और मेमोरी डीबी।

मैं एक सुंदर और साफ समाधान की तलाश कर रहा हूं (अधिक सटीक रूप से एक सर्वोत्तम अभ्यास)।

अद्यतन और स्पष्टीकरण:

@ डारिन आपके विचारपूर्ण उत्तर के लिए धन्यवाद, यह वही है जो मैंने अब तक किया है। लेकिन संयोग से मुझे एक अनुरोध मिला है, जिसमें कई अटैचमेंट हैं, मैं Step2Viewउदाहरण के लिए डिज़ाइन करता हूं कि कौन सा उपयोगकर्ता इसमें दस्तावेज़ों को असिंक्रोनस रूप से अपलोड कर सकता है, लेकिन उन अटैचमेंट्स को एक तालिका के लिए संदर्भात्मक संबंध के साथ एक तालिका में सहेजा जाना चाहिए जो पहले सहेजे जाने चाहिए थेStep1View

इस प्रकार मुझे डोमेन ऑब्जेक्ट को Step1(आंशिक रूप से) में सहेजना चाहिए , लेकिन मैं समर्थित कोर डोमेन ऑब्जेक्ट का कारण नहीं बन सकता, जो कि आंशिक रूप से किसी Step1 के ViewModel में आंशिक रूप से मैप किया जाता है, बिना परिवर्तित हुए प्रॉम्प्स के बिना सहेजा नहीं जा सकता है Step2ViewModel


@ जानी, क्या आपने कभी इस के अपलोड टुकड़े का पता लगाया? मैं आपके दिमाग को चुनना चाहूंगा। मैं इस सटीक मुद्दे पर काम कर रहा हूं।
डग चैंबरलेन

1
इस ब्लॉग में समाधान काफी सरल और सीधे आगे है। यह उनकी दृश्यता और विनीत jquery सत्यापन को स्वाइप करके "कदम" के रूप में divs का उपयोग करता है।
दिमित्री एफिमेंको

जवाबों:


229

पहले आपको अपने विचार में किसी भी डोमेन ऑब्जेक्ट का उपयोग नहीं करना चाहिए। आपको दृश्य मॉडल का उपयोग करना चाहिए। प्रत्येक दृश्य मॉडल में केवल वे गुण होंगे जो दिए गए दृश्य के साथ-साथ इस दिए गए दृश्य के लिए मान्य विशेषता के लिए आवश्यक हैं। इसलिए यदि आपके पास 3 चरण विज़ार्ड हैं, तो इसका मतलब है कि आपके पास 3 दृश्य मॉडल होंगे, प्रत्येक चरण के लिए एक:

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

    ...
}

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

    ...
}

और इसी तरह। उन सभी दृश्य मॉडल को मुख्य विज़ार्ड दृश्य मॉडल द्वारा समर्थित किया जा सकता है:

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

तब आपके पास विज़ार्ड प्रक्रिया के प्रत्येक चरण को प्रस्तुत करने और मुख्य 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);
}

अब चरण 2 दृश्य के अंदर आप MVC वायदा से Html.Serialize सहायक का उपयोग कर सकते हैं ताकि चरण 1 को किसी छिपे हुए फ़ील्ड में प्रपत्र के अंदर क्रमबद्ध किया जा सके (यदि आप चाहें तो ViewState का प्रकार):

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

और चरण 2 की 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);
}

और इसलिए जब तक आप अंतिम चरण तक नहीं पहुंच जाते हैं जहां आपके पास होगा WizardViewModel सभी डेटा भर । फिर आप व्यू मॉडल को अपने डोमेन मॉडल पर मैप करेंगे और इसे प्रोसेसिंग के लिए सर्विस लेयर पर भेज देंगे। सेवा की परत किसी भी सत्यापन नियम और इतने पर ही हो सकती है ...

एक अन्य विकल्प भी है: जावास्क्रिप्ट का उपयोग करना और सभी को एक ही पृष्ठ पर रखना। वहाँ कई jquery प्लगइन्स हैं जो विज़ार्ड कार्यक्षमता प्रदान करते हैं ( स्टेपी एक अच्छा है)। यह मूल रूप से क्लाइंट पर divs दिखाने और छिपाने का मामला है, जिस स्थिति में आपको चरणों के बीच लगातार स्थिति के बारे में चिंता करने की आवश्यकता नहीं है।

लेकिन इससे कोई फर्क नहीं पड़ता कि आप जो भी समाधान चुनते हैं वह हमेशा व्यू मॉडल का उपयोग करें और उन व्यू मॉडल पर सत्यापन करें। जब तक आप डेटा एनोटेशन सत्यापन विशेषताओं को अपने डोमेन मॉडल पर चिपका रहे हैं, तब तक आप बहुत कठिन संघर्ष करेंगे क्योंकि डोमेन मॉडल विचारों के अनुकूल नहीं हैं।


अपडेट करें:

ठीक है, कई टिप्पणियों के कारण मैं यह निष्कर्ष निकालता हूं कि मेरा उत्तर स्पष्ट नहीं था। और मुझे सहमत होना चाहिए। इसलिए मुझे अपने उदाहरण को और विस्तृत करने का प्रयास करना चाहिए।

हम एक इंटरफ़ेस को परिभाषित कर सकते हैं जिसे सभी स्टेप व्यू मॉडल को लागू करना चाहिए (यह सिर्फ एक मार्कर इंटरफ़ेस है):

public interface IStepViewModel
{
}

फिर हम विज़ार्ड के लिए 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; }
}

अगले हम मुख्य विज़ार्ड व्यू मॉडल को परिभाषित करते हैं जिसमें चरणों की सूची और एक वर्तमान कदम सूचकांक शामिल हैं:

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

इस नियंत्रक के बारे में टिप्पणी की जोड़ी:

  • अनुक्रमणिका POST कार्रवाई [Deserialize]Microsoft फ्यूचर्स लाइब्रेरी से विशेषताओं का उपयोग करती है इसलिए सुनिश्चित करें कि आपने MvcContribNuGet स्थापित किया है । यही कारण है कि दृश्य मॉडल को [Serializable]विशेषता के साथ सजाया जाना चाहिए
  • सूचकांक 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;
    }
}

यह बाइंडर एक विशेष छिपे हुए क्षेत्र का उपयोग करता है जिसे स्टेप टाइप कहा जाता है जिसमें प्रत्येक चरण का ठोस प्रकार होगा और जिसे हम प्रत्येक अनुरोध पर भेजेंगे।

इस मॉडल बाइंडर में पंजीकृत किया जाएगा Application_Start:

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

पहेली के अंतिम गायब बिट दृश्य हैं। यहाँ मुख्य ~/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" />
    }
}

और आपको यह काम करने की आवश्यकता है। बेशक अगर आप चाहते थे कि आप कस्टम संपादक टेम्प्लेट को परिभाषित करके विज़ार्ड के कुछ या सभी चरणों के रंगरूप को महसूस कर सकें। उदाहरण के लिए आइए इसे चरण 2 के लिए करें। इसलिए हम एक ~/Views/Wizard/EditorTemplates/Step2ViewModel.cshtmlआंशिक परिभाषित करते हैं :

@model Step2ViewModel

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

यहां बताया गया है कि संरचना कैसी दिखती है:

यहां छवि विवरण दर्ज करें

बेशक सुधार की गुंजाइश है। इंडेक्स 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);
}

जो अधिक है कि कैसे POST कार्यों को देखना चाहिए। मैं अगली बार के लिए यह सुधार छोड़ रहा हूँ :-)


1
@Doug चेम्बरलेन, मैं का उपयोग AutoMapper मेरे विचार मॉडल और डोमेन मॉडलों के बीच परिवर्तित करने के लिए।
डारिन दिमित्रोव

1
@ डड चैंबरलेन, कृपया मेरा अद्यतन उत्तर देखें। मुझे आशा है कि यह मेरी प्रारंभिक पोस्ट की तुलना में चीजों को थोड़ा अधिक स्पष्ट करता है।
डैरिन दिमित्रोव

20
+1 @ जानी: आपको वास्तव में डारिन को इस उत्तर के लिए 50 अंक देने होंगे। यह बहुत व्यापक है। और वह ViewModel और डोमेन मॉडल का उपयोग करने की आवश्यकता को दोहराने में कामयाब रहे ;-)
टॉम चैंटलर

3
मुझे कहीं भी Deserialize विशेषता नहीं मिल सकती है ... इसके अलावा mvccontrib के कोडप्लेक्स पृष्ठ में मुझे यह 94fa6078a115 जेरेमी स्किनर द्वारा 1 अगस्त 2010 को शाम 5:55 बजे मिलेगा। पदावनत Deserialize बांधने की मशीन निकालें आप मुझे करने का सुझाव देते हैं?
चॉक नॉरिस

2
मुझे एक समस्या मिली जबकि मैंने अपने विचारों को नाम नहीं दिया था Step1, Step2, आदि ... मेरा नाम कुछ और सार्थक है, लेकिन वर्णमाला नहीं। इसलिए, मैंने गलत क्रम में अपने मॉडल प्राप्त करना समाप्त कर दिया। मैंने IStepViewModel इंटरफ़ेस में एक StepNumber गुण जोड़ा। अब मैं WizardViewModel की प्रारंभिक विधि में इसके द्वारा सॉर्ट कर सकता हूं।
जेफ रेड्डी

13

अमित बग्गा के जवाब पर पूरक करने के लिए आप नीचे पाएंगे कि मैंने क्या किया। भले ही कम सुरुचिपूर्ण मैं इस तरह से डारिन के उत्तर की तुलना में सरल लगता हूं।

नियंत्रक:

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

मॉडल :

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

11

मैं आपको सुझाव दूंगा कि आप Jquery का उपयोग करके क्लाइंट पर पूर्ण प्रक्रिया की स्थिति बनाए रखें।

उदाहरण के लिए हमारे पास एक तीन कदम जादूगर प्रक्रिया है।

  1. Step1 के साथ प्रस्तुत उपयोगकर्ता जिस पर एक बटन है "अगला"
  2. नेक्स्ट पर क्लिक करने पर हम एक अजाक्स रिक्वेस्ट बनाते हैं और Step2 नामक एक DIV बनाते हैं और HTML को उस DIV में लोड करते हैं।
  3. Step3 पर हमारे पास बटन पर "समाप्त" लेबल है बटन पर क्लिक करके $ .post कॉल का उपयोग करके डेटा पोस्ट करें।

इस तरह आप आसानी से फॉर्म पोस्ट डेटा से अपनी डोमेन ऑब्जेक्ट को आसानी से बना सकते हैं और यदि डेटा में त्रुटियां हैं, तो सभी त्रुटि संदेश को पकड़े हुए JSON को वापस लौटाएं और उन्हें डिव में प्रदर्शित करें।

कृपया स्टेप्स को विभाजित करें

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

उपरोक्त केवल एक प्रदर्शन है जो आपको अंतिम परिणाम प्राप्त करने में मदद करेगा। अंतिम चरण पर आपको डोमेन ऑब्जेक्ट बनाना होगा और विज़ार्ड ऑब्जेक्ट और स्टोर से सही मानों को डेटाबेस में पॉप्युलेट करना होगा।


हाँ, यह एक दिलचस्प समाधान है, लेकिन हमारे पास दुर्भाग्य से ग्राहक की ओर से खराब इंटरनेट कनेक्शन है, और उसे हमें फाइलों का एक गुच्छा भेजना चाहिए। इसलिए हमने उस समाधान को पहले खारिज कर दिया।
जहान

क्या आप कृपया मुझे उस डेटा की मात्रा बता सकते हैं जिसे क्लाइंट अपलोड करने जा रहा है।
अमित बग्गा

कई फाइलें, लगभग दस, हर एक लगभग 1 एमबी।
जहान

5

विजार्ड एक साधारण मॉडल को संसाधित करने में सिर्फ सरल कदम हैं। विज़ार्ड के लिए कई मॉडल बनाने का कोई कारण नहीं है। आपको बस एक ही मॉडल बनाना होगा और इसे एक ही कंट्रोलर में क्रियाओं के बीच पारित करना होगा।

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

उपरोक्त coed मूर्ख सरल है इसलिए अपने खेतों को वहां बदलें। अगला हम एक सरल क्रिया से शुरू करते हैं जो हमारे विज़ार्ड को आरंभ करती है।

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

यह "विज़ार्डस्टेप 1.cshtml" (यदि रेजर का उपयोग कर रहा है) देखें तो कॉल करता है। यदि आप चाहें तो आप टेम्प्लेट विज़ार्ड का उपयोग कर सकते हैं। हम सिर्फ पोस्ट को एक अलग एक्शन में रीडायरेक्ट करेंगे।

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

ध्यान देने वाली बात यह है कि हम इसे एक अलग कार्रवाई में पोस्ट करेंगे; विज़ार्डस्टेप 2 कार्रवाई

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

इस क्रिया में हम जाँचते हैं कि क्या हमारा मॉडल वैध है, और यदि ऐसा है तो हम इसे अपने विजार्डस्टेप 2 सी.एच.एस.टी.एम.एल दृश्य पर भेजते हैं और हम इसे सत्यापन त्रुटियों के साथ एक चरण में वापस भेज देते हैं। प्रत्येक चरण में हम इसे अगले चरण पर भेजते हैं, उस चरण को मान्य करते हैं और आगे बढ़ते हैं। अब कुछ समझदार डेवलपर्स अच्छी तरह से कह सकते हैं कि हम इस तरह के कदमों के बीच नहीं बढ़ सकते हैं यदि हम चरणों के बीच [आवश्यक] विशेषताओं या अन्य डेटा एनोटेशन का उपयोग करते हैं। और आप सही होंगे, इसलिए उन वस्तुओं पर त्रुटियों को हटा दें जिन्हें अभी तक जांचना बाकी है। नीचे की तरह।

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

अंत में हम डेटा स्टोर में एक बार मॉडल को सहेजेंगे। यह एक उपयोगकर्ता को रोकता है जो एक विज़ार्ड शुरू करता है लेकिन डेटाबेस के अधूरे डेटा को बचाने के लिए इसे समाप्त नहीं करता है।

मुझे आशा है कि आप पहले से बताए गए तरीकों में से किसी का उपयोग करने और बनाए रखने के लिए विज़ार्ड को लागू करने की इस विधि को बहुत आसान पाएंगे।

पढ़ने के लिए धन्यवाद।


क्या आपके पास एक पूर्ण समाधान है जो मैं बाहर की कोशिश कर सकता हूं? धन्यवाद
mpora

5

मैं इन आवश्यकताओं को संभालने का अपना तरीका साझा करना चाहता था। मैं SessionState का उपयोग बिल्कुल नहीं करना चाहता था, और न ही मैं इसे क्लाइंट साइड हैंडल करना चाहता था, और धारावाहिक विधि के लिए MVC फ्यूचर्स की आवश्यकता होती है जिसे मैं अपने प्रोजेक्ट में शामिल नहीं करना चाहता था।

इसके बजाय मैंने एक HTML हेल्पर बनाया जो मॉडल के सभी गुणों के माध्यम से पुनरावृति करेगा और प्रत्येक के लिए एक कस्टम छिपा हुआ तत्व उत्पन्न करेगा। यदि यह एक जटिल संपत्ति है तो यह उस पर पुनरावर्ती रूप से चलेगा।

आपके फॉर्म में वे प्रत्येक "विज़ार्ड" चरण में नए मॉडल डेटा के साथ नियंत्रक पर पोस्ट किए जाएंगे।

मैंने यह 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));
        }
    }
}

अब अपने "विज़ार्ड" के सभी चरणों के लिए आप एक ही बेस मॉडल का उपयोग कर सकते हैं और "स्टेप 1,2,3" मॉडल गुणों को @ Html.HiddenClassFor सहायक में एक लैम्ब्डा अभिव्यक्ति का उपयोग करके पास कर सकते हैं।

यदि आप चाहें तो आप प्रत्येक चरण पर एक बैक बटन भी रख सकते हैं। बस आपके फॉर्म में एक बैक बटन है जो इसे फॉर्मेशन विशेषता का उपयोग करके कंट्रोलर पर StepNBack एक्शन के लिए पोस्ट करेगा। नीचे दिए गए उदाहरण में शामिल नहीं है लेकिन आपके लिए सिर्फ एक विचार है।

वैसे भी यहाँ एक मूल उदाहरण है:

यहाँ आपका MODEL है

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

यहां आपका कंट्रोलर है

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

यहाँ आपके VIEWS हैं

चरण 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>
}

चरण 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>
}

चरण 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" },
        });

    <button type="submit">Submit</button>
}

1
क्या आप दृश्य मॉडल और नियंत्रक प्रदान करके अपने समाधान को और स्पष्ट कर सकते हैं?
टायलर डर्डन

2

@ डारिन के उत्तर से अधिक जानकारी जोड़ना।

क्या होगा यदि आपके पास प्रत्येक चरण के लिए अलग-अलग डिज़ाइन शैली है और प्रत्येक को अलग-अलग आंशिक दृश्य में बनाए रखना चाहता है या क्या होगा यदि आपके पास प्रत्येक चरण के लिए कई गुण हैं?

उपयोग Html.EditorForकरते समय हमारे पास आंशिक दृश्य का उपयोग करने के लिए सीमा होती है।

Sharedनामित फ़ोल्डर के तहत 3 आंशिक दृश्य बनाएं :Step1ViewModel.cshtml , Step3ViewModel.cshtml , Step3ViewModel.cshtml

संक्षिप्तता के लिए मैं सिर्फ 1 पटकथा दृश्य पोस्ट कर रहा हूं, अन्य कदम डारिन के उत्तर के समान हैं।

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

<div class="container">
    <h2>Personal Details</h2>

    <div class="form-group">
        <label class="control-label col-sm-2" for="email">First Name:</label>
        <div class="col-sm-10">
            @Html.TextBoxFor(x => x.FirstName)
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2" for="pwd">Last Name:</label>
        <div class="col-sm-10">
            @Html.TextBoxFor(x => x.LastName)
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2" for="pwd">Phone No:</label>
        <div class="col-sm-10"> 
            @Html.TextBoxFor(x => x.PhoneNo)
        </div>
    </div>
    <div class="form-group">
        <label class="control-label col-sm-2" for="pwd">Email Id:</label>
        <div class="col-sm-10">
            @Html.TextBoxFor(x => x.EmailId)
        </div>
    </div>


</div>

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

<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.Partial(""+ viewName + "", currentStep);

    if (Model.CurrentStepIndex > 0)
    {

     <input type="submit" value="Previous" name="prev" class="btn btn-warning" />

    }

    if (Model.CurrentStepIndex < Model.Steps.Count - 1)
    {

      <input type="submit" value="Next" name="next" class="btn btn-info" />

    }
    else
    {

      <input type="submit" value="Finish" name="finish" class="btn btn-success" />

    }
}

अगर कुछ बेहतर समाधान है, तो कृपया दूसरों को बताने के लिए टिप्पणी करें।


-9

एक विकल्प समान तालिकाओं का सेट बनाना है जो प्रत्येक चरण में एकत्रित डेटा को संग्रहीत करेगा। फिर अंतिम चरण में अगर सब ठीक हो जाता है तो आप अस्थायी डेटा की प्रतिलिपि बनाकर वास्तविक इकाई बना सकते हैं और इसे स्टोर कर सकते हैं।

अन्य को Value Objectsप्रत्येक चरण के लिए बनाना है और फिर Cacheया में स्टोर करना है Session। फिर अगर सब ठीक हो जाता है तो आप उनसे अपनी डोमेन ऑब्जेक्ट बना सकते हैं और उसे बचा सकते हैं


1
अच्छा होगा अगर वोट डालने वाले लोग भी अपनी वजह बताएं।
मार्टिन

आपको वोट नहीं दिया, लेकिन आपका जवाब सवाल के लिए पूरी तरह अप्रासंगिक है। ओपी पूछ रहा है कि विज़ार्ड कैसे बनाया जाए, जबकि आप इस पर जवाब देते हैं कि पीठ में प्रतिक्रिया को कैसे संभालना है।
विमुद्रीकरण

1
मैं आमतौर पर वोट नहीं करता, लेकिन जब मैं करता हूं, तो मैं यह सुनिश्चित करता हूं कि इसका उत्थान :-)
सुहेल मुमताज अवन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.