(三)学习了解OrchardCore笔记——灵魂中间件ModularTenantContainerMiddleware的第一行①的模块部分
了解到了OrchardCore主要由两个中间件(ModularTenantContainerMiddleware和ModularTenantRouterMiddleware)构成,下面开始了解ModularTenantContainerMiddleware中间件第一行代码。
了解asp.net core机制的都知道中间件是由构造函数和Invoke(或者InokeAsync)方法构成,构造函数不过就是个依赖注入的过程,Invoke也可以直接依赖注入,两者的区别是构造函数时全局的,Invoke只是当次请求,所以从Inovke开始了解!
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锁住,原来还可以有这样的细节。
然后try里又确认一次有没初始化(判断_initialized),为啥呢?我一脸懵逼,后面感觉好像可以接受,比较前面时有很多请求,说不定有个某个请求已经初始化ShellHost了呢,所以_initializingSemaphore.WaitAsync开始后又要确认一次。如果前面不先确认,那么又全部只能一个请求进来,这就很不合理了。我快被绕晕了,真佩服开发者的大脑。
终于来到PreCreateAndRegisterShellsAsync方法了。_loggoer部分直接跳过,就是asp.net core的日志记录而已。OrchardCore是一个模块化多租户的cms,模块化就体现在这里。
var features = _extensionManager.LoadFeaturesAsync();
加载所有的功能,ExtensionManager(OrchardCore\OrchardCore\Extensions\ExtensionManager.cs)的LoadFeaturesAsync方法。
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是如何实现的(毕竟困扰我这么久的东西必须记录下才能印象深刻),我看下下篇能不能把功能拆出来说吧。