Search code examples
c#asp.netasp.net-mvcasp.net-mvc-5tempdata

Redirect to Area without changing the URL and TempData


In my project I have to redirect from one controller to another controller which is present inside the Areas named SIP. If use the following method the redirection works successfully and also the TempData value is passed to the other controller:

TempData["sipModel"] = 1;
return RedirectToAction("Index", "Home", new { area = "SIP" });

But in this case the URL gets changed while my requirement is to keep the same URL, to achieve that I went though other answers and used the method TransferToAction() mentioned

in this answer This works perfectly and I'm able to redirect to the other area without changing the URL with the following code:

TempData["sipModel"] = 1;
return this.TransferToAction("Index", "Home", new { area = "SIP"});

However, in this case the TempData value is not retained and I get Null Reference Exception while trying to read the same.

I tried to use the following code mentioned in the other answer:

public static TransferToRouteResult TransferToAction(this System.Web.Mvc.Controller controller, string actionName, string controllerName, object routeValues)
        {
            controller.TempData.Keep();
            controller.TempData.Save(controller.ControllerContext, controller.TempDataProvider);
            return new TransferToRouteResult(controller.Request.RequestContext, actionName, controllerName, routeValues);
        }

But this doesn't work out. Can somebody please suggest me how can I fix this or any other better approach to achieve this result. Thanks.

Edited:

The URL is like:

https://myproject/Home/Index?cid=ABC-1234&pid=xyz123456abc

I have a complex data in a class which also needs to be passed from the one controller to the other (which is present in the Area SIP), for that I've been using TempData, I've used an integer here just as a sample.

In the first controller method I've if-else condition, so:

if (companyCode = 'X')
 return View();
else
 TempData["sipModel"] = 1;
 return RedirectToAction("Index", "Home", new { area = "SIP" }); OR (this.TransferToAction("Index", "Home", new { area = "SIP"});)

Solution

  • Server.TransferRequest is completely unnecessary in MVC. This is an antiquated feature that was only necessary in ASP.NET because the request came directly to a page and there needed to be a way to transfer a request to another page. Modern versions of ASP.NET (including MVC) have a routing infrastructure that can be customized to route directly to the resource that is desired. There is no point of letting the request reach a controller only to transfer it to another controller when you can simply make the request go directly to the controller and action you want.

    So, given your example is not a complete set of requirements I will make the following assumptions. Adjust these as necessary for your requirements.

    1. If there are no query string parameters passed to the home page, it will stay on the home page.
    2. If there is a query parameter cid or pid on the home page, we will send the request to the Index action of the HomeController of the SID area.
    3. We will pass a metadata parameter "sipModel" with value 1 in the latter case and omit the parameter in the first case.

    First of all, we subclass RouteBase and put our custom logic there. A more complete scenario might have dependent services and options passed in through the constructor, and even have its own MapRoute extension methods to wire it together.

    public class CustomHomePageRoute : RouteBase
    {
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            RouteData result = null;
    
            // Only handle the home page route
            if (httpContext.Request.Path == "/")
            {
                var cid = httpContext.Request.QueryString["cid"];
                var pid = httpContext.Request.QueryString["pid"];
    
                result = new RouteData(this, new MvcRouteHandler());
    
                if (string.IsNullOrEmpty(cid) && string.IsNullOrEmpty(pid))
                {
                    // Go to the HomeController.Index action of the non-area
                    result.Values["controller"] = "Home";
                    result.Values["action"] = "Index";
    
                    // NOTE: Since the controller names are ambiguous between the non-area
                    // and area route, this extra namespace info is required to disambiguate them.
                    // This is not necessary if the controller names differ.
                    result.DataTokens["Namespaces"] = new string[] { "WebApplication23.Controllers" };
                }
                else
                {
                    // Go to the HomeController.Index action of the SID area
                    result.Values["controller"] = "Home";
                    result.Values["action"] = "Index";
    
                    // This tells MVC to change areas to SID
                    result.DataTokens["area"] = "SID";
    
                    // Set additional data for sipModel.
                    // This can be read from the HomeController.Index action by 
                    // adding a parameter "int sipModel".
                    result.Values["sipModel"] = 1;
    
                    // NOTE: Since the controller names are ambiguous between the non-area
                    // and area route, this extra namespace info is required to disambiguate them.
                    // This is not necessary if the controller names differ.
                    result.DataTokens["Namespaces"] = new string[] { "WebApplication23.Areas.SID.Controllers" };
                }
            }
    
            // If this isn't the home page route, this should return null
            // which instructs routing to try the next route in the route table.
            return result;
        }
    
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            var controller = Convert.ToString(values["controller"]);
            var action = Convert.ToString(values["action"]);
    
            if (controller.Equals("Home", StringComparison.OrdinalIgnoreCase) &&
                action.Equals("Index", StringComparison.OrdinalIgnoreCase))
            {
                // Route to the Home page URL
                return new VirtualPathData(this, "");
            }
    
            return null;
        }
    }
    

    To wire this into MVC, we just edit the RouteConfig as follows:

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            // Add the custom route to the static routing collection
            routes.Add(new CustomHomePageRoute());
    
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                namespaces: new string[] { "WebApplication23.Controllers" }
            );
        }
    }
    

    This passes an extra route value sipModel to the PID area's HomeController.Index method. So, we need to adjust the method signature to accept that parameter.

    namespace WebApplication23.Areas.SID.Controllers
    {
        public class HomeController : Controller
        {
            // GET: SID/Home
            public ActionResult Index(int sipModel)
            {
                return View();
            }
        }
    }
    

    As you can see, there really is no reason to use TempData, either. TempData relies on session state by default. It has its uses, but you should always think twice about using session state in MVC as it can often be avoided entirely.