Domanda

Ho le seguenti due metodi di azione (semplificato per la domanda):

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

Quindi, se la convalida passa, rioriento ad un'altra pagina (di conferma).

Se si verifica un errore, ho bisogno di visualizzare la stessa pagina con l'errore.

Se faccio return View(), viene visualizzato l'errore, ma se lo faccio return RedirectToAction (come sopra), perde gli errori del modello.

Non sono sorpreso dalla questione, chiedo solo come voi ragazzi gestire questo?

ho potuto naturalmente solo restituire la stessa vista al posto del redirect, ma ho la logica nella sezione "Creare" metodo che popola i dati di visualizzazione, che avrei dovuto duplicare.

Qualche suggerimento?

È stato utile?

Soluzione

È necessario avere la stessa istanza di Review sulla vostra azione HttpGet. Per fare questo si dovrebbe salvare un oggetto in Review review variabile TEMP sulla vostra azione HttpPost e quindi ripristinarlo sull'azione 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"]});
   }   
}

Se si desidera far funzionare tutto questo, anche se il browser viene aggiornata dopo la prima esecuzione dell'azione HttpGet, si potrebbe fare questo:

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

In caso contrario, il rinfresco review oggetto pulsante sarà vuoto perchè non ci sarebbe alcun dato in TempData["Review"].

Altri suggerimenti

Ho dovuto risolvere questo problema oggi me stesso, e sono imbattuto in questa domanda.

Alcune delle risposte sono utili (usando TempData), ma in realtà non rispondere alla domanda a portata di mano.

Il miglior consiglio che ho trovato è stato in questo post del blog:

http: //www.jefclaes. essere / 2012/06 / persistente-model-stato-quando-con-prg.html

In sostanza, utilizzare TempData per salvare e ripristinare l'oggetto ModelState. Tuttavia, è molto più pulito, se si abstract questa via in attributi.

per es.

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

Poi, come per il vostro esempio, è possibile salvare / ripristinare la ModelState in questo modo:

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

Se anche voi volete passare del modello, insieme a TempData (come BigB suggerito), allora si può ancora fare anche questo.

Perché non creare una funzione privata con la logica nel metodo "Crea" e chiamare questo metodo sia dal Get e il metodo Post e proprio non ritorno View ().

ho potuto usare TempData["Errors"]

TempData sono passati dall'altra parte le azioni di conservazione dei dati 1 volta.

Vi suggerisco di restituire la vista, ed evitare la duplicazione tramite un attributo sull'azione. Ecco un esempio di popolamento per visualizzare i dati. Si potrebbe fare qualcosa di simile con la logica metodo create.

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
}

Ecco un esempio:

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

Ho un metodo che aggiunge Stato modello ai dati Temp. Ho poi ho un metodo nel mio controller di base che i dati di controlli temporanei per gli eventuali errori. Se li ha, li aggiunge di nuovo a ModelState.

Il mio scenario è un po 'più complicata, come sto usando il modello PRG quindi il mio ViewModel ( "SummaryVM") è in TempData, e il mio schermo Summary visualizza. C'è un piccolo modulo in questa pagina per pubblicare alcune informazioni ad un'altra azione. La complicazione è venuto da un obbligo per l'utente di modificare alcuni campi in SummaryVM in questa pagina.

Summary.cshtml ha la sintesi di convalida che cattura ModelState errori che creeremo.

@Html.ValidationSummary()

La mia forma deve ora POST a un HttpPost azione per Sommario (). Ho un altro molto piccolo ViewModel per rappresentare campi modificati, e modelbinding otterrà questi per me.

La nuova forma:

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

e l'azione ...

[HttpPost]
public ActionResult Summary(EditedItemsVM vm)

In qui faccio un po 'di validazione e rilevo alcune cattive di ingresso, quindi ho bisogno di tornare alla pagina Riepilogo con gli errori. Per questo uso io TempData, che sopravvivere ad un reindirizzamento. Se non v'è alcun problema con i dati, sostituisco l'oggetto SummaryVM con una copia (ma con i campi modificati cambiato ovviamente) poi fare un RedirectToAction ( "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");

L'azione del controller Sintesi, dove tutto questo ha inizio, cerca eventuali errori nel TempData e li aggiunge alla ModelState.

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

Io preferisco aggiungere un metodo alla mia ViewModel che popola i valori di default:

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

Poi mi chiamo quando mai ho bisogno i dati originali in questo modo:

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

Microsoft ha rimosso la possibilità di memorizzare i tipi di dati complessi in TempData, quindi le risposte precedenti non funzionano più; è possibile memorizzare solo tipi semplici come corde. Ho modificato la risposta @ asgeo1 al lavoro come previsto.

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

Da qui, si può semplicemente aggiungere l'annotazione dei dati richiesti su un metodo di controllo, se necessario.

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

[SetTempDataModelStateAttribute]
    [HttpPost]
    public async Task<IActionResult> MethodName()
    {
            ModelState.AddModelError("KEY HERE", "ERROR HERE");
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top