OrchardCore实现模块化核心原理分析

若改造项目,也因历史遗留问题,数据库表设计也可能存在不合理,此时从头开始再搭建如此庞大的架子,感觉会有点虚空,同时也要考虑团队内部情况,不是那么容易上手,反而可能会违背初心,花更多时间和精力在各种模型理解上 我们完全可以为后续做铺垫,先搭建出底层基本设施,再基于此做灵活扩展即可,每个公司项目具体情况都不一样,比如仓储模式可能需要结合项目进行对应 改造,仓储只是提供了一种基本思想,若真将网上普遍流传的模式照搬可能并不是那么好用,可能会认为仓储莫不是一种反模式 .NET Core模块化插件 .NET Core内置提供了AssemblyLoadContext加载dll插件方式实现模块化,然后将其进行注册 var mvcBuilder = services.AddMvc(); foreach (var module in modules) { // Register controller from modules mvcBuilder.AddApplicationPart(module.Assembly); } 这种方式虽可行,在我看来只能作为一种临时解决方案并不利于长期,因为需额外创建一个新的项目,然后加载所生成dll,由于没有底层设施做支撑,所以极易引起版本不一致问题,而且手动被迫性质太强,实现模块化方案最终的目标则只需关注业务逻辑实现,我们来看看OrchardCore如何实现模块化。 OrchardCore模块化思想 这里我们并不讨论和ABP vNext二者谁更强大,没有任何意义,比如需结合现有项目情况、项目大小、是否为多租户、实施难度等等多方面考虑才能得出基本结论,而不是一味追求当前主流 比如我们只是想实现模块化方案,建议选择OrchardCore来实施,因为很简单,我们可将其剥离为我所用,而后结合项目情况是否考虑利用ABP vNext来进行分层处理。借鉴核心思想、才能保证一切可在控制范围内 首先我们先从整体上对OrchardCore做个认识,细枝末节暂不考虑:基于ASP.NET Core多租户模块化应用框架。 版本管理:无论是底层设施、基本框架、模块都通过包管理,同时框架和包版本基本(包管理走框架包版本)可以统一管理(对于版本升级很重要) 核心思想:模块实现模块特性,通过MSBuild构建主程序所添加实现模块特性的模块包,底层设施扫描模块特性将其注册到容器中,当然模块和模块特性都可进行基本信息描述 OrchardCore模块化原理 整个项目架构如下图所示 OrchardCore:底层设施以及可能需要添加的组件(比如本地化、日志、文件存储、缓存、Lucene等) OrchardCore.Frameworks:MVC框架 OrchardCore.Modules:模块化包(比如邮件服务、后台作业服务、第三方集成等等) OrchardCore.Modules.Cms:Cms模块包 OrchardCore.Themes:主题管理 OrchardCore.Cms.Web:主程序 我以内置所提供示例程序给大家讲解整个详细流程,而后有需要更细致了解的童鞋就可以很快上手了,如下示例主程序加载示例模块,主程序直接采用引用该示例模块(实际则是通过nuget下载该模块) 正常情况下我们通过nuget直接下载的是程序包,而OrchardCore对于入口则是利用MSBuild加载targets文件(其他组件则直接下载对应包),而targets引用对应包,通过这种中转方式根据我的理解主要解决了两个问题,其一则是可以屏蔽底层设施包(一次性下载),最重要的是通过targets文件可自动添加主程序程序集所加载模块包特性 是不是感觉有点懵,那到底是如何加载模块包特性的呢?来,请看如下图,我们以实际操作从头再来做一个完整梳理(注意:为排版美观,如下都将省略OrchardCore前缀) 【1】创建Mvc.Web程序,在nuget上下载Application.Mvc.Targets包 【2】创建Mvc.HelloWorld模块,在nuget上下载引用Module.Targets包 【3】Mvc.Web主程序引用我们所使用的Mvc.HelloWorld模块 【4】Application.Mvc.Targets包引用Application.Targets(引入底层设施)和MVC.Core(引入MVC框架) 【5】示例模块引入模块包,该包中存在模块特性(Module类) 【6】Application.Targets包下存在Application.Targets.targets文件,由于主程序引用了该包,添加所引用实现模块特性的包程序集信息到主程序集 到这里我们已经研究完主程序如何识别模块包,接下来则是如何加载模块包以及对应注册服务信息 OrcharCore核心在于OrchardCore和OrchardCore.Abstractions这两个底层设施包 归根到底,其底层设施源码一部分可能从官方源码拷贝过来(自我猜测),为实现多租户模式,势必要构建租户的容器和路由中间件,这中间就涉及在容器中需要维护每一个租户上下文(ShellContext),并且也要跟踪每个租户的状态。 ModularTenantContainerMiddleware作为创建租户容器中间件 ModularTenantRouterMiddleware作为租户路由中间件

January 10, 2024

OrchardCore入门指南:创建Orchard Core CMS网站

在本指南中,您将使用项目模板将Orchard Core设置为内容管理系统。 您需要什么 .NET SDK的当前版本。您可以从以下网址下载它:https://dotnet.microsoft.com/download. 文本编辑器和终端,您可以在其中键入 dotnet 命令。 创建项目 有多种创建Orchard Core网站和模块的方法。您可以在这里了解更多信息。 在本指南中,我们将使用“代码生成模板”。您可以使用此命令安装最新稳定版本的模板: dotnet new install OrchardCore.ProjectTemplates::1.5.0-* 注意 要使用模板的开发分支,请添加 –nuget-source https://nuget.cloudsmith.io/orchardcore/preview/v3/index.json 创建一个空文件夹来存放你的网站。打开终端,进入该文件夹并运行以下命令: dotnet new occms -n MySite 这样就在一个名为 MySite 的文件夹中创建了一个新的Orchard Core CMS项目。 设置站点 应用程序已经由模板创建,但尚未设置。 通过执行以下命令启动应用程序: dotnet run –project .\MySite\MySite.csproj Note 如果你正在使用模板的开发分支,请在运行应用程序之前运行dotnet restore .\MySite\MySite.csproj –source https://nuget.cloudsmith.io/orchardcore/preview/v3/index.json。 现在您的应用程序应该正在运行,并监听以下端口: 现在监听 on: https://localhost:5001 和 on: http://localhost:5000。应用已经启动,按 Ctrl+C 可以关闭应用。 在浏览器中打开 https://localhost:5001 可以显示设置屏幕。 为了演示的目的,我们将使用 Blog 配方创建网站。Blog 配方是 Orchard Core 的 入门配方之一,其中包含一系列功能和配置 Orchard Core 网站的步骤。 完成设置表单,选择 Blog 配方和 SQLite 数据库。 提交表单后,几秒钟后您将可以看到一个博客站点。 为了配置并开始编写内容,您可以转到 https://localhost:5001/admin。 概要 ...

June 30, 2023

OrchardCore入门指南:创建一个模块化的 ASP.NET Core 应用程序

你将要构建什么 您将创建一个模块化的 ASP.NET Core MVC 网络应用程序,类似于包含在 Orchard Core 中的 “Hello World” 应用程序示例。它包括一个网络应用程序和一个模块。网络应用程序提供了布局,而模块注册了路由并响应首页请求。您可以参考 Orchard Core 中的以下项目以获取更多信息。 src/OrchardCore.Mvc.Web src/OrchardCore.Modules/OrchardCore.Mvc.HelloWorld 所需材料 当前版本的 .NET SDK。您可以从此处下载: https://dotnet.microsoft.com/download. 一个文本编辑器和一个终端,您可以在其中运行 dotnet CLI 命令。 创建 Orchard Core 网站和模块 有不同的方法可以为 Orchard Core 创建网站和模块。您可以在 此处 了解更多信息。 在本指南中,我们将使用我们的代码生成模板。您可以使用以下命令安装模板的最新稳定版本: dotnet new install OrchardCore.ProjectTemplates::1.5.0-* Note 如果想使用模板的开发分支,请添加 –nuget-source https://nuget.cloudsmith.io/orchardcore/preview/v3/index.json 创建一个名为OrchardCore.Mvc的空文件夹,它将包含我们的项目。打开一个终端,进入该文件夹并运行以下命令来创建Web应用程序: dotnet new ocmvc -n OrchardCore.Mvc.Web 接下来,创建“ Hello World”模块。 dotnet new ocmodulemvc -n OrchardCore.Mvc.HelloWorld 将Web应用程序中指向该模块的项目引用添加到其中。 dotnet add OrchardCore.Mvc.Web reference OrchardCore.Mvc.HelloWorld 可选地,您可以添加一个解决方案文件,该文件引用了Web应用程序和模块,以便在Visual Studio中打开解决方案。 dotnet new sln -n OrchardCore.Mvc dotnet sln add OrchardCore.Mvc.Web\OrchardCore.Mvc.Web.csproj dotnet sln add OrchardCore.Mvc.HelloWorld\OrchardCore.Mvc.HelloWorld.csproj 测试生成的应用程序 从包含两个项目的OrchardCore.Mvc根文件夹中运行以下命令启动Web应用程序: ...

June 30, 2023

OrchardCore入门指南

指南 无论你在构建什么,这些指南旨在让你尽快使用最新的Orchard Core项目版本和Orchard团队推荐的技术,进入工作状态。 入门指南 这些指南旨在在15-30分钟内完成,提供快速的、实践操作的指令,用于使用Orchard Core构建任何开发任务的“Hello World”。在大多数情况下,唯一的先决条件是一个.NET SDK和一个文本编辑器。 创建一个模块化的ASP.NET Core应用程序 在启动时运行代码 自定义编码设置 Orchard Core CMS指南 这些指南专门针对Orchard Core CMS: 创建Orchard Core CMS网站 向管理导航添加菜单项 安装本地化文件 如何使用资产转码器/绑定器/缩小器管道- 集成 Facebook 插件 实现全文检索 将 AzureAD 集成为外部提供程序 教程 这些教程旨在在2-3小时内完成,提供更深入、上下文探讨企业应用程序开发主题,并让您准备实现现实世界的解决方案。 使用 Razor Pages 构建解耦的网站 构建一个来自 Web 模板的网站(待定) 实施自助式 SaaS 解决方案(待定)

June 20, 2023

Entity Framework Core 创建DbContext的两种方法

最近创建基于firstcode的开发模式,整理了一下如何使用Entity Framework Core 创建DbContext。 1、第一种重写DbContext的OnConfiguring方法,每次生成一个DbContext的方法的时候就会重新来这个方法这里读一下配置。 这种情况下,首先尝试通过调用 Program.CreateHostBuilder()、调用 Build(),然后访问 Services 属性来获取服务提供程序。 public class Program { public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); // EF Core uses this method at design time to access the DbContext public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults( webBuilder => webBuilder.UseStartup<Startup>()); } public class Startup { public void ConfigureServices(IServiceCollection services) => services.AddDbContext<ApplicationDbContext>(); public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { } } public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } } 2、使用不带参数的构造函数 ...

May 8, 2023

C#中引用类型的赋值、浅拷贝和深拷贝

赋值和深复制、浅复制并不是一样的,含义是不一样的。赋值。指的是 “ 等号= ”。它相当于是给引用对象起一个别名。浅度复制和深度复制。指的是类实现 ICloneable接口,重写该接口的唯一方法。注意:不管是深度复制还是浅度复制,都是通过ICloneable接口去实现的。 值类型变量存储的是变量的值,直接储存在栈内存中。引用类型变量存储的是变量所在的内存地址,引用类型变量的实际数据存储于托管堆,变量本身仅仅是一个指向堆中实际数据的地址,存储于栈内存中,通常是四个字节。 值类型Value存储在线程堆栈中。引用类型Reference存储在托管堆上。 全局数据区:存放全局变量,静态数据,常量。代码区:存放所有的程序代码。栈区:存放为运行而分配的局部变量,参数,返回数据,返回地址等。堆区:即自由存储区 为了理解值类型变量和引用类型变量的内存分配模型,我们应先区分两种不同的内存区域——线程堆栈Thread Stack和托管堆Managed Heap。每一个正在运行的程序都对应着一个进程Process,在一个进程内部,可以有一个或多个线程Thread,每个线程都拥有一块“自留地”,成为线程堆栈,大小为1M,用于保存自身的一些数据,如函数中定义的局部变量、函数调用时传送的参数值等。现在我们可以解释第一句话——值类型存储在线程堆栈中,也就是说所有值类型的变量都是在线程堆栈中分配的。另一块内存区域称为堆Heap,在.NET这种托管环境下,堆由CLR(Common Language Runtime)管理,所以又称托管堆Managed Heap。例如使用new关键字创建类的对象实例时,分配给对象的内存单元就位于托管堆中。 1、赋值。赋值和深度复制,浅度复制完全是不同的概念,并没有什么关系,很多文章说赋值对于值类型是深度复制,对于引用类型是浅度复制,这种说法是不正确的,它的本质是在线程栈上产生一样的副本。 2、浅度复制。值类型成员独立,但是引用类型成员共享。 3、深度复制。值类型成员和引用类型成员都是独立的,即完完全全的一个全新的副本,称之为深度复制。 (1)String字符串对象是引用对象,但是很特殊,它表现的如值对象一样,即对它进行赋值,分割,合并,并不是对原有的字符串进行操作,而是返回一个新的字符串对象。但这其实是运算符重载的结果,将string实现为语义遵循一般的、直观的字符串规则。 String对象被分配在堆上,而不是栈上。 (2)Array数组对象是引用对象,在进行赋值的时候,实际上返回的是源对象的另一份引用而已;因此如果要对数组对象进行真正的复制(深拷贝),那么需要新建一份数组对象,然后将源数组的值逐一拷贝到目的对象中。

April 7, 2023

C#实现两个顺序链表的合并

最近开始刷leetcode,刷了一周时间,确实会上瘾,不过能巩固很多基础知识,包括算法和语言。我现在是架构与编码并行。今天遇到的是两个列表的合并,刚开始我自己写了一个笨方法来合并,虽然通过了测试,但是代码质量不高,于是研究了其他人写的代码。 //传入的两个链表本身是从小到大排序好 public ListNode MergeTwoLists(ListNode list1, ListNode list2) { ListNode head = new ListNode(); //定义新的链表头 ListNode current = head;//定义链表最新的next地址 //遍历两个链表,任何一个完毕后就终止循环 while (list1 != null && list2 != null) { if (list1.val < list2.val) { //如果list1的元素值小,则把当前list1赋予current.next,注意这里相当于head.next current.next = list1; list1 = list1.next; } else { current.next = list2; list2 = list2.next; } //将current变量指向current.next,相当于head.next,当进入下一次循环给current.next赋值的时候,相当于是给head.next.next赋值了,一直这么循环到最后 current = current.next; } //head链表的next指向未循环完成的部分 current.next = list1 == null ? list2 : list1; //返回合并后的链表 return head.next; } 我刚开始发懵了,居然没理解这部分代码,因为忽略了“每次循环之后current的引用指向已经发生变化”。

April 7, 2023

.NET7下string的改进

string是开发过程中,使用频度最高的类型之一,所以在构建类型时作了很多处理,如“不可变性”,“保留性”等特点。string的常量是在"“引号中进行赋值的。 var str1 = "这是一段文字"; Console.WriteLine(str1); 为了字符串的格式化,引入了$““定义方式,这样就可以在字符串中用{}来标注格式化的内容了。 var str2 = $"时间:{DateTime.Now}"; Console.WriteLine(str2); //输出结果是:时间:1/6/2023 15:37:13 var str2_1 = $"时间:{DateTime.Now:yyyy-MM-dd}"; Console.WriteLine(str2_1); //输出结果是:时间:2023-01-06 为了解决字符串内容的换行,引定入@"",来定义有换行的字符串,比如下面的一条SQL查询,可以按格式化后的样式来定义。$和@可以混用,不分先后。 var str3 = @"SELECT ID ,Question ,Score ,QuestionTypeID ,SubjectTypeID FROM Questions"; Console.WriteLine(str3); var str3_1 = @$"SELECT ID ,Question ,Score ,QuestionTypeID ,SubjectTypeID FROM Questions WHERE Score>{10}"; Console.WriteLine(str3_1); 其实原始字符串还解决了一个问题,就是字符串中有"的问题,以前需要有转义字符来实现,现在原始字符串都搞定了。 Console.WriteLine("\"a\" 是小写的");//通过\来转义 Console.WriteLine(@"""a"" 是小写的");//前缀是@时,通过"转义 最佳demo是json字符串的定义,用原始字符串的方式定义json字符串,最合适不过了。 var jsonString = """ { "irstName": "John", "astName": "Smith", "ex": "male", "ge": 25, "ddress": { "treetAddress": "21 2nd Street", "ity": "New York", "tate": "NY", "ostalCode": "10021" } } """; Console.WriteLine(jsonString) 通过下图,看到的json原始字符串,一目了然: ...

January 12, 2023

[转]NPM和webpack的关系

入门前端的坑也很久了,以前很多大小项目,前端都是传统式开发,一直在重复造轮子;接触VUE后,对vue-cli有了解后,仅仅知道vue-cli是一个vue项目的脚手架,可以快速的构建一个vue的基于npm的模块化项目,vue内部的打包机制其实还是借助webpack;但是对webpack\npm\node\nodejs这几个在前端模块化中的高频词总是傻傻分不清,不知道他们之间的具体关系,今天花了些功夫查阅了网上大神的回答和官方教程给出的解释写一篇小白文,总结一下这几个概念或者说高频词汇之间的关系 what is webpack? Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。即WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。 webpack的核心作用 模块化开发中,我们会编写大量模块,如果不打包就上线,那么页面加载或交互时,将会发起大量请求。为了性能优化,需要使用webpack这样的打包器对模块进行打包整合,以减少请求数。就像简单的vue项目,所有组件最终都将被打包到一个app.js中。 相较于无差别打包依赖模块的传统打包器,webpack的核心优势在于它从入口文件出发,递归构建依赖关系图。通过这样的依赖梳理,webpack打包出的bundle不会包含重复或未使用的模块,实现了按需打包,极大的减少了冗余。 webpack是一个工具,这个工具可以帮你处理好各个包/模块之间的依赖关系(modules with dependencies),并将这些复杂依赖关系的静态文件打包成一个或很少的静态文件,提供给浏览器访问使用;除此之外,webpack因为可以提高兼容性,可以将一些浏览器尚不支持的新特性转换为可以支持格式,进而减少由新特性带来的浏览器的兼容性问题。 好,我们通过介绍,我们有个概念,webpack是一个打包工具,可以帮你把你的项目这里的项目其实就是指通过模块化开发的项目 打包为简洁版的浏览器可识别的静态资源。 what is npm? 介绍了webpack,我们可能会疑问,我的JS,CSS,HTML文件分开写,挺好的呀,为什么要使用webpack工具,进行复杂的各项配置。在传统前端开发模式下,我们确实是按照JS/CSS/HTML文件分开写的模式就可以,但是随着前端的发展,社区的壮大,各种前端的库和框架层出不穷,我们项目中可能会使用很多额外的库,如何有效管理这些引入的库文件是一个大问题,而且我们知道基于在HTML中使用script引入的方式,有两个弊端,一个是会重复引入,二是当库文件数量很多时管理成为一个大难题。面对这样的局面,为了简化开发的复杂度,前端社区涌现了很多实践方法。模块化就是其中一项成功实践,而npm就是这样在社区 其实就是node社区中产生的。 npm 由三个独立的部分组成:网站、注册表(registry)、命令行工具 (CLI)。 网站 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。 注册表 是一个巨大的数据库,保存了每个包(package)的信息。CLI 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。 一般来说提起npm有两个含义,一个是说npm官方网站,一个就是说npm包管理工具。npm社区或官网是一个巨大的Node生态系统,社区成员可以随意发布和安装npm生态中的包,也就是不用在重复造轮子了,别人造好了,你直接安装到你的项目中就可以使用,但是因为前面说了,当包引入数量很多时管理就成为了一个问题,这个就是npm为开发者行了方便之处,npm已经为你做好了依赖和版本的控制,也就是说使用npm可以让你从繁杂的依赖安装和版本冲突中解脱出来,进而关注你的业务而不是库的管理。 webpack就是将你从npm中安装的包打包成更小的浏览器可读的静态资源,这里需要注意的是,webpack只是一个前端的打包工具,打包的是静态资源,和后台没有关系,虽然webpack依赖于node环境。 what is node or nodejs? 其实node和nodejs两个概念没有太大差别,我个人认为唯一的区别就是,人们说起node的时候语境更多的是再说node环境,而说nodejs时更多的是在说node是一门可以提供后端能力的技术。本质上来说,node就是nodejs,nodejs就是node 简单的说 Node.js 就是运行在服务端的 JavaScript。Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。 node环境基于V8引擎提供了一种可以让JS代码跑在后端的能力,这就是node。其实这里的node本身和我们这篇讲的前端模块化没啥关系。但是因为npm是产生与node社区,node中也是通过npm来加载模块的,所以有必要说一下他们之间的关系。 npm 是 Node.js 官方提供的包管理工具,他已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制。 webpack npm node之间关系? webpack是npm生态中的一个模块,我们可以通过全局安装webpack来使用webpack对项目进行打包; webpack的运行依赖于node的环境,没有node是不能打包的,但是webpack打包后的项目本身只是前端静态资源和后台没有关系,也就是说不依赖与node,只要有后台能力的都可以部署项目。 npm是于Node社区中产生的,是nodejs的官方包管理工具,当你下载安装好node的时候,npm cli 就自动安装好了。 正是因为npm的包管理,使得项目可以模块化的开发,而模块化的开发带来的这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就是webpack工具存在的意义 原文地址:https://blog.csdn.net/AngelLover2017/article/details/84801673

January 2, 2023

C#泛型接口的协变和逆变

一、协变和逆变是什么? 先从字面上理解 协变(Covariance)、逆变(Contravariance)。 co- 是英文中表示“协同”、“合作”的前缀。协变 的字面意思就是 “与变化的方向相同”。 contra- 是英文中表示“相反”的前缀,逆变 的字面意思就是是 “与变化方向相反”。 官方:协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。 那么问题来了,这里的“变化方向”指的是什么? C# 中对于对象(即对象引用),仅存在一种隐式类型转换,即 子类型的对象引用到父类型的对象引用的转换。这里的变化指的就是这种 子->父 的类型转换。 协变与逆变虽然从名字上看是两个完全相反的转换,但其实只是“子类型引用到父类型引用”这一过程在函数中使用的“两个不同阶段”而已,接下来将详细说明这点。 二、为什么需要协变和逆变 三、协变例子 四、逆变例子 五、.NET自带的协变和逆变委托和泛型

October 21, 2022