Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)

前言 以前做一个金融软件项目,软件要英文、繁体版本,开始甲方弄了好几个月,手动一条一条替换,发现很容易出错,因为有金融专业术语,字符串在不同语义要特殊处理,第三方工具没法使用。最后我用Roslyn写了一个工具,只需10分钟就能翻译整个软件,100%准确
做完上个项目发现Roslyn还可以深度开发,写了一个工具:代码助手,可解决项目所有琐碎重复性操作,代码完全自动化
原理 Roslyn是啥?
XmlDocument,XDocument可以解析xml,同样 Roslyn 可解析项目中C#代码。c#常用插件ReSharper,只能重构一些很规范的代码(生成IEqualityComparer,IComparer接口...),用Roslyn可以自动化业务代码
自己写 代码助手 Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片




一,类->视图->增删改查 全自动 需求:一个数据库所有表的增删改查
实现:Roslyn+T4实现类自动生成增删改查界面
Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片



生成最终效果
Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片



每个属性对应不同的控件:
Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片


自动生成单个对象编辑预览

Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片




Roslyn+T4实现

  1. EnvDTE获取当前打开项目
  2. Roslyn api CodeAnalysis 分析当前项目
  3. 获取代码Symbol
  4. T4代码自动化
T4主要代码
<#@ include file="Include\base.t4" #> <#@ include file="Include\CodeAnalysis.t4" #> <# param.Task = DoAsync(); #> <#+ Dictionary dic; int depthLevel = 2; //嵌套类展开深度async Task DoAsync() { var Modules = "Modules"; var ns = "Test.Database"; //获取名称空间 Test.Database 所有类 var DbContextName = "sakilaEntities"; //测试数据库 var skipClass = new[] { DbContextName, }.ToHashSet(); //排除类var modulesProjectItem = new ProjectItemEntry(@"Test\View\Modules"); //获取项目view的路径 var viewModelProjectItem = new ProjectItemEntry(@"Test\ViewModel\Modules"); //获取项目ViewModel的路径 /* modulesProjectItem.Delete(); viewModelProjectItem.Delete(); return ""; */var analysisCore = await param.TryAnalysisSolutionAsync(cancellationToken); //Roslyn 分析当前工程 var solution = analysisCore.Workspace.CurrentSolution; //Roslyn 的解决方案await TypeSymbols.InitializeTypeSymbolsAsync(solution, cancellationToken); //获取名称空间 Test.Database 所有类 var list = await solution.GetAllSymbolsAsync((project, symbol) => { var displayString = symbol.ContainingNamespace.ToDisplayString(); if (displayString == ns && !skipClass.Contains(symbol.Name)) { return true; } return false; }); var items = list.Select(p => { var entry = new ClassEntry(p); entry.DoProperty(); return entry; }).ToList(); #>


T4生成文本
<#+ //xaml { var notMapHashSet = new HashSet(); //每个属性类型对就的控件,如果没有映射,写日志 double index = 0; dic = items.ToDictionary(p=> p.Name); foreach (var c in items) { index++; var xamlOutput = StartNewFile(modulesProjectItem.GetRelativePath(c.MainViewPair.XamlFileName), true); //打开一个新文件 param.Log($@"{index / items.Count:P1}:{c.MainViewPair.ClassName}"); //xaml #> ..........................................

<#+ public class XamlCsPair { public readonly string ClassName = string.Empty; //类名 public readonly string XamlFileName = string.Empty; //View绝对路径 public readonly string CsFileName = string.Empty; //ViewModel绝对路径public readonly string ViewModelClassName = string.Empty; //View类名 public readonly string ViewModelFileName = string.Empty; //ViewModel 文件名 public XamlCsPair(string className, string vewModelClassName) { ClassName = className; className = className.GetValidFileName("_"); //类名到文件名,移除非法字符XamlFileName = $"{className}.xaml"; CsFileName = $"{XamlFileName}.cs"; ViewModelClassName = vewModelClassName; ViewModelFileName = $"{ViewModelClassName}.cs"; } } public class ClassEntry { public ProjectSymbolEntry Entry { get; } public INamedTypeSymbol Symbol { get; }//类Symbol public readonly string Name = string.Empty; //类的名称 public readonly string NameZh = string.Empty; //类的中文名称 public readonly ITypeSymbol Type; //类的类型Symbol public readonly string TypeName = string.Empty; //类的类型名称public readonly string ClassName = string.Empty; //目标类名public readonly XamlCsPair MainViewPair; public readonly XamlCsPair EditViewPair; public List Properties { get; set; } = new List(); //类的属性集合 public ClassEntry(ProjectSymbolEntry entry) { Entry = entry; var symbol = entry.Symbol; Symbol = symbol; Name = symbol.Name; NameZh = Name.ToZh(cacheMode: CacheMode.OneWay); //百度api英文翻译中文 Type = symbol.GetTypeSymbol(); TypeName = Type.GetDisplayShortName(); ClassName = Name.ToValidIdentifier(removeChars:"_".ToCharArray()); MainViewPair = new XamlCsPair($"{ClassName}View", $"{ClassName}ViewModel"); EditViewPair = new XamlCsPair($"{ClassName}ViewEditorWindow", $"{ClassName}ViewEditorViewModel"); }public void DoProperty() { Properties.Clear(); foreach (var member in Symbol.GetMembers().OfType()) { Properties.Add(new PropertyEntry(this, member)); } } } public class PropertyEntry { public ClassEntry Class { get; } public IPropertySymbol Symbol { get; }//属性的Symbol public readonly string Name = string.Empty; public readonly string NameZh = string.Empty; //属性中文名称 public readonly ITypeSymbol Type; //属性类型 public readonly string TypeName = string.Empty; //属性类型名称public PropertyEntry(ClassEntry entry, IPropertySymbol symbol) { Class = entry; Symbol = symbol; Name = symbol.Name; NameZh = Name.ToZh(cacheMode: CacheMode.OneWay); //百度api英文翻译中文 Type = symbol.GetTypeSymbol(); //属性Roslyn类型 TypeName = Type.GetDisplayShortName(); //属性Roslyn名称 } } public class ProjectItemEntry { public ProjectItem ProjectItem { get; } //dte对应的一个项目文件 public string Namespace { get; }//ProjectItem的命名空间 public string FileName { get; }//项目文件绝对路径public ProjectItemEntry(string projectRelativePath) { ProjectItem = dte.GetProjectItemByRelativePath(projectRelativePath).TryCreateDir(); Namespace = (string)ProjectItem.Properties.Item("DefaultNamespace").Value; FileName = ProjectItem.GetFileName(); } public void Delete() { ProjectItem.DeleteChildren(); FileName.DeleteSubFiles(); } public string GetRelativePath(string relativePath) { return Path.Combine(FileName, relativePath); } } #>


Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片
Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)
文章图片

【Roslyn+T4+EnvDTE项目完全自动化|Roslyn+T4+EnvDTE项目完全自动化 (一)】

    推荐阅读