使用VS 插件和 Roslyn 从项目实体类分别自动生成DTO 类 接口类 应用类

 前言

项目使用的ABP vNext 开发,需要将 Entity 转换成Output, Input 等DTO,接着还要根据模块生成 IAppService 和 AppService,MapProfile 等等一系列的文件和方法,这些重复机械的工作,做下来还没正式写代码,就搞到见到 Entity 都头痛。于是想想,看看网上有没有生成 DTO 的工具,发现有 52ABP 代码生成器,它是做成 VS 的插件形式来工作的,很不幸的,不知道什么原因在 VS2022里面用不到,各种报错;可能年月太久了,到Github上面看到很多留言主理人都没有回复。但是给了我一个思路,要不自己做一个试试。

虽然是没做过 VS 插件,不过学习新知识要比重复机械的工作有趣得多,于是乎就开始上网找资料学习。网上关于 VS 插件的文章真不多,巨硬的官方教程简直就是简陋,不管再少再简陋,比起重复机械的工作,只要有挑战我都能够充满热情,直接开干。

先放最终效果图:

 

 

 

一开始我是使用标准的 VSIX 模板,也完成了,后来发现了写 VS 插件的一个利器  Extensibility Essentials 2022 ,是一个扩展包,在VS - 扩展 - 管理扩展 - 联机 里面,安装一下就好了

 

 

 

安装完之后,在新建 VSIX 项目的时候就会看到有 Community 结尾的项目模板:

 

如何使用 Extensibility Essentials

//官方方法
DTE2 dte2 = Package.GetGlobalService(typeof(DTE)) as DTE2;

// Extensibility 直接调用 VS 这个关键字里面封装好的方法就可以
var fAllProjects = awaite VS.Solutions.GetAllProjectsAsync()

更多关于 Extensibility Essentials,请参考官方网站,另外强烈建议下载里面的 PDF 里面写得很详细,看完基本都懂。

 

接下来我们开始:

1.新建 VSIX 项目

 

 

  •  选择 VSIX Project w/Command (Community) 的启动模板。
  • 新建一个WPF的窗体
  • Packge不需要修改
复制代码
 1 [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
 2     [InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)]
 3     [ProvideMenuResource("Menus.ctmenu", 1)]
 4     [Guid(PackageGuids.EntityGenerateString)]
 5     public sealed class EntityGeneratePackage : ToolkitPackage
 6     {
 7         protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
 8         {
 9             //await (new MainWindowsControl()).ShowDialogAsync(); --- 这样写的话之后在第一次打开初始化的时候触发,可以打开,但因为这是单实例所以关闭窗口之后再打开不能在触发了
10             await this.RegisterCommandsAsync();
11          12         }
13     }
复制代码
  • Command:添加触发窗体启动
[Command("7a47431e-f20c-4288-9f2b-a7cf847b0a8a", 0x0100)]
    internal sealed class Command1 : BaseCommand<Command1>
    {
        protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
        {
            await (new MainWindowsControl()).ShowDialogAsync();
        }
    }

注意 :[Command("7a47431e-f20c-4288-9f2b-a7cf847b0a8a", 0x0100)] 里面要和 VSCT 文件里 的 GUID 和 Id 一一对应,否则不会触发。

 

  • VSCT :这个文件管理插件放在 VS 哪个位置和图标,我这里是 选择文件夹 - 右键 的菜单里面 
复制代码
<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <Extern href="stdidcmd.h"/>
  <Extern href="vsshlids.h"/>
  <Include href="KnownImageIds.vsct"/>
  <Include href="VSGlobals.vsct"/>

  <Commands package="EntityGenerate">
    <Groups>
      <Group guid="EntityGenerate" id="MyMenuGroup" priority="0x0600">
        <Parent guid="MainWindowRightClickCmdSet" id="CodeWindowRightClickMenu"/>
      </Group>
    </Groups>
    <Buttons>
      <Button guid="EntityGenerate" id="MainWindowCommandId" priority="0x0100" type="Button">
        <Parent guid="EntityGenerate" id="MyMenuGroup" />
        <Icon guid="ImageCatalogGuid" id="StatusInformation" />
        <CommandFlag>IconIsMoniker</CommandFlag>
        <Strings>
          <ButtonText>生成Dto,Application</ButtonText>
        </Strings>
      </Button>
    </Buttons>
  </Commands>

  <Symbols>
    <GuidSymbol name="EntityGenerate" value="{7a47431e-f20c-4288-9f2b-a7cf847b0a8a}">
      <IDSymbol name="MyMenuGroup" value="0x1020" />
      <IDSymbol name="MainWindowCommandId" value="0x0100" />
    </GuidSymbol>

    <GuidSymbol name="MainWindowRightClickCmdSet" value="{D309F791-903F-11D0-9EFC-00A0C911004F}">
      <IDSymbol name="CodeWindowRightClickMenu" value="0x0431" />
    </GuidSymbol>
  </Symbols>
</CommandTable>
复制代码

菜单效果:

 

 

 

更多关于 VSIX 插件的文章可以看看博友们写的文章。

 

2.使用 Roslyn 

一开始想得很简单反射一下就好了,后面一想,这个只能获取到 Entity 类的文件....那么 Roslyn 这个大杀器就要出场了,Roslyn 我就不介绍了,就是微软出的用来自动分析代码的工具。

安装 Microsoft.CodeAnalysis.CSharp 这个包,200多M......

  • 新建一个继承 CSharpSyntaxWalker 的类,里面有很多可以 override 的方法,每个方法对应的是获取节点的时机,把一个 Class 解释成 Tree ,然后不断的访问里面的 node
复制代码
 public class ClassCollector : CSharpSyntaxWalker
    {
        public readonly Dictionary<string, List<DtoPropertyTemplate>> Models = new Dictionary<string, List<DtoPropertyTemplate>>();
        public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>();

        public override void VisitUsingDirective(UsingDirectiveSyntax node)
        {
            this.Usings.Add(node);
        }

        public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        {
            var classnode = node.Parent as ClassDeclarationSyntax;
            if (!Models.ContainsKey(classnode.Identifier.ValueText))
            {
                Models.Add(classnode.Identifier.ValueText, new List<DtoPropertyTemplate>());
            }
            Models[classnode.Identifier.ValueText].Add(new DtoPropertyTemplate
            {
                CSharpType = node.ChildNodesAndTokens().Where(p => p.Kind().ToString()=="NullableType"||p.Kind().ToString()=="PredefinedType"||p.Kind().ToString()=="IdentifierName").FirstOrDefault().ToFullString(),
                Name = node.Identifier.ValueText,
                Attributes = node.AttributeLists.ToArray(),
            });
        }
    }
复制代码

上面代码把 .cs 文件里面的 Namespace,Using, ClassName,Property Name ,Property Type  ,Attributes 等加载出来,这样就可以得到一个我们需要的 Class 的全部信息。

  • 生成 DTO
复制代码
 1  public void GenerationDto(string code, DtoTemplate dtoTemplate)
 2         {
 3             var fCSharpSyntaxTree = CSharpSyntaxTree.ParseText(code);
 4             //SyntaxTree的根root
 5             var fCodeSyntaxTreeRoot = (CompilationUnitSyntax)fCSharpSyntaxTree.GetRoot();
 6             //member
 7             var fTreeRootFirstmember = fCodeSyntaxTreeRoot.Members[0];
 8             //命名空间Namespace
 9             var fCodeNamespace = (NamespaceDeclarationSyntax)fTreeRootFirstmember;
10             //类 class
11             var fProgramDeclaration = (ClassDeclarationSyntax)fCodeNamespace.Members[0];
12 
13             #region 生成Class
14 
15             var ClassCollector = new ClassCollector();
16 
17             ClassCollector.Visit(fProgramDeclaration);
18 
19             foreach (var fClass in ClassCollector.Models)
20             {
21                 string fClassName = $"{fClass.Key.Replace("_", "")}{dtoTemplate.DtoName}";
22                 // Create a namespace: (namespace CodeGenerationSample)
23                 var fNamespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName($"{dtoTemplate.Namespace}.{dtoTemplate.ApplicationName}.Contracts")).NormalizeWhitespace();
24 
25                 // Add System using statement: (using System)
26                 fNamespace = fNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")),
27                     SyntaxFactory.UsingDirective(SyntaxFactory.ParseName($"System.ComponentModel.DataAnnotations")));
28 
29                 //  Create a class: (class Order)
30                 var fClassDeclaration = SyntaxFactory.ClassDeclaration(fClassName);
31 
32                 // Add the public modifier: (public class Order)
33                 fClassDeclaration = fClassDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));
34 
35                 // Inherit BaseEntity<T> and implement IHaveIdentity: (public class Order : BaseEntity<T>, IHaveIdentity)
36                 fClassDeclaration = fClassDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(dtoTemplate.BaseClass)));
37 
38                 foreach (var fProperties in fClass.Value)
39                 {
40                     // Create a Property: (public int Quantity { get; set; })
41                     var fPropertyDeclaration = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName(fProperties.CSharpType), fProperties.Name)
42                     .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
43                     .AddAccessorListAccessors(
44                         SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
45                         SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)))
46                     .AddAttributeLists(fProperties.Attributes);
47 
48                     fClassDeclaration = fClassDeclaration.AddMembers(fPropertyDeclaration);
49                 }
50 
51                 fNamespace = fNamespace.AddMembers(fClassDeclaration);
52 
53                 var fNewClass = fNamespace.NormalizeWhitespace().ToFullString();
54 
55                 FileHelper.WriteTo($"{dtoTemplate.TragetPath}\\{ApplicationName}\\Dto", $"{fClassName}.cs", fNewClass);
56             }
57             #endregion
58         }
复制代码

 

  • 生成 Application
复制代码
 1 public void GenerationApplication(List<string> entities)
 2         {
 3             #region ApplicationService
 4             // Create a namespace: (namespace CodeGenerationSample)
 5             var fNamespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName($"{txbNamespace.Text}.{ApplicationName}")).NormalizeWhitespace();
 6 
 7             // Add System using statement: (using System)
 8             fNamespace = fNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")),
 9                 SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks")),
10                 SyntaxFactory.UsingDirective(SyntaxFactory.ParseName($"{txbNamespace.Text}.{ApplicationName}.Contracts")));
11 
12             //  Create a class: (class Order)
13             var fClassDeclaration = SyntaxFactory.ClassDeclaration($"{ApplicationName}AppService");
14             // Add the public modifier: (public class Order)
15             fClassDeclaration = fClassDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));
16 
17             // Inherit BaseEntity<T> and implement IHaveIdentity: (public class Order : BaseEntity<T>, IHaveIdentity)
18             fClassDeclaration = fClassDeclaration.AddBaseListTypes(
19                 SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(txbAbpApplicationBase.Text)),
20                 SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName($"I{ApplicationName}AppService")));
21 
22             // Create a stament with the body of a method.
23             var fConstructorStatement = SyntaxFactory.ParseStatement("");
24 
25             // 构造函数
26             var fConstructorDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(""), $"{ApplicationName}AppService")
27                 .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
28                 .WithBody(SyntaxFactory.Block(fConstructorStatement));
29 
30             fClassDeclaration = fClassDeclaration.AddMembers(fConstructorDeclaration);
31 
32             // Create a stament with the body of a method.
33             var fDefaultStatement = SyntaxFactory.ParseStatement("throw new System.NotImplementedException();");
34             foreach (var entity in entities)
35             {
36                 var fEntity = entity.Replace("_", "");
37                 //新增
38                 var fFuntionCreateDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName($"async Task<v1Result>"), $"Create{fEntity}Async")
39                 .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
40                 .AddParameterListParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier($"{fEntity.ToCamelCase()}Create")).WithType(SyntaxFactory.ParseTypeName($"{fEntity}Create")))
41                 .WithBody(SyntaxFactory.Block(fDefaultStatement));
42 
43                 //修改
44                 var fFuntionEditDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("async Task<v1Result>"), $"Update{fEntity}Async")
45                 .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
46                 .AddParameterListParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier($"{fEntity.ToCamelCase()}Update")).WithType(SyntaxFactory.ParseTypeName($"{fEntity}Update")))
47                 .WithBody(SyntaxFactory.Block(fDefaultStatement));
48 
49                 //删除
50                 var fFuntionDeleteDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("async Task<v1Result>"), $"Delete{fEntity}Async")
51                 .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
52                 .AddParameterListParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier($"{fEntity.ToCamelCase()}Id")).WithType(SyntaxFactory.ParseTypeName($"Guid")))
53                 .WithBody(SyntaxFactory.Block(fDefaultStatement));
54 
55                 //查询一个
56                 var fFuntionGetDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName($"async Task<{fEntity}Output>"), $"Get{fEntity}Async")
57                 .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
58                 .AddParameterListParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier($"{fEntity.ToCamelCase()}Id")).WithType(SyntaxFactory.ParseTypeName($"Guid")))
59                 .WithBody(SyntaxFactory.Block(fDefaultStatement));
60 
61                 //分页查询
62                 var fFuntionPagedListDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName($"async Task<PagedList<{fEntity}Output>>"), $"PagedList{fEntity}Async")
63                 .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
64                 .AddParameterListParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier($"{fEntity.ToCamelCase()}Query")).WithType(SyntaxFactory.ParseTypeName($"{fEntity}Query")))
65                 .WithBody(SyntaxFactory.Block(fDefaultStatement));
66 
67                 fClassDeclaration = fClassDeclaration.AddMembers(fFuntionCreateDeclaration, fFuntionEditDeclaration, fFuntionDeleteDeclaration, fFuntionGetDeclaration, fFuntionPagedListDeclaration);
68             }
69 
70             fNamespace = fNamespace.AddMembers(fClassDeclaration);
71 
72             var fNewClass = fNamespace.NormalizeWhitespace().ToFullString();
73 
74             FileHelper.WriteTo($"{TargetAppServicePath}\\{ApplicationName}", $"{ApplicationName}AppService.cs", fNewClass);
75             #endregion
76         }
复制代码

 

  • 生成 MappProfile
复制代码
public void GenerationApplicationMapProfile(List<string> entities)
        {
            #region ApplicationService
            // Create a namespace: (namespace CodeGenerationSample)
            var fNamespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName($"{txbNamespace.Text}.{ApplicationName}")).NormalizeWhitespace();

            // Add System using statement: (using System)
            fNamespace = fNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("AutoMapper")),
                SyntaxFactory.UsingDirective(SyntaxFactory.ParseName($"{txbNamespace.Text}.data.{ApplicationName}")),
                SyntaxFactory.UsingDirective(SyntaxFactory.ParseName($"{txbNamespace.Text}.{ApplicationName}.Contracts")));

            //  Create a class: (class Order)
            var fClassDeclaration = SyntaxFactory.ClassDeclaration($"{ApplicationName}MapperProfile");
            // Add the public modifier: (public class Order)
            fClassDeclaration = fClassDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));

            // Inherit BaseEntity<T> and implement IHaveIdentity: (public class Order : BaseEntity<T>, IHaveIdentity)
            fClassDeclaration = fClassDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName($"Profile")));

            // 构造函数
            var fConstructorDeclaration = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(""), $"{ApplicationName}MapperProfile")
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));

            foreach (var entity in entities)
            {
                //Entity to Output
                var fOutputSyntax = SyntaxFactory.ParseStatement($"CreateMap<{entity}, {entity.Replace("_", "")}Output>();");

                //Create to Entity
                var fCreateSyntax = SyntaxFactory.ParseStatement($"CreateMap<{entity.Replace("_", "")}Create, {entity}>();");

                //Update to Entity
                var fUpdateSyntax = SyntaxFactory.ParseStatement($"CreateMap<{entity.Replace("_", "")}Update, {entity}>().ForMember(client => client.Id, opt => opt.Ignore());");

                fConstructorDeclaration = fConstructorDeclaration.AddBodyStatements(fOutputSyntax, fCreateSyntax, fUpdateSyntax);
            }

            fClassDeclaration = fClassDeclaration.AddMembers(fConstructorDeclaration);

            fNamespace = fNamespace.AddMembers(fClassDeclaration);

            var fNewClass = fNamespace.NormalizeWhitespace().ToFullString();

            FileHelper.WriteTo($"{TargetAppServicePath}\\{ApplicationName}", $"{ApplicationName}MapperProfile.cs", fNewClass);
            #endregion
        }
复制代码

 

  • 生成 IAppService,标准的增/删/改/查
复制代码
 1  public void GenerationApplicationContract(List<string> entities)
 2         {
 3             #region ApplicationServiceContracts
 4             // Create a namespace: (namespace CodeGenerationSample)
 5             var fNamespace = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName($"{txbNamespace.Text}.{ApplicationName}.Contracts")).NormalizeWhitespace();
 6 
 7             // Add System using statement: (using System) System.Threading.Tasks
 8             fNamespace = fNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System")),
 9                 SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks")));
10 
11             //  Create a class: (class Order)
12             var fInterfaceDeclaration = SyntaxFactory.InterfaceDeclaration($"I{ApplicationName}AppService");
13             // Add the public modifier: (public class Order)
14             fInterfaceDeclaration = fInterfaceDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));
15 
16             foreach (var entity in entities)
17             {
18                 var fEntity = entity.Replace("_", "");
19                 //新增
20                 var fInterfaceCreateDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName($"Task<v1Result> Create{fEntity}Async({fEntity}Create {fEntity.ToCamelCase()}Create)"));
21                 var fFieldCreateDeclaration = SyntaxFactory.FieldDeclaration(fInterfaceCreateDeclaration);
22 
23                 //修改
24                 var fInterfaceEditDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName($"Task<v1Result> Update{fEntity}Async({fEntity}Update {fEntity.ToCamelCase()}Update)"));
25                 var fFieldEditDeclaration = SyntaxFactory.FieldDeclaration(fInterfaceEditDeclaration);
26 
27                 //删除
28                 var fInterfaceDeleteDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName($"Task<v1Result> Delete{fEntity}Async(Guid {fEntity.ToCamelCase()}Id)"));
29                 var fFieldDeleteDeclaration = SyntaxFactory.FieldDeclaration(fInterfaceDeleteDeclaration);
30 
31                 //查询一个
32                 var fInterfaceGetDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName($"Task<{fEntity}Output> Get{fEntity}Async(Guid {fEntity.ToCamelCase()}Id)"));
33                 var fFieldGetDeclaration = SyntaxFactory.FieldDeclaration(fInterfaceGetDeclaration);
34 
35                 //分页查询
36                 var fInterfacePagedListDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName($"Task<PagedList<{fEntity}Output>> PagedList{fEntity}Async({fEntity}Query {fEntity.ToCamelCase()}Query)"));
37                 var fFieldPagedListDeclaration = SyntaxFactory.FieldDeclaration(fInterfacePagedListDeclaration);
38 
39                 fInterfaceDeclaration = fInterfaceDeclaration.AddMembers(fFieldCreateDeclaration, fFieldEditDeclaration, fFieldDeleteDeclaration, fFieldGetDeclaration, fFieldPagedListDeclaration);
40             }
41 
42             fNamespace = fNamespace.AddMembers(fInterfaceDeclaration);
43 
44             var fNewClass = fNamespace.NormalizeWhitespace().ToFullString();
45 
46             FileHelper.WriteTo($"{TargetContractsPath}\\{ApplicationName}", $"I{ApplicationName}AppService.cs", fNewClass);
47             #endregion
48         }
复制代码

 

posted @   卡叔  阅读(524)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示