ASP.NET MVC - 如何在 RedirectToAction 中保留 ModelState 错误?

发布于 2024-10-11 07:36:03 字数 855 浏览 0 评论 0原文

我有以下两种操作方法(针对问题进行了简化):

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

因此,如果验证通过,我将重定向到另一个页面(确认)。

如果发生错误,我需要显示包含错误的同一页面。

如果我返回View(),则会显示错误,但如果我返回RedirectToAction(如上所述),则会丢失模型错误。

我对这个问题并不感到惊讶,只是想知道你们如何处理这个问题?

我当然可以返回相同的视图而不是重定向,但我在“创建”方法中有逻辑,该方法填充视图数据,我必须复制该数据。

有什么建议吗?

I have the following two action methods (simplified for question):

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

So, if the validation passes, i redirect to another page (confirmation).

If an error occurs, i need to display the same page with the error.

If i do return View(), the error is displayed, but if i do return RedirectToAction (as above), it loses the Model errors.

I'm not surprised by the issue, just wondering how you guys handle this?

I could of course just return the same View instead of the redirect, but i have logic in the "Create" method which populates the view data, which i'd have to duplicate.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

帅气尐潴 2024-10-18 07:36:03

今天我必须自己解决这个问题,并遇到了这个问题。

有些答案很有用(使用 TempData),但并没有真正回答手头的问题。

我发现的最好的建议是在这篇博客文章中:

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

基本上,使用 TempData 来保存和恢复 ModelState 对象。但是,如果将其抽象为属性,就会干净得多。

例如

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

,然后根据您的示例,您可以像这样保存/恢复 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"]});
    }   
}

如果您还想在 TempData 中传递模型(如 bigb 建议),那么您仍然可以这样做。

I had to solve this problem today myself, and came across this question.

Some of the answers are useful (using TempData), but don't really answer the question at hand.

The best advice I found was on this blog post:

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

Basically, use TempData to save and restore the ModelState object. However, it's a lot cleaner if you abstract this away into attributes.

E.g.

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

Then as per your example, you could save / restore the ModelState like so:

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

If you also want to pass the model along in TempData (as bigb suggested) then you can still do that too.

兲鉂ぱ嘚淚 2024-10-18 07:36:03

您需要在 HttpGet 操作上具有相同的 Review 实例。
为此,您应该在 HttpPost 操作的临时变量中保存对象 Review review,然后在 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"] 中不会有任何数据。

You need to have the same instance of Review on your HttpGet action.
To do that you should save an object Review review in temp variable on your HttpPost action and then restore it on HttpGet action.

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

If you want this to work even if the browser is refreshed after the first execution of the HttpGet action, you could do this:

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

Otherwise on refresh button object review will be empty because there wouldn't be any data in TempData["Review"].

面犯桃花 2024-10-18 07:36:03

为什么不使用“Create”方法中的逻辑创建一个私有函数,并从 Get 和 Post 方法调用此方法,然后返回 View()。

Why not create a private function with the logic in the "Create" method and calling this method from both the Get and the Post method and just do return View().

油饼 2024-10-18 07:36:03

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

Microsoft removed the ability to store complex data types in TempData, therefore the previous answers no longer work; you can only store simple types like strings. I have altered the answer by @asgeo1 to work as expected.

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

From here, you can simply add the required data annotation on a controller method as needed.

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


[SetTempDataModelStateAttribute]
[HttpPost]
public async Task<IActionResult> MethodName()
{
    ModelState.AddModelError("KEY HERE", "ERROR HERE");
}
执笏见 2024-10-18 07:36:03

我可以使用 TempData["Errors"]

TempData 在保存数据的操作中传递 1 次。

I could use TempData["Errors"]

TempData are passed accross actions preserving data 1 time.

小ぇ时光︴ 2024-10-18 07:36:03

我建议您返回视图,并通过操作的属性避免重复。以下是填充以查看数据的示例。您可以对创建方法逻辑执行类似的操作。

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

I suggest you return the view, and avoid duplication via an attribute on the action. Here is an example of populating to view data. You could do something similar with your create method logic.

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
}

Here is an example:

[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);
}
如歌彻婉言 2024-10-18 07:36:03

我有一种将模型状态添加到临时数据的方法。然后,我的基本控制器中有一个方法可以检查临时数据是否有任何错误。如果有它们,它将它们添加回 ModelState。

I have a method that adds model state to temp data. I then have a method in my base controller that checks temp data for any errors. If it has them, it adds them back to ModelState.

杀手六號 2024-10-18 07:36:03

我的场景有点复杂,因为我使用 PRG 模式,因此我的 ViewModel(“SummaryVM”)位于 TempData 中,并且我的摘要屏幕显示它。此页面上有一个小表单,用于将一些信息发布到另一个操作。
复杂性来自于用户需要编辑此页面上的 SummaryVM 中的某些字段。

Summary.cshtml 具有验证摘要,它将捕获我们将创建的 ModelState 错误。

@Html.ValidationSummary()

我的表单现在需要 POST 到 Summary() 的 HttpPost 操作。我有另一个非常小的 ViewModel 来表示已编辑的字段,模型绑定会将这些字段提供给我。

新表单:

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

和操作...

[HttpPost]
public ActionResult Summary(EditedItemsVM vm)

在这里我做了一些验证,并检测到一些错误的输入,因此我需要返回到包含错误的“摘要”页面。为此,我使用 TempData,它将在重定向后继续存在。
如果数据没有问题,我将 SummaryVM 对象替换为副本(但编辑的字段当然会更改),然后执行 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");

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

My scenario is a little bit more complicated as I am using the PRG pattern so my ViewModel ("SummaryVM") is in TempData, and my Summary screen displays it. There is a small form on this page to POST some info to another Action.
The complication has come from a requirement for the user to edit some fields in SummaryVM on this page.

Summary.cshtml has the validation summary which will catch ModelState errors that we'll create.

@Html.ValidationSummary()

My form now needs to POST to a HttpPost action for Summary(). I have another very small ViewModel to represent edited fields, and modelbinding will get these to me.

The new form:

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

and the action...

[HttpPost]
public ActionResult Summary(EditedItemsVM vm)

In here I do some validation and I detect some bad input, so I need to return to the Summary page with the errors. For this I use TempData, which will survive a redirection.
If there is no issue with the data, I replace the SummaryVM object with a copy (but with the edited fields changed of course) then do a 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");

The Summary controller action, where all this begins, looks for any errors in the tempdata and adds them to the 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);
            }
        }
ゃ懵逼小萝莉 2024-10-18 07:36:03

我在这里只给出示例代码
在您的 viewModel 中,您可以添加一个“ModelStateDictionary”类型的属性

public ModelStateDictionary ModelStateErrors { get; set; }

,在您的 POST 操作方法中,您可以直接编写代码

model.ModelStateErrors = ModelState; 

,然后将此模型分配给 Tempdata,如下所示

TempData["Model"] = model;

,当您重定向到其他控制器的操作方法时,然后在控制器中您必须读取 Tempdata 值

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

就是这样。您不必为此编写操作过滤器。如果您想将模型状态错误获取到另一个控制器的另一个视图,这与上面的代码一样简单。

I am giving just sample code here
In your viewModel you can add one property of type "ModelStateDictionary" as

public ModelStateDictionary ModelStateErrors { get; set; }

and in your POST action menthod you can write code directly like

model.ModelStateErrors = ModelState; 

and then assign this model to Tempdata like below

TempData["Model"] = model;

and when you redirect to other controller's action method then in controller you have to read the Tempdata value

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

That's it. You don't have to write action filters for this. This is as simple as above code if you want to get Model state errors to another view of another controller.

守护在此方 2024-10-18 07:36:03
    public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            if (controller.TempData.ContainsKey("ModelState"))
            {
                var modelState = ModelStateHelpers.DeserialiseModelState(controller.TempData["ModelState"].ToString());
                controller.ViewData.ModelState.Merge(modelState);
            }
            base.OnActionExecuting(filterContext);
        }
    }
    public class SetTempDataModelStateAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            controller.TempData["ModelState"] = ModelStateHelpers.SerialiseModelState(controller.ViewData.ModelState);
            base.OnActionExecuted(filterContext);
        }
    }

当我解决一些问题时,我遇到了很多不明显的障碍。我将一步一步地指出一切。
我的评论将部分复制当前分支的答案

  1. 实现两个属性。您必须显式指定控制器的类型(filterContext.Controller 作为控制器),因为默认为对象类型。
  2. 从本文中显式实现序列化 ModelState https:// andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/
  3. 在检查startup.cs中的实现缓存时,如果目标操作中的TempData为空。您需要添加内存Cache或SqlServerCache或其他https://stackoverflow.com/a/41500275/11903993
    public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            if (controller.TempData.ContainsKey("ModelState"))
            {
                var modelState = ModelStateHelpers.DeserialiseModelState(controller.TempData["ModelState"].ToString());
                controller.ViewData.ModelState.Merge(modelState);
            }
            base.OnActionExecuting(filterContext);
        }
    }
    public class SetTempDataModelStateAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            controller.TempData["ModelState"] = ModelStateHelpers.SerialiseModelState(controller.ViewData.ModelState);
            base.OnActionExecuted(filterContext);
        }
    }

When I resolve some problem, I ran into a lot of non-obvious obstacles. I will indicate everything step by step.
My comments will partially duplicate the answers from the current branch

  1. Implement two attributes. You must explicitly specify the type for controller (filterContext.Controller as Controller) because default is object type.
  2. Explicitly implement serialization ModelState from this article https://andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/
  3. If TempData is empty in destination action when check implementation cache in startup.cs. You need add memoryCache or SqlServerCache or another https://stackoverflow.com/a/41500275/11903993
呆° 2024-10-18 07:36:03

我更喜欢向我的 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!");
    }

I prefer to add a method to my ViewModel which populates the default values:

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

Then I call it when ever I need the original data like this:

    [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!");
    }
一城柳絮吹成雪 2024-10-18 07:36:03

我创建了此属性

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace Network.Utilites
{
    public class PreserveModelStateAttribute : ActionFilterAttribute
    {
        private const string KeyListKey = "__preserveModelState_keys";

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            if (controller != null && !controller.ViewData.ModelState.IsValid)
            {
                var keys = controller.ViewData.ModelState.Keys.ToList();
                controller.TempData[KeyListKey] = keys;
                controller.TempData[ModelStateDictionaryTempDataKey()] = controller.ViewData.ModelState;
            }
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            if (controller != null && controller.TempData.ContainsKey(KeyListKey))
            {
                var keys = (IEnumerable<string>)controller.TempData[KeyListKey];
                var tempDataModelState = (ModelStateDictionary)controller.TempData[ModelStateDictionaryTempDataKey()];
                foreach (var key in keys)
                {
                    if (!controller.ViewData.ModelState.ContainsKey(key))
                    {
                        controller.ViewData.ModelState.Add(key, tempDataModelState[key]);
                    }
                    else
                    {
                        foreach (var error in tempDataModelState[key].Errors)
                        {
                            controller.ViewData.ModelState[key].Errors.Add(error);
                        }
                    }
                }
            }
        }

        private static string ModelStateDictionaryTempDataKey()
        {
            return "__preserveModelState_modelState";
        }
    }
}

,但您将此属性用于两个操作[PreserveModelState]

I make this attribute

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;

namespace Network.Utilites
{
    public class PreserveModelStateAttribute : ActionFilterAttribute
    {
        private const string KeyListKey = "__preserveModelState_keys";

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            if (controller != null && !controller.ViewData.ModelState.IsValid)
            {
                var keys = controller.ViewData.ModelState.Keys.ToList();
                controller.TempData[KeyListKey] = keys;
                controller.TempData[ModelStateDictionaryTempDataKey()] = controller.ViewData.ModelState;
            }
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var controller = filterContext.Controller as Controller;
            if (controller != null && controller.TempData.ContainsKey(KeyListKey))
            {
                var keys = (IEnumerable<string>)controller.TempData[KeyListKey];
                var tempDataModelState = (ModelStateDictionary)controller.TempData[ModelStateDictionaryTempDataKey()];
                foreach (var key in keys)
                {
                    if (!controller.ViewData.ModelState.ContainsKey(key))
                    {
                        controller.ViewData.ModelState.Add(key, tempDataModelState[key]);
                    }
                    else
                    {
                        foreach (var error in tempDataModelState[key].Errors)
                        {
                            controller.ViewData.ModelState[key].Errors.Add(error);
                        }
                    }
                }
            }
        }

        private static string ModelStateDictionaryTempDataKey()
        {
            return "__preserveModelState_modelState";
        }
    }
}

but you use this attrubute for both actions [PreserveModelState]

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文