(六)学习了解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 (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;
其实就是为了返回_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)。
本来打算做一个系列的,看起来太简单了,没必要,自己设置下断点追踪下。下篇快速过另外一个中间件就结束了,回头看看能不能整理成一个框架图出来,从框架讲应该比源码(太啰嗦还容易乱)好多了,主要我还不会画图是个问题。