keep viewdata on RedirectToAction
Asked Answered
N

7

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

This doesnt keep the "info" text in the viewdata after the redirectToAction. How would I get around this issue in the most elegant way?

My current idea is to put the stuff from the Index controlleraction in a [NonAction] and call that method from both the Index action and in the CreateUser action, but I have a feeling there must be a better way.

Thanks.

Nonparous answered 4/8, 2009 at 8:37 Comment(0)
G
89

You can use TempData.

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

TempData exists exactly for this situation. It uses Session as storage, but it will not be around after the second response.

From MSDN:

A typical use for a TempDataDictionary object is to pass data from an action method when it redirects to another action method. For example, an action method might store information about an error in the controller's TempData property (which returns a TempDataDictionary object) before it calls the RedirectToAction method. The next action method can then handle the error and render a view that displays an error message.

Gunas answered 4/8, 2009 at 8:52 Comment(3)
Interesting, never heard of it. :-)Nonparous
it's a bummer tho that you have to use "TempData" in the view too, and can't just keep using ViewData there. But it works nicely, so thanks.Nonparous
See the copy extension method blog.eworldui.net/post/2008/06/…Cyclopean
D
13

Use ViewData if your data should be accessible in View during "this" request. Use `TempData' if your data is for "next" request (for example POST-REDIRECT-GET design pattern).

Durbar answered 4/8, 2009 at 9:0 Comment(0)
B
2

If you need this more than once, a nice workaround would be creating ActionFilterAttributes which export/import the tempdata to viewdata and vice-versa. You can pass your ModelState in this way very nicely as well (demonstrated here - #13). With a few adjustments to that piece of code you would have a clean solution, I think.

Belleslettres answered 26/10, 2009 at 14:52 Comment(0)
R
2

You could use the TempData controller property, but it has the disadvantage that it uses the session storage in the background. This means that you'll have extra work getting it to function on a web farm and you'll need to have sessions enabled in your application in the first place.

An alternative is to use cookies if you only need to transport a short message. This does require proper encryption of the cookie. Not relying on the TempData property also allows you to set messages in a non MVC context, for example in a classic ASHX page.

Take a look at FlashMessage which can save you some work implementing this yourself.

Radius answered 30/9, 2015 at 7:10 Comment(0)
U
1

Since TempData appears to use storage, and any form of ITempDataProvider that is not "in-process", requires the object to be Serializable, TempData seems woefully inadequate in web farm situations... (ViewDataDictionary isn't itself serializable...) Does anyone have any suggestions for this?

Unfounded answered 24/6, 2014 at 18:30 Comment(1)
I'm looking into this now. The data is going to have to go via the client if you're using a RedirectToAction. So I think that leaves us with only 'RouteValues' - which equates to QueryString, or Cookies!Cheater
C
0

The answer is TempData. The usage difference for clarification is as the following:

TempData = passing data from Action to another Action

ViewData = passing data from Action to a View

Combo answered 21/4, 2022 at 9:42 Comment(0)
L
0

I use ActionFilters to pass query parm to ViewData, and Controller method parameters to ViewData for the returnUrl. Which was based on 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 />
Longeron answered 29/11, 2023 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.