127 votes

Comment simuler Server.Transfer dans ASP.NET MVC ?

En ASP.NET MVC, vous pouvez retourner une redirection ActionResult assez facilement :

 return RedirectToAction("Index");

 or

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

Cela donnera en fait une redirection HTTP, ce qui est normalement correct. Cependant, lorsque vous utilisez Google Analytics, cela pose de gros problèmes car le référent d'origine est perdu, et Google ne sait donc pas d'où vous venez. Cela entraîne la perte d'informations utiles telles que les termes des moteurs de recherche.

Par ailleurs, cette méthode présente l'avantage de supprimer tous les paramètres qui peuvent provenir des campagnes, mais me permet tout de même de les capturer côté serveur. En les laissant dans la chaîne de requête, on risque de voir des gens mettre en signet, sur Twitter ou sur un blog, un lien qu'ils ne devraient pas. J'ai constaté à plusieurs reprises que des personnes ont envoyé sur Twitter des liens vers notre site contenant des identifiants de campagne.

Quoi qu'il en soit, j'écris un contrôleur de "passerelle" pour toutes les visites entrantes sur le site que je peux rediriger vers des endroits différents ou des versions alternatives.

Pour l'instant, je me soucie davantage de Google (que de la mise en signet accidentelle), et je veux pouvoir envoyer à quelqu'un qui visite / à la page qu'ils obtiendraient s'ils allaient à /home/7 qui est la version 7 d'une page d'accueil.

Comme je l'ai déjà dit, si je fais cela, je perds la possibilité pour Google d'analyser le référent :

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

Ce que je veux vraiment, c'est un

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

qui me permettra d'obtenir cette vue sans redirection côté client. Je ne pense pas qu'une telle chose existe, cependant.

Actuellement, la meilleure chose que je puisse faire est de dupliquer toute la logique du contrôleur pour HomeController.Index(..) dans mon GatewayController.Index Action. Cela signifie que j'ai dû déplacer 'Views/Home' en 'Shared' pour qu'il soit accessible. Il doit y avoir un meilleur moyen.

0 votes

Qu'est-ce qu'un ServerTransferAction que vous essayiez de reproduire ? Est-ce une chose réelle ? (je n'ai trouvé aucune information à ce sujet... merci pour la question, d'ailleurs, la réponse ci-dessous est superbe).

1 votes

Recherchez Server.Transfer(...). C'est un moyen de faire une 'redirection' du côté du serveur où le client reçoit la page redirigée sans redirection du côté du client. En général, ce n'est pas recommandé avec le routage moderne.

1 votes

Le "transfert" est une fonctionnalité ASP.NET désuète qui n'est plus nécessaire dans MVC en raison de la possibilité de aller directement à l'action correcte du contrôleur en utilisant le routage. Voir cette réponse pour les détails.

132voto

Markus Olsson Points 12651

Que diriez-vous d'une classe TransferResult ? (basée sur Réponse de 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);
        }
    }
}

Mis à jour : Fonctionne maintenant avec MVC3 (en utilisant le code de Le message de Simon ). Il s'agit de debe (je n'ai pas pu le tester) fonctionne également dans MVC2 en regardant s'il est exécuté ou non dans le pipeline intégré de IIS7+.

Pour une transparence totale ; Dans notre environnement de production, nous n'avons jamais utilisé directement le TransferResult. Nous utilisons un TransferToRouteResult qui à son tour appelle l'exécution du TransferResult. Voici ce qui s'exécute actuellement sur mes serveurs de production.

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

Et si vous utilisez T4MVC (si ce n'est pas le cas... faites-le !) cette extension pourrait s'avérer utile.

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

En utilisant ce petit bijou, vous pouvez faire

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

1 votes

Cela fonctionne très bien. faites attention à ne pas vous retrouver avec une boucle infinie - comme je l'ai fait lors de ma première tentative en passant la mauvaise URL. j'ai fait une petite modification pour permettre à une collection de valeurs de route d'être passée, ce qui pourrait être utile à d'autres. posté ci-dessus ou ci-dessous...

0 votes

Mise à jour : cette solution semble bien fonctionner, et bien que je ne l'utilise que de manière très limitée, je n'ai pas encore rencontré de problèmes.

0 votes

Un problème : il n'est pas possible de rediriger une demande POST vers une demande GET, mais ce n'est pas nécessairement une mauvaise chose. il faut cependant être prudent.

48voto

Simon_Weaver Points 31141

Edit : Mis à jour pour être compatible avec ASP.NET MVC 3

Si vous utilisez IIS7, la modification suivante semble fonctionner pour ASP.NET MVC 3. Merci à @nitin et @andy d'avoir signalé que le code original ne fonctionnait pas.

Edit 4/11/2011 : TempData ne fonctionne pas avec Server.TransferRequest à partir de MVC 3 RTM.

J'ai modifié le code ci-dessous pour qu'il lève une exception - mais pas d'autre solution pour le moment.


Voici ma modification basée sur la version modifiée de Markus du message original de Stan. J'ai ajouté un constructeur supplémentaire pour prendre un dictionnaire de valeurs de route - et je l'ai renommé MVCTransferResult pour éviter la confusion qu'il pourrait être juste une redirection.

Je peux maintenant faire ce qui suit pour une redirection :

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

Ma classe modifiée :

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

1 votes

Cela semble ne pas fonctionner dans MVC 3 RC. Échec de HttpHandler.ProcessRequest() : "HttpContext.SetSessionStateBehavior" ne peut être invoqué qu'avant l'apparition de l'événement "HttpApplication.AcquireRequestState".

0 votes

Je n'ai pas encore eu l'occasion d'examiner MVC3. Faites-moi savoir si vous trouvez une solution.

0 votes

Est-ce que Server.TransferRquest, comme suggéré par Nitin, fait ce que l'on essaie de faire ci-dessus ?

15voto

Nitin Points 310

Vous pouvez utiliser Server.TransferRequest sur IIS7+ à la place.

12voto

J'ai découvert récemment que ASP.NET MVC ne supporte pas Server.Transfer(), j'ai donc créé une méthode stub (inspirée de Default.aspx.cs).

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

9voto

AaronLS Points 12720

Plutôt que de simuler un transfert de serveur, MVC est toujours capable de faire réellement un Server.TransferRequest :

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

0 votes

N'hésitez pas à ajouter du texte à votre réponse pour l'expliquer davantage.

0 votes

Notez que cela nécessite MVCv3 et plus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X