103

私は次の2つのアクションメソッドを持っています(質問のために簡略化されています):

[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(上記のように)実行するとモデルエラーが失われます。

私はこの問題に驚いていません、あなたたちがこれをどのように処理するのか疑問に思っていますか?

もちろん、リダイレクトの代わりに同じビューを返すこともできますが、「Create」メソッドにロジックがあり、ビューデータを入力します。これを複製する必要があります。

助言がありますか?

4

11 に答える 11

85

私は今日この問題を自分で解決しなければならず、この質問に出くわしました。

一部の回答は(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"]});
    }   
}

(bigbが提案したように)TempDataでモデルを渡したい場合でも、それを行うことができます。

于 2012-08-19T04:30:21.433 に答える
52

アクション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"]ます。

于 2011-01-10T00:54:11.423 に答える
7

「Create」メソッドのロジックを使用してプライベート関数を作成し、GetメソッドとPostメソッドの両方からこのメソッドを呼び出して、View()を返すだけではどうでしょうか。

于 2011-01-10T11:11:43.620 に答える
5

使用できますTempData["Errors"]

TempDataは、データを1回保持するアクション間で渡されます。

于 2011-01-10T00:52:42.670 に答える
4

ビューを返し、アクションの属性による重複を避けることをお勧めします。データを表示するためにデータを入力する例を次に示します。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
}

次に例を示します。

[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);
}
于 2011-01-10T00:56:15.990 に答える
2

モデルの状態を一時データに追加するメソッドがあります。次に、ベースコントローラーに、一時データにエラーがないかチェックするメソッドがあります。それらがある場合は、ModelStateに追加し直します。

于 2011-01-10T01:27:14.773 に答える
2

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");
}
于 2018-09-07T16:30:40.490 に答える
1

PRGパターンを使用しているため、ViewModel( "SummaryVM")がTempDataにあり、Summary画面に表示されるため、シナリオは少し複雑になります。このページには、別のアクションに情報を投稿するための小さなフォームがあります。複雑なのは、ユーザーがこのページのSummaryVMの一部のフィールドを編集する必要があるためです。

Summary.cshtmlには、作成するModelStateエラーをキャッチする検証サマリーがあります。

@Html.ValidationSummary()

私のフォームは、Summary()のHttpPostアクションにPOSTする必要があります。編集されたフィールドを表す別の非常に小さなViewModelがあり、modelbindingがこれらを取得します。

新しいフォーム:

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

これがすべて始まるSummarycontrollerアクションは、tempdata内のエラーを探し、それらを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);
            }
        }
于 2015-12-10T17:22:30.650 に答える
1
    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. 2つの属性を実装します。デフォルトはオブジェクトタイプであるため、コントローラーのタイプ(filterContext.Controller as Controller)を明示的に指定する必要があります。
  2. この記事からシリアル化ModelStateを明示的に実装するhttps://andrewlock.net/post-redirect-get-using-tempdata-in-asp-net-core/
  3. startup.csの実装キャッシュを確認するときに、宛先アクションでTempDataが空の場合。memoryCacheまたはSqlServerCacheまたは別のhttps://stackoverflow.com/a/41500275/11903993を追加する必要があります
于 2021-07-29T21:50:40.043 に答える
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!");
    }
于 2017-12-23T00:13:35.400 に答える
0

ここではサンプルコードのみを示しています。viewModelに「ModelStateDictionary」タイプのプロパティを1つ追加できます。

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

それでおしまい。このためにアクションフィルターを作成する必要はありません。別のコントローラーの別のビューでモデル状態エラーを取得する場合、これは上記のコードと同じくらい簡単です。

于 2020-08-31T14:18:00.240 に答える