Как перенаправить действие MVC без возврата 301? (используя бета-версию MVC 4) - PullRequest
7 голосов
/ 05 марта 2012

Я работаю над решением ASP.NET MVC, которое имеет несколько различных меню. Отображаемое меню зависит от роли текущего пользователя.

В MVC 3 у меня был некоторый пользовательский код для поддержки этого сценария, с помощью одного метода контроллера, который возвращал бы правильное меню. Это будет сделано путем передачи запроса соответствующему контроллеру и выполнения действий в зависимости от текущего пользователя.

Этот код, похоже, не работает в MVC 4, и я ищу помощь, чтобы это исправить.

Сначала я добавил вспомогательный класс TransferResult для выполнения перенаправления:

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

Во-вторых, я изменил T4MVC, чтобы он выдавал несколько вспомогательных методов контроллера, в результате чего каждый контроллер имел этот метод:

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

Это позволило мне иметь общее действие контроллера для возврата меню без необходимости загромождать представления какой-либо условной логикой:

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

Однако код в ExecuteResult в классе TransferResult, похоже, не работает с текущим предварительным выпуском MVC 4. Это дает мне следующую ошибку (указывающую на строку «httpHandler.ProcessRequest»):

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

Есть идеи, как это исправить?

PS: я понимаю, что могу добиться того же, используя простое расширение HtmlHelper, которое я сейчас использую в качестве обходного пути. Однако у меня есть много других сценариев, в которых этот метод позволяет мне смешивать и повторно использовать действия, и я не хотел бы отказываться от этой гибкости при переходе на MVC 4.

Ответы [ 2 ]

5 голосов
/ 14 марта 2012

Иногда я думаю, что «MVC» следует называть «RCMV» для «Представления модели контроллера маршрутизатора», поскольку это действительно тот порядок, в котором все происходит. Кроме того, поскольку это просто «MVC», люди всегда склонны забывать о маршрутизации. Самое замечательное в MVC - это то, что маршрутизация настраивается и расширяется. Я считаю, что то, что вы пытаетесь сделать, может быть решено с помощью специального обработчика маршрута.

Я не проверял это, но вы должны быть в состоянии сделать что-то вроде этого:

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

Тогда ваш обработчик маршрута будет выглядеть так:

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

И, наконец, обработайте перенаправление в вашем 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);
    }
}

Возможно, это не на 100% правильно, но это должно привести вас в правильном направлении, чтобы не допустить того, что в MVC4 не рекомендуется.

1 голос
/ 24 января 2013

Я думаю, что TransferResult должен быть включен во фреймворк, чтобы каждый разработчик не боролся с необходимостью переопределять его для разных версий, когда он ломается.(как в этой теме и, например, в следующей: Реализация TransferResult в MVC 3 RC - не работает ).

Если вы согласны со мной, я просто хотел бы призвать васчтобы проголосовать за "Server.Transfer", который будет включен в структуру MVC: http://aspnetwebstack.codeplex.com/workitem/798

...