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

基于ASP.NET Core的整洁架构

干净体系结构将业务逻辑和应用程序模型置于应用程序的中心。 而不是让业务逻辑依赖于数据访问或其他基础设施,此依赖关系被倒置:基础结构和实现细节依赖于应用程序内核。 此功能是通过在应用程序核心中定义抽象或接口来实现的,然后通过基础设施层中定义的类型实现。 将此体系结构可视化的常用方法是使用一系列同心圆,类似于洋葱。 遵循依赖倒置原则以及域驱动设计原则 (DDD) 的应用程序倾向于达到类似的体系结构。 多年来,这种体系结构有多种名称。 最初的名称之一是六边形体系结构,然后是端口 - 适配器。 最近,它被称为洋葱体系结构或干净体系结构。 此电子书中将后一种名称“干净体系结构”用作此体系结构的名称。 在此关系图中,依赖关系流向最里面的圆。 “应用程序内核”因其位于此关系图的核心位置而得名。 从关系图上可见,该应用程序内核在其他应用程序层上没有任何依赖项。 应用程序的实体和接口位于正中心。 在外圈但仍在应用程序核心中的是域服务,它通常实现内圈中定义的接口。 在应用程序内核外面,UI 和基础结构层均依赖于应用程序内核,但不一定彼此依赖。

June 7, 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

The Importance of SOLID Design Principles

Author: Stephen Watts SOLID is a popular set of design principles that are used in object-oriented software development. SOLID is an acronym that stands for five key design principles: single responsibility principle, open-closed principle, Liskov substitution principle, interface segregation principle, and dependency inversion principle. All five are commonly used by software engineers and provide some important benefits for developers. The SOLID principles were developed by Robert C. Martin in a 2000 essay, “Design Principles and Design Patterns,” although the acronym was coined later by Michael Feathers. In his essay, Martin acknowledged that successful software will change and develop. As it changes, it becomes increasingly complex. Without good design principles, Martin warns that software becomes rigid, fragile, immobile, and viscous. The SOLID principles were developed to combat these problematic design patterns. ...

April 19, 2023

里氏替换原则Liskov substitation principle

为什么叫里氏替换原则? 里氏替换原则在SOLID这五个设计原则中是比较特殊的存在:如果违反了里氏替换原则,不只是降低软件设计的优雅性,很可能会导致Bug。 里氏替换原则译自Liskov substitution principle。Liskov是一位计算机科学家,也就是Barbara Liskov,麻省理工学院教授,也是美国第一个计算机科学女博士,师从图灵奖得主John McCarthy教授,人工智能概念的提出者。 Robert Martin在《敏捷软件开发:原则、模式与实践》一书中对原论文的解读:子类型(subtype)必须能够替换掉他们的基类型(base type)。这个是更简明的一种表述。 违背 LSP 原则的一个简单示例 一个非常明显地违背 LSP原则的示例就是使用 RTTI(Run Time Type Identification)来根据对象类型选择函数执行。 void DrawShape(const Shape& s) { if (typeid(s) == typeid(Square)) DrawSquare(static_cast<Square&>(s)); else if (typeid(s) == typeid(Circle)) DrawCircle(static_cast<Circle&>(s)); } 正方形和长方形,违背原则的微妙之处 很多情况下对 LSP 原则的违背方式都十分微妙。设想在一个应用程序中使用了 Rectangle 类,描述如下: public class Rectangle { private double _width; private double _height; public void SetWidth(double w) { _width = w; } public void SetHeight(double w) { _height = w; } public double GetWidth() { return _width; } public double GetHeight() { return _height; } } 违反里氏替换原则的危害 当我们违反了这一原则会带来有一些危害:反直觉。期望所有子类行为是一致的,但如果不一致可能需要文档记录,或者在代码跑失败后涨此知识;不可读。如果子类行为不一致,可能需要不同的逻辑分支来适配不同的行为,徒增代码复杂度;不可用。可能出错的地方终将会出错。 ...

April 19, 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