ASP.NET MVC拡張非同期アクション機能(上)

11370 ワード

この文書は上下2つの部分に分かれており、『Extend ASP.NET MVC for Asynchronous Action』からすべての内容を入手することもできます.
非同期要求処理はASP.NET 2.0に導入された高度な特性は、IO Complete Portに依存し、IO密集型アプリケーションのスループットを向上させる上で非常に重要です(原理記述とパフォーマンステストを参照).しかし現在ASP.NET MVCフレームワークには非同期アクション機能が欠けている.これは趙さんがよく口にしている「現在のASP.NET MVCに欠けている非常に重要な機能」だ.TechED 2008 ChinaのSessionでは、いわゆる「ソリューション」を提供したことがありますが、複雑さの高さでそのソリューションに制限が多すぎます.TechED上の遺憾を補うため、そして準備する.NET開発大会でのASP.NET MVCベストプラクティスのSessionは、春節休暇中にこの方面の問題をよく考えて、比較的良い拡張を得ました:完全で、便利で、しかも非常に軽くて--コアロジックコードは200行ぐらいしかありません.これは、ほとんどの機能がフレームワークの既成の内容に委託され、拡張の安定性を確保し、効率的で、後方互換性が高いことを意味しています.
特筆すべきは、私は1/26号でASPに基づいています.NET MVCのBetaバージョンはこの拡張の最初のバージョンを書き、やがてマイクロソフトがASPを発表した.NET MVC RC.ソリューションを移植する過程でASPを発見しました.NET MVC RCはフレームワーク設計に大きな改善を行い、拡張を構築する際の戦略を少し変えました.嬉しいことに、RCバージョンのこれらの変更は、特に現在の「ローエンド」レベルの拡張を構築するのに容易になりました.ASP.NET MVCフレームワークは、「どこでも拡張可能」という約束を実現した.
では、この拡張の実現方法を詳しく分析します.

要求処理方式の変更


基本的な改造戦略を策定する前に、ASPを理解する必要があります.NET MVCフレームワークの現在のアーキテクチャと要求処理プロセス.次のようになります.
  • アプリケーションの起動時(この時点ではリクエストは受け付けていない)、MVCリクエストに対するRouteポリシーをASPに登録する.NET Routingモジュール.このとき、各Routeポリシー(すなわちRouteオブジェクト)におけるRouteHandler属性はASP.NET MVCフレームワークのMvcRouteHandler.
  • 当ASP.NET Routingモジュールは、あるRouteポリシーに一致するHTTP要求を受信すると、そのRouteオブジェクトのRouteHandlerオブジェクトのGetHttpHandlerを呼び出してHttpHandlerを取得し、ASPに渡す.NET実行.MvcRouteHandlerは、常にMvcHandlerオブジェクトを返します.
  • MvcHandlerは、実行時にRouteDataのコントロール値を取り出し、IControllerインタフェースを実現するコントローラオブジェクトを構築し、IControllerインタフェースのExecuteメソッドを呼び出してコントローラを実行します.
  • は1つのASPに対して.NET MVCアプリケーションでは、ほとんどのコントローラがSystemを継承する.Web.Mvc.Controllerタイプ.Controllerクラスは、RouteDataからaction値を取得し、IActionInvokerインタフェースを実装するオブジェクトに渡してアクションを実行します.
  • ……
  • このプロセスを非同期処理に変更する場合は、ASPに適合させる必要があります.NETアーキテクチャにおける非同期処理方式.ASP.NETアーキテクチャの非同期要求に対する処理は、非同期ページ、非同期Http Moduleなど、いくつかの方法で実現することができるが、現在の状況に最も適した方法は、非同期Http Handlerである.非同期Handlerを実装するには、従来のIHttpHandlerインタフェースではなく、要求を処理するHandlerにIHttpAsyncHandlerインタフェースを実装させる必要がある.IHttpAsyncHandlerインタフェースにおけるBeginProcessRequestとEndProcessRequestの2つの方法で構成する.NETのAPM(Aynchronous Programming Model、非同期プログラミングモデル)モードは、HTTPリクエストを処理するために「二段式」の異歩調を使用することができます.
    非同期アクションをサポートする場合は、現在のリクエスト情報に基づいて、IHttpHandlerオブジェクトが実行されるか、IHttpAsyncHandlerオブジェクトが実行されるかを確認する必要があります.そしてASP.NET MVCフレームワークは、デフォルトではHttp Handler(すなわち、MvcHandlerオブジェクト)内部でコントローラのチェック、構築、呼び出しを行います.これはもう遅いので、これらの論理をRoutingの過程に繰り上げなければなりません.幸いなことに、ASP.NET RoutingでサポートされているIruteHandlerはASPのようです.NETのIHttpHandler Factoryは,場合によっては異なるHandlerを生成して実行できる.したがって,新しいIrouteHandlerタイプを構築すればよい.そこでAsyncMvcRouteHandlerが誕生しました.その中のコードの一部はフレームワークのMvcHandlerと同じです.ある程度はMvcHandlerでやっていたことを繰り上げただけですから.
    public class AsyncMvcRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            string controllerName = requestContext.RouteData.GetRequiredString("controller");
    
            var factory = ControllerBuilder.Current.GetControllerFactory();
            var controller = factory.CreateController(requestContext, controllerName);
            if (controller == null)
            {
                throw new InvalidOperationException(...);
            }
    
            var coreController = controller as Controller;
            if (coreController == null)
            {
                return new SyncMvcHandler(controller, factory, requestContext);
            }
            else
            {
    
                string actionName = requestContext.RouteData.GetRequiredString("action");
                return IsAsyncAction(coreController, actionName, requestContext) ?
                    (IHttpHandler)new AsyncMvcHandler(coreController, factory, requestContext) :
                    (IHttpHandler)new SyncMvcHandler(controller, factory, requestContext);
            }
        }
    
        internal static bool IsAsyncAction(
            Controller controller, string actionName, RequestContext requestContext)
        {
            ...
        }
    }
    
    GetHttpHandlerメソッドでは、まずRouteDataのControllerフィールドからコントローラの名前を取得し、ControllerBuilderに登録されているFactoryによってIControllerインタフェースを実現するコントローラオブジェクトを作成します.Controllerクラスに含まれるActionInvokerを使用してActionの非同期需要の検出を支援する必要があるため、Controllerタイプに変換しようとします.変換に成功すると、RouteDataのアクションフィールドの値が取り出され、現在のアクションが非同期で実行されるべきかどうかをIsAsyncActionメソッドで確認します.もしそうであれば、IHttpAsyncHandlerを実装したAsyncMvcHandlerオブジェクトを返します.そうでなければ、IHttpHandlerを実装したSyncMvcHandlerオブジェクトを返します.
    AsyncMvcRouteHandlerの使用については、MapRouteでRoute Handlerを再設定するだけです.
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
        routes.MapRoute(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
        ).RouteHandler = new AsyncMvcRouteHandler();
    }
    

    非同期アクションであるかどうかを確認


    上記のコードから、非同期アクションを実行する場合は、コントローラオブジェクトがControllerタイプである必要があります.この約束の目的は、Controllerクラスに含まれるIActionInvokerを使用することである.正確には、Controller ActionInvokerタイプの機能である.したがって、ControllerのActionInvokerオブジェクトは、Controller ActionInvokerのインスタンスを返す必要があります.
    Controller ActionInvokerには、1つのControllerまたはActionの記述オブジェクトを返すことができるいくつかの補助方法があります.1つのAction記述オブジェクトから、このActionに関する様々な情報を取得することができ、AsyncActionAttributeがマークされているかどうかは、このActionが非同期で実行されるべきかどうかを判断する根拠である.次のようになります.
    private static object s_methodInvokerMutex = new object();
    private static MethodInvoker s_controllerDescriptorGetter;
    
    internal static bool IsAsyncAction(
        Controller controller, string actionName, RequestContext requestContext)
    {
        var actionInvoker = controller.ActionInvoker as ControllerActionInvoker;
        if (actionInvoker == null) return false;
    
        if (s_controllerDescriptorGetter == null)
        {
            lock (s_methodInvokerMutex)
            {
                if (s_controllerDescriptorGetter == null)
                {
                    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
                    MethodInfo method = typeof(ControllerActionInvoker).GetMethod(
                        "GetControllerDescriptor", bindingFlags);
                    s_controllerDescriptorGetter = new MethodInvoker(method);
                }
            }
        }
    
        var controllerContext = new ControllerContext(requestContext, controller);
        var controllerDescriptor = (ControllerDescriptor)s_controllerDescriptorGetter.Invoke(
            actionInvoker, controllerContext);
        var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
        return actionDescriptor == null ? false :
            actionDescriptor.GetCustomAttributes(typeof(AsyncActionAttribute), false).Any();
    }
    
    ControllerActionInvokerタイプには、ControllerContextタイプのパラメータを受け入れ、現在のコントローラを記述するControllerDescriptorオブジェクトを返し、FindActionメソッドでActionDescriptorオブジェクトを取得して実行するActionを記述するprotectedメソッドGetControllerDescriptorがあります.存在しないアクションの場合はfalseを返し、最後にSyncMvcHandlerオブジェクトによってデフォルトの動作を実行します.アクションにAsyncActionAttributeタグがある場合にのみ、非同期で実行され、trueが返されることを示します.さらに、このコードにはMethodInvokerが使用されています.これはFast Reflection Libraryに由来する補助クラスです.反射呼び出し機能を実現していますが、その性能はメソッドの直接呼び出しに非常に近いので、この記事ではこのプロジェクトの機能と使用について詳しく説明します.
    このコードはASPに関連する.NET MVC RCバージョンは、Betaバージョンに基づいて改良されています.従来のControllerActionInvokerクラスではActionメソッドを取得するMethodInfoのみであり、RCにおける各記述オブジェクトのような抽象タイプはない.現在の設計から,反射に基づく抽象的な記述タイプのサブクラスを用いている.たとえば、デフォルトでは、ActionDescriptor抽象タイプを使用してアクセスするのは、実際にはReflectedActionDescriptorタイプのインスタンスです.これは、オブジェクトを記述することによって抽象化されるため、非常に有用な改善です.
  • は、異なるインプリメンテーション方式を使用して各オブジェクトを記述し、デフォルトでは反射ベース(すなわち「約束」)のインプリメンテーションを使用し、必要に応じてプロファイルベースの方式を使用して既存のインプリメンテーションを置き換えることもできます.
  • 特定のオブジェクトの記述を使用して、内部の詳細にこだわらなくてもよい.例えば、非同期のActionは、2つの方法から構成される場合がある.
  • には特定の記述オブジェクトがあり、アクションが非同期で実行されるべきかどうか、Session Stateを無効にするべきかどうかなど、追加の属性を追加するのも便利です.
  • ……
  •  
    上一篇:ASP.NET MVC拡張非同期アクション機能(下)