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.

9voto

Brian Sullivan Points 6392

Ne pourriez-vous pas simplement créer une instance du contrôleur vers lequel vous souhaitez rediriger, invoquer la méthode d'action que vous souhaitez, puis renvoyer le résultat de cette action ? Quelque chose comme :

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

6 votes

Non, le contrôleur que vous créez n'aura pas les choses comme la demande et la réponse configurées correctement sur lui. Cela peut entraîner des problèmes.

1 votes

Je suis d'accord avec @JeffWalkerCodeRanger : la même chose aussi après avoir défini la propriété otherController.ControllerContext = this.ControllerContext;

7voto

Je voulais réacheminer la demande actuelle vers un autre contrôleur/action, tout en gardant le chemin d'exécution exactement le même que si ce deuxième contrôleur/action était demandé. Dans mon cas, Server.Request ne fonctionnerait pas car je voulais ajouter des données supplémentaires. Cela équivaut en fait au gestionnaire actuel qui exécute un autre GET/POST HTTP, puis transmet les résultats au client. Je suis sûr qu'il y aura de meilleures façons d'y parvenir, mais voici ce qui fonctionne pour moi :

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

Vous avez raison : j'ai mis ce code dans

public class RedirectOnErrorAttribute : ActionFilterAttribute, IExceptionFilter

et je l'utilise pour afficher les erreurs aux développeurs, alors qu'il utilisera une redirection normale en production. Notez que je n'ai pas voulu utiliser la session ASP.NET, la base de données, ou d'autres moyens pour passer les données d'exception entre les requêtes.

5voto

Richard Szalay Points 42486

Instancez simplement l'autre contrôleur et exécutez sa méthode d'action.

0 votes

Cela n'affichera pas l'URL souhaitée dans la barre d'adresse.

1 votes

@arserbin3 - Server.Transfer ne le fera pas non plus. Cette exigence est probablement la raison pour laquelle la question originale a été postée.

2voto

JoshBerke Points 34238

Vous pourriez créer l'autre contrôleur et invoquer la méthode d'action renvoyant le résultat. Cela vous obligera toutefois à placer votre vue dans le dossier partagé.

Je ne sais pas si c'est ce que vous vouliez dire par "double", mais.. :

return new HomeController().Index();

Modifier

Une autre option pourrait être de créer votre propre ControllerFactory, de cette façon vous pouvez déterminer quel contrôleur créer.

0 votes

C'est peut-être la bonne approche, mais le contexte ne semble pas être correct, même si je dis hc.ControllerContext = this.ControllerContext. De plus, il cherche ensuite la vue dans ~/Views/Gateway/5.aspx et ne la trouve pas.

0 votes

De plus, vous perdez tous les filtres d'action. Vous devriez probablement essayer d'utiliser la méthode Execute de l'interface IController que vos contrôleurs doivent mettre en œuvre. Par exemple : ((IController)new HomeController()).Execute(...). De cette façon, vous participez toujours au pipeline de l'invocateur d'action. Il faudrait cependant trouver exactement ce qu'il faut passer à Execute... Reflector pourrait vous aider :)

0 votes

Je n'aime pas l'idée de créer un nouveau contrôleur, je pense que vous êtes mieux de définir votre propre usine de contrôleur qui semble être le point d'extension approprié pour cela. Mais je n'ai qu'effleuré la surface de ce cadre, donc je pourrais être loin du compte.

2voto

NightOwl888 Points 4622

Server.TransferRequest es complètement inutile en MVC . Il s'agit d'une fonctionnalité archaïque qui n'était nécessaire dans ASP.NET que parce que la demande arrivait directement sur une page et qu'il fallait trouver un moyen de transférer une demande vers une autre page. Les versions modernes de ASP.NET (y compris MVC) disposent d'une infrastructure de routage qui peut être personnalisée pour acheminer les requêtes vers directement à la ressource souhaitée. Il est inutile de laisser la demande atteindre un contrôleur pour ensuite la transférer à un autre contrôleur alors que vous pouvez simplement faire en sorte que la demande aille directement au contrôleur et à l'action que vous souhaitez.

De plus, puisque vous répondez à la original demande, il n'y a pas besoin de rentrer quoi que ce soit dans la TempData ou autre stockage juste pour acheminer la demande au bon endroit. Au lieu de cela, vous arrivez à l'action du contrôleur avec la requête originale intacte. Vous pouvez également être sûr que Google approuvera cette approche, car elle se déroule entièrement du côté du serveur.

Bien que vous puissiez faire beaucoup de choses à partir des deux IRouteConstraint y IRouteHandler le point d'extension le plus puissant pour l'acheminement est l'outil de routage. RouteBase sous-classe. Cette classe peut être étendue pour fournir à la fois des routes entrantes et la génération d'URL sortantes, ce qui en fait un guichet unique pour tout ce qui concerne l'URL et l'action qu'elle exécute.

Donc, pour suivre votre deuxième exemple, pour passer de / a /home/7 vous avez simplement besoin d'une route qui ajoute les valeurs de route appropriées.

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

Mais pour en revenir à votre exemple initial où vous avez une page aléatoire, c'est plus complexe car les paramètres de la route ne peuvent pas changer au moment de l'exécution. Donc, cela pourrait être fait avec un RouteBase comme suit.

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

Qui peut être enregistré dans le routage comme :

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

Notez que dans l'exemple ci-dessus, il pourrait être judicieux de stocker également un cookie enregistrant la version de la page d'accueil sur laquelle l'utilisateur est arrivé, de sorte que lorsqu'il revient, il reçoit la même version de la page d'accueil.

Notez également qu'en utilisant cette approche, vous pouvez personnaliser le routage pour prendre en compte les paramètres des chaînes de requête (il les ignore complètement par défaut) et le diriger vers une action de contrôleur appropriée.

Exemples supplémentaires

1 votes

Que se passe-t-il si je ne veux pas transférer immédiatement en entrant dans une action, mais plutôt laisser cette action faire un certain travail et ensuite transférer conditionnellement à une autre action. Changer mon routage pour aller directement à la cible du transfert ne fonctionnera pas, donc cela ressemble à ceci Server.TransferRequest n'est pas, après tout, "complètement inutile en MVC".

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