(六)學習瞭解OrchardCore筆記——靈魂中介軟體ModularTenantContainerMiddleware快速過了
這原始碼下面好像沒啥說的,都是簡單的封裝,自己除錯跟蹤下就明白了,猶豫了幾天,講下去感覺沒玩沒了的基礎知識,我準備快速過了。
上次講到ExtensionManager的擴充套件,往下原始碼就是功能了
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 (varfeature 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;
其實就是為了返回_features,把擴充套件封裝、排序、篩選下,真的沒啥說的,自己跟蹤吧,跳過,返回ShellHost的PreCreateAndRegisterShellsAsync方法。
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"); } }
這個其實也沒啥好說的,就是載入完功能載入配置,然後沒啟動就啟動(每個租戶都啟動)。這裡說一個我自己的坑就是_shellSettingsManager.LoadSettingsAsync()這個的實現ShellSettingsManager的LoadSettingsAsync方法第一行EnsureConfigurationAsync()這個方法,我們看看這個程式碼
private async Task EnsureConfigurationAsync() { if (_configuration != null) { return; } await _semaphore.WaitAsync(); try { if (_configuration != null) { return; } var lastProviders = (_applicationConfiguration as IConfigurationRoot)?.Providers .Where(p => p is EnvironmentVariablesConfigurationProvider || p is CommandLineConfigurationProvider) .ToArray(); var configurationBuilder = await new ConfigurationBuilder() .AddConfiguration(_applicationConfiguration) .AddSourcesAsync(_tenantsConfigSources); if (lastProviders.Count() > 0) { configurationBuilder.AddConfiguration(new ConfigurationRoot(lastProviders)); } var configuration = configurationBuilder.Build().GetSection("OrchardCore"); _configuredTenants = configuration.GetChildren() .Where(section => Enum.TryParse<TenantState>(section["State"], ignoreCase: true, out var result)) .Select(section => section.Key) .Distinct() .ToArray(); _configBuilderFactory = async (tenant) => { await _semaphore.WaitAsync(); try { var builder = new ConfigurationBuilder().AddConfiguration(_configuration); builder.AddConfiguration(configuration.GetSection(tenant)); return await builder.AddSourcesAsync(tenant, _tenantConfigSources); } finally { _semaphore.Release(); } }; _configuration = configuration; } finally { _semaphore.Release(); } }
裡面有這個_applicationConfiguration as IConfigurationRoot,我一開始直接找IConfigurationRoot的實現,由於前面跳過服務,不知道這裡的IConfigurationRoot並沒有注入自己的依賴注入,繞了很多彎路,我真是傻啊,IConfigurationRoot是微軟預設的依賴注入,這點注意下就沒啥難度了。
ShellHost這裡就直接過了,回到中介軟體ModularTenantContainerMiddleware去
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初始化了,shellSetting就是之前初始化中的配置讀取對應上下文的設定(多租戶挑啊),加Header什麼的,這個太容易理解了吧,剩下幾行都有註釋了,最後熟悉的asp.net core呼叫下箇中間件的_next.Invoke(httpContext)。
本來打算做一個系列的,看起來太簡單了,沒必要,自己設定下斷點追蹤下。下篇快速過另外一箇中間件就結束了,回頭看看能不能整理成一個框架圖出來,從框架講應該比原始碼(太囉嗦還容易亂)好多了,主要我還不會畫圖是個問題。