Microsoft OWIN и конвейер обработки запросов ASP.NET. Часть четвёртая, OWIN и конвейер обработки запросов IIS/ASP.NET.

Данная статья последняя из серии "Конвейер обработки запросов IIS/ASP.NET и спецификация OWIN" и содержит иллюстрацию схемы конвейера IIS/ASP.NET в случае когда применяется спецификация OWIN. Также будет дано краткое описание того, что происходит внутри ковейера и как он работает. Для начала, ниже показана схема конвейера, подобная тому, которая была приведена здесь. Для тех кому будет интересно покапаться в коде ещё глубже, есть готовый проект приложения. Можно скачать и эксперементировать используя символы отладки .NET Framework или .NET Reflector.

Из рисунка (ссылка на pdf файл высокого качества) видно, что ключевую роль здесь играет обычный HTTP-модуль, который подключает конвейер OWIN.

 namespace Microsoft.Owin.Host.SystemWeb
{
    using AppFunc = Func<IDictionary<string, object>, Task>;
 
    internal sealed class OwinHttpModule : IHttpModule
    {
        private static IntegratedPipelineBlueprint _blueprint;
        private static bool _blueprintInitialized;
        private static object _blueprintLock = new object();
 
        public void Init(HttpApplication context)
        {
            IntegratedPipelineBlueprint blueprint =
                LazyInitializer.EnsureInitialized(
                    ref _blueprint,
                    ref _blueprintInitialized,
                    ref _blueprintLock,
                    InitializeBlueprint);
 
            if (blueprint != null)
            {
                var integratedPipelineContext =
                    new IntegratedPipelineContext(blueprint);
                integratedPipelineContext.Initialize(context);
            }
        }
 
        public void Dispose()
        {
        }
 
        private IntegratedPipelineBlueprint InitializeBlueprint()
        {
            IntegratedPipelineBlueprintStage firstStage = null;
 
            Action<IAppBuilder> startup = OwinBuilder.GetAppStartup();
            OwinAppContext appContext = OwinBuilder.Build(builder =>
            {
                EnableIntegratedPipeline(builder, stage => firstStage = stage);
                startup.Invoke(builder);
            });
 
            string basePath = 
                Utils.NormalizePath(HttpRuntime.AppDomainAppVirtualPath);
 
            return new IntegratedPipelineBlueprint(
                appContext, firstStage, basePath);
        }
 
        private static void EnableIntegratedPipeline(IAppBuilder app,
            Action<IntegratedPipelineBlueprintStage> onStageCreated)
        {
            var stage =
                new IntegratedPipelineBlueprintStage
                {Name = "PreHandlerExecute"};
            onStageCreated(stage);
            Action<IAppBuilder, string> stageMarker = (builder, name) =>
            {
                Func<AppFunc, AppFunc> decoupler = next =>
                {
                    if (string.Equals(name, stage.Name, 
                        StringComparison.OrdinalIgnoreCase))
                    {
                        // no decoupling needed when 
                        // pipeline is already split at this name
                        return next;
                    }
                    if (!IntegratedPipelineContext.VerifyStageOrder(
                        name, stage.Name))
                    {
                        // Stage markers added out of order will be ignored.
                        // Out of order stages/middleware 
                        // may be run earlier than expected.
                        // TODO: LOG
                        return next;
                    }
                    stage.EntryPoint = next;
                    stage = new IntegratedPipelineBlueprintStage
                    {
                        Name = name,
                        NextStage = stage,
                    };
                    onStageCreated(stage);
                    return (AppFunc)IntegratedPipelineContext.ExitPointInvoked;
                };
                app.Use(decoupler);
            };
            app.Properties[Constants.IntegratedPipelineStageMarker] 
                = stageMarker;
            app.Properties[Constants.BuilderDefaultApp] =
                (Func<IDictionary<string, object>, Task>)
                IntegratedPipelineContext.DefaultAppInvoked;
        }
    }
}

Как видно из вышеприведённого кода, модуль не подписывается на события приложения HttpApplication, а инициализирует конвейер на стадии загрузки и инициализации модулей.

 for (int i = 0; i < this._moduleCollection.Count; i++)
{
    this._currentModuleCollectionKey = this._moduleCollection.GetKey(i);
    IHttpModule module = this._moduleCollection.Get(i);
    ModuleConfigurationInfo info = _moduleConfigInfo[i];
    module.Init(this);
    this.ProcessEventSubscriptions(out notification, out notification2);
    if ((notification != 0) || (notification2 != 0))
    {
        this.RegisterIntegratedEvent(appContext, info.Name, notification,
         notification2, info.Type, info.Precondition, false);
    }
}

А поскольку экземпляры HttpApplication создаются фабрикой HttpApplicationFactory и переиспользуются, то и повторное подключение конвейера OWIN не нужно, да и к тому же экземпляр основного типа IntegratedPipelineBlueprint (который и есть, грубо говоря, конвейер OWIN) один единственный на домен приложения (используется статическая ссылка и отложенная инициализация с проверкой на наличие экземпляра последней). Метод InitializeBlueprint

 private IntegratedPipelineBlueprint InitializeBlueprint()
{
    IntegratedPipelineBlueprintStage firstStage = null;
    Action<IAppBuilder> startup = OwinBuilder.GetAppStartup();
    OwinAppContext appContext = OwinBuilder.Build(delegate (IAppBuilder builder) {
        EnableIntegratedPipeline(builder, stage => firstStage = stage);
        startup(builder);
    });
    return new IntegratedPipelineBlueprint(appContext, firstStage,
     Utils.NormalizePath(HttpRuntime.AppDomainAppVirtualPath));
}

создаёт экземпляр построителя приложения на основе метода конфигурации.

 [assembly: Microsoft.Owin.OwinStartup(typeof(Startup))]
 
namespace SignalROwinIISApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            appBuilder.MapSignalR();
 
            var httpConfiguration = new HttpConfiguration();
 
            httpConfiguration.Formatters.Clear();
            httpConfiguration.Formatters.Add(new JsonMediaTypeFormatter());
 
            httpConfiguration.Formatters.JsonFormatter.SerializerSettings = 
                new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };
 
            httpConfiguration.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional });
 
            appBuilder.UseWebApi(httpConfiguration);
        }
    }
}

Тем самым подключаются два Модуля OWIN: SignalR и WebAPI. Именно они будут обрабатывать запросы поступающие к этим двум фреймворкам.

Ну а остальные запросы, например к Web Forms или MVC, будут идти как обычно, в обход спецификации OWIN, так как они эту спецификацию не реализуют. В данной статье я попытался показать насколько усложняется конвейер IIS/ASP.NET при использовании спецификации OWIN. А он как уже известно, и так устроен очень непросто.