Browse Source

Generate DTOs in the client-side for static c# proxy generation.

pull/14429/head
maliming 3 years ago
parent
commit
1f63d5bb42
No known key found for this signature in database GPG Key ID: 96224957E51C89E
  1. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs
  2. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs
  3. 463
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs
  4. 4
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs
  5. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/ServiceProxyGeneratorBase.cs
  6. 3
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs
  7. 36
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs
  8. 40
      framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/InterfaceMethodApiDescriptionModel.cs

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs

@ -1,4 +1,5 @@
using System;
using Volo.Abp.Http.Modeling;
namespace Volo.Abp.Cli;
@ -26,9 +27,9 @@ public static class CliUrls
return $"{NuGetRootPath}{apiKey}/v3/package/{packageId}/index.json";
}
public static string GetApiDefinitionUrl(string url)
public static string GetApiDefinitionUrl(string url, ApplicationApiDescriptionModelRequestDto model = null)
{
url = url.EnsureEndsWith('/');
return $"{url}api/abp/api-definition";
return $"{url}api/abp/api-definition{(model != null ? model.IncludeTypes ? "?includeTypes=true" : string.Empty : string.Empty)}";
}
}

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs

@ -68,8 +68,9 @@ public abstract class ProxyCommandBase<T> : IConsoleCommand, ITransientDependenc
var source = commandLineArgs.Options.GetOrNull(Options.Source.Short, Options.Source.Long);
var workDirectory = commandLineArgs.Options.GetOrNull(Options.WorkDirectory.Short, Options.WorkDirectory.Long) ?? Directory.GetCurrentDirectory();
var folder = commandLineArgs.Options.GetOrNull(Options.Folder.Long);
var withContracts = commandLineArgs.Options.ContainsKey(Options.WithoutContracts.Long);
return new GenerateProxyArgs(CommandName, workDirectory, module, url, output, target, apiName, source, folder, commandLineArgs.Options);
return new GenerateProxyArgs(CommandName, workDirectory, module, url, output, target, apiName, source, folder, withContracts, commandLineArgs.Options);
}
public virtual string GetUsageInfo()
@ -162,5 +163,10 @@ public abstract class ProxyCommandBase<T> : IConsoleCommand, ITransientDependenc
public const string Short = "wd";
public const string Long = "working-directory";
}
public static class WithoutContracts
{
public const string Long = "without-contracts";
}
}
}

463
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs

@ -17,36 +17,70 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
{
public const string Name = "CSHARP";
private const string UsingPlaceholder = "<using placeholder>";
private const string MethodPlaceholder = "<method placeholder>";
private const string ClassName = "<className>";
private const string ServiceInterface = "<serviceInterface>";
private readonly static string[] ServicePostfixes = { "AppService", "ApplicationService", "IntService", "IntegrationService" , "Service"};
private const string DefaultNamespace = "ClientProxies";
private const string Namespace = "<namespace>";
private const string ProxyDirectory = "ClientProxies";
private static readonly string[] ServicePostfixes = { "AppService", "ApplicationService", "IntService", "IntegrationService" , "Service"};
private const string AppServicePrefix = "Volo.Abp.Application.Services";
private readonly string _clientProxyGeneratedTemplate = "// This file is automatically generated by ABP framework to use MVC Controllers from CSharp" +
$"{Environment.NewLine}<using placeholder>" +
private const string NamespacePlaceholder = "<namespace>";
private const string UsingPlaceholder = "<using>";
private const string MethodPlaceholder = "<method>";
private const string PropertyPlaceholder = "<property>";
private const string ClassNamePlaceholder = "<className>";
private const string ServiceInterfacePlaceholder = "<serviceInterface>";
private const string DtoClassNamePlaceholder = "<dtoName>";
private static readonly string ClassTemplate = "// This file is automatically generated by ABP framework to use MVC Controllers from CSharp" +
$"{Environment.NewLine}<using>" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" +
$"{Environment.NewLine}namespace <namespace>;" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}[Dependency(ReplaceServices = true)]" +
$"{Environment.NewLine}[ExposeServices(typeof(<serviceInterface>), typeof(<className>))]" +
$"{Environment.NewLine}[IntegrationService]" +
$"{Environment.NewLine}public partial class <className> : ClientProxyBase<<serviceInterface>>, <serviceInterface>" +
$"{Environment.NewLine}{{" +
$"{Environment.NewLine} <method placeholder>" +
$"{Environment.NewLine} <method>" +
$"{Environment.NewLine}}}" +
$"{Environment.NewLine}";
private static readonly string ClassTemplateEmptyPart = "// This file is part of <className>, you can customize it here" +
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" +
$"{Environment.NewLine}namespace <namespace>;" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}public partial class <className>" +
$"{Environment.NewLine}{{" +
$"{Environment.NewLine}}}" +
$"{Environment.NewLine}";
private readonly string _clientProxyTemplate = "// This file is part of <className>, you can customize it here" +
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" +
$"{Environment.NewLine}namespace <namespace>;" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}public partial class <className>" +
$"{Environment.NewLine}{{" +
$"{Environment.NewLine}}}" +
$"{Environment.NewLine}";
private readonly List<string> _usingNamespaceList = new()
private static readonly string InterfaceTemplate = "// This file is automatically generated by ABP framework to use MVC Controllers from CSharp" +
$"{Environment.NewLine}<using>" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" +
$"{Environment.NewLine}namespace <namespace>;" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}public interface <serviceInterface> : IApplicationService" +
$"{Environment.NewLine}{{" +
$"{Environment.NewLine} <method>" +
$"{Environment.NewLine}}}" +
$"{Environment.NewLine}";
private static readonly string DtoTemplate = "// This file is automatically generated by ABP framework to use MVC Controllers from CSharp" +
$"{Environment.NewLine}using System;" +
$"{Environment.NewLine}using System.Collections.Generic;" +
$"{Environment.NewLine}using Volo.Abp.Application.Dtos;" +
$"{Environment.NewLine}using Volo.Abp.ObjectExtending;" +
$"{Environment.NewLine}<using>" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" +
$"{Environment.NewLine}namespace <namespace>;" +
$"{Environment.NewLine}" +
$"{Environment.NewLine}public class <dtoName>" +
$"{Environment.NewLine}{{" +
$"{Environment.NewLine} <property>" +
$"{Environment.NewLine}}}" +
$"{Environment.NewLine}";
private static readonly List<string> ClassUsingNamespaceList = new()
{
"using System;",
"using System.Threading.Tasks;",
@ -57,6 +91,14 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
"using Volo.Abp.Http.Client.ClientProxying;"
};
private static readonly List<string> InterfaceUsingNamespaceList = new()
{
"using System;",
"using System.Threading.Tasks;",
"using Volo.Abp.Application.Dtos;",
"using Volo.Abp.Application.Services;"
};
public CSharpServiceProxyGenerator(
CliHttpClientFactory cliHttpClientFactory,
IJsonSerializer jsonSerializer) :
@ -64,160 +106,187 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
{
}
public async override Task GenerateProxyAsync(GenerateProxyArgs args)
public override async Task GenerateProxyAsync(GenerateProxyArgs args)
{
CheckWorkDirectory(args.WorkDirectory);
CheckFolder(args.Folder);
if (args.CommandName == RemoveProxyCommand.Name)
{
RemoveClientProxyFile(args);
var folder = args.Folder.IsNullOrWhiteSpace() ? ProxyDirectory : args.Folder;
var folderPath = Path.Combine(args.WorkDirectory, folder);
if (Directory.Exists(folderPath))
{
Directory.Delete(folderPath, true);
}
Logger.LogInformation($"Delete {GetLoggerOutputPath(folderPath, args.WorkDirectory)}");
return;
}
var applicationApiDescriptionModel = await GetApplicationApiDescriptionModelAsync(args);
var applicationApiDescriptionModel = await GetApplicationApiDescriptionModelAsync(args, new ApplicationApiDescriptionModelRequestDto
{
IncludeTypes = !args.WithoutContracts
});
foreach (var controller in applicationApiDescriptionModel.Modules.Values.SelectMany(x => x.Controllers))
foreach (var controller in applicationApiDescriptionModel.Modules.Values.SelectMany(x => x.Controllers)
.Where(x => x.Value.Interfaces.Any() && ServicePostfixes.Any(s => x.Value.Interfaces.Last().Type.EndsWith(s))))
{
if (ShouldGenerateProxy(controller.Value))
{
await GenerateClientProxyFileAsync(args, controller.Value);
}
await GenerateClassFileAsync(args, controller.Value);
}
foreach (var controller in applicationApiDescriptionModel.Modules.Values.SelectMany(x => x.Controllers).Where(x => ShouldGenerateProxy(x.Value)))
{
await GenerateClassFileAsync(args, controller.Value);
}
if (!args.WithoutContracts)
{
await GenerateDtoFileAsync(args, applicationApiDescriptionModel);
}
await CreateGenerateProxyJsonFile(args, applicationApiDescriptionModel);
await CreateJsonFile(args, applicationApiDescriptionModel);
}
private async Task CreateGenerateProxyJsonFile(GenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel)
private void DeleteClientProxyDirectory(GenerateProxyArgs args)
{
var folder = args.Folder.IsNullOrWhiteSpace() ? DefaultNamespace : args.Folder;
var filePath = Path.Combine(args.WorkDirectory, folder, $"{args.Module}-generate-proxy.json");
using (var writer = new StreamWriter(filePath))
}
private bool ShouldGenerateProxy(ControllerApiDescriptionModel controllerApiDescription)
{
if (!controllerApiDescription.Interfaces.Any())
{
await writer.WriteAsync(JsonSerializer.Serialize(applicationApiDescriptionModel, indented: true));
return false;
}
var serviceInterface = controllerApiDescription.Interfaces.Last();
return ServicePostfixes.Any(x => serviceInterface.Type.EndsWith(x));
}
private void RemoveClientProxyFile(GenerateProxyArgs args)
private async Task CreateJsonFile(GenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel)
{
var folder = args.Folder.IsNullOrWhiteSpace() ? DefaultNamespace : args.Folder;
var folderPath = Path.Combine(args.WorkDirectory, folder);
if (Directory.Exists(folderPath))
var folder = args.Folder.IsNullOrWhiteSpace() ? ProxyDirectory : args.Folder;
var filePath = Path.Combine(args.WorkDirectory, folder, $"{args.Module}-generate-proxy.json");
using (var writer = new StreamWriter(filePath))
{
Directory.Delete(folderPath, true);
await writer.WriteAsync(JsonSerializer.Serialize(applicationApiDescriptionModel, indented: true));
}
Logger.LogInformation($"Delete {GetLoggerOutputPath(folderPath, args.WorkDirectory)}");
}
private async Task GenerateClientProxyFileAsync(
private async Task GenerateClassFileAsync(
GenerateProxyArgs args,
ControllerApiDescriptionModel controllerApiDescription)
{
var folder = args.Folder.IsNullOrWhiteSpace() ? DefaultNamespace : args.Folder;
var folder = args.Folder.IsNullOrWhiteSpace()
? ProxyDirectory + Path.DirectorySeparatorChar + GetTypeNamespace(controllerApiDescription.Type).Replace(".", Path.DirectorySeparatorChar.ToString())
: args.Folder;
var appServiceTypeFullName = controllerApiDescription.Interfaces.Last().Type;
var appServiceTypeName = appServiceTypeFullName.Split('.').Last();
var clientProxyName = $"{controllerApiDescription.ControllerName}ClientProxy";
var rootNamespace = $"{GetTypeNamespace(controllerApiDescription.Type)}.{folder.Replace('/', '.')}";
var rootNamespace = $"{GetTypeNamespace(controllerApiDescription.Type)}";
var clientProxyBuilder = new StringBuilder(_clientProxyTemplate);
clientProxyBuilder.Replace(ClassName, clientProxyName);
clientProxyBuilder.Replace(Namespace, rootNamespace);
var classTemplateEmptyPart = new StringBuilder(ClassTemplateEmptyPart);
classTemplateEmptyPart.Replace(ClassNamePlaceholder, clientProxyName);
classTemplateEmptyPart.Replace(NamespacePlaceholder, rootNamespace);
var filePath = Path.Combine(args.WorkDirectory, folder, $"{clientProxyName}.cs");
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
if (!File.Exists(filePath))
{
using (var writer = new StreamWriter(filePath))
{
await writer.WriteAsync(clientProxyBuilder.ToString());
await writer.WriteAsync(classTemplateEmptyPart.ToString());
}
Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}");
}
await GenerateClientProxyGeneratedFileAsync(
args,
controllerApiDescription,
clientProxyName,
appServiceTypeName,
appServiceTypeFullName,
rootNamespace,
folder);
}
private async Task GenerateClientProxyGeneratedFileAsync(
GenerateProxyArgs args,
ControllerApiDescriptionModel controllerApiDescription,
string clientProxyName,
string appServiceTypeName,
string appServiceTypeFullName,
string rootNamespace,
string folder)
{
var clientProxyBuilder = new StringBuilder(_clientProxyGeneratedTemplate);
var usingNamespaceList = new List<string>(_usingNamespaceList)
{
$"using {GetTypeNamespace(appServiceTypeFullName)};"
};
var classTemplate = new StringBuilder(ClassTemplate);
clientProxyBuilder.Replace(ClassName, clientProxyName);
clientProxyBuilder.Replace(Namespace, rootNamespace);
clientProxyBuilder.Replace(ServiceInterface, appServiceTypeName);
foreach (var action in controllerApiDescription.Actions.Values)
var classUsingNamespaceList = new List<string>(ClassUsingNamespaceList)
{
if (!ShouldGenerateMethod(appServiceTypeFullName, action))
{
continue;
}
$"using {GetTypeNamespace(appServiceTypeFullName)};"
};
GenerateMethod(action, clientProxyBuilder, usingNamespaceList);
if (!controllerApiDescription.IntegrationService)
{
classTemplate.Replace($"{Environment.NewLine}[IntegrationService]", string.Empty);
}
foreach (var usingNamespace in usingNamespaceList)
classTemplate.Replace(ClassNamePlaceholder, clientProxyName);
classTemplate.Replace(NamespacePlaceholder, rootNamespace);
classTemplate.Replace(ServiceInterfacePlaceholder, appServiceTypeName);
foreach (var action in controllerApiDescription.Actions.Values.Where(x => ShouldGenerateMethod(appServiceTypeFullName, x)))
{
clientProxyBuilder.Replace($"{UsingPlaceholder}", $"{usingNamespace}{Environment.NewLine}{UsingPlaceholder}");
GenerateClassMethod(action, classTemplate, classUsingNamespaceList);
}
clientProxyBuilder.Replace($"{Environment.NewLine}{UsingPlaceholder}", string.Empty);
clientProxyBuilder.Replace($"{Environment.NewLine}{Environment.NewLine} {MethodPlaceholder}", string.Empty);
var filePath = Path.Combine(args.WorkDirectory, folder, $"{clientProxyName}.Generated.cs");
classTemplate.Replace($"{UsingPlaceholder}", string.Join(Environment.NewLine, classUsingNamespaceList.Distinct().OrderBy(x => x).Select(x => x)));
classTemplate.Replace($"{Environment.NewLine}{Environment.NewLine} {MethodPlaceholder}", string.Empty);
filePath = Path.Combine(args.WorkDirectory, folder, $"{clientProxyName}.Generated.cs");
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using (var writer = new StreamWriter(filePath))
{
await writer.WriteAsync(clientProxyBuilder.ToString());
await writer.WriteAsync(classTemplate.ToString());
Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}");
}
if (!args.WithoutContracts)
{
var interfaceTemplate = new StringBuilder(InterfaceTemplate);
interfaceTemplate.Replace(ServiceInterfacePlaceholder, appServiceTypeName);
var @interface = controllerApiDescription.Interfaces.Last();
var interfaceUsingNamespaceList = new List<string>(InterfaceUsingNamespaceList)
{
$"using {GetTypeNamespace(appServiceTypeFullName)};"
};
foreach (var method in @interface.Methods)
{
GenerateInterfaceMethod(method, interfaceTemplate, interfaceUsingNamespaceList);
}
interfaceTemplate.Replace($"{UsingPlaceholder}", string.Join(Environment.NewLine, interfaceUsingNamespaceList.Distinct().OrderBy(x => x).Select(x => x)));
interfaceTemplate.Replace($"{Environment.NewLine}{Environment.NewLine} {MethodPlaceholder}", string.Empty);
interfaceTemplate.Replace(NamespacePlaceholder, rootNamespace);
filePath = Path.Combine(args.WorkDirectory, folder, $"{appServiceTypeName}.cs");
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using (var writer = new StreamWriter(filePath))
{
await writer.WriteAsync(interfaceTemplate.ToString());
Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}");
}
}
}
private void GenerateMethod(
private void GenerateClassMethod(
ActionApiDescriptionModel action,
StringBuilder clientProxyBuilder,
List<string> usingNamespaceList)
{
var methodBuilder = new StringBuilder();
var returnTypeName = GetRealTypeName(action.ReturnValue.Type, usingNamespaceList);
if (!action.Name.EndsWith("Async"))
if (action.Name.EndsWith("Async"))
{
GenerateSynchronizationMethod(action, returnTypeName, methodBuilder, usingNamespaceList);
clientProxyBuilder.Replace(MethodPlaceholder, $"{methodBuilder}{Environment.NewLine} {MethodPlaceholder}");
return;
GenerateAsyncClassMethod(action, returnTypeName, methodBuilder, usingNamespaceList);
}
else
{
GenerateSyncClassMethod(action, returnTypeName, methodBuilder, usingNamespaceList);
}
GenerateAsynchronousMethod(action, returnTypeName, methodBuilder, usingNamespaceList);
clientProxyBuilder.Replace(MethodPlaceholder, $"{methodBuilder}{Environment.NewLine} {MethodPlaceholder}");
}
private void GenerateSynchronizationMethod(ActionApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List<string> usingNamespaceList)
private void GenerateSyncClassMethod(ActionApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List<string> usingNamespaceList)
{
methodBuilder.AppendLine($"public virtual {returnTypeName} {action.Name}(<args>)");
@ -235,7 +304,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
methodBuilder.AppendLine(" }");
}
private void GenerateAsynchronousMethod(
private void GenerateAsyncClassMethod(
ActionApiDescriptionModel action,
string returnTypeName,
StringBuilder methodBuilder,
@ -261,14 +330,9 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
var args = action.ParametersOnMethod.Any() ? argsTemplate : string.Empty;
if (returnTypeName == "void")
{
methodBuilder.AppendLine($" await RequestAsync(nameof({action.Name}), {args});");
}
else
{
methodBuilder.AppendLine($" return await RequestAsync<{returnTypeName}>(nameof({action.Name}), {args});");
}
methodBuilder.AppendLine(returnTypeName == "void"
? $" await RequestAsync(nameof({action.Name}), {args});"
: $" return await RequestAsync<{returnTypeName}>(nameof({action.Name}), {args});");
foreach (var parameter in action.ParametersOnMethod)
{
@ -280,41 +344,192 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
methodBuilder.AppendLine(" }");
}
private bool ShouldGenerateProxy(ControllerApiDescriptionModel controllerApiDescription)
private void GenerateInterfaceMethod(InterfaceMethodApiDescriptionModel action, StringBuilder clientProxyBuilder, List<string> usingNamespaceList)
{
if (!controllerApiDescription.Interfaces.Any())
var methodBuilder = new StringBuilder();
var returnTypeName = GetRealTypeName(action.ReturnValue.Type, usingNamespaceList);
if (action.Name.EndsWith("Async"))
{
return false;
GenerateAsyncInterfaceMethod(action, returnTypeName, methodBuilder, usingNamespaceList);
}
else
{
GenerateSyncInterfaceMethod(action, returnTypeName, methodBuilder, usingNamespaceList);
}
var serviceInterface = controllerApiDescription.Interfaces.Last();
return ServicePostfixes.Any(x => serviceInterface.Type.EndsWith(x));
clientProxyBuilder.Replace(MethodPlaceholder, $"{methodBuilder}{Environment.NewLine} {MethodPlaceholder}");
}
private void GenerateSyncInterfaceMethod(InterfaceMethodApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List<string> usingNamespaceList)
{
methodBuilder.AppendLine($"public {returnTypeName} {action.Name}(<args>)");
foreach (var parameter in action.ParametersOnMethod.GroupBy(x => x.Name).Select(x => x.First()))
{
methodBuilder.Replace("<args>", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, <args>");
}
if (action.ParametersOnMethod.Count == 0)
{
methodBuilder.Replace("(<args>)", "(<args>);");
}
methodBuilder.Replace("<args>", string.Empty);
methodBuilder.Replace(", )", ");");
}
private void GenerateAsyncInterfaceMethod(InterfaceMethodApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List<string> usingNamespaceList)
{
var returnSign = returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>";
methodBuilder.AppendLine($"{returnSign} {action.Name}(<args>)");
foreach (var parameter in action.ParametersOnMethod)
{
methodBuilder.Replace("<args>", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, <args>");
}
if (action.ParametersOnMethod.Count == 0)
{
methodBuilder.Replace("(<args>)", "(<args>);");
}
methodBuilder.Replace("<args>", string.Empty);
methodBuilder.Replace(", )", ");");
}
private bool ShouldGenerateMethod(string appServiceTypeName, ActionApiDescriptionModel action)
{
var shouldGenerateMethod = action.ImplementFrom.StartsWith(AppServicePrefix) || action.ImplementFrom.StartsWith(appServiceTypeName);
return action.ImplementFrom.StartsWith(AppServicePrefix) ||
action.ImplementFrom.StartsWith(appServiceTypeName) ||
IsAppServiceInterface(GetRealTypeName(action.ImplementFrom));
}
private async Task GenerateDtoFileAsync(GenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel)
{
var types = new List<string>();
if (!shouldGenerateMethod)
foreach (var controller in applicationApiDescriptionModel.Modules.Values.First().Controllers)
{
shouldGenerateMethod = IsAppServiceInterface(GetRealTypeName(action.ImplementFrom));
foreach (var action in controller.Value.Actions)
{
foreach (var parameter in action.Value.ParametersOnMethod)
{
types.AddIfNotContains(parameter.Type);
}
types.AddIfNotContains(action.Value.ReturnValue.Type);
}
}
types = RemoveSystemAndDtoType(types);
foreach (var type in applicationApiDescriptionModel.Types.Where(x => types.Contains(x.Key)))
{
GetRelatedTypes(types, applicationApiDescriptionModel, type.Value);
}
types = RemoveSystemAndDtoType(types);
foreach (var type in applicationApiDescriptionModel.Types.Where(x => types.Contains(x.Key)))
{
var dto = new StringBuilder(DtoTemplate);
var dtoUsingNamespaceList = new List<string>()
{
$"using {GetTypeNamespace(type.Key)};"
};
dto.Replace(NamespacePlaceholder, GetTypeNamespace(type.Key));
dto.Replace(DtoClassNamePlaceholder,GetRealTypeName(type.Key, dtoUsingNamespaceList) + (type.Value.BaseType.IsNullOrEmpty() ? "" : $" : {GetRealTypeName(type.Value.BaseType, dtoUsingNamespaceList)}"));
var properties = new StringBuilder();
for (var i = 0; i < type.Value.Properties.Length; i++)
{
var property = type.Value.Properties[i];
properties.Append("public ");
properties.Append(GetRealTypeName(property.Type, dtoUsingNamespaceList));
properties.Append($" {property.Name}");
properties.Append(" { get; set; }");
if (i < type.Value.Properties.Length - 1)
{
properties.AppendLine();
properties.AppendLine();
properties.Append(" ");
}
}
dto.Replace($"{UsingPlaceholder}", string.Join(Environment.NewLine, dtoUsingNamespaceList.OrderBy(x => x).Select(x => x)));
dto.Replace(PropertyPlaceholder, properties.ToString());
var folder = args.Folder.IsNullOrWhiteSpace()
? ProxyDirectory + Path.DirectorySeparatorChar + GetTypeNamespace(type.Key)
.Replace(".", Path.DirectorySeparatorChar.ToString())
: args.Folder;
var filePath = Path.Combine(args.WorkDirectory, folder, $"{GetRealTypeName(type.Key)}.cs");
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
using (var writer = new StreamWriter(filePath))
{
await writer.WriteAsync(dto.ToString());
Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}");
}
}
}
private static void GetRelatedTypes(List<string> types, ApplicationApiDescriptionModel applicationApiDescriptionModel, TypeApiDescriptionModel typeApiDescriptionModel)
{
if (!typeApiDescriptionModel.BaseType.IsNullOrEmpty() &&
!typeApiDescriptionModel.BaseType.StartsWith("Volo.Abp.Application.Dtos") &&
!typeApiDescriptionModel.BaseType.StartsWith("Volo.Abp.ObjectExtending"))
{
types.AddIfNotContains(typeApiDescriptionModel.BaseType);
}
foreach (var property in typeApiDescriptionModel.Properties.Where(x => !types.Contains(x.Type)))
{
types.AddIfNotContains(property.Type);
if (applicationApiDescriptionModel.Types.ContainsKey(property.Type))
{
GetRelatedTypes(types, applicationApiDescriptionModel, applicationApiDescriptionModel.Types[property.Type]);
}
}
}
return shouldGenerateMethod;
private static List<string> RemoveSystemAndDtoType(List<string> types)
{
for (var i = 0; i < types.Count; i++)
{
var className = types[i];
if (className.StartsWith("Volo.Abp.Application.Dtos.") && className.Contains("<") && className.Contains(">"))
{
types[i] = className.Substring(
className.IndexOf("<", StringComparison.Ordinal) + 1,
className.IndexOf(">", StringComparison.Ordinal) -
className.IndexOf("<", StringComparison.Ordinal) -1);
}
}
return types.Where(x => !x.StartsWith("System.") && !x.StartsWith("[System.")).Distinct().OrderBy(x => x).ToList();
}
private bool IsAppServiceInterface(string typeName)
private static bool IsAppServiceInterface(string typeName)
{
return typeName.StartsWith("I") && ServicePostfixes.Any(typeName.EndsWith);
}
private string GetTypeNamespace(string typeFullName)
private static string GetTypeNamespace(string typeFullName)
{
return typeFullName.Substring(0, typeFullName.LastIndexOf('.'));
}
private string GetRealTypeName(string typeName, List<string> usingNamespaceList = null)
{
if (typeName.StartsWith("[") && typeName.EndsWith("]"))
{
return GetRealTypeName(typeName.Substring(1, typeName.Length - 2), usingNamespaceList) + "[]";
}
var filter = new[] { "<", ",", ">" };
var stringBuilder = new StringBuilder();
var typeNames = typeName.Split('.');
@ -361,7 +576,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
return stringBuilder.ToString();
}
private void AddUsingNamespace(List<string> usingNamespaceList, string typeName)
private static void AddUsingNamespace(List<string> usingNamespaceList, string typeName)
{
var rootNamespace = $"using {GetTypeNamespace(typeName)};";
if (usingNamespaceList.Contains(rootNamespace))
@ -372,7 +587,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
usingNamespaceList.Add(rootNamespace);
}
private string NormalizeTypeName(string typeName)
private static string NormalizeTypeName(string typeName)
{
var nullable = string.Empty;
if (typeName.EndsWith("?"))
@ -398,7 +613,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
return $"{typeName}{nullable}";
}
private void CheckWorkDirectory(string directory)
private static void CheckWorkDirectory(string directory)
{
if (!Directory.Exists(directory))
{
@ -412,7 +627,7 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase<CSharpServi
}
}
private void CheckFolder(string folder)
private static void CheckFolder(string folder)
{
if (!folder.IsNullOrWhiteSpace() && Path.HasExtension(folder))
{

4
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs

@ -25,6 +25,8 @@ public class GenerateProxyArgs
public string Folder { get; }
public bool WithoutContracts { get; }
[NotNull]
public Dictionary<string, string> ExtraProperties { get; set; }
@ -38,6 +40,7 @@ public class GenerateProxyArgs
string apiName,
string source,
string folder,
bool withoutContracts,
Dictionary<string, string> extraProperties = null)
{
CommandName = Check.NotNullOrWhiteSpace(commandName, nameof(commandName));
@ -49,6 +52,7 @@ public class GenerateProxyArgs
ApiName = apiName;
Source = source;
Folder = folder;
WithoutContracts = withoutContracts;
ExtraProperties = extraProperties ?? new Dictionary<string, string>();
}
}

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/ServiceProxyGeneratorBase.cs

@ -27,13 +27,13 @@ public abstract class ServiceProxyGeneratorBase<T> : IServiceProxyGenerator wher
public abstract Task GenerateProxyAsync(GenerateProxyArgs args);
protected virtual async Task<ApplicationApiDescriptionModel> GetApplicationApiDescriptionModelAsync(GenerateProxyArgs args)
protected virtual async Task<ApplicationApiDescriptionModel> GetApplicationApiDescriptionModelAsync(GenerateProxyArgs args, ApplicationApiDescriptionModelRequestDto requestDto = null)
{
Check.NotNull(args.Url, nameof(args.Url));
var client = CliHttpClientFactory.CreateClient();
var apiDefinitionResult = await client.GetStringAsync(CliUrls.GetApiDefinitionUrl(args.Url));
var apiDefinitionResult = await client.GetStringAsync(CliUrls.GetApiDefinitionUrl(args.Url, requestDto));
var apiDefinition = JsonSerializer.Deserialize<ApplicationApiDescriptionModel>(apiDefinitionResult);
var moduleDefinition = apiDefinition.Modules.FirstOrDefault(x => string.Equals(x.Key, args.Module, StringComparison.CurrentCultureIgnoreCase)).Value;
@ -43,6 +43,7 @@ public abstract class ServiceProxyGeneratorBase<T> : IServiceProxyGenerator wher
}
var apiDescriptionModel = ApplicationApiDescriptionModel.Create();
apiDescriptionModel.Types = apiDefinition.Types;
apiDescriptionModel.AddModule(moduleDefinition);
return apiDescriptionModel;

3
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs

@ -14,6 +14,8 @@ public class ControllerApiDescriptionModel
public bool IsRemoteService { get; set; }
public bool IntegrationService { get; set; }
public string ApiVersion { get; set; }
public string Type { get; set; }
@ -34,6 +36,7 @@ public class ControllerApiDescriptionModel
ControllerName = controllerName,
ControllerGroupName = groupName,
IsRemoteService = isRemoteService,
IntegrationService = IntegrationServiceAttribute.IsDefinedOrInherited(type),
ApiVersion = apiVersion,
Type = type.FullName,
Actions = new Dictionary<string, ActionApiDescriptionModel>(),

36
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Volo.Abp.Http.Modeling;
@ -7,6 +10,10 @@ public class ControllerInterfaceApiDescriptionModel
{
public string Type { get; set; }
public string Name { get; set; }
public InterfaceMethodApiDescriptionModel[] Methods { get; set; }
public ControllerInterfaceApiDescriptionModel()
{
@ -14,9 +21,34 @@ public class ControllerInterfaceApiDescriptionModel
public static ControllerInterfaceApiDescriptionModel Create(Type type)
{
return new ControllerInterfaceApiDescriptionModel
var model = new ControllerInterfaceApiDescriptionModel
{
Type = type.FullName
Type = type.FullName,
Name = type.Name
};
var methods = new List<InterfaceMethodApiDescriptionModel>();
var methodInfos = new List<MethodInfo>();
foreach (var methodInfo in type.GetMethods())
{
methodInfos.Add(methodInfo);
methods.Add(InterfaceMethodApiDescriptionModel.Create(methodInfo));
}
foreach (var @interface in type.GetInterfaces())
{
foreach (var method in @interface.GetMethods())
{
if (!methodInfos.Contains(method))
{
methods.Add(InterfaceMethodApiDescriptionModel.Create(method));
}
methodInfos.Add(method);
}
}
model.Methods = methods.ToArray();
return model;
}
}

40
framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/InterfaceMethodApiDescriptionModel.cs

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.Http.Modeling;
[Serializable]
public class InterfaceMethodApiDescriptionModel
{
public string Name { get; set; }
public IList<MethodParameterApiDescriptionModel> ParametersOnMethod { get; set; }
public ReturnValueApiDescriptionModel ReturnValue { get; set; }
public InterfaceMethodApiDescriptionModel()
{
}
public static InterfaceMethodApiDescriptionModel Create([NotNull] MethodInfo method)
{
return new InterfaceMethodApiDescriptionModel
{
Name = method.Name,
ReturnValue = ReturnValueApiDescriptionModel.Create(method.ReturnType),
ParametersOnMethod = method
.GetParameters()
.Select(MethodParameterApiDescriptionModel.Create)
.ToList(),
};
}
public override string ToString()
{
return $"[InterfaceMethodApiDescriptionModel {Name}]";
}
}
Loading…
Cancel
Save