Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to redirect MVC action without returning 301? (using MVC 4 beta)

I'm working on an ASP.NET MVC solution that has a number of different menus. The menu to display depends on the role of the currently logged in user.

In MVC 3 I had some custom code to support this scenario, by having a single controller method that would return the right menu. It would do this by deferring the request to the appropriate controller and action depending on the current user.

This code appears to be broken in MVC 4 and I'm looking for help to fix it.

First, I added a TransferResult helper class to perform the redirection:

public class TransferResult : RedirectResult
{
    #region Transfer to URL
    public TransferResult( string url ) : base( url )
    {
    }
    #endregion

    #region Transfer using RouteValues
    public TransferResult( object routeValues ) : base( GetRouteUrl( routeValues ) )
    {
    }

    private static string GetRouteUrl( object routeValues )
    {
        var url = new UrlHelper( new RequestContext( new HttpContextWrapper( HttpContext.Current ), new RouteData() ), RouteTable.Routes );
        return url.RouteUrl( routeValues );
    }
    #endregion

    #region Transfer using ActionResult (T4MVC only)
    public TransferResult( ActionResult result ) : base( GetRouteUrl( result.GetT4MVCResult() ) )
    {
    }

    private static string GetRouteUrl( IT4MVCActionResult result )
    {
        var url = new UrlHelper( new RequestContext( new HttpContextWrapper( HttpContext.Current ), new RouteData() ), RouteTable.Routes );
        return url.RouteUrl( result.RouteValueDictionary );
    }
    #endregion

    public override void ExecuteResult( ControllerContext context )
    {
        HttpContext httpContext = HttpContext.Current;
        httpContext.RewritePath( Url, false );
        IHttpHandler httpHandler = new MvcHttpHandler();
        httpHandler.ProcessRequest( HttpContext.Current );
    }
}

Second, I modified T4MVC to emit a few controller helper methods, resulting in every controller having this method:

protected TransferResult Transfer( ActionResult result )
{
    return new TransferResult( result );
}

This allowed me to have a shared controller action to return a menu, without having to clutter the views with any conditional logic:

public virtual ActionResult Menu()
{
    if( Principal.IsInRole( Roles.Administrator ) )
        return Transfer( MVC.Admin.Actions.Menu() );
    return View( MVC.Home.Views.Partials.Menu );
}

However, the code in ExecuteResult in the TransferResult class does not seem to work with the current preview release of MVC 4. It gives me the following error (pointing to the "httpHandler.ProcessRequest" line):

'HttpContext.SetSessionStateBehavior' can only be invoked before
'HttpApplication.AcquireRequestState' event is raised.

Any idea how to fix this?

PS: I realize that I could achieve the same using a simple HtmlHelper extension, which is what I'm currently using as a workaround. However, I have many other scenarios where this method has allowed me to mix and reuse actions, and I would hate to give up this flexibility when moving to MVC 4.

like image 275
Morten Mertner Avatar asked Mar 04 '12 22:03

Morten Mertner


People also ask

How redirect another action method in MVC?

To redirect the user to another action method from the controller action method, we can use RedirectToAction method. Above action method will simply redirect the user to Create action method.

How redirect a specific view from controller in MVC?

You can use the RedirectToAction() method, then the action you redirect to can return a View. The easiest way to do this is: return RedirectToAction("Index", model); Then in your Index method, return the view you want.

Which ActionResult redirect to another action method by using its URL?

RedirectResult is an ActionResult that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), or Permanent Redirect (308) response with a Location header to the supplied URL. It will redirect us to the provided URL, it doesn't matter if the URL is relative or absolute.


2 Answers

Sometimes I think "MVC" should be called "RCMV" for "Router Controller Model View" since that is really the order that things happen. Also, since it is just "MVC", people always tend to forget about routing. The great thing about MVC is that routing configurable and extensible. I believe what you are trying to do could be solved with a custom route handler.

I haven't tested this, but you should be able to do something like this:

routes.Add(
   new Route(
       "{controller}/{action}/{id}", 
       new RouteValueDictionary(new { controller = "Home", action = "Menu" }), 
       new MyRouteHandler(Roles.Administrator, new { controller = "Admin" })));

Then your route handler would look like this:

public class MyRouteHandler : IRouteHandler
{
    public string Role { get; set; }

    public object RouteValues { get; set; }

    public MyRouteHandler(string role, object routeValues)
    {
        Role = role;
        RouteValues = routeValues;
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MyHttpHandler(Role, RouteValues);
    }
}

And finally handle the re-routing in your HttpHandler:

public class MyHttpHandler : IHttpHandler
{
    public string Role { get; set; }

    public object RouteValues { get; set; }

    public MyHttpHandler(string role, object routeValues)
    {
        Role = role;
        RouteValues = routeValues;
    }

    public void ProcessRequest(HttpContext httpContext)
    {
        if (httpContext.User.IsInRole(Role))
        {
            RouteValueDictionary routeValues = new RouteValueDictionary(RouteValues);

            // put logic here to create path similar to what you were doing
            // before but you will need to replace any keys in your route 
            // with the values from the dictionary created above.

            httpContext.RewritePath(path);
        }

        IHttpHandler handler = new MvcHttpHandler();
        handler.ProcessRequest(httpContext);
    }
}

That may not be 100% correct, but it should get you in the right direction in a way that shouldn't run into anything deprecated in MVC4.

like image 55
Doug Lampe Avatar answered Sep 22 '22 02:09

Doug Lampe


I think a TransferResult should be included in the framework without each developer wrestling with having to reimplement it for different versions when it becomes broken. (as in this thread and for example the following thread too: Implementing TransferResult in MVC 3 RC - does not work ).

If you agree with me, I would just like to encourage you to vote for "Server.Transfer" to become included in the MVC framework itself: http://aspnetwebstack.codeplex.com/workitem/798

like image 25
user310457 Avatar answered Sep 22 '22 02:09

user310457