Как имитировать сервер.Переход в ASP.NET MVC?



In ASP.NET MVC вы можете вернуть перенаправление ActionResult довольно легко:



 return RedirectToAction("Index");

or

return RedirectToRoute(new { controller = "home", version = Math.Random() * 10 });


это фактически даст перенаправление HTTP, что обычно нормально. Однако при использовании google analytics это вызывает большие проблемы, потому что исходный реферер теряется, поэтому google не знает, откуда вы пришли. Это приводит к потере полезной информации, такой как любые термины поисковой системы.



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



в любом случае, я пишу контроллер "шлюза" для всех входящих посещений сайта, которые я могу перенаправить в разные места или альтернативные версии.



сейчас я больше забочусь о Google (чем случайные закладки), и я хочу, чтобы иметь возможность отправить кого-то, кто посещает / на страницу, которую они получат, если пойдут в /home/7, который является версией 7 домашней страницы.



как я уже говорил, Если я это сделаю, я потеряю возможность для google анализировать реферер:



 return RedirectToAction(new { controller = "home", version = 7 });


то, что я действительно хочу, это



 return ServerTransferAction(new { controller = "home", version = 7 });


который даст мне это представление без перенаправления на стороне клиента.
Я не думаю, что такая вещь существует.



в настоящее время лучшее, что я могу придумать, это дублировать всю логику контроллера для HomeController.Index(..) в своем GatewayController.Index действие. Это означает, что я должен был двигаться 'Views/Home' на 'Shared' так что это было доступно. Должен быть лучший способ??..

496   14  

14 ответов:

как насчет класса TransferResult? (на основе Stans ответ)

/// <summary>
/// Transfers execution to the supplied url.
/// </summary>
public class TransferResult : ActionResult
{
    public string Url { get; private set; }

    public TransferResult(string url)
    {
        this.Url = url;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        var httpContext = HttpContext.Current;

        // MVC 3 running on IIS 7+
        if (HttpRuntime.UsingIntegratedPipeline)
        {
            httpContext.Server.TransferRequest(this.Url, true);
        }
        else
        {
            // Pre MVC 3
            httpContext.RewritePath(this.Url, false);

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

обновление: теперь работает с MVC3 (используя код от Саймон должности). Это должны (не удалось протестировать его) также работают в MVC2, глядя на то, работает ли он в интегрированном конвейере IIS7+.

для полной прозрачности; в нашей производственной среде мы никогда не использовали TransferResult напрямую. Мы используем TransferToRouteResult, который в свою очередь вызывает выполняет TransferResult. Вот что на самом деле работает на моем рабочем сервере.

public class TransferToRouteResult : ActionResult
{
    public string RouteName { get;set; }
    public RouteValueDictionary RouteValues { get; set; }

    public TransferToRouteResult(RouteValueDictionary routeValues)
        : this(null, routeValues)
    {
    }

    public TransferToRouteResult(string routeName, RouteValueDictionary routeValues)
    {
        this.RouteName = routeName ?? string.Empty;
        this.RouteValues = routeValues ?? new RouteValueDictionary();
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        var urlHelper = new UrlHelper(context.RequestContext);
        var url = urlHelper.RouteUrl(this.RouteName, this.RouteValues);

        var actualResult = new TransferResult(url);
        actualResult.ExecuteResult(context);
    }
}

если вы используете T4MVC (если не... делай!) это расширение может пригодиться.

public static class ControllerExtensions
{
    public static TransferToRouteResult TransferToAction(this Controller controller, ActionResult result)
    {
        return new TransferToRouteResult(result.GetRouteValueDictionary());
    }
}

используя этот маленький драгоценный камень вы можете сделать

// in an action method
TransferToAction(MVC.Error.Index());

изменить: обновлено, чтобы быть совместимым с ASP.NET MVC 3

при условии, что вы используете IIS7, следующая модификация, похоже, работает ASP.NET MVC 3. Спасибо @nitin и @andy за указание на исходный код не работает.

Edit 4/11/2011: TempData разрывается с сервером.TransferRequest от MVC 3 RTM

изменен код ниже, чтобы вызвать исключение - но нет другого решения на этом время.


вот моя модификация, основанная на модифицированной версии Маркуса оригинального сообщения Стэна. Я добавил дополнительный конструктор, чтобы взять словарь значений маршрута-и переименовал его MVCTransferResult, чтобы избежать путаницы, что это может быть просто перенаправление.

теперь я могу сделать следующее для редиректа:

return new MVCTransferResult(new {controller = "home", action = "something" });

мой измененный класс:

public class MVCTransferResult : RedirectResult
{
    public MVCTransferResult(string url)
        : base(url)
    {
    }

    public MVCTransferResult(object routeValues):base(GetRouteURL(routeValues))
    {
    }

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

    public override void ExecuteResult(ControllerContext context)
    {
        var httpContext = HttpContext.Current;

        // ASP.NET MVC 3.0
        if (context.Controller.TempData != null && 
            context.Controller.TempData.Count() > 0)
        {
            throw new ApplicationException("TempData won't work with Server.TransferRequest!");
        }

        httpContext.Server.TransferRequest(Url, true); // change to false to pass query string parameters if you have already processed them

        // ASP.NET MVC 2.0
        //httpContext.RewritePath(Url, false);
        //IHttpHandler httpHandler = new MvcHttpHandler();
        //httpHandler.ProcessRequest(HttpContext.Current);
    }
}

можно использовать сервер.TransferRequest на IIS7+ вместо этого.

недавно я узнал, что ASP.NET MVC не поддерживает сервер.Transfer () поэтому я создал метод заглушки (вдохновленный по умолчанию.aspx.цезий.)

    private void Transfer(string url)
    {
        // Create URI builder
        var uriBuilder = new UriBuilder(Request.Url.Scheme, Request.Url.Host, Request.Url.Port, Request.ApplicationPath);
        // Add destination URI
        uriBuilder.Path += url;
        // Because UriBuilder escapes URI decode before passing as an argument
        string path = Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
        // Rewrite path
        HttpContext.Current.RewritePath(path, false);
        IHttpHandler httpHandler = new MvcHttpHandler();
        // Process request
        httpHandler.ProcessRequest(HttpContext.Current);
    }

Не могли бы вы просто создать экземпляр контроллера, который вы хотели бы перенаправить, вызвать метод действия, который вы хотите, а затем вернуть результат этого? Что-то вроде:

 HomeController controller = new HomeController();
 return controller.Index();

Я хотел перенаправить текущий запрос на другой контроллер/действие, сохраняя при этом путь выполнения точно таким же, как если бы этот второй контроллер / действие был запрошен. В моем случае, сервер.Запрос не будет работать, потому что я хотел добавить больше данных. Это фактически эквивалентно текущему обработчику, выполняющему другой HTTP GET / POST, а затем передающему результаты клиенту. Я уверен, что будут лучшие способы достичь этого, но вот что работает для меня:

RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Public");
routeData.Values.Add("action", "ErrorInternal");
routeData.Values.Add("Exception", filterContext.Exception);

var context = new HttpContextWrapper(System.Web.HttpContext.Current);
var request = new RequestContext(context, routeData);

IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(filterContext.RequestContext, "Public");
controller.Execute(request);

ваш угадайте правильно: я положил этот код в

public class RedirectOnErrorAttribute : ActionFilterAttribute, IExceptionFilter

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

вместо того, чтобы имитировать передачу сервера, MVC по-прежнему способен выполнять сервер.TransferRequest:

public ActionResult Whatever()
{
    string url = //...
    Request.RequestContext.HttpContext.Server.TransferRequest(url);
    return Content("success");//Doesn't actually get returned
}

просто скопируйте другой контроллер и выполните его метод действия.

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

Я не уверен, что это то, что вы подразумевали под дубликатом, но:

return new HomeController().Index();

Edit

другой вариант может заключаться в создании собственного ControllerFactory, таким образом, вы можете определить, какой контроллер для создания.

разве маршрутизация не позаботится об этом сценарии для вас? т. е. для сценария, описанного выше, вы можете просто создать обработчик маршрута, который реализовал эту логику.

для тех, кто использует маршрутизацию на основе выражений, используя только класс TransferResult выше, Вот метод расширения контроллера, который делает трюк и сохраняет TempData. Нет необходимости в TransferToRouteResult.

public static ActionResult TransferRequest<T>(this Controller controller, Expression<Action<T>> action)
    where T : Controller
{
     controller.TempData.Keep();
     controller.TempData.Save(controller.ControllerContext, controller.TempDataProvider);
     var url = LinkBuilder.BuildUrlFromExpression(controller.Request.RequestContext, RouteTable.Routes, action);
     return new TransferResult(url);
}

Server.TransferRequest - это совершенно не нужно в MVC. Это устаревшая функция, которая была необходима только в ASP.NET потому что запрос пришел непосредственно на страницу, и там должен быть способ передать запрос на другую страницу. Современные версии ASP.NET (включая MVC) имеют инфраструктуру маршрутизации, которая может быть настроена на маршрут напрямую к ресурсу, который нужен. Нет никакого смысла позволять запросу достигать контроллера только для передачи его другой контроллер, когда вы можете просто сделать запрос перейти непосредственно к контроллеру и действия, которые вы хотите.

более того, так как вы отвечаете на оригинал запрос, нет никакой необходимости, чтобы заправить что-нибудь в TempData или другое хранилище только ради маршрутизации запроса в нужное место. Вместо этого вы приходите к действию контроллера с исходным запросом неповрежденным. Вы также можете быть уверены, что Google одобрит этот подход, поскольку он происходит полностью на стороне сервера.

в то время как вы можете сделать совсем немного с обеих IRouteConstraint и IRouteHandler, самая мощная точка расширения для маршрутизации является RouteBase подкласс. Этот класс может быть расширен для обеспечения как входящих маршрутов, так и исходящих URL-адресов, что делает его единым магазином для всего, что связано с URL-адресом и действием, которое выполняет URL-адрес.

Итак, чтобы следовать вашему второму примеру, чтобы получить от / до /home/7, вам просто нужен маршрут это добавляет соответствующие значения маршрута.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Routes directy to `/home/7`
        routes.MapRoute(
            name: "Home7",
            url: "",
            defaults: new { controller = "Home", action = "Index", version = 7 }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

но возвращаясь к исходному примеру, где у вас есть случайная страница, это сложнее, потому что параметры маршрута не могут измениться во время выполнения. Итак, это можно сделать с помощью RouteBase подкласс следующим образом.

public class RandomHomePageRoute : RouteBase
{
    private Random random = new Random();

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        RouteData result = null;

        // Only handle the home page route
        if (httpContext.Request.Path == "/")
        {
            result = new RouteData(this, new MvcRouteHandler());

            result.Values["controller"] = "Home";
            result.Values["action"] = "Index";
            result.Values["version"] = random.Next(10) + 1; // Picks a random number from 1 to 10
        }

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

, который может быть зарегистрирован в маршрутизации, таких как:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Routes to /home/{version} where version is randomly from 1-10
        routes.Add(new RandomHomePageRoute());

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

Примечание В приведенном выше примере может иметь смысл также хранить файл cookie, записывающий версию домашней страницы, в которую вошел пользователь, поэтому когда они возвращаются, они получают ту же версию домашней страницы.

Обратите также внимание, что с помощью этого подхода вы можете настроить маршрутизацию, чтобы учитывать параметры строки запроса (по умолчанию она полностью игнорирует их) и соответственно маршрутизировать к соответствующему действию контроллера.

дополнительные Примеры

Не ответ как таковой, но ясно, что требование будет заключаться не только в том, чтобы фактическая навигация "делала" эквивалентную функциональность сервера Webforms.Transfer (), но и для того, чтобы все это полностью поддерживалось в рамках модульного тестирования.

поэтому ServerTransferResult должен "выглядеть" как RedirectToRouteResult и быть максимально похожим с точки зрения иерархии классов.

Я думаю сделать это, глядя на рефлектор, и делать все, что угодно RedirectToRouteResult класс, а также различные методы базового класса контроллера делают, а затем "добавление" последнего к контроллеру с помощью методов расширения. Может быть, это могут быть статические методы в одном классе, для удобства / лени загрузки?

Если я соберусь сделать это, я опубликую его, иначе, возможно, кто-то еще может опередить меня!

я достиг этого, используя Html.RenderAction помощник в виде:

@{
    string action = ViewBag.ActionName;
    string controller = ViewBag.ControllerName;
    object routeValues = ViewBag.RouteValues;
    Html.RenderAction(action, controller, routeValues);
}

и в моем контроллере:

public ActionResult MyAction(....)
{
    var routeValues = HttpContext.Request.RequestContext.RouteData.Values;    
    ViewBag.ActionName = "myaction";
    ViewBag.ControllerName = "mycontroller";
    ViewBag.RouteValues = routeValues;    
    return PartialView("_AjaxRedirect");
}

Comments

    Ничего не найдено.