了解到了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, "");
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 > )
{
// 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 = }, (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 = }, (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是如何实现的(毕竟困扰我这么久的东西必须记录下才能印象深刻),我看下下篇能不能把功能拆出来说吧。

(三)学习了解OrchardCore笔记——灵魂中间件ModularTenantContainerMiddleware的第一行①的模块部分的更多相关文章

  1. (五)学习了解OrchardCore笔记——灵魂中间件ModularTenantContainerMiddleware的第一行②模块的功能部分

    在(三)的时候已经说到模块集合用ForEachAsync的扩展方法分配多个任务,把每个modules的ManifestInfo分析出来的功能加入ConcurrentDictionary.我们先看看这个 ...

  2. (二)学习了解OrchardCore笔记——开篇:OrchardCore的中间件

    现在开始看Starpup的中间件.这是一个扩展方法app.UseOrchardCore() public void Configure(IApplicationBuilder app, IHostEn ...

  3. (一)学习了解OrchardCore笔记——开篇:基于asp.net core的OrchardCore

    想深入了解OrchadCore源码许久了,但是读源码的时候遇到很多问题而网上的参考资料太少了(几乎都是OrchadCms不带OrchardCore的),现在解决得差不多了,做下笔记方便自己查看,有错误 ...

  4. (四)学习了解OrchardCore笔记——将模块的名字添加到程序集的ModuleName

    关于如何将模块名添加到程序集的ModuleName说简单吧也简单,说不简单吧也不简单. 简单的原因是代码只有几行,不简单的原因是这些都不是c#,都是MSbuild的代码.这可真难为我了,所以这个地方我 ...

  5. (学习笔记)laravel 中间件

    (学习笔记)laravel 中间件 laravel的请求在进入逻辑处理之前会通过http中间件进行处理. 也就是说http请求的逻辑是这样的: 建立中间件 首先,通过Artisan命令建立一个中间件. ...

  6. Python学习的个人笔记(基础语法)

    Python学习的个人笔记 题外话: 我是一个大二的计算机系的学生,这份python学习个人笔记是趁寒假这一周在慕课网,w3cschool,还有借鉴了一些博客,资料整理出来的,用于自己方便的时候查阅, ...

  7. 菜鸟教程之学习Shell script笔记(上)

    菜鸟教程之学习Shell script笔记 以下内容是,学习菜鸟shell教程整理的笔记 菜鸟教程之shell教程:http://www.runoob.com/linux/linux-shell.ht ...

  8. Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation

    原文:Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

  9. Elasticsearch7.X 入门学习第二课笔记----基本api操作和CRUD

    原文:Elasticsearch7.X 入门学习第二课笔记----基本api操作和CRUD 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链 ...

随机推荐

  1. Quartz SpringBoot 简单整合一下

    一次简单的代码整合记录. 数据库准备 如果是MySQL可能出现一些小问题.比如联合主键长度超限制,已经记录解决办法了. CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NA ...

  2. springboot使用自定义异常

    sprinboot使用自定义注解 创建自定义异常类,继承RuntimeException public class MyException extends RuntimeException {   p ...

  3. @hdu - 5822@ color

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个每个点出度都为 1 的有向连通图以及 m 种颜色.求本质 ...

  4. SQL2008R2安装碰到问题的解决方法(iso文件用对应的工具)

    SQL2008R2安装碰到问题的解决方法     安装谁不会啊,这么简单,是啊,可是匹配包时就不是那么顺利啊.就像以前的Ruby还专挑匹配版本的包一样,不像现在的Py自动为我们找,这是Mar 7贴在Q ...

  5. numpy中数组(矩阵)的乘法

    我们知道在处理数据的时候,使用矩阵间的运算将会是方便直观的.matlab有先天的优势,算矩阵是它的专长.当然我们用python,经常要用到的可能是numpy这个强大的库. 矩阵有两种乘法,点乘和对应项 ...

  6. MFC_VC++_时间获取与保存列表控件内容到文件操作方法

    MFC_VC++_时间获取与保存列表控件内容到excel文件操作方法 void CDataView::OnBnClickedBtnExporttoexcel() { CTime time = CTim ...

  7. npm: no such file or directory, scandir '.../node_modules/node-sass/vendor'

    运行vue报错 npm run dev 解决办法,运行:npm rebuild node-sass

  8. Spring Boot 整合 Apollo

    简介 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理场景 ...

  9. mysql 出现You can't specify target table for update in FROM clause错误的解决方法

    mysql出现You can’t specify target table for update in FROM clause 这个错误的意思是不能在同一个sql语句中,先select同一个表的某些值 ...

  10. java scoket aIO 通信

    AsynchronousServerSocketChannel assc.accept(this, new ServerCompletionHandler()); 第一个参数是服务器的处理类,第二个参 ...