Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Данная статья последняя из серии "Конвейер обработки запросов 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 проверяет рабочие процессы пулов приложений. Если нужного нет, то он создаётся).