zoukankan      html  css  js  c++  java
  • 基于vs插件的abp代码生成器

      工作了这么多年,一直都在小公司摸爬滚打,对于小公司而言,开发人员少,代码风格五花八门。要想用更少的人,更快的速度,开发更规范的代码,那自然离不开代码生成器。之前用过动软的,也用过T4,后面又接触了力软。相较而言,力软的代码生成做的体验还是很不错的(不是给他打广告哈)。最近在看abp,发现要按他的规范来开发的话,工作量还是蛮大的,所以他们官方也开发了配套的代码生成器,不过都要收费。国内这块好像做的好点的就52abp了,还有个Magicodes.Admin。前者是类似于官方的做成了vs插件,还比较好用,后者是线上的,据说是生成后可以同步到git仓库,咱也没用过,所以也不清楚好不好用。前段时间稍微空闲点,就参考Magicodes.Admin和52abp搭了个框子,顺便也研究了下基于vs插件的代码生成器,abp的代码生成器也可以做成力软那样的,只不过需要用户先update-database数据库而已,代码生成部分原理都差不多,这里就不提了,这里主要是记录下vs插件开发代码生成器的过程。

    先上下框子截图:

    开发过程:

    新建VS插件项目

    1、新建项目

    这里我们要新建VSIX Project

    2、建好项目后,右键添加新建项,这里我们选Custom Command

    添加好了后,我们修改Command1Package.vsct这个文件:

    这里改的是菜单显示的文字,然后我们可以F5运行起来瞧瞧。F5运行后,会另外开启一个vs,如下图:

    默认的菜单会被添加到“工具”这个菜单栏中,如下图:

    咱们要做代码生成器,肯定不是希望把菜单加在这里的,那要怎么改呢?  还是刚才那个文件,具体位置在:

        <Groups>
          <Group guid="guidCommand1PackageCmdSet" id="MyMenuGroup" priority="0x0600">
            <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
          </Group>
        </Groups>

    关于这个id,几个常用的有下面几个:

    IDM_VS_CTXT_SOLNNODE  是指的解决方案资源管理器里的解决方案
    
    IDM_VS_CTXT_SOLNFOLDER    是指的解决方案资源管理器里的 解决方案里的文件夹,不是项目里的哈,这个文件夹是虚拟的,没有实际的文件夹映射
    
    IDM_VS_CTXT_PROJNODE  是指的解决方案资源管理器里的项目
    
    IDM_VS_CTXT_FOLDERNODE  是指的解决方案资源管理器里的项目里的文件夹
    
    IDM_VS_CTXT_ITEMNODE  是指的解决方案资源管理器里的项目里的项,就例如cs、js文件

    我们这里要用的就是"IDM_VS_CTXT_ITEMNODE",改完后我们再F5运行下,这个时候我们要打开一个项目了。右键点击瞧瞧(上面那个abp代码生成器是我之前做的,忽略哈):

    好了,要的就是这个效果,接下来就要开始做代码生成的了。

    代码生成

    代码生成主要分为三个步骤,1、获取所选文件以及当前项目基本信息。2、生成后端代码。3、生成前端代码

    1、获取所选文件以及当前项目基本信息

    做VS插件,离不了DTE2这个类,具体的可参考:https://docs.microsoft.com/en-us/dotnet/api/envdte._dte?view=visualstudiosdk-2017

     首先我们要获取DTE2实例,我们打开Command1Package.cs这个类修改初始化方法:

    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
            {
                // When initialized asynchronously, the current thread may be a background thread at this point.
                // Do any initialization that requires the UI thread after switching to the UI thread.
                await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
    
                DTE2 _dte = await GetServiceAsync(typeof(DTE)) as DTE2;
                await AbpCustomCommand.InitializeAsync(this, _dte);
            }

    同时修改Command1.cs的初始化方法:

            public static DTE2 _dte;
            /// <summary>
            /// Initializes the singleton instance of the command.
            /// </summary>
            /// <param name="package">Owner package, not null.</param>
            public static async Task InitializeAsync(AsyncPackage package, DTE2 dte)
            {
                _dte = dte;
                // Switch to the main thread - the call to AddCommand in Command1's constructor requires
                // the UI thread.
                await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
                
                OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService;
                Instance = new Command1(package, commandService);
            }

    获取到了DTE2实例了,我们就可以开始获取我们要的基本信息了,我们在Command1.cs类的Execute方法中加入下面代码(注释写的都比较清楚,就不多写了):

    #region 获取出基础信息
                        //获取当前点击的类所在的项目
                        Project topProject = selectProjectItem.ContainingProject;
                        //当前类在当前项目中的目录结构
                        string dirPath = GetSelectFileDirPath(topProject, selectProjectItem);
    
                        //当前类命名空间
                        string namespaceStr = selectProjectItem.FileCodeModel.CodeElements.OfType<CodeNamespace>().First().FullName;
                        //当前项目根命名空间
                        string applicationStr = "";
                        if (!string.IsNullOrEmpty(namespaceStr))
                        {
                            applicationStr = namespaceStr.Substring(0, namespaceStr.IndexOf("."));
                        }
                        //当前类
                        CodeClass codeClass = GetClass(selectProjectItem.FileCodeModel.CodeElements);
                        //当前项目类名
                        string className = codeClass.Name;
                        //当前类中文名 [Display(Name = "供应商")]
                        string classCnName = "";
                        //当前类说明 [Description("品牌信息")]
                        string classDescription = "";
                        //获取类的中文名称和说明
                        foreach (CodeAttribute classAttribute in codeClass.Attributes)
                        {
                            switch (classAttribute.Name)
                            {
                                case "Display":
                                    if (!string.IsNullOrEmpty(classAttribute.Value))
                                    {
                                        string displayStr = classAttribute.Value.Trim();
                                        foreach (var displayValueStr in displayStr.Split(','))
                                        {
                                            if (!string.IsNullOrEmpty(displayValueStr))
                                            {
                                                if (displayValueStr.Split('=')[0].Trim() == "Name")
                                                {
                                                    classCnName = displayValueStr.Split('=')[1].Trim().Replace(""", "");
                                                }
                                            }
                                        }
                                    }
                                    break;
                                case "Description":
                                    classDescription = classAttribute.Value;
                                    break;
                            }
                        }
    
                        //获取当前解决方案里面的项目列表
                        List<ProjectItem> solutionProjectItems = GetSolutionProjects(_dte.Solution);
                        #endregion

    上面用到了几个辅助方法:

            #region 辅助方法
            /// <summary>
            /// 获取所有项目
            /// </summary>
            /// <param name="projectItems"></param>
            /// <returns></returns>
            private IEnumerable<ProjectItem> GetProjects(ProjectItems projectItems)
            {
                foreach (EnvDTE.ProjectItem item in projectItems)
                {
                    yield return item;
    
                    if (item.SubProject != null)
                    {
                        foreach (EnvDTE.ProjectItem childItem in GetProjects(item.SubProject.ProjectItems))
                            if (childItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems)
                                yield return childItem;
                    }
                    else
                    {
                        foreach (EnvDTE.ProjectItem childItem in GetProjects(item.ProjectItems))
                            if (childItem.Kind == EnvDTE.Constants.vsProjectItemKindSolutionItems)
                                yield return childItem;
                    }
                }
            }
    
            /// <summary>
            /// 获取解决方案里面所有项目
            /// </summary>
            /// <param name="solution"></param>
            /// <returns></returns>
            private List<ProjectItem> GetSolutionProjects(Solution solution)
            {
                List<ProjectItem> projectItemList = new List<ProjectItem>();
                var projects = solution.Projects.OfType<Project>();
                foreach (var project in projects)
                {
                    var projectitems = GetProjects(project.ProjectItems);
    
                    foreach (var projectItem in projectitems)
                    {
                        projectItemList.Add(projectItem);
                    }
                }
    
                return projectItemList;
            }
    
            /// <summary>
            /// 获取类
            /// </summary>
            /// <param name="codeElements"></param>
            /// <returns></returns>
            private CodeClass GetClass(CodeElements codeElements)
            {
                var elements = codeElements.Cast<CodeElement>().ToList();
                var result = elements.FirstOrDefault(codeElement => codeElement.Kind == vsCMElement.vsCMElementClass) as CodeClass;
                if (result != null)
                {
                    return result;
                }
                foreach (var codeElement in elements)
                {
                    result = GetClass(codeElement.Children);
                    if (result != null)
                    {
                        return result;
                    }
                }
                return null;
            }
    
            /// <summary>
            /// 获取当前所选文件去除项目目录后的文件夹结构
            /// </summary>
            /// <param name="selectProjectItem"></param>
            /// <returns></returns>
            private string GetSelectFileDirPath(Project topProject, ProjectItem selectProjectItem)
            {
                string dirPath = "";
                if (selectProjectItem != null)
                {
                    //所选文件对应的路径
                    string fileNames = selectProjectItem.FileNames[0];
                    string selectedFullName = fileNames.Substring(0, fileNames.LastIndexOf('\'));
    
                    //所选文件所在的项目
                    if (topProject != null)
                    {
                        //项目目录
                        string projectFullName = topProject.FullName.Substring(0, topProject.FullName.LastIndexOf('\'));
    
                        //当前所选文件去除项目目录后的文件夹结构
                        dirPath = selectedFullName.Replace(projectFullName, "");
                    }
                }
    
                return dirPath.Substring(1);
            }
    
            /// <summary>
            /// 添加文件到项目中
            /// </summary>
            /// <param name="folder"></param>
            /// <param name="content"></param>
            /// <param name="fileName"></param>
            private void AddFileToProjectItem(ProjectItem folder, string content, string fileName)
            {
                try
                {
                    string path = Path.GetTempPath();
                    Directory.CreateDirectory(path);
                    string file = Path.Combine(path, fileName);
                    File.WriteAllText(file, content, System.Text.Encoding.UTF8);
                    try
                    {
                        folder.ProjectItems.AddFromFileCopy(file);
                    }
                    finally
                    {
                        File.Delete(file);
                    }
                }
                catch (Exception ex)
                {
    
                }
            }
    
            /// <summary>
            /// 添加文件到指定目录
            /// </summary>
            /// <param name="directoryPathOrFullPath"></param>
            /// <param name="content"></param>
            /// <param name="fileName"></param>
            private void AddFileToDirectory(string directoryPathOrFullPath, string content, string fileName = "")
            {
                try
                {
                    string file = string.IsNullOrEmpty(fileName) ? directoryPathOrFullPath : Path.Combine(directoryPathOrFullPath, fileName);
                    File.WriteAllText(file, content, System.Text.Encoding.UTF8);
                }
                catch (Exception ex)
                {
    
                }
            }
            #endregion

     2、生成后端代码

    具体代码生成这里用到了razor引擎,我们先配置razor引擎:

            private void InitRazorEngine()
            {
                var config = new TemplateServiceConfiguration
                {
                    TemplateManager = new EmbeddedResourceTemplateManager(typeof(Template))
                };
                Engine.Razor = RazorEngineService.Create(config);
            }

    然后在Command1.cs的构造函数里面初始化razor引擎。接着按照我们需要的项目结构来构建生成流程,具体如下:

                        //1.同级目录添加 Authorization 文件夹
                        //2.往新增的 Authorization 文件夹中添加 xxxPermissions.cs 文件 
                        //3.往新增的 Authorization 文件夹中添加 xxxAuthorizationProvider.cs 文件
                        //4.往当前项目根目录下文件夹 Authorization 里面的AppAuthorizationProvider.cs类中的SetPermissions方法最后加入 SetxxxPermissions(pages); 
                        //5.往xxxxx.Application项目中增加当前所选文件所在的文件夹
                        //6.往第五步新增的文件夹中增加Dto目录
                        //7.往第六步新增的Dto中增加CreateOrUpdatexxxInput.cs  xxxEditDto.cs  xxxListDto.cs  GetxxxForEditOutput.cs  GetxxxsInput.cs这五个文件
                        //8.编辑CustomDtoMapper.cs,添加映射
                        //9.往第五步新增的文件夹中增加 xxxAppService.cs和IxxxAppService.cs 类
                        //10.编辑DbContext

    用razor引擎,自然少不了模板,这里就贴一个模板出来,其他的兄弟们自己查看源码哈:

    @using CodeBuilder.Models.TemplateModels
    @inherits RazorEngine.Templating.TemplateBase<CodeBuilder.Models.TemplateModels.ServiceFileModel>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Dynamic.Core;
    using System.Text;
    using System.Threading.Tasks;
    using Abp.Application.Services.Dto;
    using @Model.Namespace.@(Model.DirName).Dto;
    using Abp.Domain.Repositories;
    using Abp.AutoMapper;
    using Microsoft.EntityFrameworkCore;
    using Abp.Authorization;
    using Abp.Linq.Extensions;
    using abpAngular.Authorization;
    using Abp.Collections.Extensions;
    using Abp.Extensions;
    
    namespace @Model.Namespace.@Model.DirName
    {
        /// <summary>
        /// @(Model.CnName)服务
        /// </summary>
        [AbpAuthorize(@(Model.Name)Permissions.Node)]
        public class @(Model.Name)AppService : AbpFrameAppServiceBase, I@(Model.Name)AppService
        {
            private readonly IRepository<@(Model.Name), long> _repository;
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="repository"></param>
            public @(Model.Name)AppService(IRepository<@(Model.Name), long> repository)
            {
                _repository = repository;
            }
        
            /// <summary>
            /// 拼接查询条件
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            private IQueryable<@(Model.Name)> Create@(Model.Name)Query(Get@(Model.Name)sInput input)
            {
                var query = _repository.GetAll();
        
                //此处写自己的查询条件
                //query = query.WhereIf(!input.Filter.IsNullOrEmpty(),
                //p => p.Name.Contains(input.Filter) || p.DValue.Contains(input.Filter));
    
                //query = query.WhereIf(input.DictionaryItemId.HasValue, p => p.DictionaryItemId == input.DictionaryItemId);
    
                return query;
            }
    
            /// <summary>
            /// 获取更新@(Model.CnName)的数据
            /// </summary>
            [AbpAuthorize(@(Model.Name)Permissions.Node)]
            public async Task<PagedResultDto<@(Model.Name)ListDto>> Get@(Model.Name)s(Get@(Model.Name)sInput input)
            {
                var query = Create@(Model.Name)Query(input);
    
                var count = await query.CountAsync();
    
                var entityList = await query
                    .OrderBy(input.Sorting).AsNoTracking()
                    .PageBy(input)
                    .ToListAsync();
    
                var entityListDtos = entityList.MapTo<List<@(Model.Name)ListDto>>();
    
                return new PagedResultDto<@(Model.Name)ListDto>(count, entityListDtos);
            }
    
            /// <summary>
            /// 获取更新@(Model.CnName)的数据
            /// </summary>
            [AbpAuthorize(@(Model.Name)Permissions.Create, @(Model.Name)Permissions.Edit)]
            public async Task<Get@(Model.Name)ForEditOutput> Get@(Model.Name)ForEdit(NullableIdDto<long> input)
            {
                var output = new Get@(Model.Name)ForEditOutput();
                @(Model.Name)EditDto editDto;
                if (input.Id.HasValue)
                {
                    var entity = await _repository.GetAsync(input.Id.Value);
                    editDto = entity.MapTo<@(Model.Name)EditDto>();
                }
                else
                {
                    editDto = new @(Model.Name)EditDto();
                }
    
                output.@(Model.Name) = editDto;
    
                return output;
            }
    
            /// <summary>
            /// 创建或编辑@(Model.CnName)
            /// </summary>
            [AbpAuthorize(@(Model.Name)Permissions.Create, @(Model.Name)Permissions.Edit)]
            public async Task CreateOrUpdate@(Model.Name)(CreateOrUpdate@(Model.Name)Input input)
            {
                if (!input.@(Model.Name).Id.HasValue)
                {
                    await Create@(Model.Name)Async(input);
                }
                else
                {
                    await Update@(Model.Name)Async(input);
                }
            }
    
            /// <summary>
            /// 新建
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            [AbpAuthorize(@(Model.Name)Permissions.Create)]
            public async Task<@(Model.Name)ListDto> Create@(Model.Name)Async(CreateOrUpdate@(Model.Name)Input input)
            {
                var entity = input.@(Model.Name).MapTo<@(Model.Name)>();
                return (await _repository.InsertAsync(entity)).MapTo<@(Model.Name)ListDto>();
            }
    
            /// <summary>
            /// 编辑
            /// </summary>
            /// <param name="input"></param>
            /// <returns></returns>
            [AbpAuthorize(@(Model.Name)Permissions.Edit)]
            public async Task<@(Model.Name)ListDto> Update@(Model.Name)Async(CreateOrUpdate@(Model.Name)Input input)
            {
                var entity = input.@(Model.Name).MapTo<@(Model.Name)>();
                return (await _repository.UpdateAsync(entity)).MapTo<@(Model.Name)ListDto>();
            }
    
            /// <summary>
            /// 删除@(Model.CnName)
            /// </summary>
            [AbpAuthorize(@(Model.Name)Permissions.Delete)]
            public async Task Delete(EntityDto<long> input)
            {
                await _repository.DeleteAsync(input.Id);
            }
    
            /// <summary>
            /// 批量删除@(Model.CnName)
            /// </summary>
            [AbpAuthorize(@(Model.Name)Permissions.BatchDelete)]
            public async Task BatchDelete(List<long> input)
            {
                await _repository.DeleteAsync(a => input.Contains(a.Id));
            }
        }
    }

    接着我们开始生成,基本方法都差不多,我们贴一个新建和编辑的代码瞧瞧:

    新建:

            /// <summary>
            /// 创建Permissions权限常量类
            /// </summary>
            /// <param name="applicationStr">根命名空间</param>
            /// <param name="name">类名</param>
            /// <param name="authorizationFolder">父文件夹</param>
            private void CreatePermissionFile(string applicationStr, string name, ProjectItem authorizationFolder)
            {
                var model = new PermissionsFileModel() { Namespace = applicationStr, Name = name };
                string content = Engine.Razor.RunCompile("PermissionsTemplate", typeof(PermissionsFileModel), model);
                string fileName = $"{name}Permissions.cs";
                AddFileToProjectItem(authorizationFolder, content, fileName);
            }

    编辑:

    /// <summary>
            /// 添加权限
            /// </summary>
            /// <param name="topProject"></param>
            /// <param name="className"></param>
            private void SetPermission(Project topProject, string className)
            {
                ProjectItem AppAuthorizationProviderProjectItem = _dte.Solution.FindProjectItem(topProject.FileName.Substring(0, topProject.FileName.LastIndexOf("\")) + "\Authorization\AppAuthorizationProvider.cs");
                if (AppAuthorizationProviderProjectItem != null)
                {
                    CodeClass codeClass = GetClass(AppAuthorizationProviderProjectItem.FileCodeModel.CodeElements);
                    var codeChilds = codeClass.Members;
                    foreach (CodeElement codeChild in codeChilds)
                    {
                        if (codeChild.Kind == vsCMElement.vsCMElementFunction && codeChild.Name == "SetPermissions")
                        {
                            var insertCode = codeChild.GetEndPoint(vsCMPart.vsCMPartBody).CreateEditPoint();
                            insertCode.Insert("            Set" + className + "Permissions(pages);
    ");
                            insertCode.Insert("
    ");
                        }
                    }
                    AppAuthorizationProviderProjectItem.Save();
                }
            }

    其他的都自己查看源码哈

    3、生成前端代码

    前端生成流程如下:

    //1 往app\admin文件夹下面加xxx文件夹
    //2 往新增的文件夹加xxx.component.html   xxx.component.ts   create-or-edit-xxx-modal.component.html  create-or-edit-xxx-modal.component.ts这4个文件
    //3 修改app\admin\admin.module.ts文件,  import新增的组件   注入组件
    //4 修改app\admin\admin-routing.module.ts文件   添加路由
    //5 修改 app\shared\layout\nav\app-navigation.service.ts文件   添加菜单
    //6 修改 shared\service-proxies\service-proxy.module.ts文件  提供服务

    前端和后端的生成大部分都差不多,不过修改的因为咱们这是针对vs的插件,所以没法编辑vscode里的文件,这里我用了笨办法,对应要改的文件中加了特殊标识,类似于 // {#insert import code#},然后生成了代码文件后,我们替换掉标识符,贴段代码出来:

            /// <summary>
            /// 注入服务
            /// </summary>
            /// <param name="frontPath"></param>
            /// <param name="name"></param>
            private void AddProxy(string frontPath, string name)
            {
                string routesCode = "ApiServiceProxies."+ name + "ServiceProxy,
    ";
                routesCode += "        // {#insert routes code#}
    ";
    
                string proxyFilePath = frontPath + "shared\service-proxies\service-proxy.module.ts";
                string proxyContent = File.ReadAllText(proxyFilePath);
                proxyContent = proxyContent.Replace("// {#insert proxy code#}", routesCode);
    
                AddFileToDirectory(proxyFilePath, proxyContent);
            }

    至此,代码生成器基本功能就算是OK了,不过要达到完善水平,要做的事情还很多,这里列出几点:

    1、代码封装

    2、生成进度条

    3、异步提升生成效率

    4、添加交互界面

    5、根据实体类的字段类型生成对应的前端控件

    6、还没想好。。。

    至于框子,要做的就更多了,现在就只是弄了个基本的,后面还考虑下面几点:

    1、完善文章模块

    2、文件存储模块(本地,七牛云,阿里云)

    3、消息模块

    4、短信模块

    5、微信模块

    6、还没想好。。。

    这个项目最后的愿景的能基于这个框子做几套基础的开源应用出来,比如基础的商城、ERP、CRM等,DOTNET领域基础开源应用太少了,2019年再不努力点,DOTNET后面的路就更难了,市场都没有了,咱们在技术圈里自Hi也没什么意义了,大家一起加油吧。

    最近家里有些事情需要在家办公,各位有要兼职的或者有项目的可以聊聊哇

    Git仓库

    后端仓库:https://gitee.com/uTu/abpFrame_Angular

    前端仓库:https://gitee.com/uTu/abpFrame_Angular_Front

    代码生成器仓库:https://gitee.com/uTu/abpCodeBuilder

    参考资料:

    前端:https://www.cnblogs.com/FocusNet/p/10030749.html?tdsourcetag=s_pcqq_aiomsg  

    代码生成器相关:https://github.com/wakuflair/ABPHelper

    https://github.com/i542873057/SJNScaffolding

    https://www.c-sharpcorner.com/article/visual-studio-extensibility-creating-your-first-visual-studio-vsix-package-d/

    https://docs.microsoft.com/zh-cn/visualstudio/extensibility/extensibility-hello-world?view=vs-2017

    https://docs.microsoft.com/en-us/dotnet/api/envdte._dte?view=visualstudiosdk-2017

  • 相关阅读:
    怎样用ZBrush中的Curves和Insert笔刷创建四肢
    如何利用ZBrush中的DynaMesh创建身体(二)
    如何利用ZBrush中的DynaMesh创建身体(一)
    如何用ZBrush雕刻出栩栩如生的头发(二)
    Fisker大师用ZBrush制作兽人萨尔全过程
    如何用ZBrush雕刻出栩栩如生的头发(一)
    ZBrush中的SubTool工具该怎样使用
    Access denied for user 'Administrator'@'localhost' (using password: YES)
    java.lang.NoClassDefFoundError: org/apache/ibatis/session/SqlSession
    Unable to install breakpoint in
  • 原文地址:https://www.cnblogs.com/L_tommy/p/10872389.html
Copyright © 2011-2022 走看看