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. А он как уже известно, и так устроен очень непросто.
Comments
- Anonymous
January 03, 2015
Отличная серия статей!!!, так держать!!! Правда всё же для большей наглядности, необходимо немного подправить как скриншот основного рисунка, так и сам рисунок. Предлагаю: номер 2 расположить на стрелке HTTP.SYS-World Wide Web Publishing Service (W3SVC) (2. ...Если нужного приложения нет, запрос передаётся W3SVC.), номер 3 расположить на стрелке, W3SVC-WAS (W3SVC передаёт запрос WAS.), номер 5 расположить на стрелке WAS-Пулы приложений (WAS проверяет рабочие процессы пулов приложений. Если нужного нет, то он создаётся).