(给DotNet加星标,提高.Net技术


转自:L_tommy
cnblogs.com/L_tommy/p/10872389.html

工作了这么多年,一向都在小公司摸爬滚打,关于小公司而言,开发人员少,代码风格形形色色。要想用更少的人,更快的速度,开发更标准的代码,那天然离不开代码生成器。之前用过动软的,也用过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);\r\n");
insertCode.Insert("\r\n");
}
}
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,\r\n";
routesCode += " // {#insert routes code#}\r\n";
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


引荐阅览

(点击标题可跳转阅览)

.NET Core中依靠注入服务运用总结

ASP.NET Core MVC 模块化

.NET Core 3.0 可回收程序集加载上下文


看完本文有收成?请转发共享给更多人

重视「DotNet」加星标,提高.Net技术 

好文章,我在看❤️

推荐阅读