将ViewData保留在RedirectToAction中

45
[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”文本保存在viewdata中。 我应该如何以最优雅的方式解决这个问题?

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

谢谢。

7个回答

89
您可以使用 TempDataTempData["info"] = "账户已创建。"TempData 正是为了这种情况而存在的。它使用 Session 作为存储,但在第二个响应之后就不再存在了。
来自 MSDN 的说明:

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


有趣,从未听说过。 :-) - Thomas Stock
2
很遗憾的是,在视图中也必须使用“TempData”,而不能继续在那里使用ViewData。但它运行得很好,所以谢谢。 - Thomas Stock
请查看拓展方法 http://blog.eworldui.net/post/2008/06/MVC-Post-Redirect-Get-Sample-Updated.aspx。 - James S

13

如果您的数据需要在“this”请求期间在View中访问,则使用ViewData。如果您的数据是为“next”请求而设置的(例如POST-REDIRECT-GET设计模式),请使用TempData


2
您可以使用TempData控制器属性,但它的缺点是它在后台使用会话存储。这意味着您需要额外的工作来使其在Web Farm上运行,并且您需要首先启用应用程序中的会话。
如果您只需要传输短消息,则可以使用cookie。这需要对cookie进行适当的加密。不依赖于TempData属性还允许您在非MVC上下文中设置消息,例如在经典的ASHX页面中。
请查看FlashMessage,它可以帮助您节省一些自己实现的工作。

2
如果您需要多次使用此功能,一个不错的解决方法是创建ActionFilterAttributes,将tempdata导出/导入到viewdata中,反之亦然。您也可以很好地通过这种方式传递您的ModelState(在这里演示-#13)。 对那段代码进行一些调整,我认为您会得到一个干净的解决方案。

1

由于TempData似乎使用存储,并且任何不是“进程内”的ITempDataProvider形式都需要对象可序列化,因此在Web Farm环境中,TempData似乎非常不足...(ViewDataDictionary本身不可序列化...)有人对此有什么建议吗?


我现在正在研究这个。如果您使用RedirectToAction,数据将必须通过客户端传递。因此,我认为我们只剩下“RouteValues”-它等同于QueryString或Cookies! - Ian Grainger

0
答案是TempData。为了澄清,两者的使用区别如下:
TempData = 从一个Action传递数据到另一个Action
ViewData = 从一个Action传递数据到一个View

0
我使用ActionFilters将查询参数传递给ViewData,并将Controller方法参数传递给ViewData以用于返回URL。这是基于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?[parmName]?.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 />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接