在 RedirectToAction 上保留视图数据

问题描述 投票:0回答:7
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateUser([Bind(Exclude = "Id")] User user)
{
        ...
        db.SubmitChanges();
        ViewData["info"] = "The account has been created.";
        return RedirectToAction("Index", "Admin");
}

这不会在redirectToAction之后保留视图数据中的“info”文本。 我如何以最优雅的方式解决这个问题?

我当前的想法是将 Index 控制器操作中的内容放入 [NonAction] 中,并从 Index 操作和 CreateUser 操作中调用该方法,但我有一种感觉必须有更好的方法。

谢谢。

asp.net-mvc viewdata redirecttoaction
7个回答
89
投票

您可以使用

TempData

TempData["info"] = "The account has been created."
.

TempData 正是针对这种情况而存在的。它使用 Session 作为存储,但在第二次响应后它就不会存在了。

来自 MSDN:

TempDataDictionary 对象的典型用途是在重定向到另一个操作方法时从操作方法传递数据。例如,操作方法可能会在调用 RedirectToAction 方法之前将有关错误的信息存储在控制器的 TempData 属性(返回 TempDataDictionary 对象)中。然后,下一个操作方法可以处理错误并渲染显示错误消息的视图。


13
投票

如果在“此”请求期间应在

ViewData
中访问您的数据,请使用
View
。如果您的数据用于“下一个”请求,请使用“TempData”(例如POST-REDIRECT-GET设计模式)。


2
投票

如果您多次需要此操作,一个不错的解决方法是创建 ActionFilterAttributes,将临时数据导出/导入到视图数据,反之亦然。您也可以通过这种方式很好地传递 ModelState(已在 here 演示 - #13)。 我认为,通过对该代码段进行一些调整,您将获得一个干净的解决方案。


2
投票

您可以使用

TempData
控制器属性,但它的缺点是它在后台使用会话存储。这意味着您需要额外的工作才能使其在网络场上运行,并且您需要首先在应用程序中启用会话。

如果您只需要传输短消息,另一种方法是使用 cookie。这确实需要对 cookie 进行适当的加密。不依赖

TempData
属性还允许您在非 MVC 上下文中设置消息,例如在经典的 ASHX 页面中。

看看FlashMessage,它可以为您节省一些自己实现此操作的工作。


1
投票

由于 TempData 似乎使用存储,并且任何形式的非“进程中”的 ITempDataProvider 都要求对象可序列化,因此 TempData 在网络场情况下似乎严重不足......(ViewDataDictionary 本身不可序列化...... )有人对此有什么建议吗?


0
投票

答案是TempData。澄清的用法差异如下:

TempData = 将数据从 Action 传递到另一个 Action

ViewData = 将数据从 Action 传递到 View


0
投票

这是我用来将查询参数传递给 ViewData,并将 Controller 方法参数传递给 ViewData 作为 returnUrl 的方法。这是基于 https://andrewlock.net/using-an-iactionfilter-to-read-action-method-parameter-values-in-asp-net-core-mvc/

// Here are the filters
namespace MyNamespace.Utilities.Filters {
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Primitives;
    using System.Linq;
    
    public class ReturnUrlQueryToViewDataActionFilter : IActionFilter {
    
        private readonly string parmName = "ReturnUrl"; // I actually keep this in a global static variable, but put here for example
    
        public void OnActionExecuting(ActionExecutingContext context) {         
            if (context.Controller is Controller controller && controller.Url is IUrlHelper urlHelper) {
                if (context.HttpContext.Request.Query[parmName] is StringValues stringValues/*Possible to have more than one matching query values(like a list) which would be comma seperated*/
                        && stringValues.Count == 1 /*returnurl itself should never be a list form, even though query parms object make it possible, so if more than 1, just ignore it completly*/
                        && stringValues.Single() is string returnUrl
                        && !string.IsNullOrWhiteSpace(returnUrl)) {
                    if (urlHelper.IsLocalUrl(returnUrl)) {
                        controller.ViewData[parmName] = returnUrl;                       
                    } else if (context.HttpContext.RequestServices.GetRequiredService<ILogger<ReturnUrlQueryToViewDataActionFilter>>() is ILogger logger) {// WAS NOT A LOCAL RETURN URL 
                        logger.LogWarning("RedirectUrl was NOT LOCAL URL: '{badReturnUrl}'; ViewData value WAS NOT SET.", returnUrl);
                    }
                }
            }
        }
        public void OnActionExecuted(ActionExecutedContext context) { }
    }
    // Used to take a "Named" parameter on the Action method
    public class ReturnUrlParameterToViewDataActionFilter : IActionFilter {
        private readonly string parmName = "ReturnUrl"; // I actually keep this in a global static variable, but put here for example
        public void OnActionExecuting(ActionExecutingContext context) {
            if (context.Controller is Controller controller && controller.Url is IUrlHelper urlHelper) {
                if (context.ActionArguments.TryGetValue(parmName, out object value)
                    && value is object obj
                    && obj != null
                    && obj.ToString() is string returnUrl
                    && !string.IsNullOrWhiteSpace(returnUrl)) {
                    if (urlHelper.IsLocalUrl(returnUrl)) {
                        controller.ViewData[parmName] = returnUrl;                      
                    } else if (context.HttpContext.RequestServices.GetRequiredService<ILogger<ReturnUrlQueryToViewDataActionFilter>>() is ILogger logger) {// WAS NOT A LOCAL RETURN URL 
                        logger.LogWarning("RedirectUrl was NOT LOCAL URL: '{badReturnUrl}'; ViewData value WAS NOT SET.", returnUrl);
                    }
                }
            }
        }

        public void OnActionExecuted(ActionExecutedContext context) { }
    }
}

// add to the DI Container
services.AddScoped<ReturnUrlQueryToViewDataActionFilter>();
services.AddScoped<ReturnUrlParameterToViewDataActionFilter>();


 // Example Usage of ReturnUrlQueryToViewDataActionFilter over a get method that may or maynot include a query parameter for "ReturnUrl", 
 [HttpGet]
[ServiceFilter(typeof(ReturnUrlQueryToViewDataActionFilter))]
public async Task<IActionResult> Edit(Guid? id, CancellationToken cancellationToken) {
    // do a get to db to get the model and return to view that will ultimatly do a "Post Action"
    // that uses a form with a hidden input for "ReturnUrl" that will then get picked up by the ReturnUrlParameterToViewDataActionFilter
}

// Example Usage of ReturnUrlParameterToViewDataActionFilter over a Post method that has a parameter on the Controller Action for "ReturnUrl"

[HttpPost]
[ValidateAntiForgeryToken]
[ServiceFilter(typeof(ReturnUrlParameterToViewDataActionFilter))]
public async Task<IActionResult> Edit(Guid id, string Name, string Description, string ReturnUrl){
    // value gets sent back to Edit View if reshown to user IE. ModelState was not valid
    
     // If Edit is successfull redirect the user (for safety check if Url is local, even though that is checked in the filters, can never be to safe     
     if(this.Url.IsLocal(ReturnUrl)){        
         // could be a few other things to check here for making sure url is correct depending 
         // on if you use virtual apps/paths, like checking for returnUrl.StartsWith('/') then prepend a ~ to get the virtual path to map correctly, but since this part can be very different depending on your setup      
         retun Redirect(this.Url.Content(ReturnUrl));        
     } 
}


// I also use this TagHelper to take the ViewData value and transform it into a hidden field
namespace MyNamespace.Utilities.TagHelpers {
    using Microsoft.AspNetCore.Razor.TagHelpers;
    public class ReturnUrlHiddenInputTagHelper : TagHelper {     
        private readonly string parmName = "ReturnUrl"; // I actually keep this in a global static variable, but put here for example   
        
        [ViewContext, HtmlAttributeNotBound]
        public ViewContext ViewContext { get; set; } // will be null until Process()   
        
        public override void Process(TagHelperContext context, TagHelperOutput output) {            
            string returnUrl = this.ViewContext?.ViewData?[CommonViewDataNames.ReturnUrl]?.ToString();
            if (string.IsNullOrWhiteSpace(returnUrl)) {// If no return url, dont display
                output.SuppressOutput();
                return;
            }
            output.TagName = "input";
            output.TagMode = TagMode.SelfClosing;
            output.Attributes.Add("type", "hidden");
            output.Attributes.Add("name", parmName);
            output.Attributes.Add("value", returnUrl);
        }
     
    }
}
// Then inside your _ViewImports.cshtml include whatever your base namespace is that holds the TagHelper
@addTagHelper *, MyNamespace


// Then inside the Edit.cshtml form you put, which will put a hidden field from the ViewData value if located, if not it does nothing
<return-url-hidden-input />
© www.soinside.com 2019 - 2024. All rights reserved.