كيفية محاكاة Server.Transfer في ASP.NET MVC؟




asp.net-mvc (12)

ألا يمكنك فقط إنشاء مثيل لوحدة التحكم التي ترغب في إعادة التوجيه إليها ، واستدعاء طريقة الإجراء التي تريدها ، ثم إرجاع نتيجة ذلك؟ شيء مثل:

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

في ASP.NET MVC يمكنك إرجاع عملية إعادة توجيه ActionResult بسهولة تامة:

 return RedirectToAction("Index");

 or

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

سيعطي هذا بالفعل عملية إعادة توجيه HTTP ، والتي عادة ما تكون جيدة. ومع ذلك ، عند استخدام تحليلات google ، يتسبب ذلك في مشكلات كبيرة ؛ نظرًا لأن المحيل الأصلي مفقود حتى لا يعرف google من أين أتيت. يفقد هذا معلومات مفيدة مثل أي شروط محرك البحث.

وكملاحظة جانبية ، تتميز هذه الطريقة بإزالة أي معلمات قد تأتي من الحملات ولكنها تسمح لي أيضًا بالتقاطها على جانب الخادم. تركهم في سلسلة الاستعلام يؤدي إلى إشارات مرجعية أو تويتر أو مدونًا رابطًا لا ينبغي لهم فعله. لقد شاهدت هذا عدة مرات عندما يكون لدى الأشخاص روابط لموقع تويتر تحتوي على معرّفات الحملة.

على أي حال ، أنا أكتب وحدة تحكم "بوابة" لجميع الزيارات الواردة إلى الموقع والتي يمكنني إعادة توجيهها إلى أماكن مختلفة أو إصدارات بديلة.

في الوقت الحالي ، أهتم بمزيد من المعلومات حول Google في الوقت الحالي (من الإشارة المرجعية غير المقصودة) ، وأريد أن أتمكن من إرسال شخص يزور / إلى الصفحة التي سيحصل عليها إذا ذهبوا إلى /home/7 ، وهو الإصدار 7 من الصفحة الرئيسية .

كما قلت من قبل ، إذا فعلت ذلك ، فأنا أفقد القدرة على استخدام google لتحليل المحيل:

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

ما أريد حقا هو

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

الذي سيحصل على هذا الرأي دون إعادة توجيه جانب العميل. لا أعتقد أن مثل هذا الشيء موجود.

حاليا أفضل شيء يمكن أن أقوم به هو تكرار منطق تحكم كامل لـ HomeController.Index(..) في GatewayController.Index Action GatewayController.Index بي. هذا يعني أنني اضطررت لنقل 'Views/Home' إلى 'Shared' حتى يمكن الوصول إليها. يجب أن تكون هناك طريقة أفضل؟؟..


تحرير: تحديث لتكون متوافقة مع ASP.NET MVC 3

بشرط استخدام IIS7 يبدو أن التعديل التالي يعمل لـ ASP.NET MVC 3. بفضلnitin وandy للإشارة إلى أن الكود الأصلي لم يعمل.

تحرير 4/11/2011: فواصل TempData مع Server.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);
    }
}

لا توجد إجابة بحد ذاتها ، ولكن من الواضح أن المتطلبات لن تكون فقط بالنسبة للملاحة الفعلية "للقيام" بالوظائف المكافئة لخادم Webforms Server.Transfer () ، ولكن أيضًا لكل هذا يتم دعمه بالكامل في اختبار الوحدة.

لذلك يجب أن "يبحث" ServerTransferResult مثل RedirectToRouteResult وتكون مشابهة قدر ممكن من حيث التسلسل الهرمي للفئة.

أفكر في القيام بذلك من خلال النظر في العاكس ، وفعل أي فئة RedirectToRouteResult وكذلك أساليب فئة قاعدة المراقب المالي المختلفة ، ثم "إضافة" الأخير إلى المراقب المالي عبر طرق التمديد. ربما هذه يمكن أن تكون أساليب ثابتة داخل نفس الفئة ، لسهولة التنزيل / الكسل؟

إذا حصلت على جولة للقيام بذلك ، سأقوم بنشرها ، وإلا ربما قد يضربونني شخص آخر!


كنت أرغب في إعادة توجيه الطلب الحالي إلى وحدة تحكم / إجراء آخر ، مع الاحتفاظ بمسار التنفيذ تمامًا كما هو الحال في حالة طلب وحدة التحكم / الإجراء الثاني. في حالتي ، لن يعمل 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 أو قاعدة بيانات أو بعض الطرق الأخرى لتمرير بيانات الاستثناء بين الطلبات.


بدلاً من محاكاة عملية نقل خادم ، لا يزال MVC قادراً على القيام بالفعل بـ Server.TransferRequest :

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

لأي شخص يستخدم التوجيه المبني على تعبير ، باستخدام فئة 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 على IIS7 + بدلاً من ذلك.


لا يهتم فقط هذا السيناريو بالنسبة لك؟ بمعنى السيناريو الذي تم وصفه أعلاه ، يمكنك فقط إنشاء معالج توجيه لتنفيذ هذا المنطق.


Server.TransferRequest غير ضروري بشكل كامل في MVC . هذه ميزة قديمة كانت ضرورية فقط في ASP.NET لأن الطلب جاء مباشرة إلى صفحة وهناك حاجة إلى طريقة لنقل طلب إلى صفحة أخرى. تحتوي الإصدارات الحديثة من ASP.NET (بما في ذلك MVC) على بنية أساسية توجيهية يمكن تخصيصها لتوجيهها مباشرةً إلى المورد المطلوب. لا توجد فائدة من السماح للطلب بالوصول إلى وحدة تحكم فقط لنقلها إلى وحدة تحكم أخرى عندما يمكنك ببساطة أن تجعل الطلب ينتقل مباشرةً إلى وحدة التحكم والإجراء الذي تريده.

والأكثر من ذلك هو أنه بما أنك تستجيب للطلب الأصلي ، فلا داعي TempData أي شيء في TempData أو أي وحدة تخزين أخرى فقط من أجل توجيه الطلب إلى المكان الصحيح. بدلاً من ذلك ، تصل إلى إجراء وحدة التحكم مع الطلب الأصلي سليمة. يمكنك أيضًا أن تطمئن إلى أن Google ستوافق على هذا الأسلوب نظرًا لأنه يتم بالكامل من جانب الخادم.

بينما يمكنك القيام IRouteConstraint من 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 }
        );
    }
}

لاحظ في المثال أعلاه ، قد يكون من المنطقي أيضًا تخزين ملف تعريف ارتباط يسجل إصدار الصفحة الرئيسية الذي دخل فيه المستخدم عند عودته ، حيث يتلقى نفس إصدار الصفحة الرئيسية.

لاحظ أيضًا أنه باستخدام هذا الأسلوب ، يمكنك تخصيص التوجيه لأخذ معلمات سلسلة الاستعلام في الاعتبار (يتجاهلها بشكل افتراضي بشكل افتراضي) والطريق إلى إجراء وحدة تحكم مناسبة وفقًا لذلك.

أمثلة إضافية

  • https://.com/a/31958586
  • https://.com/a/36774498
  • https://.com/a/36168395

مجرد مثال وحدة التحكم الأخرى وتنفيذ طريقة عملها.


هل يمكن أن تصل إلى وحدة تحكم أخرى واستدعاء طريقة الإجراء إرجاع النتيجة. هذا سوف يتطلب منك وضع طريقة العرض الخاصة بك في المجلد المشترك ومع ذلك.

لست متأكدًا إذا كان هذا ما تعنيه بالنسخة المكررة ، ولكن:

return new HomeController().Index();

تصحيح

قد يكون هناك خيار آخر لإنشاء جهاز التحكم الخاص بك ، وبهذه الطريقة يمكنك تحديد أي وحدة تحكم لإنشاء.


ماذا عن الطبقة 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());




server.transfer