ABPHelper.CLI及其依賴項簡單介紹


目錄

ABPHelper.CLI

命令行CLI實現ABP VNEXT中CRUD代碼的生成,用戶只需要創建一個實體類,即可生成該表的CRUD,並添加到項目中。

使用前請確保備份您的源文件!

入門

  1. 安裝 AbpHelper CLI 工具

    dotnet tool install EasyAbp.AbpHelper -g

如果您更喜歡GUI,那么還有一個UI工具: AbpHelper.GUI

  1. 如果以前安裝過,請使用以下命令更新它:

    dotnet tool update EasyAbp.AbpHelper -g

  2. 使用 ABP CLI 創建一個ABP 應用

    abp new MyToDo

  3. 創建實體

public class Todo : FullAuditedEntity<Guid>
{
    public string Content { get; set; }
    public bool Done { get; set; }
}

  1. 運行 AbpHelper

abphelper generate crud Todo -d C:\MyTodo

  • generate crud 是生成CRUD文件的子命令
  • Todo 指定了我們先前創建的實體名
  • -d 指定了由ABP CLI創建的ABP項目的根目錄

AbpHelper 將生成所有的CRUD , 甚至包括添加遷移和數據庫更新!

  1. 運行這個 DbMigrator 項目去遷移數據庫
  2. 啟動你的應用
  3. 用默認的管理員帳戶登錄,看到神奇的事發生了!

運行demo

如果看不到 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

如果我們想實現代碼生成器,我們需要解決什么問題呢。

  1. 提供.NET接口的模板引擎,比如Razor,Sciban等
  2. 模板一般放在文件中,我們需要知道如何讀取這些資源文件,文本文件。
  3. 如果我們使用code first,通常需要創建一個實體類,當創建好一個類后,怎么解析出這個類名,屬性,命名空間呢,而不是另外去輸入這些參數。(只需要代碼的路徑)
    1. 實體名Name,比如BaseItem
    2. 命名空間NameSpace LinCms.Base.BaseItems或Volo.Abp.BaseItems
    3. 主鍵類型PrimaryKey,比如是Guid,還是int,還是long
  4. 明確有哪些變量,如何控制輸入。
    1. 模板的位置TemplatePath : ./Templates
    2. 根據模板生成的代碼的輸出目錄OutputDirectory : 相對路徑 或 絕對路徑 ./outputD:/code/github/code-scaffolding

Scriban

Scriban是一種快速、強大、安全和輕量級的文本模板語言和.NET引擎,具有解析liquid模板的兼容模式

創建一個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增加如下一行
true

新建目錄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

ABP VNEXT的代碼生成的可視化界面,使用方式看README就好,不多介紹。

幫助ABP VNext的開發者快速構建單表的CRUD的。

如果你自己研究一下這些類庫的使用方法,特別是Sciban。我們也能實現代碼生成器,幫助改善公司及自己已有項目的開發流程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM