如何将类型(按组)指向不同的 class and/or 命名空间?
How to point typings (by groups) to different class and/or namespace?
我能够为代码库中的所有 api 集生成类型,但是,我无法根据某些特征将它们分离到各种 类 and/or 名称空间中。作为示例业务需求,我想要基于类型的某些特征的三个不同的 类(and/or 命名空间),而不管它们来自何处。 Public(服务)、私有(服务)和内部(服务),但我想在运行时生成类型。这是我到目前为止所拥有的。我已经尝试覆盖 FunClassCodeGenerator()
中的 类。它有效,但会生成重复的 类 并且如某些阅读资料 AFAIK 所述,打字稿中没有部分 类。所有实验代码和评论都已删除。
// etc this is prepopulated.....
var newApiClasses = new List<Type>();
builder.ExportAsClasses(newApiClasses, conf => conf
.FlattenHierarchy()
.Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
.WithCodeGenerator<FunClassCodeGenerator>()
.WithMethods(p => !p.GetCustomAttributes<AreaAttribute>().Any()
&& !p.DeclaringType.GetCustomAttributes<AreaAttribute>().Any(),p => p.WithCodeGenerator<AngularActionCallGenerator>())
.AddImport("{ Injectable }", "@angular/core")
.AddImport("{ Observable }", "rxjs")
.AddImport("{ Router }", "@angular/router")
.Decorator(@"Injectable({
providedIn: 'root'
})" + "\r\n")
.DontIncludeToNamespace(true)
.ExportTo(FileName($"{nameof(Routes)}.test")));
public static class ServicesGenerator
{
public class FunClassCodeGenerator : ClassCodeGenerator
{
public override RtClass GenerateNode(Type element, RtClass result, TypeResolver resolver)
{
var r = base.GenerateNode(element, result, resolver);
Console.WriteLine($"\n\n\n\n" +
$"Information 102: {JsonConvert.SerializeObject(r.Name, Formatting.Indented)}" +
$"\n\n\n\n");
// constructor (router: Router) { }
r.Members.Add(new RtConstructor()
{
Order = 500,
Body = new RtRaw("this.router = router;"),
Arguments = new List<RtArgument>()
{
new RtArgument()
{
// not sure if this is private... private
Identifier = new RtIdentifier("router"),
Type = new RtSimpleTypeName("Router")
}
},
});
// router: Router;
r.Members.Add(new RtField()
{
Order = -1,
Identifier = new RtIdentifier("router"),
Type = new RtSimpleTypeName("Router"),
LineAfter = "\r\n"
});
return r;
}
}
public class AngularActionCallGenerator : MethodCodeGenerator
{
public override RtFunction GenerateNode(MethodInfo element, RtFunction result, TypeResolver resolver)
{
Console.Write($"104: {nameof(MethodCodeGenerator)} me first");
if (IsNotWanted(element))
return null;
Console.WriteLine($"Information 500: {JsonConvert.SerializeObject(result, Formatting.Indented)}");
result = base.GenerateNode(element, result, resolver);
if (result == null)
return null;
result.Order = 1000;
Console.WriteLine($"Information 501: {JsonConvert.SerializeObject(result, Formatting.Indented)}");
result.IsStatic = false;
var hasArea = element.GetCustomAttributes<AreaAttribute>().Any();
var isHttpPost = element.GetCustomAttributes<HttpPostAttribute>().Any();
var isHttpGet = !element.GetCustomAttributes<HttpPostAttribute>().Any();
var isAuthOnly = !element.GetCustomAttributes<AllowAnonymousAttribute>().Any()
&& (element.GetCustomAttributes<AuthorizeAttribute>().Any()
|| element.DeclaringType?.GetCustomAttributes<AuthorizeAttribute>().Any() == true);
// here we are overriding return type to corresponding promise
var retType = result.ReturnType;
var isVoid = retType is RtSimpleTypeName && ((RtSimpleTypeName)retType).TypeName == "void";
// we use TypeResolver to get "any" type to avoid redundant type name construction
// (or because I'm too lazy to manually construct "any" type)
if (isVoid)
retType = resolver.ResolveTypeName(typeof(object));
// Here we override TS method return type to make it angular.IPromise
// We are using RtSimpleType with generic parameter of existing method type
result.ReturnType = new RtSimpleTypeName(new[] { retType }, "", $"Promise");
var flatReturnType = $"Promise<{retType}>";
var p = element.GetParameters().Select(c => string.Format("'{0}': {0}", c.Name));
var dataParameters = string.Join(", ", p);
// Here we get path to controller
// It is quite simple solution requiring /{controller}/{action} route
var area = element.DeclaringType?.GetCustomAttributes<AreaAttribute>().FirstOrDefault()
?.RouteValue;
var controller = element.DeclaringType?.Name.Replace("Controller", string.Empty);
var url = string.Join('/', new[] { "api", area, controller, element.Name }.Where(p2 => !string.IsNullOrWhiteSpace(p2)));
var postOptions = $@", {{
method: 'POST', {(dataParameters.Length > 0 ? $"\r\n\tbody: JSON.stringify({$"{{{dataParameters}}}"})," : string.Empty)}
headers: {{
'Content-Type': 'application/json'
}}
}}";
var getOptions = dataParameters.Length > 0
? $@" + new URLSearchParams({{ {string.Join(", ", element.GetParameters().Select(c => string.Format("{0}: {0}.toString()", c.Name)))}}})"
: string.Empty;
var fetch = $@"return fetch(`{url}{(getOptions.Length > 0 ? "?" : string.Empty)}`{(isHttpPost ? postOptions : getOptions)})
.then(res => {{ var result = res.json(); console.log('this.router.url', this.router.url); console.log('result', res, result); if (res.redirected) {{ this.router.navigateByUrl(new URL(res.url).pathname) }} return result as {flatReturnType} }});";
var code =
$"// IsHttpPost: {isHttpPost}" +
$"\r\n" +
$"// IsHttpGet: {isHttpGet}" +
$"\r\n" +
$"// IsAuthOnly: {isAuthOnly}" +
$"\r\n" +
$"{fetch}"
+ (false
? "\r\n" +
"// was: var params = {{ {dataParameters} }}; return this.http.post('{url}', params).then((response) => {{ response.data['requestParams'] = params; return response.data; }});"
: string.Empty);
var body = new RtRaw(code);
result.Body = body;
result.LineAfter = "\r\n";
return result;
}
}
public static bool IsNotWanted(MethodInfo element)
{
if (element == null)
return true;
if (element.IsDefined(typeof(NonActionAttribute)))
return true;
if (element.IsFamily)
return true;
// element.GetBaseDefinition().DeclaringType == element.DeclaringType && , typeof(ControllerBase), typeof(Controller)
if (new[] { typeof(IDisposable), typeof(IActionFilter), typeof(IAsyncActionFilter) }.Contains(element.ReflectedType))
return true;
if (element.IsPrivate)
return true;
if (element.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any()) // || element.IsOverride())
return true;
return false;
}
}
以下是对我自己的问题的粗略(但正确)回答。这些语句中的每一个都采用多种类型 (类) 及其方法,并根据标准轻松地将它们分开。请注意生成器仍将生成(空)类,无论它们是否具有任何符合条件的成员(方法)。这就是为什么我们在 types 的参数中使用 where 子句以及在第二组中使用 WithMethods(带参数)的原因代码。
builder.ExportAsClasses(types: allTypes.Where(p => p.IsSubclassOf(typeof(BaseAreaApiController))
&& p.IsSubclassOf(typeof(BaseMembersAreaApiController))).ToList(), configuration: conf => conf
.Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
.WithCodeGenerator<FunClassCodeGenerator>()
.WithPublicMethods(p => p.WithCodeGenerator<AngularActionCallGenerator>())
.AddImport("{ Injectable }", "@angular/core")
.AddImport("{ Observable }", "rxjs")
.AddImport("{ Router }", "@angular/router")
.Decorator("Injectable({ providedIn: 'root' })" + "\r\n")
.OverrideNamespace("AreaServices")
.ExportTo(FileName($"services.private")));
builder.ExportAsClasses(allTypes.Where(p => !p.IsSubclassOf(typeof(BaseAreaApiController))).ToList(), conf => conf
.Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
.WithCodeGenerator<FunClassCodeGenerator>()
.WithMethods(p => !p.GetCustomAttributes<AuthorizeAttribute>().Any(), p2 => p2.WithCodeGenerator<AngularActionCallGenerator>())
.AddImport("{ Injectable }", "@angular/core")
.AddImport("{ Observable }", "rxjs")
.AddImport("{ Router }", "@angular/router")
.Decorator("Injectable({ providedIn: 'root' })" + "\r\n")
.OverrideNamespace("PublicServices")
.ExportTo(FileName($"services.public")));
我能够为代码库中的所有 api 集生成类型,但是,我无法根据某些特征将它们分离到各种 类 and/or 名称空间中。作为示例业务需求,我想要基于类型的某些特征的三个不同的 类(and/or 命名空间),而不管它们来自何处。 Public(服务)、私有(服务)和内部(服务),但我想在运行时生成类型。这是我到目前为止所拥有的。我已经尝试覆盖 FunClassCodeGenerator()
中的 类。它有效,但会生成重复的 类 并且如某些阅读资料 AFAIK 所述,打字稿中没有部分 类。所有实验代码和评论都已删除。
// etc this is prepopulated.....
var newApiClasses = new List<Type>();
builder.ExportAsClasses(newApiClasses, conf => conf
.FlattenHierarchy()
.Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
.WithCodeGenerator<FunClassCodeGenerator>()
.WithMethods(p => !p.GetCustomAttributes<AreaAttribute>().Any()
&& !p.DeclaringType.GetCustomAttributes<AreaAttribute>().Any(),p => p.WithCodeGenerator<AngularActionCallGenerator>())
.AddImport("{ Injectable }", "@angular/core")
.AddImport("{ Observable }", "rxjs")
.AddImport("{ Router }", "@angular/router")
.Decorator(@"Injectable({
providedIn: 'root'
})" + "\r\n")
.DontIncludeToNamespace(true)
.ExportTo(FileName($"{nameof(Routes)}.test")));
public static class ServicesGenerator
{
public class FunClassCodeGenerator : ClassCodeGenerator
{
public override RtClass GenerateNode(Type element, RtClass result, TypeResolver resolver)
{
var r = base.GenerateNode(element, result, resolver);
Console.WriteLine($"\n\n\n\n" +
$"Information 102: {JsonConvert.SerializeObject(r.Name, Formatting.Indented)}" +
$"\n\n\n\n");
// constructor (router: Router) { }
r.Members.Add(new RtConstructor()
{
Order = 500,
Body = new RtRaw("this.router = router;"),
Arguments = new List<RtArgument>()
{
new RtArgument()
{
// not sure if this is private... private
Identifier = new RtIdentifier("router"),
Type = new RtSimpleTypeName("Router")
}
},
});
// router: Router;
r.Members.Add(new RtField()
{
Order = -1,
Identifier = new RtIdentifier("router"),
Type = new RtSimpleTypeName("Router"),
LineAfter = "\r\n"
});
return r;
}
}
public class AngularActionCallGenerator : MethodCodeGenerator
{
public override RtFunction GenerateNode(MethodInfo element, RtFunction result, TypeResolver resolver)
{
Console.Write($"104: {nameof(MethodCodeGenerator)} me first");
if (IsNotWanted(element))
return null;
Console.WriteLine($"Information 500: {JsonConvert.SerializeObject(result, Formatting.Indented)}");
result = base.GenerateNode(element, result, resolver);
if (result == null)
return null;
result.Order = 1000;
Console.WriteLine($"Information 501: {JsonConvert.SerializeObject(result, Formatting.Indented)}");
result.IsStatic = false;
var hasArea = element.GetCustomAttributes<AreaAttribute>().Any();
var isHttpPost = element.GetCustomAttributes<HttpPostAttribute>().Any();
var isHttpGet = !element.GetCustomAttributes<HttpPostAttribute>().Any();
var isAuthOnly = !element.GetCustomAttributes<AllowAnonymousAttribute>().Any()
&& (element.GetCustomAttributes<AuthorizeAttribute>().Any()
|| element.DeclaringType?.GetCustomAttributes<AuthorizeAttribute>().Any() == true);
// here we are overriding return type to corresponding promise
var retType = result.ReturnType;
var isVoid = retType is RtSimpleTypeName && ((RtSimpleTypeName)retType).TypeName == "void";
// we use TypeResolver to get "any" type to avoid redundant type name construction
// (or because I'm too lazy to manually construct "any" type)
if (isVoid)
retType = resolver.ResolveTypeName(typeof(object));
// Here we override TS method return type to make it angular.IPromise
// We are using RtSimpleType with generic parameter of existing method type
result.ReturnType = new RtSimpleTypeName(new[] { retType }, "", $"Promise");
var flatReturnType = $"Promise<{retType}>";
var p = element.GetParameters().Select(c => string.Format("'{0}': {0}", c.Name));
var dataParameters = string.Join(", ", p);
// Here we get path to controller
// It is quite simple solution requiring /{controller}/{action} route
var area = element.DeclaringType?.GetCustomAttributes<AreaAttribute>().FirstOrDefault()
?.RouteValue;
var controller = element.DeclaringType?.Name.Replace("Controller", string.Empty);
var url = string.Join('/', new[] { "api", area, controller, element.Name }.Where(p2 => !string.IsNullOrWhiteSpace(p2)));
var postOptions = $@", {{
method: 'POST', {(dataParameters.Length > 0 ? $"\r\n\tbody: JSON.stringify({$"{{{dataParameters}}}"})," : string.Empty)}
headers: {{
'Content-Type': 'application/json'
}}
}}";
var getOptions = dataParameters.Length > 0
? $@" + new URLSearchParams({{ {string.Join(", ", element.GetParameters().Select(c => string.Format("{0}: {0}.toString()", c.Name)))}}})"
: string.Empty;
var fetch = $@"return fetch(`{url}{(getOptions.Length > 0 ? "?" : string.Empty)}`{(isHttpPost ? postOptions : getOptions)})
.then(res => {{ var result = res.json(); console.log('this.router.url', this.router.url); console.log('result', res, result); if (res.redirected) {{ this.router.navigateByUrl(new URL(res.url).pathname) }} return result as {flatReturnType} }});";
var code =
$"// IsHttpPost: {isHttpPost}" +
$"\r\n" +
$"// IsHttpGet: {isHttpGet}" +
$"\r\n" +
$"// IsAuthOnly: {isAuthOnly}" +
$"\r\n" +
$"{fetch}"
+ (false
? "\r\n" +
"// was: var params = {{ {dataParameters} }}; return this.http.post('{url}', params).then((response) => {{ response.data['requestParams'] = params; return response.data; }});"
: string.Empty);
var body = new RtRaw(code);
result.Body = body;
result.LineAfter = "\r\n";
return result;
}
}
public static bool IsNotWanted(MethodInfo element)
{
if (element == null)
return true;
if (element.IsDefined(typeof(NonActionAttribute)))
return true;
if (element.IsFamily)
return true;
// element.GetBaseDefinition().DeclaringType == element.DeclaringType && , typeof(ControllerBase), typeof(Controller)
if (new[] { typeof(IDisposable), typeof(IActionFilter), typeof(IAsyncActionFilter) }.Contains(element.ReflectedType))
return true;
if (element.IsPrivate)
return true;
if (element.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any()) // || element.IsOverride())
return true;
return false;
}
}
以下是对我自己的问题的粗略(但正确)回答。这些语句中的每一个都采用多种类型 (类) 及其方法,并根据标准轻松地将它们分开。请注意生成器仍将生成(空)类,无论它们是否具有任何符合条件的成员(方法)。这就是为什么我们在 types 的参数中使用 where 子句以及在第二组中使用 WithMethods(带参数)的原因代码。
builder.ExportAsClasses(types: allTypes.Where(p => p.IsSubclassOf(typeof(BaseAreaApiController))
&& p.IsSubclassOf(typeof(BaseMembersAreaApiController))).ToList(), configuration: conf => conf
.Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
.WithCodeGenerator<FunClassCodeGenerator>()
.WithPublicMethods(p => p.WithCodeGenerator<AngularActionCallGenerator>())
.AddImport("{ Injectable }", "@angular/core")
.AddImport("{ Observable }", "rxjs")
.AddImport("{ Router }", "@angular/router")
.Decorator("Injectable({ providedIn: 'root' })" + "\r\n")
.OverrideNamespace("AreaServices")
.ExportTo(FileName($"services.private")));
builder.ExportAsClasses(allTypes.Where(p => !p.IsSubclassOf(typeof(BaseAreaApiController))).ToList(), conf => conf
.Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
.WithCodeGenerator<FunClassCodeGenerator>()
.WithMethods(p => !p.GetCustomAttributes<AuthorizeAttribute>().Any(), p2 => p2.WithCodeGenerator<AngularActionCallGenerator>())
.AddImport("{ Injectable }", "@angular/core")
.AddImport("{ Observable }", "rxjs")
.AddImport("{ Router }", "@angular/router")
.Decorator("Injectable({ providedIn: 'root' })" + "\r\n")
.OverrideNamespace("PublicServices")
.ExportTo(FileName($"services.public")));