手把手教你实现自己的abp代码生成器
代码生成器的原理无非就是得到字段相关信息(字段名,字段类型,字段注释等),然后根据模板,其实就是字符串的拼接与替换生成相应代码。
所以第一步我们需要解决如何得到字段的相关信息,有两种方式
- 通过反射获得程序集类的字段相关信息
- 读取数据库得到表的字段的相关信息
-
新建一个.NET Core控制台项目 取名AbpCodeGenerator
-
新建类DocsByReflection
/// <summary> /// Utility class to provide documentation for various types where available with the assembly /// </summary> public class DocsByReflection { /// <summary> /// Provides the documentation comments for a specific method /// </summary> /// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param> /// <returns>The XML fragment describing the method</returns> public static XmlElement XMLFromMember(MethodInfo methodInfo) { // Calculate the parameter string as this is in the member name in the XML string parametersString = ""; foreach (ParameterInfo parameterInfo in methodInfo.GetParameters()) { if (parametersString.Length > 0) { parametersString += ","; } parametersString += parameterInfo.ParameterType.FullName; } //AL: 15.04.2008 ==> BUG-FIX remove ?)?if parametersString is empty if (parametersString.Length > 0) return XMLFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")"); else return XMLFromName(methodInfo.DeclaringType, 'M', methodInfo.Name); } /// <summary> /// Provides the documentation comments for a specific member /// </summary> /// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param> /// <returns>The XML fragment describing the member</returns> public static XmlElement XMLFromMember(MemberInfo memberInfo) { // First character [0] of member type is prefix character in the name in the XML return XMLFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name); } /// <summary> /// Provides the documentation comments for a specific type /// </summary> /// <param name="type">Type to find the documentation for</param> /// <returns>The XML fragment that describes the type</returns> public static XmlElement XMLFromType(Type type) { // Prefix in type names is T return XMLFromName(type, 'T', ""); } /// <summary> /// Obtains the XML Element that describes a reflection element by searching the /// members for a member that has a name that describes the element. /// </summary> /// <param name="type">The type or parent type, used to fetch the assembly</param> /// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param> /// <param name="name">Where relevant, the full name qualifier for the element</param> /// <returns>The member that has a name that describes the specified reflection element</returns> private static XmlElement XMLFromName(Type type, char prefix, string name) { string fullName; if (String.IsNullOrEmpty(name)) { fullName = prefix + ":" + type.FullName; } else { fullName = prefix + ":" + type.FullName + "." + name; } XmlDocument xmlDocument = XMLFromAssembly(type.Assembly); XmlElement matchedElement = null; foreach (XmlElement xmlElement in xmlDocument["doc"]["members"]) { if (xmlElement.Attributes["name"].Value.Equals(fullName)) { if (matchedElement != null) { throw new DocsByReflectionException("Multiple matches to query", null); } matchedElement = xmlElement; break; } } if (matchedElement == null) { throw new DocsByReflectionException("Could not find documentation for specified element", null); } return matchedElement; } /// <summary> /// A cache used to remember Xml documentation for assemblies /// </summary> static Dictionary<Assembly, XmlDocument> cache = new Dictionary<Assembly, XmlDocument>(); /// <summary> /// A cache used to store failure exceptions for assembly lookups /// </summary> static Dictionary<Assembly, Exception> failCache = new Dictionary<Assembly, Exception>(); /// <summary> /// Obtains the documentation file for the specified assembly /// </summary> /// <param name="assembly">The assembly to find the XML document for</param> /// <returns>The XML document</returns> /// <remarks>This version uses a cache to preserve the assemblies, so that /// the XML file is not loaded and parsed on every single lookup</remarks> public static XmlDocument XMLFromAssembly(Assembly assembly) { if (failCache.ContainsKey(assembly)) { throw failCache[assembly]; } try { if (!cache.ContainsKey(assembly)) { // load the docuemnt into the cache cache[assembly] = XMLFromAssemblyNonCached(assembly); } return cache[assembly]; } catch (Exception exception) { failCache[assembly] = exception; throw exception; } } /// <summary> /// Loads and parses the documentation file for the specified assembly /// </summary> /// <param name="assembly">The assembly to find the XML document for</param> /// <returns>The XML document</returns> private static XmlDocument XMLFromAssemblyNonCached(Assembly assembly) { string assemblyFilename = assembly.CodeBase; const string prefix = "file:///"; if (assemblyFilename.StartsWith(prefix)) { using (StreamReader streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename.Substring(prefix.Length), ".xml"))) { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(streamReader); return xmlDocument; } } else { throw new DocsByReflectionException("Could not ascertain assembly filename", null); } } } /// <summary> /// An exception thrown by the DocsByReflection library /// </summary> [Serializable] class DocsByReflectionException : Exception { /// <summary> /// Initializes a new exception instance with the specified /// error message and a reference to the inner exception that is the cause of /// this exception. /// </summary> /// <param name="message">The error message that explains the reason for the exception.</param> /// <param name="innerException">The exception that is the cause of the current exception, or null if none.</param> public DocsByReflectionException(string message, Exception innerException) : base(message, innerException) { } }
-
新建类MetaTableInfo
public class MetaTableInfo { /// <summary> /// 类的注释 /// </summary> public string ClassAnnotation { get; set; } /// <summary> /// 属性名称 /// </summary> public string Name { get; set; } /// <summary> /// 属性类型 /// </summary> public string PropertyType { get; set; } /// <summary> /// 属性注释 /// </summary> public string Annotation { get; set; } /// <summary> /// 根据类名 反射得到类的信息 /// </summary> /// <param name="className">类名</param> /// <returns></returns> public static List<MetaTableInfo> GetMetaTableInfoListForAssembly(string className) { var list = new List<MetaTableInfo>(); //读取的程序集路径 需换成自己项目的下的程序集路径 string sourceAssemblyPath = "D:\\Project\\ZhouDaFu.XinYunFen.Core\\bin\\Debug\\netcoreapp2.0\\ZhouDaFu.XinYunFen.Core.dll"; Type[] types = Assembly.LoadFrom(sourceAssemblyPath).GetTypes(); foreach (var type in types) { if (type.Name.Equals(className)) { var classAnnotation = string.Empty; try { //获取类的注释 XmlElement xmlFromType = DocsByReflection.XMLFromType(type.GetTypeInfo()); classAnnotation = xmlFromType["summary"].InnerText.Trim(); } catch { } foreach (PropertyInfo properties in type.GetProperties()) { var metaTableInfo = new MetaTableInfo(); try { XmlElement documentation = DocsByReflection.XMLFromMember(type.GetProperty(properties.Name)); metaTableInfo.Annotation = documentation["summary"].InnerText.Trim(); metaTableInfo.ClassAnnotation = classAnnotation; } catch { metaTableInfo.Annotation = ""; } metaTableInfo.Name = properties.Name; if (properties.PropertyType == typeof(int)) { metaTableInfo.PropertyType = "int"; } else if (properties.PropertyType == typeof(int?)) { metaTableInfo.PropertyType = "int?"; } else if (properties.PropertyType == typeof(long)) { metaTableInfo.PropertyType = "long"; } else if (properties.PropertyType == typeof(long?)) { metaTableInfo.PropertyType = "long?"; } else if (properties.PropertyType == typeof(DateTime?)) { metaTableInfo.PropertyType = "DateTime?"; } else if (properties.PropertyType == typeof(decimal)) { metaTableInfo.PropertyType = "decimal"; } else if (properties.PropertyType == typeof(decimal?)) { metaTableInfo.PropertyType = "decimal?"; } else if (properties.PropertyType == typeof(string)) { metaTableInfo.PropertyType = "string"; } else if (properties.PropertyType == typeof(bool)) { metaTableInfo.PropertyType = "bool"; } else { metaTableInfo.PropertyType = properties.PropertyType.ToString().Split('.').Last().Replace("]", ""); } list.Add(metaTableInfo); } } } return list; } }
-
在Program类中添加如下代码
//反射程序集的方式生成相应代码 string className = "User";//跟类名保持一致 var metaTableInfoList = MetaTableInfo.GetMetaTableInfoListForAssembly(className);
启动控制台,获得User相关信息,报如下错误
这是缺少ABP相关程序集引用,使用NuGet安装如下程序集
-
启动控制台得到如下结果,剩下的就是字符串拼接了
-
新建一个txt文件模板(直接在代码里面拼接字符串也行,只不过那样修改起来麻烦),取名IndexJsTemplate.txt,添加如下代码
(function () { $(function () { var _${{entity_Name_Plural_Here}}Table = $('#MainTable'); var _{{entity_Name_Plural_Here}}Service = abp.services.app.{{entity_Name_Plural_Here}}; var _permissions = { create: abp.auth.hasPermission('{{Permission_Value_Here}}.Create'), edit: abp.auth.hasPermission('{{Permission_Value_Here}}.Edit'), 'delete': abp.auth.hasPermission('{{Permission_Value_Here}}.Delete') }; var _createOrEditModal = new app.ModalManager({ viewUrl: abp.appPath + '{{App_Area_Name_Here}}/{{Entity_Name_Plural_Here}}/CreateOrEditModal', scriptUrl: abp.appPath + 'view-resources/Areas/{{App_Area_Name_Here}}/Views/{{Entity_Name_Plural_Here}}/_CreateOrEditModal.js', modalClass: 'CreateOrEdit{{Entity_Name_Here}}Modal' }); var dataTable = _${{entity_Name_Plural_Here}}Table.DataTable({ paging: true, serverSide: true, processing: true, listAction: { ajaxFunction: _{{entity_Name_Plural_Here}}Service.getAll, inputFilter: function () { return { filter: $('#{{Entity_Name_Plural_Here}}TableFilter').val() }; } }, columnDefs: [ { width: 120, targets: 0, data: null, orderable: false, autoWidth: false, defaultContent: '', rowAction: { cssClass: 'btn btn-brand dropdown-toggle', text: '<i class="fa fa-cog"></i> ' + app.localize('Actions') + ' <span class="caret"></span>', items: [ { text: app.localize('Edit'), visible: function () { return _permissions.edit; }, action: function (data) { _createOrEditModal.open({ id: data.record.id }); } }, { text: app.localize('Delete'), visible: function () { return _permissions.delete; }, action: function (data) { delete{{Entity_Name_Here}}(data.record); } }] } }{{Property_Looped_Template_Here}} ] }); function get{{Entity_Name_Plural_Here}}() { dataTable.ajax.reload(); } function delete{{Entity_Name_Here}}({{entity_Name_Here}}) { abp.message.confirm( '', function (isConfirmed) { if (isConfirmed) { _{{entity_Name_Plural_Here}}Service.delete({ id: {{entity_Name_Here}}.id }).done(function () { get{{Entity_Name_Plural_Here}}(true); abp.notify.success(app.localize('SuccessfullyDeleted')); }); } } ); } $('#Export{{Entity_Name_Here}}ToExcelButton').click(function () { _{{entity_Name_Here}}Service .get{{Entity_Name_Here}}ToExcel({}) .done(function (result) { app.downloadTempFile(result); }); }); $('#CreateNew{{Entity_Name_Here}}Button').click(function () { _createOrEditModal.open(); }); abp.event.on('app.createOrEdit{{Entity_Name_Here}}ModalSaved', function () { get{{Entity_Name_Plural_Here}}(); }); $('#Get{{Entity_Name_Plural_Here}}Button').click(function (e) { e.preventDefault(); get{{Entity_Name_Plural_Here}}(); }); $(document).keypress(function(e) { if(e.which === 13) { get{{Entity_Name_Plural_Here}}(); } }); }); })();
-
首先读取文件模板,然后去替换掉“{{}}” 这种类似占位符的变量,然后再输出一个新的文件,代码如下
static void Main(string[] args) { //反射程序集的方式生成相应代码 string className = "User";//跟类名保持一致 var metaTableInfoList = MetaTableInfo.GetMetaTableInfoListForAssembly(className); SetIndexJsTemplate(className, metaTableInfoList); } /// <summary> /// 生成IndexJsTemplate /// </summary> /// <param name="className"></param> public static void SetIndexJsTemplate(string className, List<MetaTableInfo> metaTableInfoList) { var rootPath = (Directory.GetCurrentDirectory().Split(new string[] { "bin" }, StringSplitOptions.RemoveEmptyEntries))[0]; string templateContentDirectory = rootPath + "IndexJsTemplate.txt"; var templateContent = Read(templateContentDirectory); StringBuilder sb = new StringBuilder(); var i = 1; foreach (var item in metaTableInfoList) { sb.AppendLine(", {"); sb.AppendLine("targets: " + i + ","); sb.AppendLine("data: \"" + GetFirstToLowerStr(item.Name) + "\""); sb.AppendLine("}"); i++; } var property_Looped_Template_Here = sb.ToString(); templateContent = templateContent .Replace("{{Entity_Name_Plural_Here}}", className) .Replace("{{Entity_Name_Here}}", className) .Replace("{{entity_Name_Here}}", GetFirstToLowerStr(className)) .Replace("{{entity_Name_Plural_Here}}", GetFirstToLowerStr(className)) .Replace("{{App_Area_Name_Here}}", "App_Area_Name") .Replace("{{Property_Looped_Template_Here}}", property_Looped_Template_Here) .Replace("{{Permission_Value_Here}}", "Pages.Administration." + className + "") ; Write(rootPath, "Index.js", templateContent); } #region 文件读取 public static string Read(string path) { using (StreamReader sr = new StreamReader(path, Encoding.Default)) { StringBuilder sb = new StringBuilder(); String line; while ((line = sr.ReadLine()) != null) { sb.AppendLine(line.ToString()); } return sb.ToString(); } } /// <summary> /// /// </summary> /// <param name="filePath">文件保存路径</param> /// <param name="fileName">文件名</param> /// <param name="templateContent">模板内容</param> public static void Write(string filePath, string fileName, string templateContent) { if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } using (FileStream fs = new FileStream(filePath + fileName, FileMode.Create)) { //获得字节数组 byte[] data = Encoding.Default.GetBytes(templateContent); //开始写入 fs.Write(data, 0, data.Length); } } /// <summary> /// /// </summary> /// <param name="filePath">文件保存路径</param> /// <param name="templateContent">模板内容</param> public static void Write(string filePath, string templateContent) { using (FileStream fs = new FileStream(filePath, FileMode.Create)) { //获得字节数组 byte[] data = Encoding.Default.GetBytes(templateContent); //开始写入 fs.Write(data, 0, data.Length); } } #endregion #region 首字母小写 /// <summary> /// 首字母小写 /// </summary> /// <param name="s"></param> /// <returns></returns> public static string GetFirstToLowerStr(string str) { if (!string.IsNullOrEmpty(str)) { if (str.Length > 1) { return char.ToLower(str[0]) + str.Substring(1); } return char.ToLower(str[0]).ToString(); } return null; } #endregion }
-
启动控制台,即在当前项目下生成相应代码,这里分享一个我之前写好的基于abp.zero core 5.3.0的