瞭解到了OrchardCore主要由兩個中間件(ModularTenantContainerMiddleware和ModularTenantRouterMiddleware)構成,下面開始瞭解ModularTenantContainerMiddleware中間件第一行代碼。shell
瞭解asp.net core機制的都知道中間件是由構造函數和Invoke(或者InokeAsync)方法構成,構造函數不過就是個依賴注入的過程,Invoke也能夠直接依賴注入,二者的區別是構造函數時全局的,Invoke只是當次請求,因此從Inovke開始瞭解!c#
public async Task Invoke(HttpContext httpContext) { // Ensure all ShellContext are loaded and available. await _shellHost.InitializeAsync(); var shellSettings = _runningShellTable.Match(httpContext); // We only serve the next request if the tenant has been resolved. if (shellSettings != null) { if (shellSettings.State == TenantState.Initializing) { httpContext.Response.Headers.Add(HeaderNames.RetryAfter, "10"); httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; await httpContext.Response.WriteAsync("The requested tenant is currently initializing."); return; } // Makes 'RequestServices' aware of the current 'ShellScope'. httpContext.UseShellScopeServices(); var shellScope = await _shellHost.GetScopeAsync(shellSettings); // Holds the 'ShellContext' for the full request. httpContext.Features.Set(new ShellContextFeature { ShellContext = shellScope.ShellContext, OriginalPath = httpContext.Request.Path, OriginalPathBase = httpContext.Request.PathBase }); await shellScope.UsingAsync(scope => _next.Invoke(httpContext)); } }
如今從第一行代碼_shellHost.InitializeAsync()開始看起。_shellHost是一個IShellHost單例,是類ShellHost的實例化,這個很明顯就是asp.net core自帶的依賴注入生成的(這是在註冊服務的時候已經配置好的,具體追蹤services.AddOrchardCms就很清晰了)。如今直接找ShellHost的InitializeAsync方法和PreCreateAndRegisterShellsAsync方法。安全
public async Task InitializeAsync() { if (!_initialized) { // Prevent concurrent requests from creating all shells multiple times await _initializingSemaphore.WaitAsync(); try { if (!_initialized) { await PreCreateAndRegisterShellsAsync(); } } finally { _initialized = true; _initializingSemaphore.Release(); } } } private async Task PreCreateAndRegisterShellsAsync() { if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Start creation of shells"); } // Load all extensions and features so that the controllers are registered in // 'ITypeFeatureProvider' and their areas defined in the application conventions. var features = _extensionManager.LoadFeaturesAsync(); // Is there any tenant right now? var allSettings = (await _shellSettingsManager.LoadSettingsAsync()).Where(CanCreateShell).ToArray(); var defaultSettings = allSettings.FirstOrDefault(s => s.Name == ShellHelper.DefaultShellName); var otherSettings = allSettings.Except(new[] { defaultSettings }).ToArray(); await features; // The 'Default' tenant is not running, run the Setup. if (defaultSettings?.State != TenantState.Running) { var setupContext = await CreateSetupContextAsync(defaultSettings); AddAndRegisterShell(setupContext); allSettings = otherSettings; } if (allSettings.Length > 0) { // Pre-create and register all tenant shells. foreach (var settings in allSettings) { AddAndRegisterShell(new ShellContext.PlaceHolder { Settings = settings }); }; } if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Done pre-creating and registering shells"); } }
InitializeAsync從字面上看就是初始的過程ShellHost(這個不知道怎麼翻譯比較準確)的過程,從代碼上看也是如此。多線程
首先經過_initialized判斷是否初始化過(開始沒賦值確定false),在初始化結束_initialized就會設置爲true,往下幾行的finally裏面能夠看到_initialized = true,由於ShellHost是單例,也就是啓動程序以後就會一直維持_initialized = true,簡單說就是啓動時會初始化一次ShellHost。併發
再往下看,await _initializingSemaphore.WaitAsync,這個上面也有註釋防止併發請求屢次建立全部shells,這個開始我也不懂的時候直接理解爲lock後面finally的_initializingSemaphore.Release就是lock結束,後面找了點資料試了下,其實就是隻有一個請求能夠進入初始化,構造函數只容許一個請求(SemaphoreSlim _initializingSemaphore = new SemaphoreSlim(1)),之因此說請求時由於這是中間件裏面啊,每一個請求都要經過中間件(這個得了解asp.net core中間件的原理),看了OrchardCore真給我帶來很多知識點,沒遇到這個我只知道多線程用lock鎖住,原來還能夠有這樣的細節。app
而後try裏又確認一次有沒初始化(判斷_initialized),爲啥呢?我一臉懵逼,後面感受好像能夠接受,比較前面時有不少請求,說不定有個某個請求已經初始化ShellHost了呢,因此_initializingSemaphore.WaitAsync開始後又要確認一次。若是前面不先確認,那麼又所有隻能一個請求進來,這就很不合理了。我快被繞暈了,真佩服開發者的大腦。asp.net
終於來到PreCreateAndRegisterShellsAsync方法了。_loggoer部分直接跳過,就是asp.net core的日誌記錄而已。OrchardCore是一個模塊化多租戶的cms,模塊化就體如今這裏。async
var features = _extensionManager.LoadFeaturesAsync();
加載全部的功能,ExtensionManager(OrchardCore\OrchardCore\Extensions\ExtensionManager.cs)的LoadFeaturesAsync方法。ide
public async Task<IEnumerable<FeatureEntry>> LoadFeaturesAsync() { await EnsureInitializedAsync(); return _features.Values; }
確保初始化並返回FeatureEntry集合,也就是全部功能的集合。如今進入EnsureInitializedAsync方法看看:模塊化
private async Task EnsureInitializedAsync() { if (_isInitialized) { return; } await _semaphore.WaitAsync(); try { if (_isInitialized) { return; } var modules = _applicationContext.Application.Modules; var loadedExtensions = new ConcurrentDictionary<string, ExtensionEntry>(); // Load all extensions in parallel await modules.ForEachAsync((module) => { if (!module.ModuleInfo.Exists) { return Task.CompletedTask; } var manifestInfo = new ManifestInfo(module.ModuleInfo); var extensionInfo = new ExtensionInfo(module.SubPath, manifestInfo, (mi, ei) => { return _featuresProvider.GetFeatures(ei, mi); }); var entry = new ExtensionEntry { ExtensionInfo = extensionInfo, Assembly = module.Assembly, ExportedTypes = module.Assembly.ExportedTypes }; loadedExtensions.TryAdd(module.Name, entry); return Task.CompletedTask; }); var loadedFeatures = new Dictionary<string, FeatureEntry>(); // Get all valid types from any extension var allTypesByExtension = loadedExtensions.SelectMany(extension => extension.Value.ExportedTypes.Where(IsComponentType) .Select(type => new { ExtensionEntry = extension.Value, Type = type })).ToArray(); var typesByFeature = allTypesByExtension .GroupBy(typeByExtension => GetSourceFeatureNameForType( typeByExtension.Type, typeByExtension.ExtensionEntry.ExtensionInfo.Id)) .ToDictionary( group => group.Key, group => group.Select(typesByExtension => typesByExtension.Type).ToArray()); foreach (var loadedExtension in loadedExtensions) { var extension = loadedExtension.Value; foreach (var feature in extension.ExtensionInfo.Features) { // Features can have no types if (typesByFeature.TryGetValue(feature.Id, out var featureTypes)) { foreach (var type in featureTypes) { _typeFeatureProvider.TryAdd(type, feature); } } else { featureTypes = Array.Empty<Type>(); } loadedFeatures.Add(feature.Id, new CompiledFeatureEntry(feature, featureTypes)); } }; // Feature infos and entries are ordered by priority and dependencies. _featureInfos = Order(loadedFeatures.Values.Select(f => f.FeatureInfo)); _features = _featureInfos.ToDictionary(f => f.Id, f => loadedFeatures[f.Id]); // Extensions are also ordered according to the weight of their first features. _extensionsInfos = _featureInfos.Where(f => f.Id == f.Extension.Features.First().Id) .Select(f => f.Extension); _extensions = _extensionsInfos.ToDictionary(e => e.Id, e => loadedExtensions[e.Id]); _isInitialized = true; } finally { _semaphore.Release(); } }
前面判斷初始化,多請求方面跟以前的是同樣的,這裏我又不理解了,以前不是判斷過了嗎,難道是由於這是個Task的緣由致使可能跳出到其它線程!這裏先跳過!
var modules = _applicationContext.Application.Modules;這裏是經過反射獲得全部的模塊,至於過程真是一言難盡啊,這裏卡了我好幾個星期才明白到底是個怎樣的過程。
看下OrchardCore\OrchardCore.Abstractions\Modules\ModularApplicationContext.cs這個類
public interface IApplicationContext { Application Application { get; } } public class ModularApplicationContext : IApplicationContext { private readonly IHostEnvironment _environment; private readonly IEnumerable<IModuleNamesProvider> _moduleNamesProviders; private Application _application; private static readonly object _initLock = new object(); public ModularApplicationContext(IHostEnvironment environment, IEnumerable<IModuleNamesProvider> moduleNamesProviders) { _environment = environment; _moduleNamesProviders = moduleNamesProviders; } public Application Application { get { EnsureInitialized(); return _application; } } private void EnsureInitialized() { if (_application == null) { lock (_initLock) { if (_application == null) { _application = new Application(_environment, GetModules()); } } } } private IEnumerable<Module> GetModules() { var modules = new ConcurrentBag<Module>(); modules.Add(new Module(_environment.ApplicationName, true)); var names = _moduleNamesProviders .SelectMany(p => p.GetModuleNames()) .Where(n => n != _environment.ApplicationName) .Distinct(); Parallel.ForEach(names, new ParallelOptions { MaxDegreeOfParallelism = 8 }, (name) => { modules.Add(new Module(name, false)); }); return modules; } }
明顯就是初始化Application,而後返回Application,首先看EnsureInitialized方法,而後就直接看GetModules方法。
第一行var modules = new ConcurrentBag<Module>();又觸碰到個人知識盲點了,做爲一個只會用List<T>的人,我真的徹底不知道ConcurrentBag<T>是什麼,立刻msdn下,原來ConcurrentBag<T>也是個集合,可是它是線程安全的。ok,集合開始添加數據,首先把當前項目的名稱OrchardCore.Cms.Web給加進去(modules.Add(new Module(_environment.ApplicationName, true));)。而後看
var names = _moduleNamesProviders .SelectMany(p => p.GetModuleNames()) .Where(n => n != _environment.ApplicationName) .Distinct();
找到_moduleNamesProviders的實例(雖然是個集合,可是隻有一個類實現了)AssemblyAttributeModuleNamesProvider,打開這個類,直接看構造函數。
public class AssemblyAttributeModuleNamesProvider : IModuleNamesProvider { private readonly List<string> _moduleNames; public AssemblyAttributeModuleNamesProvider(IHostEnvironment hostingEnvironment) { var assembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName)); _moduleNames = assembly.GetCustomAttributes<ModuleNameAttribute>().Select(m => m.Name).ToList(); } public IEnumerable<string> GetModuleNames() { return _moduleNames; } }
明顯的反射,經過讀取當前程序的程序集hostingEnvironment.ApplicationName就是OrchardCore.Cms.Web,也就是獲取OrchardCore.Cms.Web.dll全部自定義ModuleName屬性的Name集合(看下ModuleNameAttribute能夠明白就是構造函數傳入的那個字符串)。編譯一下程序,用反編譯工具讀取OrchardCore.Cms.Web.dll(ModuleName哪裏來的下篇再說,這也是我一直不明白卡了好幾個星期的地方)能夠看到以下的ModuleName
返回ModularApplicationContext繼續看,就是用linq調用GetModuleNames方法把List<string>抽象成集合IEnumerable<string>經過篩選惟一值Distinct返回(固然排除掉當前項目,畢竟前面已經加入過線程安全集合了modules)names,而後經過多線程方法加入前面提到那個線程安全集合modules。而後把線程安全集合返回。
var names = _moduleNamesProviders .SelectMany(p => p.GetModuleNames()) .Where(n => n != _environment.ApplicationName) .Distinct(); Parallel.ForEach(names, new ParallelOptions { MaxDegreeOfParallelism = 8 }, (name) => { modules.Add(new Module(name, false)); });
學c#第一個多線程類就是Parallel,所以很清楚就是限制最大8個線程(爲啥要限制最大8個線程,難道做者是用老i7那種4核8線程那種?),把names分別再多個線程傳遞給Module實例化而後加入線程安全集合modules返回出去給Application作構造函數的是參數實例化,這也是爲啥要用線程安全集合的緣由吧(我真哭了,像我這種菜鳥估計只會List<T>,而後foreach這種低效率的單線程方法了)。
ok,貼下Application類
public class Application { private readonly Dictionary<string, Module> _modulesByName; private readonly List<Module> _modules; public const string ModulesPath = "Areas"; public const string ModulesRoot = ModulesPath + "/"; public const string ModuleName = "Application Main Feature"; public const string ModuleDescription = "Provides components defined at the application level."; public static readonly string ModulePriority = int.MinValue.ToString(); public const string ModuleCategory = "Application"; public const string DefaultFeatureId = "Application.Default"; public const string DefaultFeatureName = "Application Default Feature"; public const string DefaultFeatureDescription = "Adds a default feature to the application's module."; public Application(IHostEnvironment environment, IEnumerable<Module> modules) { Name = environment.ApplicationName; Path = environment.ContentRootPath; Root = Path + '/'; ModulePath = ModulesRoot + Name; ModuleRoot = ModulePath + '/'; Assembly = Assembly.Load(new AssemblyName(Name)); _modules = new List<Module>(modules); _modulesByName = _modules.ToDictionary(m => m.Name, m => m); } public string Name { get; } public string Path { get; } public string Root { get; } public string ModulePath { get; } public string ModuleRoot { get; } public Assembly Assembly { get; } public IEnumerable<Module> Modules => _modules; public Module GetModule(string name) { if (!_modulesByName.TryGetValue(name, out var module)) { return new Module(string.Empty); } return module; } }
前面獲取全部模塊也就是Modules屬性能夠清晰的從構造函數那裏看到就是咱們剛剛返回的線程安全集合modules,繞來繞去我都快被繞暈了,真想一個指針指過去。好了,繼續回到咱們的初始化功能,就是ExtensionManager的EnsureInitializedAsync方法
private async Task EnsureInitializedAsync() { if (_isInitialized) { return; } await _semaphore.WaitAsync(); try { if (_isInitialized) { return; } var modules = _applicationContext.Application.Modules; var loadedExtensions = new ConcurrentDictionary<string, ExtensionEntry>(); // Load all extensions in parallel await modules.ForEachAsync((module) => { if (!module.ModuleInfo.Exists) { return Task.CompletedTask; } var manifestInfo = new ManifestInfo(module.ModuleInfo); var extensionInfo = new ExtensionInfo(module.SubPath, manifestInfo, (mi, ei) => { return _featuresProvider.GetFeatures(ei, mi); }); var entry = new ExtensionEntry { ExtensionInfo = extensionInfo, Assembly = module.Assembly, ExportedTypes = module.Assembly.ExportedTypes }; loadedExtensions.TryAdd(module.Name, entry); return Task.CompletedTask; }); var loadedFeatures = new Dictionary<string, FeatureEntry>(); // Get all valid types from any extension var allTypesByExtension = loadedExtensions.SelectMany(extension => extension.Value.ExportedTypes.Where(IsComponentType) .Select(type => new { ExtensionEntry = extension.Value, Type = type })).ToArray(); var typesByFeature = allTypesByExtension .GroupBy(typeByExtension => GetSourceFeatureNameForType( typeByExtension.Type, typeByExtension.ExtensionEntry.ExtensionInfo.Id)) .ToDictionary( group => group.Key, group => group.Select(typesByExtension => typesByExtension.Type).ToArray()); foreach (var loadedExtension in loadedExtensions) { var extension = loadedExtension.Value; foreach (var feature in extension.ExtensionInfo.Features) { // Features can have no types if (typesByFeature.TryGetValue(feature.Id, out var featureTypes)) { foreach (var type in featureTypes) { _typeFeatureProvider.TryAdd(type, feature); } } else { featureTypes = Array.Empty<Type>(); } loadedFeatures.Add(feature.Id, new CompiledFeatureEntry(feature, featureTypes)); } }; // Feature infos and entries are ordered by priority and dependencies. _featureInfos = Order(loadedFeatures.Values.Select(f => f.FeatureInfo)); _features = _featureInfos.ToDictionary(f => f.Id, f => loadedFeatures[f.Id]); // Extensions are also ordered according to the weight of their first features. _extensionsInfos = _featureInfos.Where(f => f.Id == f.Extension.Features.First().Id) .Select(f => f.Extension); _extensions = _extensionsInfos.ToDictionary(e => e.Id, e => loadedExtensions[e.Id]); _isInitialized = true; } finally { _semaphore.Release(); } }
如今modules有了,下行是var loadedExtensions = new ConcurrentDictionary<string, ExtensionEntry>();通過剛剛的線程安全集合,ConcurrentDictionary雖然接觸很少也明白這個是線程安全字典(學依賴注入的時候遇到過)。既然是線程安全字典,下面確定又是多線程來填充這個線程安全字典loadedExtensions,這裏用了一個ForEachAsync的擴展方法分配多個任務,把每一個modules的ManifestInfo分析出來的功能加入ConcurrentDictionary。具體細說估計篇幅太長了,先簡單介紹下吧。以前modules就是OrchardCore.Modules、OrchardCore.Modules.Cms和OrchardCore.Themes這三個解決方案下的項目集合,咱們稱做模塊集合,每一個模塊由一個或者多個功能組成,如今就是把功能封裝成線程安全的字典集合,而功能是有重複的,可能多個模塊用同一個功能,因此線程安全很重要!下篇要解釋下反射那些ModuleName是如何實現的(畢竟困擾我這麼久的東西必須記錄下才能印象深入),我看下下篇能不能把功能拆出來講吧。