了解到了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. 使用PyQtGraph绘制图形(1)

    首先利用numpy模块创建两个随机数组,用来作为图形绘制的数据: import pyqtgraph as pg import numpy as np x = np.random.random(50) ...

  2. 匿名实现类&匿名对象

    学习过程中经常搞不清匿名类&匿名对象怎么用,今天就把常用的方式总结一遍. 1.创建了非匿名实现类的非匿名对象 //定义USB接口 interface USB{ void inputInofo( ...

  3. WebDriverWait

    显示等待 WebDriverWait( driver, timeout, poll_frequency, ignored_exceptions) driver: 传入WebDriver实例,即webd ...

  4. 操作-读取excel

    xlrd 该模块主要用来读取excel 注:sheet表示的是excel的表,就是底下的工作栏 (1) 打开excel文件并获取所有sheet import xlrd # 打开Excel文件读取数据 ...

  5. ca75a_c++_标准IO库-利用流对象把文件内容读取到向量-操作文件

    /*ca75a_c++_标准IO库习题练习习题8.3,8.4,8.6习题8.9.8.10 ifstream inFile(fileName.c_str());1>d:\users\txwtech ...

  6. 一个JS库Lodash

    中文文档:https://www.html.cn/doc/lodash/#_aftern-func

  7. SpringBoot -- 项目结构+启动流程

    一.简述: 项目结构 二.简述:启动流程 说springboot的启动流程,当然少不了springboot启动入口类 @SpringBootApplication public class Sprin ...

  8. Eureka心跳健康检查机制和Spring boot admin 节点状态一直为DOWN的排查(忽略某一个节点的健康检查)

    https://www.jdon.com/springcloud/eureka-health-monitoring.html 运行阶段执行健康检查的目的是为了从Eureka服务器注册表中识别并删除不可 ...

  9. 慕课网--mysql开发技巧一 学习笔记

    现在存在下面的两张表,表的结构如下所示 师徒四人表结构:id,user_name,over数据:id user_name over1 唐僧 旃檀功德佛2 猪八戒 净坛使者3 孙悟空 斗战胜佛4 沙僧 ...

  10. 前端笔记(关于解决打包时报node-sass错误的问题)

    这个问题之前反复出现,试过重新从其他同事将node_modules拿过来用,但是过了几天又出同样的问题 去网上百度了好久,大多数都说是node-sass重装一下就行.可是我这边卸载都无法卸载,何谈重装 ...