了解到了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. ubuntu18.04安装qt时候的错误解决

    在ubuntu系统下安装好qt5.5后启动qtceator时提示: Qt5.5.1/Tools/QtCreator/lib/qtcreator/plugins/libHelp.so: 无法加载库Qt5 ...

  2. 组态与非组态结合的LT

    概述 最新的应用软件快速搭建平台现已投入使用.首先对名称进行规范统一一下. 英文全称:LARKIN-CN.TOP : 中文全称:拉图: 简称:LT. 特点: 组态软件开发的快速.灵活 C端软件的控件交 ...

  3. iOS-键盘弹出或隐藏时调整输入框的位置

    要达到自动调整的目标需要监听 keyboardWillShowNotification 跟 keyboardWillHideNotification, 同时需要实现点击其它地方时,通知隐藏键盘的事件 ...

  4. 2019-02-01 Python爬虫爬取豆瓣Top250

    这几天学了一点爬虫后写了个爬取电影top250的代码,分别用requests库和urllib库,想看看自己能不能搞出个啥东西,虽然很简单但还是小开心. import requests import r ...

  5. Redis在CentOS for LInux上安装详细教程

    1.首先上传安装包,这里我以 redis-5.0.8.tar.gz 为例子. Linux下载redis地址:wget http://download.redis.io/releases/redis-5 ...

  6. jwt 工具类

    public class TokenUtils { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 签名 ...

  7. MySQL-数据库和表的基本操作

    数据库和表的基本操作 数据库基础知识 创建数据库 CREATE DATABASE 数据库名称 ; 查看数据库(显示数据库名列表) SHOW DATABASES ; 查看某数据库信息(显示创建的信息) ...

  8. Linux下如何寻找相同文件?

    大家好,我是良许. 随着电脑的使用,系统里将产生很多垃圾,最典型的就是同一份文件被保存到了不同的位置,这样导致的结果就是磁盘空间被大量占用,系统运行越来越慢. 所以如果你的电脑空间告急的话,可以试着去 ...

  9. Spring中的JDBC API

    1 JdbcTemplate的诞生 JDBC作为Java平台访问关系数据库的标准API,其成功是有目共睹的.为了避免在JDBC API在使用中的种种尴尬局面(几乎程式一样的代码,繁琐的异常处理),Sp ...

  10. JavaWeb网上图书商城完整项目--day02-11.激活功能流程分析

    1.当用户注册成功之后,会给用户发送邮件,当用户点击邮件的激活按钮的时候,会调用UserServlet中的activation的方法,并且会把激活码传递到后台,后台业务层对业务进行操作