diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs index 76ad37eddc5..4c1351ffbae 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs +++ b/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 string GetNuGetPackageInfoUrl(string apiKey, string packageId) 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)}"; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs index 67c61b2a284..5efae62b902 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs @@ -68,8 +68,10 @@ private GenerateProxyArgs BuildArgs(CommandLineArgs commandLineArgs) 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 withoutContracts = commandLineArgs.Options.ContainsKey(Options.WithoutContracts.Short) || + 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, withoutContracts, commandLineArgs.Options); } public virtual string GetUsageInfo() @@ -162,5 +164,11 @@ public static class WorkDirectory public const string Short = "wd"; public const string Long = "working-directory"; } + + public static class WithoutContracts + { + public const string Short = "c"; + public const string Long = "without-contracts"; + } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs index f419f833a70..f2b151bb53a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs @@ -17,36 +17,70 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase" + + + private const string NamespacePlaceholder = ""; + private const string UsingPlaceholder = ""; + private const string MethodPlaceholder = ""; + private const string PropertyPlaceholder = ""; + private const string ClassNamePlaceholder = ""; + private const string ServiceInterfacePlaceholder = ""; + private const string DtoClassNamePlaceholder = ""; + private static readonly string ClassTemplate = "// This file is automatically generated by ABP framework to use MVC Controllers from CSharp" + + $"{Environment.NewLine}" + $"{Environment.NewLine}" + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + $"{Environment.NewLine}namespace ;" + $"{Environment.NewLine}" + $"{Environment.NewLine}[Dependency(ReplaceServices = true)]" + $"{Environment.NewLine}[ExposeServices(typeof(), typeof())]" + + $"{Environment.NewLine}[IntegrationService]" + $"{Environment.NewLine}public partial class : ClientProxyBase<>, " + $"{Environment.NewLine}{{" + - $"{Environment.NewLine} " + + $"{Environment.NewLine} " + + $"{Environment.NewLine}}}" + + $"{Environment.NewLine}"; + + private static readonly string ClassTemplateEmptyPart = "// This file is part of , you can customize it here" + + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + + $"{Environment.NewLine}namespace ;" + + $"{Environment.NewLine}" + + $"{Environment.NewLine}public partial class " + + $"{Environment.NewLine}{{" + $"{Environment.NewLine}}}" + $"{Environment.NewLine}"; - private readonly string _clientProxyTemplate = "// This file is part of , you can customize it here" + - $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + - $"{Environment.NewLine}namespace ;" + - $"{Environment.NewLine}" + - $"{Environment.NewLine}public partial class " + - $"{Environment.NewLine}{{" + - $"{Environment.NewLine}}}" + - $"{Environment.NewLine}"; - private readonly List _usingNamespaceList = new() + + private static readonly string InterfaceTemplate = "// This file is automatically generated by ABP framework to use MVC Controllers from CSharp" + + $"{Environment.NewLine}" + + $"{Environment.NewLine}" + + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + + $"{Environment.NewLine}namespace ;" + + $"{Environment.NewLine}" + + $"{Environment.NewLine}public interface : IApplicationService" + + $"{Environment.NewLine}{{" + + $"{Environment.NewLine} " + + $"{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}" + + $"{Environment.NewLine}" + + $"{Environment.NewLine}// ReSharper disable once CheckNamespace" + + $"{Environment.NewLine}namespace ;" + + $"{Environment.NewLine}" + + $"{Environment.NewLine}public " + + $"{Environment.NewLine}{{" + + $"{Environment.NewLine} " + + $"{Environment.NewLine}}}" + + $"{Environment.NewLine}"; + private static readonly List ClassUsingNamespaceList = new() { "using System;", "using System.Threading.Tasks;", @@ -57,6 +91,14 @@ public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase 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,167 @@ public CSharpServiceProxyGenerator( { } - 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); + } + + if (!args.WithoutContracts) + { + await GenerateDtoFileAsync(args, applicationApiDescriptionModel); } - await CreateGenerateProxyJsonFile(args, applicationApiDescriptionModel); + await CreateJsonFile(args, applicationApiDescriptionModel); } - private async Task CreateGenerateProxyJsonFile(GenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel) + private async Task CreateJsonFile(GenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel) { - var folder = args.Folder.IsNullOrWhiteSpace() ? DefaultNamespace : args.Folder; + 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)) { await writer.WriteAsync(JsonSerializer.Serialize(applicationApiDescriptionModel, indented: true)); } } - private void RemoveClientProxyFile(GenerateProxyArgs args) - { - var folder = args.Folder.IsNullOrWhiteSpace() ? DefaultNamespace : args.Folder; - var folderPath = Path.Combine(args.WorkDirectory, folder); - - if (Directory.Exists(folderPath)) - { - Directory.Delete(folderPath, 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(_usingNamespaceList) - { - $"using {GetTypeNamespace(appServiceTypeFullName)};" - }; - - clientProxyBuilder.Replace(ClassName, clientProxyName); - clientProxyBuilder.Replace(Namespace, rootNamespace); - clientProxyBuilder.Replace(ServiceInterface, appServiceTypeName); + var classTemplate = new StringBuilder(ClassTemplate); - foreach (var action in controllerApiDescription.Actions.Values) + var classUsingNamespaceList = new List(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(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 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 usingNamespaceList) + private void GenerateSyncClassMethod(ActionApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List usingNamespaceList) { methodBuilder.AppendLine($"public virtual {returnTypeName} {action.Name}()"); @@ -235,7 +284,7 @@ private void GenerateSynchronizationMethod(ActionApiDescriptionModel action, str methodBuilder.AppendLine(" }"); } - private void GenerateAsynchronousMethod( + private void GenerateAsyncClassMethod( ActionApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, @@ -261,14 +310,9 @@ private void GenerateAsynchronousMethod( 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,99 +324,236 @@ private void GenerateAsynchronousMethod( methodBuilder.AppendLine(" }"); } - private bool ShouldGenerateProxy(ControllerApiDescriptionModel controllerApiDescription) + private void GenerateInterfaceMethod(InterfaceMethodApiDescriptionModel action, StringBuilder clientProxyBuilder, List usingNamespaceList) { - if (!controllerApiDescription.Interfaces.Any()) + var methodBuilder = new StringBuilder(); + + var returnTypeName = GetRealTypeName(action.ReturnValue.Type, usingNamespaceList); + + if (action.Name.EndsWith("Async")) + { + GenerateAsyncInterfaceMethod(action, returnTypeName, methodBuilder, usingNamespaceList); + } + else { - return false; + 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 bool ShouldGenerateMethod(string appServiceTypeName, ActionApiDescriptionModel action) + private void GenerateSyncInterfaceMethod(InterfaceMethodApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List usingNamespaceList) { - var shouldGenerateMethod = action.ImplementFrom.StartsWith(AppServicePrefix) || action.ImplementFrom.StartsWith(appServiceTypeName); + methodBuilder.AppendLine($"public {returnTypeName} {action.Name}()"); - if (!shouldGenerateMethod) + foreach (var parameter in action.ParametersOnMethod.GroupBy(x => x.Name).Select(x => x.First())) { - shouldGenerateMethod = IsAppServiceInterface(GetRealTypeName(action.ImplementFrom)); + methodBuilder.Replace("", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, "); } - return shouldGenerateMethod; + if (action.ParametersOnMethod.Count == 0) + { + methodBuilder.Replace("()", "();"); + } + + methodBuilder.Replace("", string.Empty); + methodBuilder.Replace(", )", ");"); } - private bool IsAppServiceInterface(string typeName) + private void GenerateAsyncInterfaceMethod(InterfaceMethodApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List usingNamespaceList) { - return typeName.StartsWith("I") && ServicePostfixes.Any(typeName.EndsWith); + var returnSign = returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; + + methodBuilder.AppendLine($"{returnSign} {action.Name}()"); + + foreach (var parameter in action.ParametersOnMethod) + { + methodBuilder.Replace("", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, "); + } + + if (action.ParametersOnMethod.Count == 0) + { + methodBuilder.Replace("()", "();"); + } + + methodBuilder.Replace("", string.Empty); + methodBuilder.Replace(", )", ");"); } - private string GetTypeNamespace(string typeFullName) + private bool ShouldGenerateMethod(string appServiceTypeName, ActionApiDescriptionModel action) { - return typeFullName.Substring(0, typeFullName.LastIndexOf('.')); + return action.ImplementFrom.StartsWith(AppServicePrefix) || + action.ImplementFrom.StartsWith(appServiceTypeName) || + IsAppServiceInterface(GetRealTypeName(action.ImplementFrom)); } - private string GetRealTypeName(string typeName, List usingNamespaceList = null) + private async Task GenerateDtoFileAsync(GenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel) { - var filter = new[] { "<", ",", ">" }; - var stringBuilder = new StringBuilder(); - var typeNames = typeName.Split('.'); + var types = new List(); - if (typeNames.All(x => !filter.Any(x.Contains))) + foreach (var controller in applicationApiDescriptionModel.Modules.Values.First().Controllers) { - if (usingNamespaceList != null) + types.AddIfNotContains(applicationApiDescriptionModel.Types.Where(x => x.Key.StartsWith($"{GetTypeNamespace(controller.Value.Type)}")).Select(x => x.Key)); + } + + for (var i = 0; i < types.Count; i++) + { + var className = types[i]; + if (className.StartsWith("Volo.Abp.Application.Dtos.") && className.Contains("<") && className.Contains(">")) { - AddUsingNamespace(usingNamespaceList, typeName); + types[i] = className.Substring( + className.IndexOf("<", StringComparison.Ordinal) + 1, + className.IndexOf(">", StringComparison.Ordinal) - + className.IndexOf("<", StringComparison.Ordinal) -1); } - - return NormalizeTypeName(typeNames.Last()); } - var fullName = string.Empty; + types = types.Where(x => !x.StartsWith("System.") && !x.StartsWith("[System.")).Distinct().OrderBy(x => x).ToList(); - foreach (var item in typeNames) + foreach (var type in applicationApiDescriptionModel.Types.Where(x => types.Contains(x.Key))) { - if (filter.Any(x => item.Contains(x))) + var dto = new StringBuilder(DtoTemplate); + var dtoUsingNamespaceList = new List() + { + $"using {GetTypeNamespace(type.Key)};" + }; + + var genericTypeName = type.Key; + if (type.Value.GenericArguments != null) { - if (usingNamespaceList != null) + for (var j = 0; j < type.Value.GenericArguments.Length; j++) { - AddUsingNamespace(usingNamespaceList, $"{fullName}.{item}".TrimStart('.')); + genericTypeName = genericTypeName.Replace($"T{j}", type.Value.GenericArguments[j]); } + } - fullName = string.Empty; - - if (item.Contains('<') || item.Contains(',')) + dto.Replace(NamespacePlaceholder, GetTypeNamespace(genericTypeName)); + dto.Replace(DtoClassNamePlaceholder, type.Value.IsEnum + ? "enum " + GetRealTypeName(genericTypeName, dtoUsingNamespaceList) + : "class " + GetRealTypeName(genericTypeName, dtoUsingNamespaceList) + + (type.Value.BaseType.IsNullOrEmpty() + ? "" + : $" : {GetRealTypeName(type.Value.BaseType, dtoUsingNamespaceList)}")); + var properties = new StringBuilder(); + if (type.Value.IsEnum) + { + for (var i = 0; i < type.Value.EnumNames.Length; i++) { - stringBuilder.Append(item.Substring(0, item.IndexOf(item.Contains('<') ? '<' : ',') + 1)); - fullName = item.Substring(item.IndexOf(item.Contains('<') ? '<' : ',') + 1); + var enumName = type.Value.EnumNames[i]; + properties.Append($"{enumName} = {type.Value.EnumValues[i]}"); + + if (i < type.Value.EnumNames.Length - 1) + { + properties.Append($","); + properties.AppendLine(); + properties.Append(" "); + } } - else + } + else + { + if (!type.Value.Properties.IsNullOrEmpty()) { - stringBuilder.Append(item); + for (var i = 0; i < type.Value.Properties.Length; i++) + { + var property = type.Value.Properties[i]; + properties.Append("public "); + properties.Append(type.Value.GenericArguments.IsNullOrEmpty() + ? GetRealTypeName(property.Type, dtoUsingNamespaceList) + : GetRealTypeName(genericTypeName, dtoUsingNamespaceList)); + properties.Append($" {property.Name}"); + properties.Append(" { get; set; }"); + if (i < type.Value.Properties.Length - 1) + { + properties.AppendLine(); + properties.AppendLine(); + properties.Append(" "); + } + } } } - else + + 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(genericTypeName) + .Replace(".", Path.DirectorySeparatorChar.ToString()) + : args.Folder; + + var dtoFileName = GetRealTypeName(genericTypeName).Split("<")[0]; + var filePath = Path.Combine(args.WorkDirectory, folder, + $"{dtoFileName}.cs"); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + using (var writer = new StreamWriter(filePath)) { - fullName = $"{fullName}.{item}"; + await writer.WriteAsync(dto.ToString()); + Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}"); } } + } - return stringBuilder.ToString(); + private static bool IsAppServiceInterface(string typeName) + { + return typeName.StartsWith("I") && ServicePostfixes.Any(typeName.EndsWith); + } + + private static string GetTypeNamespace(string typeFullName) + { + return typeFullName.Substring(0, typeFullName.LastIndexOf('.')); } - private void AddUsingNamespace(List usingNamespaceList, string typeName) + private static string GetRealTypeName(string typeName, List usingNamespaceList = null) { - var rootNamespace = $"using {GetTypeNamespace(typeName)};"; - if (usingNamespaceList.Contains(rootNamespace)) + if (typeName.StartsWith("[") && typeName.EndsWith("]")) { - return; + return GetRealTypeName(typeName.Substring(1, typeName.Length - 2), usingNamespaceList) + "[]"; + } + + if (typeName.StartsWith("{") && typeName.EndsWith("}") && typeName.Contains(":")) + { + var dic = typeName.Substring(1, typeName.Length - 2).Split(":"); + var key = GetRealTypeName(dic[0], usingNamespaceList); + var value = GetRealTypeName(dic[1], usingNamespaceList); + return $"Dictionary<{key}, {value}>"; + } + + if (!typeName.Contains("<")) + { + usingNamespaceList?.AddIfNotContains($"using {GetTypeNamespace(typeName)};"); + return NormalizeTypeName(typeName.Split(".").Last()); + } + + var type = new StringBuilder(); + var s1 = typeName.Split("<"); + for (var i = 0; i < s1.Length; i++) + { + if (s1[i].Contains(",")) + { + var s2 = s1[i].Split(","); + for (var x = 0; x < s2.Length; x++) + { + type.Append(s2[x].Split(".").Last()); + if (x < s1.Length - 1) + { + type.Append(", "); + } + } + } + else + { + type.Append(s1[i].Split(".").Last()); + if (i < s1.Length - 1) + { + type.Append("<"); + } + } } - usingNamespaceList.Add(rootNamespace); + return type.ToString(); } - private string NormalizeTypeName(string typeName) + private static string NormalizeTypeName(string typeName) { var nullable = string.Empty; if (typeName.EndsWith("?")) @@ -383,14 +564,23 @@ private string NormalizeTypeName(string typeName) typeName = typeName switch { + "System.Void" => "void", "Void" => "void", + "System.Boolean" => "bool", "Boolean" => "bool", + "System.String" => "string", "String" => "string", + "System.Int32" => "int", "Int32" => "int", + "System.Int64" => "long", "Int64" => "long", + "System.Double" => "double", "Double" => "double", + "System.Object" => "object", "Object" => "object", + "System.Byte" => "byte", "Byte" => "byte", + "System.Char" => "char", "Char" => "char", _ => typeName }; @@ -398,7 +588,7 @@ private string NormalizeTypeName(string typeName) return $"{typeName}{nullable}"; } - private void CheckWorkDirectory(string directory) + private static void CheckWorkDirectory(string directory) { if (!Directory.Exists(directory)) { @@ -412,7 +602,7 @@ private void CheckWorkDirectory(string directory) } } - private void CheckFolder(string folder) + private static void CheckFolder(string folder) { if (!folder.IsNullOrWhiteSpace() && Path.HasExtension(folder)) { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs index ae3f0b4f2f5..e0219232822 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs +++ b/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 ExtraProperties { get; set; } @@ -38,6 +40,7 @@ public GenerateProxyArgs( string apiName, string source, string folder, + bool withoutContracts, Dictionary extraProperties = null) { CommandName = Check.NotNullOrWhiteSpace(commandName, nameof(commandName)); @@ -49,6 +52,7 @@ public GenerateProxyArgs( ApiName = apiName; Source = source; Folder = folder; + WithoutContracts = withoutContracts; ExtraProperties = extraProperties ?? new Dictionary(); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/ServiceProxyGeneratorBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/ServiceProxyGeneratorBase.cs index 2bcd89dc92b..512751dff06 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/ServiceProxyGeneratorBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/ServiceProxyGeneratorBase.cs @@ -27,13 +27,13 @@ protected ServiceProxyGeneratorBase(CliHttpClientFactory cliHttpClientFactory, I public abstract Task GenerateProxyAsync(GenerateProxyArgs args); - protected virtual async Task GetApplicationApiDescriptionModelAsync(GenerateProxyArgs args) + protected virtual async Task 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(apiDefinitionResult); var moduleDefinition = apiDefinition.Modules.FirstOrDefault(x => string.Equals(x.Key, args.Module, StringComparison.CurrentCultureIgnoreCase)).Value; @@ -43,6 +43,7 @@ protected virtual async Task GetApplicationApiDe } var apiDescriptionModel = ApplicationApiDescriptionModel.Create(); + apiDescriptionModel.Types = apiDefinition.Types; apiDescriptionModel.AddModule(moduleDefinition); return apiDescriptionModel; diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs index 05021a91156..81c64e8ec4b 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs +++ b/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 static ControllerApiDescriptionModel Create(string controllerName, string ControllerName = controllerName, ControllerGroupName = groupName, IsRemoteService = isRemoteService, + IntegrationService = IntegrationServiceAttribute.IsDefinedOrInherited(type), ApiVersion = apiVersion, Type = type.FullName, Actions = new Dictionary(), diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs index 5496a4fedd3..a0cfe31e263 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs +++ b/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 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(); + + var methodInfos = new List(); + 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; } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/InterfaceMethodApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/InterfaceMethodApiDescriptionModel.cs new file mode 100644 index 00000000000..53a43498549 --- /dev/null +++ b/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 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}]"; + } +}