前面分享了.net core Program類的啟動過程已經源代碼介紹,這裡將繼續講Startup類中的兩個約定方法,一個是ConfigureServices,這個方法是用來寫我們應用程式所依賴的組件。另一個Configure,它是我們MVC請求的中間件方法,也就是我們每個請求來要執行的過程都可以寫 ...
前面分享了.net core Program類的啟動過程已經源代碼介紹,這裡將繼續講Startup類中的兩個約定方法,一個是ConfigureServices,這個方法是用來寫我們應用程式所依賴的組件。另一個Configure,它是我們MVC請求的中間件方法,也就是我們每個請求來要執行的過程都可以寫在這個方法裡面。
為什麼說Startup類中的兩個方法是基於約定的呢?其實是這樣的,在.net core Program類Main方法中有個調用了Run方法這個方法從IServiceCollection容器中拿到一個IStartup類型的實例然後調用了IStartup中定義的兩個方法法,如果我們的Startup類是實現了這個介面的類 那麼就不是基於約定了,直接就可以使用,但是我們發現在vs給我們生成的Startup類並沒有實現任何介面,所以就不會是IStartup類型,那麼內部是如何去做的呢? 其實是這樣的,在註冊Startup實例的時候還有個類型叫做ConventionBasedStartup從名稱上解讀這個類就是轉換為基礎的Startup,其實卻是也是這樣的,這個類中是實現了IStartup介面,它的兩個方法中分別調用了各自的對用委托,這些委托實際執行的就是我們Startup類中定義的兩個方法,請看源代碼:
public class ConventionBasedStartup : IStartup { private readonly StartupMethods _methods; public ConventionBasedStartup(StartupMethods methods) { _methods = methods; } public void Configure(IApplicationBuilder app) { try { _methods.ConfigureDelegate(app); } catch (Exception ex) { if (ex is TargetInvocationException) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } throw; } } public IServiceProvider ConfigureServices(IServiceCollection services) { try { return _methods.ConfigureServicesDelegate(services); } catch (Exception ex) { if (ex is TargetInvocationException) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } throw; } } }
現在Startup類的方法說清楚了,我們具體來說說方法中的內容,首先說ConfigureServices(IServiceCollection services),這個方法的參數是約定好的,不能隨意改變,裡面的IServiceCollection介面其實就是我們依賴註入的容器,說的再直白一點就是我們整個MVC所需要的實例都由IServiceCollection所管理,IServiceCollection有幾個重要的擴展方法,他們都是定義在ServiceCollectionServiceExtensions靜態類中,AddTransient方法,表示用這個方法添加到IServiceCollection容器的實例在需要註入的實例中都是一個全新的實例,AddScoped方法,這個方法表示在一次請求的生命周期內共用一個實例,AddSingleton方法,這個方法表示整個程式共用一個實例,例如日誌服務,IConfiguration服務等都屬於典型Singleton。請看例子:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<TransientService>(); services.AddTransient<ServiceDemo1>(); services.AddTransient<ServiceDemo2>(); } public void Configure(IApplicationBuilder app, ServiceDemo1 demo1, ServiceDemo2 demo2 ) { demo1.Test(); demo2.Test(); app.Run(async (HttpContext context) => { await context.Response.WriteAsync("test successd"); }); } } public class TransientService { private int _updateCount; public int GetUpdateCount() {
this._updateCount = this._updateCount + 1; return this._updateCount; } } public class ServiceDemo1 { private readonly TransientService _service; public ServiceDemo1(TransientService service) { _service = service; } public void Test() { Console.WriteLine($"我是demo1的計數:{this._service.GetUpdateCount()}"); } } public class ServiceDemo2 { private readonly TransientService _service; public ServiceDemo2(TransientService service) { _service = service; } public void Test() { Console.WriteLine($"我是demo2的計數:{this._service.GetUpdateCount()}"); } }
上面的例子中會產生一下結果,可以看得出來這兩個註入的TransientService都是全新的實例
如果我們稍微改變一下註入的方法,將原本的 services.AddTransient<TransientService>();改成services.AddScoped<TransientService>();就會產生如下結果:
這個能說明什麼呢,我們有兩次註入 這個就表示TransientService保持了之前demo1的狀態 demo1和demo2是可以共用這個實例來傳輸數據的,AddSingleton方法理解起來比較簡單就不過多絮叨了,上面已經說明。
接下來再來說說Startup類中的Configure方法,Configure方法中的參數是可以變化的,也就是說你可以用依賴註入的方法在參數中註入你想要註入的實例,前面說了 這個方法是我們請求的中間件方法,這個方法中會整合我們註入的IApplicationBuilder 中調用的各種Use方法中定義的中間件 並不是說這裡面定義的代碼每次請求都會被執行,這個概念一定要搞清楚,Configure方法只會在啟動的時候執行一次,後面就不會再執行了,Configure方法只是讓我們可以定義整個MVC的處理請求的執行順序,具體的可以看看官方的文檔https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.2
其實中間件都是由IApplicationBuilder 所管理的ApplicationBuilder類實現了IApplicationBuilder 介面中的方法,看到ApplicationBuilder中的源代碼中有個屬性_components 它是一個IList<Func<RequestDelegate, RequestDelegate>>類型的委托容器,容器中的委托就是請求過來需要執行的中間件委托,當你在Configure方法中調用app.UseXXX的時候就會被註冊到這個容器中去,然後請求過來就按照順序執行容器中的每一個委托,所以這裡就解釋了前面說的Configure方法只會被執行一次的說法。下麵也貼一下ApplicationBuilder類的源代碼:
public class ApplicationBuilder : IApplicationBuilder { private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); public IServiceProvider ApplicationServices { get { return GetProperty<IServiceProvider>(Constants.BuilderProperties.ApplicationServices); } set { SetProperty(Constants.BuilderProperties.ApplicationServices, value); } } public IFeatureCollection ServerFeatures => GetProperty<IFeatureCollection>(Constants.BuilderProperties.ServerFeatures); public IDictionary<string, object> Properties { get; } public ApplicationBuilder(IServiceProvider serviceProvider) { Properties = new Dictionary<string, object>(StringComparer.Ordinal); ApplicationServices = serviceProvider; } public ApplicationBuilder(IServiceProvider serviceProvider, object server) : this(serviceProvider) { SetProperty(Constants.BuilderProperties.ServerFeatures, server); } private ApplicationBuilder(ApplicationBuilder builder) { Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal); } private T GetProperty<T>(string key) { if (!Properties.TryGetValue(key, out object value)) { return default(T); } return (T)value; } private void SetProperty<T>(string key, T value) { Properties[key] = value; } public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _components.Add(middleware); return this; } public IApplicationBuilder New() { return new ApplicationBuilder(this); } public RequestDelegate Build() { RequestDelegate requestDelegate = delegate (HttpContext context) { context.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (Func<RequestDelegate, RequestDelegate> item in _components.Reverse()) { requestDelegate = item(requestDelegate); } return requestDelegate; } }
好啦,這篇關於Startup類就算介紹完成了,下篇開始正式介紹MVC