前端也要后端的DTO咋办,代码生成走一波。

欢迎访问我的B站空间:https://space.bilibili.com/223212197

“头疼”

Angular框架使用TypeScript,拥有强类型的特点,然后不停从后端吧C#的DTO改造成TS的,好烦😖。有时遇到后端修改了前端还不知道,更烦😖。

“吃药”

作为一个时刻想着如何偷懒的程序员,烦的事情就让计算机去干,所以写一个代码生成工具。

工具代码

    public class BuildDtoToTS
    {
        public static string Build(Assembly assembly)
        {
            List<DtoClass> dtos = GetDtos(assembly);
            string code = CreateCode(dtos);
            return code.ToString();
        }

        public static void BuildToFile(Assembly assembly, string path)
        {
            var code = Build(assembly);
            string existsCode = "";
            if (System.IO.File.Exists(path) == true)
                existsCode = System.IO.File.ReadAllText(path);

            if (existsCode != code)
            {
                System.IO.File.WriteAllText(path, code);
            }
        }

        #region 构造代码

        public static string CreateCode(List<DtoClass> dtos)
        {
            StringBuilder code = new StringBuilder();
            foreach (var dto in dtos)
            {
                code.AppendLine($"/** {dto.Title}  {dto.Namespace}*/");

                code.AppendLine($"export interface {dto.Name} {{");

                foreach (var property in dto.Propertys)
                {
                    if (property.IsInverseProperty) continue;

                    string comment = $"  /** <Title><Dept> */";

                    comment = comment.Replace("<Title>", property.Title ?? "");
                    if (property.IsKey)
                        comment = comment.Replace("<Dept>", $"\r\n  @Key<Dept>");
                    if (property.IsRequired)
                        comment = comment.Replace("<Dept>", $"\r\n  @Required<Dept>");
                    if (property.StringLength > 0)
                        comment = comment.Replace("<Dept>", $"\r\n  @StringLength {property.StringLength}<Dept>");

                    comment = comment.Replace("<Dept>", "");
                    code.AppendLine(comment);

                    string fieldCode = $"{property.Name}<Nullable>: <Type>,";

                    List<Type> typeChain = new List<Type>();
                    GetTypeChain(property.Type, typeChain);
                    foreach (var type in typeChain)
                    {
                        if (type == typeof(List<>))
                        {
                            fieldCode = fieldCode.Replace("<Type>", "Array<<Type>>");
                        }
                        else if (type == typeof(Nullable<>))
                        {
                            fieldCode = fieldCode.Replace("<Nullable>", "?");
                        }
                        else if (type == typeof(string) || type == typeof(Guid))
                        {
                            fieldCode = fieldCode.Replace("<Type>", "string");
                        }
                        else if (type == typeof(int) || type == typeof(long) || type == typeof(decimal) || type == typeof(float) || type == typeof(double))
                        {
                            fieldCode = fieldCode.Replace("<Type>", "number");
                        }
                        else if (type == typeof(bool))
                        {
                            fieldCode = fieldCode.Replace("<Type>", "boolean");
                        }
                        else if (type == typeof(DateTime))
                        {
                            fieldCode = fieldCode.Replace("<Type>", "Date");
                        }
                        else
                        {
                            if (dtos.Any(x => x.Name == type.Name))//如果某个类型是自己定义的类型,那么我们直接引用那个类型
                                fieldCode = fieldCode.Replace("<Type>", type.Name);
                            else
                                fieldCode = fieldCode.Replace("<Type>", "any");
                        }
                    }
                    fieldCode = fieldCode.Replace("<Nullable>", "");//对于不为空的,取消空占位符
                    code.AppendLine($"  {fieldCode}");
                }


                code.AppendLine($"}}");
                code.AppendLine($"");
            }

            return code.ToString();
        }

        //获得真时的类型
        public static void GetTypeChain(Type type, List<Type> typeChain)
        {
            if (type.IsGenericType)
            {
                var genericType = type.GetGenericTypeDefinition();
                typeChain.Add(genericType);
            }
            else
            {
                typeChain.Add(type);
            }

            var sonType = type.GetGenericArguments();
            if (sonType.Length == 0) return;

            GetTypeChain(sonType[0], typeChain);
        }


        public static List<DtoClass> GetDtos(Assembly assembly)
        {
            List<DtoClass> dtos = new List<DtoClass>();

            var dtoCommentTypes = assembly.GetTypes().Where(x => x.GetCustomAttributes(typeof(DtoCommentsAttribute), false).Count() > 0);
            foreach (var dtoCommentType in dtoCommentTypes)
            {

                var dto = new DtoClass(dtoCommentType.Name, dtoCommentType.Namespace);

                dto.Title = dtoCommentType.GetCustomAttribute<DtoCommentsAttribute>()?.Title ?? "";

                var propertyTypes = dtoCommentType.GetProperties();
                foreach (var propertyType in propertyTypes)
                {
                    var property = new DtoProperty(propertyType.PropertyType, propertyType.Name);
                    property.Title = propertyType.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? "";
                    property.IsInverseProperty = propertyType.GetCustomAttribute<InversePropertyAttribute>() != null;

                    property.IsKey = propertyType.GetCustomAttribute<KeyAttribute>() != null;
                    property.IsRequired = propertyType.GetCustomAttribute<RequiredAttribute>() != null;
                    property.StringLength = propertyType.GetCustomAttribute<StringLengthAttribute>()?.MaximumLength ?? 0;
                    dto.Propertys.Add(property);

                }

                dtos.Add(dto);
            }

            return dtos;
        }


        #endregion


        public record DtoClass(string Name, string Namespace)
        {
            public string Title { get; set; }//类名称

            public List<DtoProperty> Propertys { get; set; } = new List<DtoProperty>();

        }


        public record DtoProperty(Type Type, string Name)
        {
            public string Title { get; set; }//字段名称

            public bool IsInverseProperty { get; set; }//是否关联属性

            public bool IsRequired { get; set; }//是否必填

            public int StringLength { get; set; }//字符串长度

            public bool IsKey { get; set; }//是否主键
        }
    }

    public class DtoCommentsAttribute : Attribute
    {
        public string Title { get; set; }
        public DtoCommentsAttribute(string title)
        {
            Title = title;
        }
    }

使用代码

#if DEBUG
	BuildDtoToTS.BuildToFile(typeof(Program).Assembly, "ClientApp/src/app/web-dto.ts");
#endif

生成效果

/** 公司信息  XXX.Server.Controllers*/
export interface CompanyDto {
  /** 公司信息
  @Key */
  CompanyId: string,
  /** 代码 */
  Code: string,
  /** 名称 */
  Name: string,
}

上面代码大概的流程就是

  1. 利用反射读取程序集中包含DtoCommentsAttribute特性的类
  2. 利用反射读取类信息和属性信息
  3. 根据需要的格式生成代码
  4. 将代码文件写入ClientApp/src/app/web-dto.ts

有了这个东西带来以下好处

  1. 省去的了手工编写DTO的麻烦🎉
  2. 后端修改了DTO,前端直接编译不过,立即发现问题🎉
  3. 编码时智能体制,显示注释等🎉
posted @ 2021-04-12 12:25  超然  阅读(436)  评论(0编辑  收藏  举报