AppBuilder(一)【Use彙總】
原始碼參見Microsoft.Owin.Host.SystemWeb.OwinBuilder
Microsoft.Owin.Builder.AppBuilder
前文講到
internal static OwinAppContext Build()
{
Action<IAppBuilder> startup = GetAppStartup();
return Build(startup);
}
GetAppStartup()
已經尋找到Startup
類,並封裝了其中的Configuration
方法,接下來就會呼叫Build(startup)
方法。
internal static OwinAppContext Build(Action<IAppBuilder> startup)
{
if (startup == null)
{
throw new ArgumentNullException("startup");
}
var appContext = new OwinAppContext(); //例項化OwinAppContext,主要獲得網站名稱和程式名稱
appContext.Initialize(startup); //進一步初始化OwinAppContext
return appContext;
}
來看看OwinAppContext.Initialize(IAppBuilder)
做了什麼
internal void Initialize(Action<IAppBuilder> startup)
{
Capabilities = new ConcurrentDictionary<string, object>(); //初始化為多執行緒安全的Dictionary,說明為多執行緒共享資源
var builder = new AppBuilder(); //初始化AppBuilder,向Properties中填入一些基本資訊,這構成了Server環境的快照
builder.Properties[Constants.OwinVersionKey] = Constants.OwinVersion;
builder.Properties[Constants.HostTraceOutputKey] = TraceTextWriter.Instance;
builder.Properties[Constants.HostAppNameKey] = AppName;
builder.Properties[Constants.HostOnAppDisposingKey] = OwinApplication.ShutdownToken;
builder.Properties[Constants.HostReferencedAssemblies] = new ReferencedAssembliesWrapper();
builder.Properties[Constants.ServerCapabilitiesKey] = Capabilities;
builder.Properties[Constants.SecurityDataProtectionProvider] = new MachineKeyDataProtectionProvider().ToOwinFunction();
builder.SetLoggerFactory(new DiagnosticsLoggerFactory());
Capabilities[Constants.SendFileVersionKey] = Constants.SendFileVersion;
CompilationSection compilationSection = (CompilationSection)System.Configuration.ConfigurationManager.GetSection(@"system.web/compilation");
bool isDebugEnabled = compilationSection.Debug;
if (isDebugEnabled)
{
builder.Properties[Constants.HostAppModeKey] = Constants.AppModeDevelopment;
}
DetectWebSocketSupportStageOne();
try
{
startup(builder); //真正的由使用者定義的middleware注入開始了,這將完成pipeline的構造
}
catch (Exception ex)
{
_trace.WriteError(Resources.Trace_EntryPointException, ex);
throw;
}
AppFunc = (AppFunc)builder.Build(typeof(AppFunc));//記錄pipeline的第一個middleware,這將是整個pipeline的入口
}
先從AppBuilder
的例項化開始
public AppBuilder()
{
_properties = new Dictionary<string, object>(); //初始化environment資源
_conversions = new Dictionary<Tuple<Type, Type>, Delegate>(); //初始化轉換器,這將不是本文重點,後期再涉及
_middleware = new List<Tuple<Type, Delegate, object[]>>(); //初始化middleware列表
_properties[Constants.BuilderAddConversion] = new Action<Delegate>(AddSignatureConversion); //記錄Conversion
_properties[Constants.BuilderDefaultApp] = NotFound;
SignatureConversions.AddConversions(this);
}
上面的原始碼展示了AppBuilder
初始化pipeline
流動所需的envrionment
和middleware
,這也是OWIN
最重要的兩個資料結構。
builder
完成例項化之後會嘗試startup(builder)
這即是呼叫使用者定義的Configuration(IAppBuilder)
方法。
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<BlogContext>(BlogContext.Create); //將資料庫上下文注入envrionment中
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); //將AppUserManager注入environment中
app.CreatePerOwinContext<AppSigninManager>(AppSigninManager.Create); //將AppSigninManager注入environment中
app.UseCookieAuthentication(new CookieAuthenticationOptions //標記CookeAuthentication這個middleware在pipeline中的stage,並配置引數
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<AppUserManager, AppUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); //標記stage
}
}
以新建MVC
工程預設生成的Starup
類為例。目前我們可以大致猜測一下這份配置方法中只進行了兩個stage
的註冊,而pipeline
有11個stage
之多,具體如下
private static readonly IList<string> StageNames = new[]
{
Constants.StageAuthenticate,
Constants.StagePostAuthenticate,
Constants.StageAuthorize,
Constants.StagePostAuthorize,
Constants.StageResolveCache,
Constants.StagePostResolveCache,
Constants.StageMapHandler,
Constants.StagePostMapHandler,
Constants.StageAcquireState,
Constants.StagePostAcquireState,
Constants.StagePreHandlerExecute,
};
接下來我們通過對app.UseCookieAuthentication(new CookieAuthenticationOptions)
的追蹤來了解如何進行middleware
的註冊。
public static class CookieAuthenticationExtensions
{
public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options)
{
return app.UseCookieAuthentication(options, PipelineStage.Authenticate); //在PipelineStage.Authenticate階段執行
}
public static IAppBuilder UseCookieAuthentication(this IAppBuilder app, CookieAuthenticationOptions options, PipelineStage stage)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
app.Use(typeof(CookieAuthenticationMiddleware), app, options); //app.Use實際上就是將middleware壓入middleware的List中
app.UseStageMarker(stage); //標記middleware所在stage
return app;
}
}
這裡涉及到AppBuilder
的兩個核心操作app.Use
和app.UseStageMarker
。
先來看看app.Use
,在AppBuilder
中定義了Use(object middleware, params object[] args)
,而在AppBuilderUseExtensions
還有兩種定義,我們逐個來解釋。
public IAppBuilder Use(object middleware, params object[] args)
{
_middleware.Add(ToMiddlewareFactory(middleware, args));
return this;
}
可見這個方法主要做的操作是將middleware
進行轉換並壓入middleware
的List中,轉換過程實際上對middleware
進行引數檢查和簽名,這將是一個非常複雜的過程,一步一步地來。
先看看ToMiddlewareFactory
方法。
private static Tuple<Type, Delegate, object[]> ToMiddlewareFactory(object middlewareObject, object[] args) //接受兩個引數,返回一個三元組
{
if (middlewareObject == null)
{
throw new ArgumentNullException("middlewareObject");
}
var middlewareDelegate = middlewareObject as Delegate; //嘗試將middlewareObject型別轉換為Delegate,而我們上面傳入的是一個Type,將會轉換失敗
if (middlewareDelegate != null) //如果轉換成功
{
return Tuple.Create(GetParameterType(middlewareDelegate), middlewareDelegate, args); //直接返回三元組,這似乎是最理想的情況
}
Tuple<Type, Delegate, object[]> factory = ToInstanceMiddlewareFactory(middlewareObject, args); //嘗試在middlewareObject類中尋找Initialize方法,並檢查args參
//數是否符合Initialize方法的引數
if (factory != null)
{
return factory;
}
factory = ToGeneratorMiddlewareFactory(middlewareObject, args); //嘗試在middlewareObject類中尋找Invoke方法,並作引數檢查
if (factory != null)
{
return factory;
}
if (middlewareObject is Type) //這是本文的app.Use方法傳入的引數型別,所以會進入這一流程中
{
return ToConstructorMiddlewareFactory(middlewareObject, args, ref middlewareDelegate);
}
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_MiddlewareNotSupported, middlewareObject.GetType().FullName));
}
接下來將分別對ToInstanceMiddlewareFactory
,ToGeneratorMiddlewareFactory
,ToConstructorMiddlewareFactory
進行介紹。
private static Tuple<Type, Delegate, object[]> ToInstanceMiddlewareFactory(object middlewareObject, object[] args)
{
MethodInfo[] methods = middlewareObject.GetType().GetMethods(); //將middleware當作一個例項處理,獲取其Type,再獲得類中所有方法
foreach (var method in methods) //遍歷方法
{
if (method.Name != Constants.Initialize) //尋找Initialize方法
{
continue;
}
ParameterInfo[] parameters = method.GetParameters(); //獲取Initialize的參數列
Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); //獲取參數列的Type型別
if (parameterTypes.Length != args.Length + 1) //Initialize的引數應該比args的引數多一個,因為第一個引數應該是下一個middleware
{
continue;
}
if (!parameterTypes
.Skip(1)
.Zip(args, TestArgForParameter)
.All(x => x))
{ //對Initialize第一個以後的引數與args進行一對一驗證,args應該為對應項的例項
continue;
}
// DynamicInvoke can't handle a middleware with multiple args, just push the args in via closure.
//開發者在此處註釋動態Invoke無法處理middleware有多個引數的情況,所以進行了一次封裝,簡化參數列
Func<object, object> func = app =>
{
object[] invokeParameters = new[] { app }.Concat(args).ToArray(); //將app(實際上為envrionment)引數與args合併
method.Invoke(middlewareObject, invokeParameters); //真正呼叫middleware的Initialize方法
return middlewareObject; //返回middlewareObject例項
};
return Tuple.Create<Type, Delegate, object[]>(parameters[0].ParameterType, func, new object[0]);
}
return null;
}
ToInstanceMiddlewareFactory
返回的是一個具體的例項,似乎不滿足Func<IDictionary<string, object>, Task>
型別,這也是目前沒弄明白的地方
private static Tuple<Type, Delegate, object[]> ToGeneratorMiddlewareFactory(object middlewareObject, object[] args)
{
MethodInfo[] methods = middlewareObject.GetType().GetMethods(); //將middlewareObject當作一個例項,獲取Type並獲取所有方法
foreach (var method in methods)
{
if (method.Name != Constants.Invoke) //尋找Invoke方法
{
continue;
}
ParameterInfo[] parameters = method.GetParameters(); //獲取Invoke方法的參數列
Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); //獲取引數的Type表
if (parameterTypes.Length != args.Length + 1)
{
continue;
}
if (!parameterTypes
.Skip(1)
.Zip(args, TestArgForParameter)
.All(x => x))
{ //將Type表與args進行一對一驗證
continue;
}
IEnumerable<Type> genericFuncTypes = parameterTypes.Concat(new[] { method.ReturnType }); //將引數Type表與返回Type合併
Type funcType = Expression.GetFuncType(genericFuncTypes.ToArray()); //獲取方法簽名Type
Delegate middlewareDelegate = Delegate.CreateDelegate(funcType, middlewareObject, method); //對例項的Invoke方法進行delegate封裝
return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
}
return null;
}
ToGeneratorMiddlewareFactory
返回型別由Invoke
方法決定,通常會是一個Task
。
private static Tuple<Type, Delegate, object[]> ToConstructorMiddlewareFactory(object middlewareObject, object[] args, ref Delegate middlewareDelegate)
{
var middlewareType = middlewareObject as Type; //嘗試將middlwareObject轉換為Type
ConstructorInfo[] constructors = middlewareType.GetConstructors(); //獲取類的構造方法
foreach (var constructor in constructors) //遍歷構造方法
{
ParameterInfo[] parameters = constructor.GetParameters(); //獲取建構函式的參數列
Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); //獲取引數的Type表
if (parameterTypes.Length != args.Length + 1) //參數列應該比args多一項,第一項應該為下一個middleware
{
continue;
}
if (!parameterTypes
.Skip(1)
.Zip(args, TestArgForParameter)
.All(x => x))
{ //對參數列的第一項之後的與args一對一校驗
continue;
}
ParameterExpression[] parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray(); //提取參數列的Type和Name
NewExpression callConstructor = Expression.New(constructor, parameterExpressions); //建立建構函式
middlewareDelegate = Expression.Lambda(callConstructor, parameterExpressions).Compile(); //對建構函式進行Lambda封裝
return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
}
throw new MissingMethodException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_NoConstructorFound, middlewareType.FullName, args.Length + 1));
}
上面三種方法讓我們看見了微軟工程師的花樣使用委託、Lambda
、Func
,如果不是資深C#程式設計師,誰知道這語言還有這些特性,但能感覺到主要是想將原來類中的初始化方法進行歸一化,封裝在一個匿名delegate
中,併合成一個三元組,後面採用統一的方法進行呼叫,這個三元組的Item1
就是共用的下一個middleware
也是當前middleware
所在pipelineStage
的簽名。
再來看看AppBuilderUseExtension
擴充套件的兩個Use
方法和一個Run
方法。
public static IAppBuilder Use<T>(this IAppBuilder app, params object[] args)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
return app.Use(typeof(T), args);
}
這個Use
方法將middleware
的Type
放在Use<T>
中,做為一個模板方法,實際上是對Use(typeof(T), args)
的一個封裝。
public static IAppBuilder Use(this IAppBuilder app, Func<IOwinContext, Func<Task> /*next*/, Task> handler)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (handler == null)
{
throw new ArgumentNullException("handler");
}
return app.Use<UseHandlerMiddleware>(handler);
}
這個Use
方法接受一個Func<IOwinContext, Func<Task> , Task>
的委託,並對Use<UseHandlerMiddleware>
進行封裝,為了後文引述方便我們將其重新命名為UseExtensionOne
。
public static void Run(this IAppBuilder app, Func<IOwinContext, Task> handler)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (handler == null)
{
throw new ArgumentNullException("handler");
}
app.Use<UseHandlerMiddleware>(handler);
}
這個Run
方法接受一個 Func<IOwinContext, Task>
的委託,並對Use<UseHandlerMiddleware>
進行封裝,將其重新命名為RunExtensionTwo
方法。
現在檢視微軟對於Middleware
的使用舉例頁面 http://www.asp.net/aspnet/overview/owin-and-katana/owin-middleware-in-the-iis-integrated-pipeline
網頁中黃色部分對middleware
的注入有具體舉例,如下
//這是對UseExtensionOne的實際應用
app.Use((context, next) =>
{
PrintCurrentIntegratedPipelineStage(context, "Middleware 1");
return next.Invoke();
});
//這是對UseExtensionOne的實際應用
app.Use((context, next) =>
{
PrintCurrentIntegratedPipelineStage(context, "2nd MW");
return next.Invoke();
});
//這是對RunExtentionTwo的實際應用
app.Run(context =>
{
PrintCurrentIntegratedPipelineStage(context, "3rd MW");
return context.Response.WriteAsync("Hello world");
});
三個很簡單的middleware
被注入到pipeline
中,但因為實際上是對Use<UseHandlerMiddleware>
的封裝,也就是對Use(typeof(T), args)
的封裝,所以實際上與 app.Use(typeof(CookieAuthenticationMiddleware), app, options)
差不多,最終都會存成一樣的三元組壓進middleware
的List
中,所以他們都會呼叫UseHandlerMiddleware
的建構函式,而檢視UseHandlerMiddleware
發現其建構函式的第一項就是一個AppFunc
,這與middlewareStage
的切換和pipeline
的流動息息相關,後面將詳細介紹其原理。
總結,本文主要講了AppBuilder
的Use
方法的具體流程與擴充套件,三種擴充套件方法實際上都是對基礎Use
的封裝,而基礎Use
方法總的來說可以接受四種middlewareObject
序號 | 說明 |
---|---|
1 | Delegate是最簡單的,直接可以封裝成三元組壓入List |
2 | 有Initialize方法的類的例項,參數列第一項為一個AppFunc或者OwinMiddleware,只要其Invoke之後能返回一個Task就行,為了避免DynamicInvoke的弊端進行了一次封裝, |
3 | 有Invoke方法的類的例項,參數列也需要匯聚到一個object[]中,這兩種設計應該是有不同需求背景的,目前不知道究竟有什麼不同 |
4 | Type,這需要對這個類的構造方法進行封裝,參考UseHandlerMiddleware的建構函式,第一個引數應該是一個AppFunc |
需要理解這四種Use
方法的不同,還需要了解pipeline
重構是如何做的,這就是下一節AppBuilder.Build
中的具體內容了,裡面有很多細節的東西,這將直接構建整個pipeline
,也是整個OWIN
最核心的地方。
相關文章
- AppBuilder(二)【UseStageMarker】APPUI
- AppBuilder(三)【BuildInternal】APPUI
- AppBuilder(四)【SignatureConversions】APPUI
- Vagrant box 命令彙總彙總
- Oracle當機案例彙總(一)Oracle
- 畢設題目彙總(一)
- 一些小問題彙總
- Markdown 編寫技巧彙總(一)
- MongoDB常用命令彙總(一)MongoDB
- jquery彙總jQuery
- Elasticsearch 一些命令彙總 以及學習總結Elasticsearch
- 一、Windows 環境搭建問題彙總Windows
- WKWebView的一些問題彙總WebView
- Spark 經典面試題彙總《一》Spark面試題
- ARM彙編指令集彙總
- Git命令彙總Git
- 引數彙總
- VUE元件彙總Vue元件
- css 技巧彙總CSS
- 資料彙總
- artisan命令彙總
- vagrant命令彙總
- OP code彙總
- bookStore疑惑彙總
- IE功能彙總
- Python類彙總Python
- 彙總資料
- go 命令彙總Go
- gstreamer命令彙總
- git 命令彙總Git
- ClickHouse 命令彙總
- keycloak文章彙總
- Android的細節知識彙總系列(一)Android
- 程式媛面試之高頻題型彙總(一)面試
- 一文彙總全球熱門新聞APIAPI
- Oracle資料庫啟動問題彙總(一)Oracle資料庫
- 前端相關彙總前端
- JavaScript字串API彙總JavaScript字串API