手把手教你实现自己的abp代码生成器

代码生成器的原理无非就是得到字段相关信息(字段名,字段类型,字段注释等),然后根据模板,其实就是字符串的拼接与替换生成相应代码。

所以第一步我们需要解决如何得到字段的相关信息,有两种方式

  • 通过反射获得程序集类的字段相关信息
  • 读取数据库得到表的字段的相关信息
  1. 新建一个.NET Core控制台项目 取名AbpCodeGenerator

    1531110372710

  2. 新建类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)
            {
    
            }
        }
    
  3. 新建类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;
            }
    
    
        }
    
  4. 在Program类中添加如下代码

    //反射程序集的方式生成相应代码 
                string className = "User";//跟类名保持一致
                var metaTableInfoList = MetaTableInfo.GetMetaTableInfoListForAssembly(className);
    

    启动控制台,获得User相关信息,报如下错误

    1531114232956

    这是缺少ABP相关程序集引用,使用NuGet安装如下程序集

    1531114396512

  5. 启动控制台得到如下结果,剩下的就是字符串拼接了

    1531114809200

  6. 新建一个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}}();
    		  }
    		});
    
        });
    })();
    
  7. 首先读取文件模板,然后去替换掉“{{}}” 这种类似占位符的变量,然后再输出一个新的文件,代码如下

     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
        }
    
  8. 启动控制台,即在当前项目下生成相应代码,这里分享一个我之前写好的基于abp.zero core 5.3.0的

    https://github.com/HisKingdom/CodeGenerator

posted @ 2018-07-11 11:45  红泥巴煮雪  阅读(1337)  评论(1编辑  收藏  举报