先补充下上个中间件缺少介绍的,我发现没那个说不下去,看看上个中间件ModularTenantContainerMiddleware的Invoke方法的第一行
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的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"); } }
看看第二个if里面var setupContext = await CreateSetupContextAsync(defaultSettings);
这个就算要补充的东西,这个方法会构建每个租户的依赖注入容器去串联每个模块startup,具体可以看看ShellContainerFactory的创建容器方法CreateContainer,这个最好自己追踪下。也就是说既有共有依赖注入,也有每个租户自己依赖注入,这个过程真心值得看,我是无心细说,直接跳到今天的主题。
直接看ModularTenantRouterMiddleware中间件的Invoke方法
public async Task Invoke(HttpContext httpContext) { if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation("Begin Routing Request"); } var shellContext = ShellScope.Context; // Define a PathBase for the current request that is the RequestUrlPrefix. // This will allow any view to reference ~/ as the tenant's base url. // Because IIS or another middleware might have already set it, we just append the tenant prefix value. if (!String.IsNullOrEmpty(shellContext.Settings.RequestUrlPrefix)) { PathString prefix = "/" + shellContext.Settings.RequestUrlPrefix; httpContext.Request.PathBase += prefix; httpContext.Request.Path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase, out PathString remainingPath); httpContext.Request.Path = remainingPath; } // Do we need to rebuild the pipeline ? if (shellContext.Pipeline == null) { await InitializePipelineAsync(shellContext); } await shellContext.Pipeline.Invoke(httpContext); }
前面没啥说的,看看最后两行代码,await InitializePipelineAsync(shellContext)和await shellContext.Pipeline.Invoke(httpContext),没有管道就初始化管道,然后调用?what?别理解错了,这个此管道非彼管道,这个按里面的名称来说是租户管道,这也是很多人说OrchardCore的类似asp.net core的原因,接下去看看这个类下面几个方法就明白了,一个套着一个调用,最终是这个
private void ConfigureTenantPipeline(IApplicationBuilder appBuilder) { var startups = appBuilder.ApplicationServices.GetServices<IStartup>(); // IStartup instances are ordered by module dependency with an 'ConfigureOrder' of 0 by default. // OrderBy performs a stable sort so order is preserved among equal 'ConfigureOrder' values. startups = startups.OrderBy(s => s.ConfigureOrder); appBuilder.UseRouting().UseEndpoints(routes => { foreach (var startup in startups) { startup.Configure(appBuilder, routes, ShellScope.Services); } }); }
appBuilder.UseRouting().UseEndpoints(),知道asp.net core应该都超级熟悉的两个中间件路由中间件和终结点中间件,startups往上看var startups = appBuilder.ApplicationServices.GetServices<IStartup>(),也就是把依赖注入的IStartup实例化,为啥是多个呢,参考前面补充去看下ShellContainerFactory就明白,这是每个模块里的Startup啊。
本系列到此结束,后面草草收场,有需要讨论留言或者私信。主要第一次写随笔没经验,后面我发现展开难度太大了,光光那个第一行我估计详细解释要几十篇。我看看以后有机会(为啥说有机会呢,因为我不会画图)从OrchardCore的架构说起会清晰点,而且我也没asp.net core的基础详解,很多言语模糊,实在太难展开了。