图片gif无法查看,请查看原文至博客园查看详情。
目录
目录
ABPHelper.CLI
Scriban
通过Microsoft.Extensions.FileProviders.Embedded获取嵌入资源
通过静态方法获取文件内容
使用Microsoft.Extensions.FileProviders.Physical获取文件内容
Microsoft.CodeAnalysis.CSharp
Humanizer.Core
System.CommandLine
Elsa
入门
使用指南
命令行
技术点如下
AbpHelper.GUI
ABPHelper.CLI
AbpHelper is a tool that helps you with developing Abp vNext applications.
https://github.com/EasyAbp/AbpHelper.CLI
命令行CLI实现ABP VNEXT中CRUD代码的生成,用户只需要创建一个实体类,即可生成该表的CRUD,并添加到项目中。
使用前请确保备份您的源文件!
入门
安装 AbpHelper CLI 工具
dotnet tool install EasyAbp.AbpHelper -g
如果您更喜欢GUI,那么还有一个UI工具: AbpHelper.GUI
如果以前安装过,请使用以下命令更新它:
dotnet tool update EasyAbp.AbpHelper -g
使用 ABP CLI 创建一个ABP 应用
abp new MyToDo
创建实体
public class Todo : FullAuditedEntity<Guid>
{public string Content { get; set; }public bool Done { get; set; }
}
运行 AbpHelper
abphelper generate crud Todo -d C:\MyTodo
generate crud
是生成CRUD文件的子命令Todo
指定了我们先前创建的实体名-d
指定了由ABP CLI创建的ABP项目的根目录
AbpHelper 将生成所有的CRUD , 甚至包括添加迁移和数据库更新!
运行这个
DbMigrator
项目去迁移数据库启动你的应用
用默认的管理员帐户登录,看到神奇的事发生了!
如果看不到 TODO 菜单,请检查您的权限并确保授予了TODO相关的权限
使用指南
运行
abphelper -h
查看帮助类似地,您可以使用
-h
或--help
选项查看以下每个命令的详细用法
命令行
generate
为ABP项目生成文件. 使用 'abphelper generate --help' 获取详情
crud
根据指定实体生成一组与CRUD相关的文件
abphelper generate crud Todo
service
根据指定的名称生成服务接口和类文件
abphelper generate service Project -f Project
methods
Generate service method(s) according to the specified name(s)
根据指定名称,给service 增加方法
abphelper generate methods Login Logout -s Project
localization
Generate localization item(s) according to the specified name(s)
根据指定名称生成localization 本地化项
abphelper generate localization MyItem1 MyItem2 MyItem3
controller
abphelper generate controller Todo
Generate controller class and methods according to the specified service
技术点如下
Scriban
Microsoft.Extensions.FileProviders.Embedded
Microsoft.CodeAnalysis.CSharp
System.CommandLine
Elsa
Humanizer.Core
如果我们想实现代码生成器,我们需要解决什么问题呢。
提供.NET接口的模板引擎,比如Razor,Sciban等
模板一般放在文件中,我们需要知道如何读取这些资源文件,文本文件。
如果我们使用code first,通常需要创建一个实体类,当创建好一个类后,怎么解析出这个类名,属性,命名空间呢,而不是另外去输入这些参数。(只需要代码的路径)
实体名Name,比如BaseItem
命名空间NameSpace LinCms.Base.BaseItems或Volo.Abp.BaseItems
主键类型PrimaryKey,比如是Guid,还是int,还是long
明确有哪些变量,如何控制输入。
模板的位置TemplatePath :./Templates
根据模板生成的代码的输出目录OutputDirectory :相对路径 或 绝对路径 ./output 或 D:/code/github/code-scaffolding
Scriban
Scriban是一种快速、强大、安全和轻量级的文本模板语言和.NET引擎,具有解析liquid模板的兼容模式
【翻译】Scriban是一种快速、强大、安全和轻量级的文本模板语言和.NET引擎,具有解析liquid模板的兼容模式
【翻译】 Scriban language( 待完成)
【翻译】Scriban runtime( 待完成)
创建一个xunit测试项目,引入包Sciban
<PackageReference Include="Scriban" Version="3.0.0-alpha.3" />
做一个小测试
[Fact]
public void Test1()
{var template = Template.Parse("Hello {{name}}!");var result = template.Render(new { Name = "World" });Assert.Equal("Hello World!", result);
}
ctrl+r ctrl+t 运行测试,正常。
写一个我们仓储接口。
[Fact]public void Test9(){var template = Template.Parse(@"using LinCms.Core.Entities;
namespace LinCms.Core.IRepositories
{public interface I{{ entity_name }}Repository : IAuditBaseRepository<{{ entity_name }}>{}
}");var result = template.Render(new { EntityName = "Doc" });Assert.Equal(@"using LinCms.Core.Entities;
namespace LinCms.Core.IRepositories{public interface IDocRepository : IAuditBaseRepository<Doc>{}}".Replace("\r\n", "").Replace(" ", ""), result.Replace("\r\n", "").Replace(" ", ""));}
最终生成的效果是
using LinCms.Core.Entities;
namespace LinCms.Core.IRepositories
{public interface IDocRepository : IAuditBaseRepository<Doc>{}
}
通过Microsoft.Extensions.FileProviders.Embedded获取嵌入资源
这是一个嵌入资源Provider,提供嵌入资源的获取,比如我们写的Sciban的模板文件。
xunit测试项目引入包
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.6" />
创建一个测试类FileTest
[Fact]
public void FileProviderTest()
{IFileProvider fileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(typeof(FileTest)));
}
出现这个错 System.InvalidOperationException:“Could not load the embedded file manifest 'Microsoft.Extensions.FileProviders.Embedded.Manifest.xml' for assembly 'OvOv.Test'.”
打开OvOv.Test.csproject PropertyGroup增加如下一行<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
新建目录Templates,新建文本文件 IRepository.txt,右键属性,生成操作(嵌入的资源),可不选复制到输出目录
using LinCms.Core.Entities;
namespace LinCms.Core.IRepositories
{public interface I{{ entity_ame }}Repository : IAuditBaseRepository<{{ entity_ame }}>{}
}
可修改csproject文件设置Templates目录下都是嵌入式资源
<ItemGroup><EmbeddedResource Include="Templates\**\**" />
</ItemGroup>
在测试方法中,通过GetFileInfo,得到IFileInfo.通过stream进行读取文本,并输出。
private readonly ITestOutputHelper output;
public FileTest(ITestOutputHelper output)
{this.output = output;
}[Fact]
public void GetTextTest()
{IFileProvider fileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(typeof(FileTest)));IFileInfo fileInfo = fileProvider.GetFileInfo("./Templates/IRepository.txt");string text;using (var stream = fileInfo.CreateReadStream()){using (var streamReader = new StreamReader(stream, Encoding.UTF8, true)){text = streamReader.ReadToEnd();}}output.WriteLine(text);
}
通过静态方法获取文件内容
不用嵌入式资源,需要右键文件 属性(复制到输出目录:如果较新则复制),可不选生成操作
[Fact]
public void ReadAllText()
{string text = File.ReadAllText("./Templates/IRepository.txt");output.WriteLine(text);
}
使用Microsoft.Extensions.FileProviders.Physical获取文件内容
引用包
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="3.1.6" />
[Theory]
[InlineData("./Templates/IRepository.txt")]
public void PhysicalFileProviderReadText(string path)
{var current = Environment.CurrentDirectory;var fileProvider = new PhysicalFileProvider(current);IFileInfo fileInfo = fileProvider.GetFileInfo(path);string text;using (var stream = fileInfo.CreateReadStream()){using (var streamReader = new StreamReader(stream, Encoding.UTF8, true)){text = streamReader.ReadToEnd();}}output.WriteLine(text);
}
var current = Environment.CurrentDirectory; 这行代码,能得到当前的目录,因为设置为输出 ,所以当前目录下有templates文件夹,并有IRepository.txt
"D:\\code\\gitee\\Code\\OvOv.Test\\bin\\Debug\\netcoreapp3.1"
Microsoft.CodeAnalysis.CSharp
代码生成还需要什么呢,创建一个实体类,根据此实体类生成表的CRUD代码。
修改OvOv.Test,引入包
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
这个包是https://github.com/dotnet/roslyn的一部分.Roslyn 是.NET编译器为C#和Visual Basic 提供了丰富的代码分析API。
再搞个CodeAnalysisTest测试类。这里我们有一个字符串,他是一个实体类,这个类有一些特点。比如
命名空间namespace
类名 class
继承的父类泛型类型 guid
有二个属性,author,title
如下代码,通过语法树,解析出这个字符串中的命名空间,输出 LinCms.Books。
说明: FullAduitEntity是一个包含CRUD的审计实体类,通常包含七个字段(创建人,创建时间,修改人,修改时间,是否删除,删除人,删除时间),另外还有一个主键。默认是long,具体可查看此文件https://github.com/luoyunchong/lin-cms-dotnetcore/blob/master/src/LinCms.Core/Entities/FullAduitEntity.cs
你也可以使用诸如Entity<Guid>,即一个泛型的实体类即可。
public class Entity<T>
{public T Id { get; set; }
}
[Fact]
public void GetNamespace()
{string text = @"
namespace LinCms.Books
{public class Book : FullAduitEntity<Guid>{public string Author { get; set; }public string Title { get; set; }}
}";SyntaxTree tree = CSharpSyntaxTree.ParseText(text);CompilationUnitSyntax root = tree.GetCompilationUnitRoot();var @namespace = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>().Single().Name.ToString();output.WriteLine(@namespace);
}
获取类名.className为Book
ClassDeclarationSyntax classDeclarationSyntax = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
string className = classDeclarationSyntax.Identifier.ToString();
获取父类baseType为FullAduitEntity 主键类型primaryKey值为Guid
BaseListSyntax baseList = classDeclarationSyntax.BaseList!;
var genericNameSyntax = baseList.DescendantNodes().OfType<SimpleBaseTypeSyntax>().First(node => !node.ToFullString().StartsWith("I")) // Not interface.DescendantNodes().OfType<GenericNameSyntax>().FirstOrDefault();string baseType;
string? primaryKey;
if (genericNameSyntax == null)
{// No generic parameter -> Entity with Composite KeysbaseType = baseList.DescendantNodes().OfType<SimpleBaseTypeSyntax>().Single().Type.ToString();primaryKey = "long";
}
else
{// Normal entitybaseType = genericNameSyntax.Identifier.ToString();primaryKey = genericNameSyntax.DescendantNodes().OfType<TypeArgumentListSyntax>().Single().Arguments[0].ToString();
}
获取该类的属性集合。
var properties = root.DescendantNodes().OfType<PropertyDeclarationSyntax>().Select(prop => new PropertyInfo(prop.Type.ToString(), prop.Identifier.ToString())).ToList();
其中PropertyInfo是用来存储属性集合的实体类
public class PropertyInfo
{public string Type { get; }public string Name { get; }public PropertyInfo(string type, string name){Type = type;Name = name;}
}
我们通过debugger查看局部变量。
Humanizer.Core
Humanizer可以用来处理strings, enums, dates, times, timespans, numbers and quantities所有的需求。
https://github.com/Humanizr/Humanizer
当我们写代码时,总避免不了写复数形式的代码,一些特殊的后缀不是直接加s就行的。所以可以用Humanizer来处理这些特殊的变量
转下划线 Underscore
复数形式(比如取集合数据时,变量名) Pluralize
转小驼峰写法(比如变量) Camelize
更多直接看README
通过扩展方法生成这些字符串。
public class EntityInfo
{public EntityInfo(string name){Name = name;}public string Name { get; }/// <summary>/// 复数/// </summary>public string NamePluralized => Name.Pluralize();/// <summary>/// 首字母小写/// </summary>public string NameCamelize => Name.Camelize();/// <summary>/// 小写+复数/// </summary>public string NameCamelizePluralized => Name.Camelize().Pluralize();}
System.CommandLine
https://github.com/dotnet/command-line-api
System.CommandLine是一组用于构建命令行应用程序的库,包括解析,调用和渲染。
他能简化命令行参数的处理,帮助我们构建自己的CLI。
对于代码生成器,不要是必须的。
Elsa
https://github.com/elsa-workflows/elsa-core
Elsa Core是一个工作流库,可在任何 .NET Core应用程序中执行工作流。工作流可以不仅使用代码来定义,也可作为JSON,YAML或XML。
代码生成器,流程多,可使用此程序工作流来处理。不是必须的。
AbpHelper.GUI
https://github.com/EasyAbp/AbpHelper.GUI
AbpHelper is a tool that helps you with developing Abp vNext applications. It can be used to call ABP CLI, generate code, manage modules, etc.
ABP VNEXT的代码生成的可视化界面,使用方式看README就好,不多介绍。
帮助ABP VNext的开发者快速构建单表的CRUD的。
如果你自己研究一下这些类库的使用方法,特别是Sciban。我们也能实现代码生成器,帮助改善公司及自己已有项目的开发流程。