(六)学习了解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)。

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

posted @ 2020-07-18 10:20  shuisen  阅读(308)  评论(0编辑  收藏  举报