ABPHelper.CLI及其依賴項簡單介紹
目錄
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 Projects
-
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增加如下一行
新建目錄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
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 Keys
baseType = baseList.DescendantNodes().OfType<SimpleBaseTypeSyntax>().Single().Type.ToString();
primaryKey = "long";
}
else
{
// Normal entity
baseType = 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所有的需求。
當我們寫程式碼時,總避免不了寫複數形式的程式碼,一些特殊的字尾不是直接加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
System.CommandLine是一組用於構建命令列應用程式的庫,包括解析,呼叫和渲染。
他能簡化命令列引數的處理,幫助我們構建自己的CLI。
對於程式碼生成器,不要是必須的。
Elsa
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。我們也能實現程式碼生成器,幫助改善公司及自己已有專案的開發流程。