web-dev-qa-db-ja.com

ASP.NET MVCでServer.Transferをシミュレートする方法は?

ASP.NET MVCでは、リダイレクトActionResultを非常に簡単に返すことができます。

_ return RedirectToAction("Index");

 or

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

これにより、実際にはHTTPリダイレクトが行われますが、通常は問題ありません。ただし、Googleアナリティクスを使用すると、元のリファラーが失われるため、Googleがあなたがどこから来たのか分からないため、大きな問題が発生します。これにより、検索エンジンの用語などの有用な情報が失われます。

補足として、この方法には、キャンペーンから発生した可能性のあるパラメーターを削除するという利点がありますが、サーバー側でそれらをキャプチャすることはできます。クエリ文字列にそれらを残しておくと、人々がブックマークしてはならない、Twitterやブログにすべきではないリンクができます。キャンペーンIDを含むサイトへのリンクをtwitterでツイートしている人がいるのを何度か見ました。

とにかく、私は別の場所または代替バージョンにリダイレクトする可能性のあるサイトへのすべての訪問のための「ゲートウェイ」コントローラを書いています。

今のところ(偶発的なブックマークよりも)Googleをもっと気にかけています。また、_/_にアクセスした場合に取得するページに_/home/7_にアクセスした人を送信できるようにしたいと思います。ホームページのバージョン7。

前に言ったように、これを行うと、Googleがリファラーを分析できなくなります。

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

私が本当に欲しいのは

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

クライアント側のリダイレクトなしでそのビューを取得できます。私はそのようなものが存在するとは思わない。

現在、私が思いつく最良の方法は、_GatewayController.Index_アクションでHomeController.Index(..)のコントローラーロジック全体を複製することです。これは、_'Views/Home'_を_'Shared'_に移動する必要があったため、アクセス可能になったことを意味します。より良い方法が必要ですか?? ..

123
Simon_Weaver

TransferResultクラスはどうですか? ( スタンスアンサー に基づく)

/// <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で動作するようになりました( Simon's post のコードを使用)。また、should(テストできませんでした)は、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());
130
Markus Olsson

編集:ASP.NET MVC 3と互換性があるように更新

IIS7を使用している場合、次の変更はASP.NET MVC 3で機能するようです。元のコードを指摘してくれた@nitinと@andyのおかげで機能しませんでした。

編集4/11/2011:TempDataはMVC 3 RTMの時点でServer.TransferRequestで中断します

例外をスローするように以下のコードを変更しました-現時点では他の解決策はありません。


これは、スタンの元の投稿のMarkusの修正版に基づいた私の修正です。ルート値ディクショナリを取得するために追加のコンストラクタを追加し、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);
    }
}
47
Simon_Weaver

代わりに、IIS7 +でServer.TransferRequestを使用できます。

14
Nitin Agarwal

最近、ASP.NET MVCはServer.Transfer()をサポートしていないことがわかったため、スタブメソッド(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);
    }
12
Stan

リダイレクト先のコントローラーのインスタンスを作成し、必要なアクションメソッドを呼び出して、その結果を返すだけではできませんか?何かのようなもの:

 HomeController controller = new HomeController();
 return controller.Index();
9
Brian Sullivan

実行パスを2番目のコントローラー/アクションが要求された場合とまったく同じに保ちながら、現在の要求を別のコントローラー/アクションに再ルーティングしたかったのです。私の場合、さらにデータを追加したいため、Server.Requestは機能しません。これは実際には、別の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セッション、データベース、またはその他の方法を使用したくないことに注意してください。

7
user191966

サーバー転送をシミュレートするのではなく、MVCは実際に Server.TransferRequest を実行できます。

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

他のコントローラーをインスタンス化し、そのアクションメソッドを実行するだけです。

5
Richard Szalay

他のコントローラーを更新し、結果を返すアクションメソッドを呼び出すことができます。ただし、これにはビューを共有フォルダーに配置する必要があります。

これがあなたが複製によって意味したものであるかどうかはわかりませんが、:

return new HomeController().Index();

編集

別のオプションとして、独自のControllerFactoryを作成することもできます。これにより、作成するコントローラーを決定できます。

2
JoshBerke

上記の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);
}
1
Stephane Legay

ルーティングはこのシナリオの面倒をみませんか?つまり、上記のシナリオでは、このロジックを実装したルートハンドラーを作成するだけで済みます。

1
Richard

Server.TransferRequestMVCでは完全に不要です。これは時代遅れの機能であり、ASP.NETでのみ必要でした。リクエストはページに直接届き、リクエストを別のページに転送する方法が必要だったためです。最新バージョンのASP.NET(MVCを含む)には、必要なリソースにを直接ルーティングするようにカスタマイズできるルーティングインフラストラクチャがあります。リクエストをコントローラに直接送信して、目的のコントローラとアクションに直接リクエストを送信できる場合にのみ、リクエストを別のコントローラに転送することはできません。

さらに、originalリクエストに応答しているため、リクエストをルーティングするためだけにTempDataまたは他のストレージに何かを入れる必要はありません。適切な場所に。代わりに、元の要求をそのままにしてコントローラーアクションに到達します。また、Googleがこのアプローチを完全にサーバー側で実行するため、Googleが承認するので安心できます。

IRouteConstraintIRouteHandlerの両方からかなりのことができますが、ルーティングの最も強力な拡張ポイントはRouteBaseサブクラスです。このクラスは、着信ルートと発信URL生成の両方を提供するように拡張できます。これにより、URLとURLが実行するアクションに関連するすべてのワンストップショップになります。

したがって、2番目の例に従って、/から/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を保存して、戻ったときに同じホームページのバージョンを受け取るようにすることもできます。

また、このアプローチを使用すると、ルーティングをカスタマイズしてクエリ文字列パラメーターを考慮に入れ(既定では完全に無視する)、それに応じて適切なコントローラーアクションにルーティングできることに注意してください。

追加の例

1
NightOwl888

これは、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");
}
0
Colin

それ自体は答えではありませんが、Webforms Server.Transfer()と同等の機能を実際にナビゲーションするためだけでなく、ユニットテスト内でこれらすべてを完全にサポートするための要件であることは明らかです。

そのため、ServerTransferResultはRedirectToRouteResultのように「見える」必要があり、クラス階層の点で可能な限り類似している必要があります。

Reflectorを見て、RedirectToRouteResultクラスとさまざまなController基本クラスメソッドが行うことによってこれを実行し、拡張メソッドを介して後者をControllerに「追加」することを考えています。たぶん、これらは同じクラス内の静的メソッドであり、ダウンロードの容易さ/怠inessさのためでしょうか?

私がこれをやり遂げたら、それを投稿します。

0
William