姐妹篇《URL地址记住不咋办,代码生成走一波。》地址:https://blog.csdn.net/TimChen44/article/details/115582820
“头疼”
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,
}
上面代码大概的流程就是
- 利用反射读取程序集中包含
DtoCommentsAttribute
特性的类 - 利用反射读取类信息和属性信息
- 根据需要的格式生成代码
- 将代码文件写入
ClientApp/src/app/web-dto.ts
有了这个东西带来以下好处
- 省去的了手工编写DTO的麻烦🎉
- 后端修改了DTO,前端直接编译不过,立即发现问题🎉
- 编码时智能体制,显示注释等🎉