ASP.NET MVC - RedirectToAction के पार मॉडलस्ट्रेट त्रुटियों को कैसे संरक्षित करें?


91

मेरे पास निम्नलिखित दो कार्य विधियाँ हैं (प्रश्न के लिए सरलीकृत):

[HttpGet]
public ActionResult Create(string uniqueUri)
{
   // get some stuff based on uniqueuri, set in ViewData.  
   return View();
}

[HttpPost]
public ActionResult Create(Review review)
{
   // validate review
   if (validatedOk)
   {
      return RedirectToAction("Details", new { postId = review.PostId});
   }  
   else
   {
      ModelState.AddModelError("ReviewErrors", "some error occured");
      return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
   }   
}

इसलिए, यदि सत्यापन पास हो जाता है, तो मैं दूसरे पृष्ठ (पुष्टिकरण) पर पुनर्निर्देशित करता हूं।

यदि कोई त्रुटि होती है, तो मुझे उसी पृष्ठ को त्रुटि के साथ प्रदर्शित करने की आवश्यकता है।

यदि मैं करता हूं return View(), तो त्रुटि प्रदर्शित होती है, लेकिन यदि मैं return RedirectToAction(जैसा कि ऊपर) करता हूं , यह मॉडल त्रुटियों को खो देता है।

मैं इस मुद्दे से हैरान नहीं हूं, बस सोच रहा था कि आप लोग इसे कैसे संभालेंगे?

मैं बेशक केवल रीडायरेक्ट के बजाय उसी दृश्य को वापस कर सकता था, लेकिन मेरे पास "क्रिएट" विधि में तर्क है जो दृश्य डेटा को पॉप्युलेट करता है, जिसे मुझे डुप्लिकेट करना होगा।

कोई सुझाव?


10
मैं सत्यापन त्रुटियों के लिए पोस्ट-रीडायरेक्ट-गेट पैटर्न का उपयोग न करके इस समस्या को हल करता हूं। मैं सिर्फ View () का उपयोग करता हूं। यह पूरी तरह से वैध है कि हूप्स के झुंड के माध्यम से कूदने के बजाय - और अपने ब्राउज़र के इतिहास के साथ रीडायरेक्ट करें।
जिमी बोगार्ड

2
और @JimmyBogard ने जो कहा है, उसके अलावा, उस Createविधि में तर्क को बाहर निकालें जो ViewData को पॉप्युलेट करता है और इसे CreateGET विधि में और CreatePOST विधि में विफल सत्यापन शाखा में भी कहता है ।
रस कैम

1
सहमत होना, समस्या से बचना इसे हल करने का एक तरीका है। मेरे Createविचार में सामानों को आबाद करने के लिए मेरे पास कुछ तर्क हैं, मैंने इसे किसी ऐसी विधि में रखा है populateStuffजिसे मैं GETअसफलता दोनों में कहता हूं POST
फ्रैंकोइस जोली

12
@JimmyBogard मैं असहमत हूं, यदि आप किसी कार्रवाई के लिए पोस्ट करते हैं और फिर उस दृश्य को वापस लौटाते हैं जिसे आप उस मुद्दे पर चलाते हैं जहां यदि उपयोगकर्ता ताज़ा करता है तो उसे फिर से उस पोस्ट को आरंभ करने के बारे में चेतावनी मिलती है।
मफिन मैन

जवाबों:


50

आपको Reviewअपनी HttpGetकार्रवाई पर एक ही उदाहरण की आवश्यकता है । ऐसा करने के लिए आपको Review reviewअपनी HttpPostकार्रवाई पर अस्थायी चर में एक ऑब्जेक्ट को सहेजना चाहिए और फिर इसे HttpGetकार्रवाई पर पुनर्स्थापित करना चाहिए।

[HttpGet]
public ActionResult Create(string uniqueUri)
{
   //Restore
   Review review = TempData["Review"] as Review;            

   // get some stuff based on uniqueuri, set in ViewData.  
   return View(review);
}
[HttpPost]
public ActionResult Create(Review review)
{
   //Save your object
   TempData["Review"] = review;

   // validate review
   if (validatedOk)
   {
      return RedirectToAction("Details", new { postId = review.PostId});
   }  
   else
   {
      ModelState.AddModelError("ReviewErrors", "some error occured");
      return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
   }   
}

यदि आप चाहते हैं कि कार्य निष्पादन के पहले निष्पादन के बाद भी यदि ब्राउज़र ताज़ा हो HttpGet, तो आप ऐसा कर सकते हैं:

  Review review = TempData["Review"] as Review;  
  TempData["Review"] = review;

नहीं तो ताज़ा बटन ऑब्जेक्ट reviewखाली हो जाएगा क्योंकि इसमें कोई डेटा नहीं होगा TempData["Review"]


2
अति उत्कृष्ट। और ताज़ा मुद्दे का उल्लेख करने के लिए एक बड़ा +1। यह सबसे पूर्ण उत्तर है इसलिए मैं इसे स्वीकार करूंगा, धन्यवाद एक गुच्छा। :)
आरपीएम १

8
यह वास्तव में शीर्षक में प्रश्न का उत्तर नहीं देता है। ModelState संरक्षित नहीं है और इसमें इनपुट HtmlHelpers जैसे प्रभाव हैं जो उपयोगकर्ता प्रविष्टि को संरक्षित नहीं करते हैं। यह लगभग वर्कअराउंड है।
जॉन फैरेल

मैंने अपने उत्तर में जो सुझाव दिया था, उसे मैंने समाप्त कर दिया।
RPM1984

17
@ जफर, मैं सहमत हूं, यह जवाब काम नहीं करता है और मॉडलस्टैट को बनाए नहीं रखता है। हालाँकि, यदि आप इसे संशोधित करते हैं तो यह कुछ ऐसा करता है TempData["ModelState"] = ModelState; और इसके साथ पुनर्स्थापित करता है ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);, तो यह काम करेगा
asgeo1

1
क्या आप return Create(uniqueUri)तब नहीं कर सकते जब मान्यता POST पर विफल हो? जैसा कि मॉडलस्टैट मान दृश्य में दिए गए ViewModel पर वरीयता लेते हैं, पोस्ट किए गए डेटा अभी भी बने रहना चाहिए।
अंजबीन जू

83

मुझे आज खुद इस समस्या को हल करना था, और इस सवाल पर आया।

कुछ उत्तर उपयोगी हैं (TempData का उपयोग करके), लेकिन वास्तव में सवाल का जवाब नहीं देते हैं।

सबसे अच्छी सलाह मुझे इस ब्लॉग पोस्ट पर मिली:

http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html

मूल रूप से, ModelState ऑब्जेक्ट को बचाने और पुनर्स्थापित करने के लिए TempData का उपयोग करें। हालाँकि, यह बहुत साफ है अगर आप इसे विशेषताओं में दूर करते हैं।

उदाहरण के लिए

public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);         
        filterContext.Controller.TempData["ModelState"] = 
           filterContext.Controller.ViewData.ModelState;
    }
}

public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        if (filterContext.Controller.TempData.ContainsKey("ModelState"))
        {
            filterContext.Controller.ViewData.ModelState.Merge(
                (ModelStateDictionary)filterContext.Controller.TempData["ModelState"]);
        }
    }
}

फिर अपने उदाहरण के अनुसार, आप मॉडलस्ट्रेट को इस तरह से सहेज / बहाल कर सकते हैं:

[HttpGet]
[RestoreModelStateFromTempData]
public ActionResult Create(string uniqueUri)
{
    // get some stuff based on uniqueuri, set in ViewData.  
    return View();
}

[HttpPost]
[SetTempDataModelState]
public ActionResult Create(Review review)
{
    // validate review
    if (validatedOk)
    {
        return RedirectToAction("Details", new { postId = review.PostId});
    }  
    else
    {
        ModelState.AddModelError("ReviewErrors", "some error occured");
        return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]});
    }   
}

यदि आप भी टेंपडाटा में मॉडल को पास करना चाहते हैं (जैसा कि बिग बी ने सुझाव दिया है) तो आप अभी भी ऐसा कर सकते हैं।


धन्यवाद। हमने आपके दृष्टिकोण के समान कुछ लागू किया है। gist.github.com/ferventcoder/4735084
ferventcoder

बहुत बढ़िया जवाब। धन्यवाद।
मार्क विकी

3
यह समाधान है जिसका कारण मैं स्टैकओवरफ़्लो का उपयोग करता हूं। धन्यवाद दोस्त!
jugg1es

@ asgeo1 - महान समाधान, लेकिन मैं आंशिक दृश्यों को दोहराने के साथ संयोजन में इसका उपयोग करके एक समस्या में भाग गया, मैंने यहां प्रश्न पोस्ट किया: stackoverflow.com/questions/28372330/…
जोश

MVC की भावना में सरल समाधान लेने और इसे बहुत ही सुरुचिपूर्ण बनाने का सुंदर उदाहरण। बहुत अच्छा!
हौगेगो

7

क्यों "बनाएँ" विधि में तर्क के साथ एक निजी फ़ंक्शन नहीं बनाते हैं और इस विधि को गेट और पोस्ट विधि दोनों से कॉल कर रहे हैं और बस दृश्य लौटाएं ()।


यह वास्तव में मैं क्या कर रहा हूँ - आपने मेरे मन को पढ़ा। +1 :)
RPM1984

1
यह वही है जो मैं भी करता हूं, केवल एक निजी कार्य करने के बजाय, मेरे पास बस अपना POST तरीका है GET विधि को त्रुटि के रूप में कॉल करें (यानी return Create(new { uniqueUri = ... });आपका तर्क DRY रहता है (जैसे कॉलिंग RedirectToAction), लेकिन पुनर्निर्देशन द्वारा किए गए मुद्दों के बिना, जैसे कि। अपना मॉडलस्टैट खोना।
डैनियल

1
@DanielLiuzzi: इस तरह से करने से URL नहीं बदलेगा। इसलिए आप url को "/ कंट्रोलर / क्रिएट /" जैसे कुछ के साथ समाप्त करते हैं।
Skorunka František

@ SkorunkaFrantišek और बिल्कुल यही बात है। प्रश्न कहता है कि यदि कोई त्रुटि होती है, तो मुझे उसी पृष्ठ को त्रुटि के साथ प्रदर्शित करने की आवश्यकता है। इस संदर्भ में, यह पूरी तरह से स्वीकार्य (और बेहतर आईएमओ) है कि यदि एक ही पृष्ठ प्रदर्शित किया जाता है तो URL नहीं बदलता है। इसके अलावा, इस दृष्टिकोण का एक फायदा यह है कि यदि प्रश्न में त्रुटि एक सत्यापन त्रुटि नहीं है, लेकिन सिस्टम त्रुटि (उदाहरण के लिए DB टाइमआउट) यह उपयोगकर्ता को फ़ॉर्म को फिर से सबमिट करने के लिए पृष्ठ को ताज़ा करने की अनुमति देता है।
डैनियल लियुज़ी

4

मैं प्रयोग कर सकता हूँ TempData["Errors"]

TempData 1 बार डेटा को संरक्षित करने वाली क्रियाओं को पार कर जाती है।


4

मेरा सुझाव है कि आप दृश्य लौटाएं, और कार्रवाई पर एक विशेषता के माध्यम से दोहराव से बचें। यहाँ डेटा देखने के लिए आबादी का एक उदाहरण है। आप अपनी विधि बनाने के तर्क के साथ कुछ ऐसा ही कर सकते हैं।

public class GetStuffBasedOnUniqueUriAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var filter = new GetStuffBasedOnUniqueUriFilter();

        filter.OnActionExecuting(filterContext);
    }
}


public class GetStuffBasedOnUniqueUriFilter : IActionFilter
{
    #region IActionFilter Members

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {

    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Controller.ViewData["somekey"] = filterContext.RouteData.Values["uniqueUri"];
    }

    #endregion
}

यहाँ एक उदाहरण है:

[HttpGet, GetStuffBasedOnUniqueUri]
public ActionResult Create()
{
    return View();
}

[HttpPost, GetStuffBasedOnUniqueUri]
public ActionResult Create(Review review)
{
    // validate review
    if (validatedOk)
    {
        return RedirectToAction("Details", new { postId = review.PostId });
    }

    ModelState.AddModelError("ReviewErrors", "some error occured");
    return View(review);
}

यह एक बुरा विचार कैसे है? मुझे लगता है कि विशेषता दूसरी कार्रवाई का उपयोग करने की आवश्यकता से बचती है क्योंकि दोनों क्रियाएं ViewData को लोड करने के लिए विशेषता का उपयोग कर सकती हैं।
1

1
कृपया पोस्ट / रीडायरेक्ट / गेट पैटर्न देखें: en.wikipedia.org/wiki/Post/Redirect/Get
DreamSonic

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

फिल्म्स कार्यों पर पुन: प्रयोज्य कोड के लिए हैं, विशेष रूप से ViewData में चीजें डालने के लिए उपयोगी हैं। TempData सिर्फ एक समाधान है।
CRice

1
@ppumkin शायद अजाक्स के साथ पोस्ट करने की कोशिश करें ताकि आपके पास अपने दृश्य सर्वर के पुनर्निर्माण में कठिन समय न हो।
सीआरएस

2

मेरे पास एक तरीका है जो मॉडल स्टेट को अस्थायी डेटा में जोड़ता है। मेरे पास मेरे आधार नियंत्रक में एक विधि है जो किसी भी त्रुटि के लिए अस्थायी डेटा की जांच करता है। यदि यह उनके पास है, तो यह उन्हें मॉडलस्टेट में वापस जोड़ता है।


1

मेरा परिदृश्य थोड़ा और अधिक जटिल है क्योंकि मैं PRG पैटर्न का उपयोग कर रहा हूं, इसलिए मेरा ViewModel ("सारांश") TempData में है, और मेरी सारांश स्क्रीन इसे प्रदर्शित करती है। इस पेज पर एक छोटी सी फॉर्म है जिसमें कुछ जानकारी को दूसरे एक्शन में पोस्ट किया जा सकता है। इस पृष्ठ पर सारांशवीएम में कुछ क्षेत्रों को संपादित करने के लिए उपयोगकर्ता के लिए एक आवश्यकता से जटिलता आ गई है।

सारांश.cshtml का सत्यापन सारांश है जो हम बनाएंगे मॉडलस्टैट त्रुटियों को पकड़ लेंगे।

@Html.ValidationSummary()

मेरे फॉर्म को अब सारांश () के लिए एक HttpPost कार्रवाई के लिए पोस्ट करने की आवश्यकता है। मेरे पास संपादित क्षेत्रों का प्रतिनिधित्व करने के लिए एक और बहुत छोटा ViewModel है, और मॉडलबाइंडिंग मुझे ये मिलेगा।

नया रूप:

@using (Html.BeginForm("Summary", "MyController", FormMethod.Post))
{
    @Html.Hidden("TelNo") @* // Javascript to update this *@

और कार्रवाई ...

[HttpPost]
public ActionResult Summary(EditedItemsVM vm)

यहां मैं कुछ सत्यापन करता हूं और कुछ खराब इनपुट का पता लगाता हूं, इसलिए मुझे त्रुटियों के साथ सारांश पृष्ठ पर वापस लौटने की आवश्यकता है। इसके लिए मैं TempData का उपयोग करता हूं, जो पुनर्निर्देशन से बचेगा। यदि डेटा के साथ कोई समस्या नहीं है, तो मैं समरीवि सारांश को कॉपी के साथ (लेकिन बदले हुए क्षेत्रों को बदल दिया जाता है) के साथ सम्‍मिलित करता हूं, फिर एक रीडायरेक्‍ट एविएशन ("NextAction") करता हूं;

// Telephone number wasn't in the right format
List<string> listOfErrors = new List<string>();
listOfErrors.Add("Telephone Number was not in the correct format. Value supplied was: " + vm.TelNo);
TempData["SummaryEditedErrors"] = listOfErrors;
return RedirectToAction("Summary");

सारांश नियंत्रक क्रिया, जहां यह सब शुरू होता है, टेंपडेटा में किसी भी त्रुटि की तलाश करता है और उन्हें मॉडलस्टेट में जोड़ता है।

[HttpGet]
[OutputCache(Duration = 0)]
public ActionResult Summary()
{
    // setup, including retrieval of the viewmodel from TempData...


    // And finally if we are coming back to this after a failed attempt to edit some of the fields on the page,
    // load the errors stored from TempData.
        List<string> editErrors = new List<string>();
        object errData = TempData["SummaryEditedErrors"];
        if (errData != null)
        {
            editErrors = (List<string>)errData;
            foreach(string err in editErrors)
            {
                // ValidationSummary() will see these
                ModelState.AddModelError("", err);
            }
        }

1

Microsoft ने TempData में जटिल डेटा प्रकारों को संग्रहीत करने की क्षमता को हटा दिया, इसलिए पिछले उत्तर अब काम नहीं करते हैं; आप केवल स्ट्रिंग्स जैसे सरल प्रकारों को संग्रहीत कर सकते हैं। मैंने उम्मीद के मुताबिक काम करने के लिए @ asgeo1 द्वारा उत्तर को बदल दिया है।

public class SetTempDataModelStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var controller = filterContext.Controller as Controller;
        var modelState = controller?.ViewData.ModelState;
        if (modelState != null)
        {
            var listError = modelState.Where(x => x.Value.Errors.Any())
                .ToDictionary(m => m.Key, m => m.Value.Errors
                .Select(s => s.ErrorMessage)
                .FirstOrDefault(s => s != null));
            controller.TempData["KEY HERE"] = JsonConvert.SerializeObject(listError);
        }
    }
}


public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var controller = filterContext.Controller as Controller;
        var tempData = controller?.TempData?.Keys;
        if (controller != null && tempData != null)
        {
            if (tempData.Contains("KEY HERE"))
            {
                var modelStateString = controller.TempData["KEY HERE"].ToString();
                var listError = JsonConvert.DeserializeObject<Dictionary<string, string>>(modelStateString);
                var modelState = new ModelStateDictionary();
                foreach (var item in listError)
                {
                    modelState.AddModelError(item.Key, item.Value ?? "");
                }

                controller.ViewData.ModelState.Merge(modelState);
            }
        }
    }
}

यहां से, आप आवश्यकतानुसार नियंत्रक विधि पर आवश्यक डेटा एनोटेशन जोड़ सकते हैं।

[RestoreModelStateFromTempDataAttribute]
[HttpGet]
public async Task<IActionResult> MethodName()
{
}


[SetTempDataModelStateAttribute]
[HttpPost]
public async Task<IActionResult> MethodName()
{
    ModelState.AddModelError("KEY HERE", "ERROR HERE");
}

अच्छी तरह से काम!। कोड को चिपकाते समय एक छोटी सी ब्रैकेट त्रुटि को ठीक करने का उत्तर दिया।
VDWWD 21

0

मैं अपने ViewModel में एक विधि जोड़ना पसंद करता हूं जो डिफ़ॉल्ट मानों को पॉप्युलेट करता है:

public class RegisterViewModel
{
    public string FirstName { get; set; }
    public IList<Gender> Genders { get; set; }
    //Some other properties here ....
    //...
    //...

    ViewModelType PopulateDefaultViewData()
    {
        this.FirstName = "No body";
        this.Genders = new List<Gender>()
        {
            Gender.Male,
            Gender.Female
        };

        //Maybe other assinments here for other properties...
    }
}

तब मैं इसे कॉल करता हूं जब कभी भी मुझे इस तरह से मूल डेटा की आवश्यकता होती है:

    [HttpGet]
    public async Task<IActionResult> Register()
    {
        var vm = new RegisterViewModel().PopulateDefaultViewValues();
        return View(vm);
    }

    [HttpPost]
    public async Task<IActionResult> Register(RegisterViewModel vm)
    {
        if (!ModelState.IsValid)
        {
            return View(vm.PopulateDefaultViewValues());
        }

        var user = await userService.RegisterAsync(
            email: vm.Email,
            password: vm.Password,
            firstName: vm.FirstName,
            lastName: vm.LastName,
            gender: vm.Gender,
            birthdate: vm.Birthdate);

        return Json("Registered successfully!");
    }

0

मैं यहाँ सिर्फ नमूना कोड दे रहा हूँ आपके विचार में आप "ModelStateDictionary" के रूप में एक प्रकार की संपत्ति जोड़ सकते हैं

public ModelStateDictionary ModelStateErrors { get; set; }

और अपने POST एक्शन मेंथोड में आप सीधे कोड लिख सकते हैं

model.ModelStateErrors = ModelState; 

और फिर नीचे की तरह इस मॉडल को टेंपडाटा में असाइन करें

TempData["Model"] = model;

और जब आप अन्य नियंत्रक क्रिया विधि पर पुनर्निर्देशित करते हैं तो नियंत्रक में आपको टेंपडाटा मूल्य पढ़ना होता है

if (TempData["Model"] != null)
{
    viewModel = TempData["Model"] as ViewModel; //Your viewmodel class Type
    if(viewModel.ModelStateErrors != null && viewModel.ModelStateErrors.Count>0)
    {
        this.ViewData.ModelState.Merge(viewModel.ModelStateErrors);
    }
}

बस। इसके लिए आपको एक्शन फिल्टर्स लिखने की जरूरत नहीं है। यदि आप किसी अन्य नियंत्रक के किसी अन्य दृश्य के लिए मॉडल स्थिति त्रुटियां प्राप्त करना चाहते हैं तो यह उपरोक्त कोड के समान सरल है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.