diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln index 64f8b664f..d7c8a7fa4 100644 --- a/aspnet-core/LINGYUN.MicroService.All.sln +++ b/aspnet-core/LINGYUN.MicroService.All.sln @@ -468,6 +468,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Notifications.Jobs", "modules\common\LINGYUN.Abp.Notifications.Jobs\LINGYUN.Abp.Notifications.Jobs.csproj", "{3E9D07D8-963C-4A61-B720-BD47593BE752}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Http.Client.Wrapper", "modules\common\LINGYUN.Abp.Http.Client.Wrapper\LINGYUN.Abp.Http.Client.Wrapper.csproj", "{942816E3-B270-40DC-9532-C1077FF59A32}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper", "modules\dapr\LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper\LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper.csproj", "{FBB50072-33BE-4B4A-8908-E98BC0C80B92}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1214,6 +1218,14 @@ Global {3E9D07D8-963C-4A61-B720-BD47593BE752}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E9D07D8-963C-4A61-B720-BD47593BE752}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E9D07D8-963C-4A61-B720-BD47593BE752}.Release|Any CPU.Build.0 = Release|Any CPU + {942816E3-B270-40DC-9532-C1077FF59A32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {942816E3-B270-40DC-9532-C1077FF59A32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {942816E3-B270-40DC-9532-C1077FF59A32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {942816E3-B270-40DC-9532-C1077FF59A32}.Release|Any CPU.Build.0 = Release|Any CPU + {FBB50072-33BE-4B4A-8908-E98BC0C80B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBB50072-33BE-4B4A-8908-E98BC0C80B92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBB50072-33BE-4B4A-8908-E98BC0C80B92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBB50072-33BE-4B4A-8908-E98BC0C80B92}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1443,6 +1455,8 @@ Global {ECC8B9A9-9E92-4493-984D-2E350A49189D} = {C5CAD011-DF84-4914-939C-0C029DCEF26F} {62971DAE-CE71-4E9C-B6F5-514C8E2B915C} = {ECC8B9A9-9E92-4493-984D-2E350A49189D} {3E9D07D8-963C-4A61-B720-BD47593BE752} = {ECC8B9A9-9E92-4493-984D-2E350A49189D} + {942816E3-B270-40DC-9532-C1077FF59A32} = {8AC72641-30D3-4ACF-89FA-808FADC55C2E} + {FBB50072-33BE-4B4A-8908-E98BC0C80B92} = {DC33925B-264D-421B-96CC-46F853CBCC70} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718} diff --git a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/AbpCliModule.cs b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/AbpCliModule.cs index 4439a5c66..bb286cacd 100644 --- a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/AbpCliModule.cs +++ b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/AbpCliModule.cs @@ -16,8 +16,9 @@ namespace LINGYUN.Abp.Cli Configure(options => { options.Commands.Clear(); - options.Commands["help"] = typeof(HelpCommand); - options.Commands["create"] = typeof(CreateCommand); + options.Commands[HelpCommand.Name] = typeof(HelpCommand); + options.Commands[CreateCommand.Name] = typeof(CreateCommand); + options.Commands[GenerateProxyCommand.Name] = typeof(GenerateProxyCommand); }); } } diff --git a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/CreateCommand.cs b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/CreateCommand.cs index fff109ddf..e948aa07e 100644 --- a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/CreateCommand.cs +++ b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/CreateCommand.cs @@ -19,6 +19,10 @@ namespace LINGYUN.Abp.Cli.Commands { public class CreateCommand : IConsoleCommand, ITransientDependency { + public const string Name = "create"; + + protected string CommandName => Name; + public class FindFile { public string Path { get; } diff --git a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/GenerateProxyCommand.cs b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/GenerateProxyCommand.cs new file mode 100644 index 000000000..c1f490609 --- /dev/null +++ b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/GenerateProxyCommand.cs @@ -0,0 +1,146 @@ +using LINGYUN.Abp.Cli.ServiceProxying; +using LINGYUN.Abp.Cli.ServiceProxying.CSharp; +using Microsoft.Extensions.DependencyInjection; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Commands; +using Volo.Abp.DependencyInjection; + +using VoloGenerateProxyArgs = Volo.Abp.Cli.ServiceProxying.GenerateProxyArgs; + +namespace LINGYUN.Abp.Cli.Commands; + +public class GenerateProxyCommand : IConsoleCommand, ITransientDependency +{ + public const string Name = "generate-proxy"; + + protected string CommandName => Name; + + protected IServiceScopeFactory ServiceScopeFactory { get; } + + public GenerateProxyCommand( + IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var serviceProxyGenerator = scope.ServiceProvider.GetRequiredService(); + + await serviceProxyGenerator.GenerateProxyAsync(BuildArgs(commandLineArgs)); + } + } + + private VoloGenerateProxyArgs BuildArgs(CommandLineArgs commandLineArgs) + { + var provider = commandLineArgs.Options.GetOrNull(Options.Provider.Short, Options.Provider.Long); + var url = commandLineArgs.Options.GetOrNull(Options.Url.Short, Options.Url.Long); + var target = commandLineArgs.Options.GetOrNull(Options.Target.Long); + var module = commandLineArgs.Options.GetOrNull(Options.Module.Short, Options.Module.Long) ?? "app"; + var output = commandLineArgs.Options.GetOrNull(Options.Output.Short, Options.Output.Long); + var apiName = commandLineArgs.Options.GetOrNull(Options.ApiName.Short, Options.ApiName.Long); + 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); + + return new GenerateProxyArgs(CommandName, workDirectory, module, url, output, target, apiName, source, folder, provider, commandLineArgs.Options); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine($" labp {CommandName}"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("-m|--module (default: 'app') The name of the backend module you wish to generate proxies for."); + sb.AppendLine("-wd|--working-directory Execution directory."); + sb.AppendLine("-u|--url API definition URL from."); + sb.AppendLine("-p|--provider The client proxy provider(http, dapr)."); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(""); + sb.AppendLine(" labp generate-proxy"); + sb.AppendLine(" labp generate-proxy -p dapr"); + sb.AppendLine(" labp generate-proxy -m identity -o Pages/Identity/client-proxies.js -url https://localhost:44302/"); + sb.AppendLine(" labp generate-proxy --folder MyProxies/InnerFolder -url https://localhost:44302/"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Generates client service proxies and DTOs to consume HTTP APIs."; + } + + public static class Options + { + public static class Provider + { + public const string Short = "p"; + public const string Long = "provider"; + } + + public static class Module + { + public const string Short = "m"; + public const string Long = "module"; + } + + public static class ApiName + { + public const string Short = "a"; + public const string Long = "api-name"; + } + + public static class Source + { + public const string Short = "s"; + public const string Long = "source"; + } + public static class Output + { + public const string Short = "o"; + public const string Long = "output"; + } + + public static class Target + { + public const string Long = "target"; + } + + public static class Prompt + { + public const string Short = "p"; + public const string Long = "prompt"; + } + + public static class Folder + { + public const string Long = "folder"; + } + + public static class Url + { + public const string Short = "u"; + public const string Long = "url"; + } + + public static class WorkDirectory + { + public const string Short = "wd"; + public const string Long = "working-directory"; + } + } +} diff --git a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/HelpCommand.cs b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/HelpCommand.cs index 4bf6038c5..e9f956b05 100644 --- a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/HelpCommand.cs +++ b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/Commands/HelpCommand.cs @@ -14,6 +14,10 @@ namespace LINGYUN.Abp.Cli.Commands { public class HelpCommand : IConsoleCommand, ITransientDependency { + public const string Name = "help"; + + protected string CommandName => Name; + public ILogger Logger { get; set; } protected AbpCliOptions AbpCliOptions { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } diff --git a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs new file mode 100644 index 000000000..3c368c8eb --- /dev/null +++ b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs @@ -0,0 +1,422 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli; +using Volo.Abp.Cli.Commands; +using Volo.Abp.Cli.Http; +using Volo.Abp.Cli.ServiceProxying; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Json; + +using VoloGenerateProxyArgs = Volo.Abp.Cli.ServiceProxying.GenerateProxyArgs; + +namespace LINGYUN.Abp.Cli.ServiceProxying.CSharp; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(IServiceProxyGenerator), typeof(CSharpServiceProxyGenerator))] +public class CSharpServiceProxyGenerator : ServiceProxyGeneratorBase, ITransientDependency +{ + public const string Name = "CSHARP"; + + private const string UsingPlaceholder = ""; + private const string MethodPlaceholder = ""; + private const string ClassName = ""; + private const string ServiceInterface = ""; + private readonly static string[] ServicePostfixes = { "AppService", "ApplicationService" , "Service"}; + private const string DefaultNamespace = "ClientProxies"; + private const string Namespace = ""; + private const string DefaultProvider = "ClientProxyBase"; + private const string Provider = ""; + 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}" + + $"{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}public partial class : <>, " + + $"{Environment.NewLine}{{" + + $"{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() + { + "using System;", + "using System.Threading.Tasks;", + "using Volo.Abp.Application.Dtos;", + "using Volo.Abp.Http.Client;", + "using Volo.Abp.Http.Modeling;", + "using Volo.Abp.DependencyInjection;", + "using Volo.Abp.Http.Client.ClientProxying;", + "using LINGYUN.Abp.Dapr;", + "using LINGYUN.Abp.Dapr.Client;", + "using LINGYUN.Abp.Dapr.Client.ClientProxying;" + }; + + public CSharpServiceProxyGenerator( + CliHttpClientFactory cliHttpClientFactory, + IJsonSerializer jsonSerializer) : + base(cliHttpClientFactory, jsonSerializer) + { + } + + public async override Task GenerateProxyAsync(VoloGenerateProxyArgs args) + { + CheckWorkDirectory(args.WorkDirectory); + CheckFolder(args.Folder); + + if (args.CommandName == RemoveProxyCommand.Name) + { + RemoveClientProxyFile(args); + return; + } + + var applicationApiDescriptionModel = await GetApplicationApiDescriptionModelAsync(args); + + foreach (var controller in applicationApiDescriptionModel.Modules.Values.SelectMany(x => x.Controllers)) + { + if (ShouldGenerateProxy(controller.Value)) + { + await GenerateClientProxyFileAsync(args, controller.Value); + } + } + + await CreateGenerateProxyJsonFile(args, applicationApiDescriptionModel); + } + + private async Task CreateGenerateProxyJsonFile(VoloGenerateProxyArgs args, ApplicationApiDescriptionModel applicationApiDescriptionModel) + { + 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)) + { + await writer.WriteAsync(JsonSerializer.Serialize(applicationApiDescriptionModel, indented: true)); + } + } + + private void RemoveClientProxyFile(VoloGenerateProxyArgs 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( + VoloGenerateProxyArgs args, + ControllerApiDescriptionModel controllerApiDescription) + { + var folder = args.Folder.IsNullOrWhiteSpace() ? DefaultNamespace : args.Folder; + + var appServiceTypeFullName = controllerApiDescription.Interfaces.Last().Type; + var appServiceTypeName = appServiceTypeFullName.Split('.').Last(); + var clientProxyName = $"{controllerApiDescription.ControllerName}ClientProxy"; + var clientProvider = args.As().Provider; + var rootNamespace = $"{GetTypeNamespace(controllerApiDescription.Type)}.{folder.Replace('/', '.')}"; + var clientProxyBuilder = new StringBuilder(_clientProxyTemplate); + clientProxyBuilder.Replace(ClassName, clientProxyName); + clientProxyBuilder.Replace(Namespace, rootNamespace); + clientProxyBuilder.Replace(Provider, clientProvider); + + 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()); + } + + Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}"); + } + + await GenerateClientProxyGeneratedFileAsync( + args, + controllerApiDescription, + clientProxyName, + appServiceTypeName, + appServiceTypeFullName, + rootNamespace, + folder); + } + + private async Task GenerateClientProxyGeneratedFileAsync( + VoloGenerateProxyArgs 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); + + foreach (var action in controllerApiDescription.Actions.Values) + { + if (!ShouldGenerateMethod(appServiceTypeFullName, action)) + { + continue; + } + + GenerateMethod(action, clientProxyBuilder, usingNamespaceList); + } + + foreach (var usingNamespace in usingNamespaceList) + { + clientProxyBuilder.Replace($"{UsingPlaceholder}", $"{usingNamespace}{Environment.NewLine}{UsingPlaceholder}"); + } + + 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"); + + using (var writer = new StreamWriter(filePath)) + { + await writer.WriteAsync(clientProxyBuilder.ToString()); + Logger.LogInformation($"Create {GetLoggerOutputPath(filePath, args.WorkDirectory)}"); + } + } + + private void GenerateMethod( + ActionApiDescriptionModel action, + StringBuilder clientProxyBuilder, + List usingNamespaceList) + { + var methodBuilder = new StringBuilder(); + + var returnTypeName = GetRealTypeName(action.ReturnValue.Type, usingNamespaceList); + + if (!action.Name.EndsWith("Async")) + { + GenerateSynchronizationMethod(action, returnTypeName, methodBuilder, usingNamespaceList); + clientProxyBuilder.Replace(MethodPlaceholder, $"{methodBuilder}{Environment.NewLine} {MethodPlaceholder}"); + return; + } + + GenerateAsynchronousMethod(action, returnTypeName, methodBuilder, usingNamespaceList); + clientProxyBuilder.Replace(MethodPlaceholder, $"{methodBuilder}{Environment.NewLine} {MethodPlaceholder}"); + } + + private void GenerateSynchronizationMethod(ActionApiDescriptionModel action, string returnTypeName, StringBuilder methodBuilder, List usingNamespaceList) + { + methodBuilder.AppendLine($"public virtual {returnTypeName} {action.Name}()"); + + foreach (var parameter in action.Parameters.GroupBy(x => x.Name).Select(x => x.First())) + { + methodBuilder.Replace("", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, "); + } + + methodBuilder.Replace("", string.Empty); + methodBuilder.Replace(", )", ")"); + + methodBuilder.AppendLine(" {"); + methodBuilder.AppendLine(" //Client Proxy does not support the synchronization method, you should always use asynchronous methods as a best practice"); + methodBuilder.AppendLine(" throw new System.NotImplementedException(); "); + methodBuilder.AppendLine(" }"); + } + + private void GenerateAsynchronousMethod( + ActionApiDescriptionModel action, + string returnTypeName, + StringBuilder methodBuilder, + List usingNamespaceList) + { + var returnSign = returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; + + methodBuilder.AppendLine($"public virtual async {returnSign} {action.Name}()"); + + foreach (var parameter in action.ParametersOnMethod) + { + methodBuilder.Replace("", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, "); + } + + methodBuilder.Replace("", string.Empty); + methodBuilder.Replace(", )", ")"); + + methodBuilder.AppendLine(" {"); + + var argsTemplate = "new ClientProxyRequestTypeValue" + + $"{Environment.NewLine} {{" + + $"{Environment.NewLine} }}"; + + 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});"); + } + + foreach (var parameter in action.ParametersOnMethod) + { + methodBuilder.Replace("", $"{Environment.NewLine} {{ typeof({GetRealTypeName(parameter.Type)}), {parameter.Name} }},"); + } + + methodBuilder.Replace(",", string.Empty); + methodBuilder.Replace(", )", ")"); + methodBuilder.AppendLine(" }"); + } + + private bool ShouldGenerateProxy(ControllerApiDescriptionModel controllerApiDescription) + { + if (!controllerApiDescription.Interfaces.Any()) + { + return false; + } + + var serviceInterface = controllerApiDescription.Interfaces.Last(); + return ServicePostfixes.Any(x => serviceInterface.Type.EndsWith(x)); + } + + private bool ShouldGenerateMethod(string appServiceTypeName, ActionApiDescriptionModel action) + { + return action.ImplementFrom.StartsWith(AppServicePrefix) || action.ImplementFrom.StartsWith(appServiceTypeName); + } + + private string GetTypeNamespace(string typeFullName) + { + return typeFullName.Substring(0, typeFullName.LastIndexOf('.')); + } + + private string GetRealTypeName(string typeName, List usingNamespaceList = null) + { + var filter = new[] { "<", ",", ">" }; + var stringBuilder = new StringBuilder(); + var typeNames = typeName.Split('.'); + + if (typeNames.All(x => !filter.Any(x.Contains))) + { + if (usingNamespaceList != null) + { + AddUsingNamespace(usingNamespaceList, typeName); + } + + return NormalizeTypeName(typeNames.Last()); + } + + var fullName = string.Empty; + + foreach (var item in typeNames) + { + if (filter.Any(x => item.Contains(x))) + { + if (usingNamespaceList != null) + { + AddUsingNamespace(usingNamespaceList, $"{fullName}.{item}".TrimStart('.')); + } + + fullName = string.Empty; + + if (item.Contains('<') || item.Contains(',')) + { + stringBuilder.Append(item.Substring(0, item.IndexOf(item.Contains('<') ? '<' : ',') + 1)); + fullName = item.Substring(item.IndexOf(item.Contains('<') ? '<' : ',') + 1); + } + else + { + stringBuilder.Append(item); + } + } + else + { + fullName = $"{fullName}.{item}"; + } + } + + return stringBuilder.ToString(); + } + + private void AddUsingNamespace(List usingNamespaceList, string typeName) + { + var rootNamespace = $"using {GetTypeNamespace(typeName)};"; + if (usingNamespaceList.Contains(rootNamespace)) + { + return; + } + + usingNamespaceList.Add(rootNamespace); + } + + private string NormalizeTypeName(string typeName) + { + var nullable = string.Empty; + if (typeName.EndsWith("?")) + { + typeName = typeName.TrimEnd('?'); + nullable = "?"; + } + + typeName = typeName switch + { + "Void" => "void", + "Boolean" => "bool", + "String" => "string", + "Int32" => "int", + "Int64" => "long", + "Double" => "double", + "Object" => "object", + "Byte" => "byte", + "Char" => "char", + _ => typeName + }; + + return $"{typeName}{nullable}"; + } + + private void CheckWorkDirectory(string directory) + { + if (!Directory.Exists(directory)) + { + throw new CliUsageException("Specified directory does not exist."); + } + + var projectFiles = Directory.GetFiles(directory, "*.csproj"); + if (!projectFiles.Any()) + { + throw new CliUsageException("No project file(csproj) found in the directory."); + } + } + + private void CheckFolder(string folder) + { + if (!folder.IsNullOrWhiteSpace() && Path.HasExtension(folder)) + { + throw new CliUsageException("Option folder should be a directory."); + } + } +} diff --git a/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs new file mode 100644 index 000000000..8be4f1660 --- /dev/null +++ b/aspnet-core/modules/cli/LINGYUN.Abp.Cli/LINGYUN/Abp/Cli/ServiceProxying/GenerateProxyArgs.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace LINGYUN.Abp.Cli.ServiceProxying; + +public class GenerateProxyArgs : Volo.Abp.Cli.ServiceProxying.GenerateProxyArgs +{ + public string Provider { get; } + + public GenerateProxyArgs( + [NotNull] string commandName, + [NotNull] string workDirectory, + string module, + string url, + string output, + string target, + string apiName, + string source, + string folder, + string provider, + Dictionary extraProperties = null) + : base(commandName, workDirectory, module, url, output, target, apiName, source, folder, extraProperties) + { + Provider = provider; + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/FodyWeavers.xml b/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/FodyWeavers.xml similarity index 100% rename from aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/FodyWeavers.xml rename to aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/FodyWeavers.xml diff --git a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/FodyWeavers.xsd b/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/FodyWeavers.xsd similarity index 100% rename from aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/FodyWeavers.xsd rename to aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/FodyWeavers.xsd diff --git a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN.Abp.HttpClient.Wrapper.csproj b/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/LINGYUN.Abp.Http.Client.Wrapper.csproj similarity index 63% rename from aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN.Abp.HttpClient.Wrapper.csproj rename to aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/LINGYUN.Abp.Http.Client.Wrapper.csproj index 40f24499c..8ac196841 100644 --- a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN.Abp.HttpClient.Wrapper.csproj +++ b/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/LINGYUN.Abp.Http.Client.Wrapper.csproj @@ -4,7 +4,7 @@ - net6.0 + netstandard2.0 @@ -12,8 +12,8 @@ - - - + + + diff --git a/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/LINGYUN/Abp/Http/Client/Wrapper/AbpHttpClientWrapperModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/LINGYUN/Abp/Http/Client/Wrapper/AbpHttpClientWrapperModule.cs new file mode 100644 index 000000000..723d010bc --- /dev/null +++ b/aspnet-core/modules/common/LINGYUN.Abp.Http.Client.Wrapper/LINGYUN/Abp/Http/Client/Wrapper/AbpHttpClientWrapperModule.cs @@ -0,0 +1,33 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.Http.Client.Wrapper; + +[DependsOn( + typeof(AbpHttpClientModule), + typeof(AbpWrapperModule))] +public class AbpHttpClientWrapperModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.ProxyClientBuildActions.Add( + (_, builder) => + { + builder.ConfigureHttpClient((provider, client) => + { + var wrapperOptions = provider.GetRequiredService>(); + var wrapperHeader = wrapperOptions.Value.IsEnabled + ? AbpHttpWrapConsts.AbpWrapResult + : AbpHttpWrapConsts.AbpDontWrapResult; + + client.DefaultRequestHeaders.TryAddWithoutValidation(wrapperHeader, "true"); + }); + }); + }); + } +} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN/Abp/HttpClient/Wrapper/AbpHttpClientWrapperModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN/Abp/HttpClient/Wrapper/AbpHttpClientWrapperModule.cs deleted file mode 100644 index 9f7aa9db9..000000000 --- a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN/Abp/HttpClient/Wrapper/AbpHttpClientWrapperModule.cs +++ /dev/null @@ -1,13 +0,0 @@ -using LINGYUN.Abp.Wrapper; -using Volo.Abp.Http.Client; -using Volo.Abp.Modularity; - -namespace LINGYUN.Abp.HttpClient.Wrapper -{ - [DependsOn( - typeof(AbpHttpClientModule), - typeof(AbpWrapperModule))] - public class AbpHttpClientWrapperModule : AbpModule - { - } -} diff --git a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN/Abp/HttpClient/Wrapper/DynamicHttpProxyInterceptorWrapClientProxy.cs b/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN/Abp/HttpClient/Wrapper/DynamicHttpProxyInterceptorWrapClientProxy.cs deleted file mode 100644 index b568c0da6..000000000 --- a/aspnet-core/modules/common/LINGYUN.Abp.HttpClient.Wrapper/LINGYUN/Abp/HttpClient/Wrapper/DynamicHttpProxyInterceptorWrapClientProxy.cs +++ /dev/null @@ -1,153 +0,0 @@ -using LINGYUN.Abp.Wrapper; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Content; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Http; -using Volo.Abp.Http.Client; -using Volo.Abp.Http.Client.Authentication; -using Volo.Abp.Http.Client.ClientProxying; -using Volo.Abp.Http.Client.DynamicProxying; - -namespace LINGYUN.Abp.HttpClient.Wrapper -{ - [Dependency(ReplaceServices = true)] - [ExposeServices(typeof(DynamicHttpProxyInterceptorClientProxy<>))] - public class DynamicHttpProxyInterceptorWrapClientProxy - : DynamicHttpProxyInterceptorClientProxy, ITransientDependency - { - protected IOptions WrapperOptions => LazyServiceProvider.LazyGetRequiredService>(); - - protected override async Task RequestAsync(ClientProxyRequestContext requestContext) - { - var response = await RequestAndGetResponseAsync(requestContext); - - var responseContent = response.Content; - - if (typeof(T) == typeof(IRemoteStreamContent) || - typeof(T) == typeof(RemoteStreamContent)) - { - /* returning a class that holds a reference to response - * content just to be sure that GC does not dispose of - * it before we finish doing our work with the stream */ - return (T)(object)new RemoteStreamContent( - await responseContent.ReadAsStreamAsync(), - responseContent.Headers?.ContentDisposition?.FileNameStar ?? - RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), - responseContent.Headers?.ContentType?.ToString(), - responseContent.Headers?.ContentLength); - } - - var stringContent = await responseContent.ReadAsStringAsync(); - - if (stringContent.IsNullOrWhiteSpace()) - { - return default; - } - - // 对于包装后的结果需要处理 - if (response.Headers.Contains(AbpHttpWrapConsts.AbpWrapResult)) - { - var wrapResult = JsonSerializer.Deserialize>(stringContent); - - ThrowExceptionForResponse(wrapResult); - - if (typeof(T) == typeof(string)) - { - return (T)(object)wrapResult.Result; - } - - return wrapResult.Result; - } - - if (typeof(T) == typeof(string)) - { - return (T)(object)stringContent; - } - - if (stringContent.IsNullOrWhiteSpace()) - { - return default; - } - - return JsonSerializer.Deserialize(stringContent); - } - - public override async Task CallRequestAsync(ClientProxyRequestContext requestContext) - { - var response = await RequestAndGetResponseAsync(requestContext); - // 对于包装后的结果需要处理 - if (response.Headers.Contains(AbpHttpWrapConsts.AbpWrapResult)) - { - var stringContent = await response.Content.ReadAsStringAsync(); - var wrapResult = JsonSerializer.Deserialize(stringContent); - - ThrowExceptionForResponse(wrapResult); - } - - return response.Content; - } - - protected virtual void ThrowExceptionForResponse(WrapResult wrapResult) - { - if (!string.Equals(wrapResult.Code, WrapperOptions.Value.CodeWithSuccess)) - { - var errorInfo = new RemoteServiceErrorInfo( - wrapResult.Message, - wrapResult.Details, - wrapResult.Code); - throw new AbpRemoteCallException(errorInfo) - { - HttpStatusCode = (int)WrapperOptions.Value.HttpStatusCode - }; - } - } - - protected virtual async Task RequestAndGetResponseAsync(ClientProxyRequestContext requestContext) - { - var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {requestContext.ServiceType.FullName}."); - var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); - - var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); - - var apiVersion = await GetApiVersionInfoAsync(requestContext); - var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + await GetUrlWithParametersAsync(requestContext, apiVersion); - - var requestMessage = new HttpRequestMessage(requestContext.Action.GetHttpMethod(), url) - { - Content = await ClientProxyRequestPayloadBuilder.BuildContentAsync(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion) - }; - - AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion); - - if (requestContext.Action.AllowAnonymous != true) - { - await ClientAuthenticator.Authenticate( - new RemoteServiceHttpClientAuthenticateContext( - client, - requestMessage, - remoteServiceConfig, - clientConfig.RemoteServiceName - ) - ); - } - - var response = await client.SendAsync( - requestMessage, - HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/, - GetCancellationToken(requestContext.Arguments) - ); - - if (!response.IsSuccessStatusCode) - { - await ThrowExceptionForResponseAsync(response); - } - - return response; - } - } -} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client.Wrapper/LINGYUN/Abp/Dapr/Client/Wrapper/AbpDaprClientWrapperModule.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client.Wrapper/LINGYUN/Abp/Dapr/Client/Wrapper/AbpDaprClientWrapperModule.cs index 9b17166bf..2766623bb 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client.Wrapper/LINGYUN/Abp/Dapr/Client/Wrapper/AbpDaprClientWrapperModule.cs +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client.Wrapper/LINGYUN/Abp/Dapr/Client/Wrapper/AbpDaprClientWrapperModule.cs @@ -1,10 +1,11 @@ -using LINGYUN.Abp.Dapr.Client.DynamicProxying; -using LINGYUN.Abp.Wrapper; +using LINGYUN.Abp.Wrapper; using Microsoft.Extensions.Options; using Volo.Abp.Http; using Volo.Abp.Http.Client; using Volo.Abp.Json; using Volo.Abp.Modularity; +using Microsoft.Extensions.DependencyInjection; +using LINGYUN.Abp.Dapr.Client.ClientProxying; namespace LINGYUN.Abp.Dapr.Client.Wrapper { @@ -14,8 +15,20 @@ namespace LINGYUN.Abp.Dapr.Client.Wrapper { public override void ConfigureServices(ServiceConfigurationContext context) { + var wrapperOptions = context.Services.ExecutePreConfiguredActions(); + Configure(options => { + options.ProxyRequestActions.Add( + (_, request) => + { + var wrapperHeader = wrapperOptions.IsEnabled + ? AbpHttpWrapConsts.AbpWrapResult + : AbpHttpWrapConsts.AbpDontWrapResult; + + request.Headers.TryAddWithoutValidation(wrapperHeader, "true"); + }); + options.OnResponse(async (response, serviceProvider) => { var stringContent = await response.Content.ReadAsStringAsync(); diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/ClientProxying/AbpDaprClientProxyOptions.cs similarity index 90% rename from aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs rename to aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/ClientProxying/AbpDaprClientProxyOptions.cs index 9aa24063e..d359469c9 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/AbpDaprClientProxyOptions.cs +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/ClientProxying/AbpDaprClientProxyOptions.cs @@ -1,10 +1,11 @@ -using System; +using LINGYUN.Abp.Dapr.Client.DynamicProxying; +using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; -namespace LINGYUN.Abp.Dapr.Client.DynamicProxying +namespace LINGYUN.Abp.Dapr.Client.ClientProxying { public class AbpDaprClientProxyOptions { diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/ClientProxying/DaprClientProxyBase.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/ClientProxying/DaprClientProxyBase.cs new file mode 100644 index 000000000..dca292e43 --- /dev/null +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/ClientProxying/DaprClientProxyBase.cs @@ -0,0 +1,124 @@ +using Dapr.Client; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Content; +using Volo.Abp.Http.Client.Authentication; +using Volo.Abp.Http.Client.ClientProxying; + +namespace LINGYUN.Abp.Dapr.Client.ClientProxying +{ + public abstract class DaprClientProxyBase : ClientProxyBase + { + protected IOptions DaprClientProxyOptions => LazyServiceProvider.LazyGetRequiredService>(); + protected IDaprClientFactory DaprClientFactory => LazyServiceProvider.LazyGetRequiredService(); + + protected async override Task RequestAsync(ClientProxyRequestContext requestContext) + { + var response = await MakeRequestAsync(requestContext); + + var responseContent = response.Content; + + if (typeof(T) == typeof(IRemoteStreamContent) || + typeof(T) == typeof(RemoteStreamContent)) + { + /* returning a class that holds a reference to response + * content just to be sure that GC does not dispose of + * it before we finish doing our work with the stream */ + return (T)(object)new RemoteStreamContent( + await responseContent.ReadAsStreamAsync(), + responseContent.Headers?.ContentDisposition?.FileNameStar ?? + RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), + responseContent.Headers?.ContentType?.ToString(), + responseContent.Headers?.ContentLength); + } + + var stringContent = await DaprClientProxyOptions + .Value + .ProxyResponseContent(response, LazyServiceProvider); + + if (stringContent.IsNullOrWhiteSpace()) + { + return default; + } + + if (typeof(T) == typeof(string)) + { + return (T)(object)stringContent; + } + + return JsonSerializer.Deserialize(stringContent); + } + + protected async override Task GetConfiguredApiVersionAsync(ClientProxyRequestContext requestContext) + { + var clientConfig = DaprClientProxyOptions.Value.DaprClientProxies.GetOrDefault(requestContext.ServiceType) + ?? throw new AbpException($"Could not get DynamicDaprClientProxyConfig for {requestContext.ServiceType.FullName}."); + var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); + + return remoteServiceConfig?.Version; + } + + private async Task MakeRequestAsync(ClientProxyRequestContext requestContext) + { + var clientConfig = DaprClientProxyOptions.Value.DaprClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get DaprClientProxyConfig for {requestContext.ServiceType.FullName}."); + var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); + + var appId = remoteServiceConfig.GetAppId(); + var apiVersion = await GetApiVersionInfoAsync(requestContext); + var methodName = await GetUrlWithParametersAsync(requestContext, apiVersion); + // See: https://docs.dapr.io/reference/api/service_invocation_api/#examples + var daprClient = DaprClientFactory.CreateClient(clientConfig.RemoteServiceName); + var requestMessage = daprClient.CreateInvokeMethodRequest( + requestContext.Action.GetHttpMethod(), + appId, + methodName); + requestMessage.Content = await ClientProxyRequestPayloadBuilder.BuildContentAsync( + requestContext.Action, + requestContext.Arguments, + JsonSerializer, + apiVersion); + + AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion); + + if (requestContext.Action.AllowAnonymous != true) + { + var httpClient = HttpClientFactory.Create(AbpDaprClientModule.DaprHttpClient); + + await ClientAuthenticator.Authenticate( + new RemoteServiceHttpClientAuthenticateContext( + httpClient, + requestMessage, + remoteServiceConfig, + clientConfig.RemoteServiceName + ) + ); + + // 其他库可能将授权标头写入到HttpClient中 + if (requestMessage.Headers.Authorization == null && + httpClient.DefaultRequestHeaders.Authorization != null) + { + requestMessage.Headers.Authorization = httpClient.DefaultRequestHeaders.Authorization; + } + } + + // 增加一个可配置的请求消息 + foreach (var clientRequestAction in DaprClientProxyOptions.Value.ProxyRequestActions) + { + clientRequestAction(appId, requestMessage); + } + + var response = await daprClient.InvokeMethodWithResponseAsync(requestMessage, GetCancellationToken(requestContext.Arguments)); + + if (!response.IsSuccessStatusCode) + { + await ThrowExceptionForResponseAsync(response); + } + + return response; + } + } +} diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs index baea6e563..2344d9892 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprClientProxyInterceptor.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using LINGYUN.Abp.Dapr.Client.ClientProxying; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprProxyInterceptorClientProxy.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprProxyInterceptorClientProxy.cs index 80d7e550f..489dec591 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprProxyInterceptorClientProxy.cs +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/LINGYUN/Abp/Dapr/Client/DynamicProxying/DynamicDaprProxyInterceptorClientProxy.cs @@ -1,134 +1,20 @@ -using Dapr.Client; -using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; +using LINGYUN.Abp.Dapr.Client.ClientProxying; using System.Net.Http; using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Content; -using Volo.Abp.Http.Client.Authentication; using Volo.Abp.Http.Client.ClientProxying; namespace LINGYUN.Abp.Dapr.Client.DynamicProxying { - public class DynamicDaprProxyInterceptorClientProxy : ClientProxyBase + public class DynamicDaprProxyInterceptorClientProxy : DaprClientProxyBase { - protected IOptions DaprClientProxyOptions => LazyServiceProvider.LazyGetRequiredService>(); - protected IDaprClientFactory DaprClientFactory => LazyServiceProvider.LazyGetRequiredService(); - - public virtual async Task CallRequestAsync(ClientProxyRequestContext requestContext) + public async virtual Task CallRequestAsync(ClientProxyRequestContext requestContext) { return await RequestAsync(requestContext); } - public virtual async Task CallRequestAsync(ClientProxyRequestContext requestContext) + public async virtual Task CallRequestAsync(ClientProxyRequestContext requestContext) { return await RequestAsync(requestContext); } - - protected override async Task RequestAsync(ClientProxyRequestContext requestContext) - { - var response = await MakeRequestAsync(requestContext); - - var responseContent = response.Content; - - if (typeof(T) == typeof(IRemoteStreamContent) || - typeof(T) == typeof(RemoteStreamContent)) - { - /* returning a class that holds a reference to response - * content just to be sure that GC does not dispose of - * it before we finish doing our work with the stream */ - return (T)(object)new RemoteStreamContent( - await responseContent.ReadAsStreamAsync(), - responseContent.Headers?.ContentDisposition?.FileNameStar ?? - RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), - responseContent.Headers?.ContentType?.ToString(), - responseContent.Headers?.ContentLength); - } - - var stringContent = await DaprClientProxyOptions - .Value - .ProxyResponseContent(response, LazyServiceProvider); - - if (stringContent.IsNullOrWhiteSpace()) - { - return default; - } - - if (typeof(T) == typeof(string)) - { - return (T)(object)stringContent; - } - - return JsonSerializer.Deserialize(stringContent); - } - - protected override async Task GetConfiguredApiVersionAsync(ClientProxyRequestContext requestContext) - { - var clientConfig = DaprClientProxyOptions.Value.DaprClientProxies.GetOrDefault(requestContext.ServiceType) - ?? throw new AbpException($"Could not get DynamicDaprClientProxyConfig for {requestContext.ServiceType.FullName}."); - var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); - - return remoteServiceConfig?.Version; - } - - private async Task MakeRequestAsync(ClientProxyRequestContext requestContext) - { - var clientConfig = DaprClientProxyOptions.Value.DaprClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get DaprClientProxyConfig for {requestContext.ServiceType.FullName}."); - var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); - - var appId = remoteServiceConfig.GetAppId(); - var apiVersion = await GetApiVersionInfoAsync(requestContext); - var methodName = await GetUrlWithParametersAsync(requestContext, apiVersion); - // See: https://docs.dapr.io/reference/api/service_invocation_api/#examples - var daprClient = DaprClientFactory.CreateClient(clientConfig.RemoteServiceName); - var requestMessage = daprClient.CreateInvokeMethodRequest( - requestContext.Action.GetHttpMethod(), - appId, - methodName); - requestMessage.Content = await ClientProxyRequestPayloadBuilder.BuildContentAsync( - requestContext.Action, - requestContext.Arguments, - JsonSerializer, - apiVersion); - - AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion); - - if (requestContext.Action.AllowAnonymous != true) - { - var httpClient = HttpClientFactory.Create(AbpDaprClientModule.DaprHttpClient); - - await ClientAuthenticator.Authenticate( - new RemoteServiceHttpClientAuthenticateContext( - httpClient, - requestMessage, - remoteServiceConfig, - clientConfig.RemoteServiceName - ) - ); - - // 其他库可能将授权标头写入到HttpClient中 - if (requestMessage.Headers.Authorization == null && - httpClient.DefaultRequestHeaders.Authorization != null) - { - requestMessage.Headers.Authorization = httpClient.DefaultRequestHeaders.Authorization; - } - } - - // 增加一个可配置的请求消息 - foreach (var clientRequestAction in DaprClientProxyOptions.Value.ProxyRequestActions) - { - clientRequestAction(appId, requestMessage); - } - - var response = await daprClient.InvokeMethodWithResponseAsync(requestMessage, GetCancellationToken(requestContext.Arguments)); - - if (!response.IsSuccessStatusCode) - { - await ThrowExceptionForResponseAsync(response); - } - - return response; - } } } diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDaprClientProxyExtensions.cs similarity index 81% rename from aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs rename to aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDaprClientProxyExtensions.cs index c5d34a9c4..27e81105a 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicDaprClientProxyExtensions.cs +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDaprClientProxyExtensions.cs @@ -2,6 +2,7 @@ using Dapr.Client; using JetBrains.Annotations; using LINGYUN.Abp.Dapr.Client; +using LINGYUN.Abp.Dapr.Client.ClientProxying; using LINGYUN.Abp.Dapr.Client.DynamicProxying; using Microsoft.Extensions.Options; using System; @@ -14,11 +15,37 @@ using Volo.Abp.Validation; namespace Microsoft.Extensions.DependencyInjection { - public static class ServiceCollectionDynamicDaprClientProxyExtensions + public static class ServiceCollectionDaprClientProxyExtensions { private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); - #region Add DaprClient Proxies + #region Add Static DaprClient Proxies + + public static IServiceCollection AddStaticDaprClientProxies( + [NotNull] this IServiceCollection services, + [NotNull] Assembly assembly, + [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName) + { + Check.NotNull(services, nameof(assembly)); + + var serviceTypes = assembly.GetTypes().Where(IsSuitableForClientProxying).ToArray(); + + foreach (var serviceType in serviceTypes) + { + AddDaprClientFactory(services, remoteServiceConfigurationName); + + services.Configure(options => + { + options.DaprClientProxies[serviceType] = new DynamicDaprClientProxyConfig(serviceType, remoteServiceConfigurationName); + }); + } + + return services; + } + + #endregion + + #region Add Dynamic DaprClient Proxies public static IServiceCollection AddDaprClientProxies( [NotNull] this IServiceCollection services, @@ -28,7 +55,7 @@ namespace Microsoft.Extensions.DependencyInjection { Check.NotNull(services, nameof(assembly)); - var serviceTypes = assembly.GetTypes().Where(IsSuitableForDynamicActorProxying).ToArray(); + var serviceTypes = assembly.GetTypes().Where(IsSuitableForClientProxying).ToArray(); foreach (var serviceType in serviceTypes) { @@ -153,7 +180,7 @@ namespace Microsoft.Extensions.DependencyInjection return services; } - private static bool IsSuitableForDynamicActorProxying(Type type) + private static bool IsSuitableForClientProxying(Type type) { //TODO: Add option to change type filter diff --git a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md index 5b59dca79..23b9f7489 100644 --- a/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md +++ b/aspnet-core/modules/dapr/LINGYUN.Abp.Dapr.Client/README.md @@ -152,7 +152,7 @@ public class InvokeClass ## 配置项说明 -* AbpDaprRemoteServiceOptions.RemoteServices 配置Dapr.AppId +* AbpRemoteServiceOptions.RemoteServices 配置Dapr.AppId ```json diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.AspNetCore.TestHost/LINGYUN/Abp/Dapr/ServiceInvocation/TestAppService.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.AspNetCore.TestHost/LINGYUN/Abp/Dapr/ServiceInvocation/TestAppService.cs index 01373762e..16bcec150 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Dapr.AspNetCore.TestHost/LINGYUN/Abp/Dapr/ServiceInvocation/TestAppService.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.AspNetCore.TestHost/LINGYUN/Abp/Dapr/ServiceInvocation/TestAppService.cs @@ -12,7 +12,6 @@ namespace LINGYUN.Abp.Dapr.ServiceInvocation [Route("api/dapr/test")] public class TestAppService : AbpController, ITestAppService { - private static int _inctement; private readonly List _cache = new List { new NameValue("name1", "value1"), @@ -29,11 +28,9 @@ namespace LINGYUN.Abp.Dapr.ServiceInvocation } [HttpPut] - public Task UpdateAsync() + public Task UpdateAsync(int inctement) { - Interlocked.Increment(ref _inctement); - - _cache[0].Value = $"value:updated:{_inctement}"; + _cache[0].Value = $"value:updated:{inctement}"; return Task.FromResult(_cache[0]); } diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/TestClientProxy.Generated.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/TestClientProxy.Generated.cs new file mode 100644 index 000000000..1e9d70f80 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/TestClientProxy.Generated.cs @@ -0,0 +1,45 @@ +// This file is automatically generated by ABP framework to use MVC Controllers from CSharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Http.Client; +using Volo.Abp.Http.Modeling; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.ClientProxying; +using LINGYUN.Abp.Dapr.ServiceInvocation; +using LINGYUN.Abp.Dapr; +using System.Linq; +using Volo.Abp; +using System.Collections.Generic; +using Volo.Abp.Reflection; +using System.Net.Http; +using LINGYUN.Abp.Dapr.Client.ClientProxying; + +// ReSharper disable once CheckNamespace +namespace LINGYUN.Abp.Dapr.ServiceInvocation.ClientProxies; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(ITestAppService), typeof(TestClientProxy))] +public partial class TestClientProxy : DaprClientProxyBase, ITestAppService +{ + public virtual async Task> GetAsync() + { + return await RequestAsync>(nameof(GetAsync)); + } + + public virtual async Task UpdateAsync(int inctement) + { + return await RequestAsync(nameof(UpdateAsync), new ClientProxyRequestTypeValue + { + { typeof(int), inctement } + }); + } + + public virtual async Task GetWrapedAsync(string name) + { + return await RequestAsync(nameof(GetWrapedAsync), new ClientProxyRequestTypeValue + { + { typeof(string), name } + }); + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/TestClientProxy.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/TestClientProxy.cs new file mode 100644 index 000000000..8727dae3b --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/TestClientProxy.cs @@ -0,0 +1,7 @@ +// This file is part of TestClientProxy, you can customize it here +// ReSharper disable once CheckNamespace +namespace LINGYUN.Abp.Dapr.ServiceInvocation.ClientProxies; + +public partial class TestClientProxy +{ +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/app-generate-proxy.json b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/app-generate-proxy.json new file mode 100644 index 000000000..227944676 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/ClientProxies/app-generate-proxy.json @@ -0,0 +1,114 @@ +{ + "modules": { + "app": { + "rootPath": "app", + "remoteServiceName": "TestDapr", + "controllers": { + "LINGYUN.Abp.Dapr.ServiceInvocation.TestAppService": { + "controllerName": "Test", + "controllerGroupName": "Test", + "isRemoteService": true, + "apiVersion": null, + "type": "LINGYUN.Abp.Dapr.ServiceInvocation.TestAppService", + "interfaces": [ + { + "type": "LINGYUN.Abp.Dapr.ServiceInvocation.ITestAppService" + } + ], + "actions": { + "GetAsync": { + "uniqueName": "GetAsync", + "name": "GetAsync", + "httpMethod": "GET", + "url": "api/dapr/test", + "supportedVersions": [], + "parametersOnMethod": [], + "parameters": [], + "returnValue": { + "type": "Volo.Abp.Application.Dtos.ListResultDto", + "typeSimple": "Volo.Abp.Application.Dtos.ListResultDto" + }, + "allowAnonymous": null, + "implementFrom": "LINGYUN.Abp.Dapr.ServiceInvocation.ITestAppService" + }, + "UpdateAsyncByInctement": { + "uniqueName": "UpdateAsyncByInctement", + "name": "UpdateAsync", + "httpMethod": "PUT", + "url": "api/dapr/test", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "inctement", + "typeAsString": "System.Int32, System.Private.CoreLib", + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "inctement", + "name": "inctement", + "jsonName": null, + "type": "System.Int32", + "typeSimple": "number", + "isOptional": false, + "defaultValue": null, + "constraintTypes": null, + "bindingSourceId": "ModelBinding", + "descriptorName": "" + } + ], + "returnValue": { + "type": "LINGYUN.Abp.Dapr.NameValue", + "typeSimple": "LINGYUN.Abp.Dapr.NameValue" + }, + "allowAnonymous": null, + "implementFrom": "LINGYUN.Abp.Dapr.ServiceInvocation.ITestAppService" + }, + "GetWrapedAsyncByName": { + "uniqueName": "GetWrapedAsyncByName", + "name": "GetWrapedAsync", + "httpMethod": "GET", + "url": "api/dapr/test/{name}", + "supportedVersions": [], + "parametersOnMethod": [ + { + "name": "name", + "typeAsString": "System.String, System.Private.CoreLib", + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null + } + ], + "parameters": [ + { + "nameOnMethod": "name", + "name": "name", + "jsonName": null, + "type": "System.String", + "typeSimple": "string", + "isOptional": false, + "defaultValue": null, + "constraintTypes": [], + "bindingSourceId": "Path", + "descriptorName": "" + } + ], + "returnValue": { + "type": "LINGYUN.Abp.Dapr.TestNeedWrapObject", + "typeSimple": "LINGYUN.Abp.Dapr.TestNeedWrapObject" + }, + "allowAnonymous": null, + "implementFrom": "LINGYUN.Abp.Dapr.ServiceInvocation.ITestAppService" + } + } + } + } + } + }, + "types": {} +} \ No newline at end of file diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN.Abp.Dapr.Client.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN.Abp.Dapr.Client.Tests.csproj index 012ccc756..f137662a1 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN.Abp.Dapr.Client.Tests.csproj +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN.Abp.Dapr.Client.Tests.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -6,6 +6,14 @@ false + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/AbpDaptClientTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/AbpDaptClientTestModule.cs index c14f7599d..ac39f5d84 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/AbpDaptClientTestModule.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/AbpDaptClientTestModule.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using System.IO; using LINGYUN.Abp.Dapr.Client.Wrapper; +using Volo.Abp.VirtualFileSystem; namespace LINGYUN.Abp.Dapr.Client.Tests { @@ -23,13 +24,11 @@ namespace LINGYUN.Abp.Dapr.Client.Tests EnvironmentName = "Testing", BasePath = Directory.GetCurrentDirectory() })); - } - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddDaprClientProxies( - typeof(AbpDaprTestModule).Assembly, - "TestDapr"); + Configure(options => + { + options.FileSets.AddEmbedded(); + }); } } } diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceStaticProxyTests.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceStaticProxyTests.cs new file mode 100644 index 000000000..cfe68d82f --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceStaticProxyTests.cs @@ -0,0 +1,53 @@ +using LINGYUN.Abp.Dapr.ServiceInvocation; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace LINGYUN.Abp.Dapr.Client.Tests +{ + public class TestAppServiceStaticProxyTests : AbpDaptClientTestBase + { + private readonly ITestAppService _service; + + public TestAppServiceStaticProxyTests() + { + _service = GetRequiredService(); + } + + protected override void BeforeAddApplication(IServiceCollection services) + { + services.AddStaticDaprClientProxies( + typeof(AbpDaprTestModule).Assembly, + "TestDapr"); + } + + [Fact] + public async Task Get_Result_Items_Count_Should_5() + { + var result = await _service.GetAsync(); + + result.Items.Count.ShouldBe(5); + } + + [Fact] + public async Task Should_Get_Wraped_Object() + { + var result = await _service.GetWrapedAsync("Test"); + + result.Name.ShouldBe("Test"); + } + + [Fact] + public async Task Update_Result_Value_Should_Value_Updated_1() + { + var result = await _service.UpdateAsync(1); + + result.Value.ShouldBe("value:updated:1"); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceTests.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceTests.cs index 40329a2f0..4e3980cbf 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceTests.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Client.Tests/LINGYUN/Abp/Dapr/Client/Tests/TestAppServiceTests.cs @@ -1,4 +1,5 @@ using LINGYUN.Abp.Dapr.ServiceInvocation; +using Microsoft.Extensions.DependencyInjection; using Shouldly; using System.Threading.Tasks; using Xunit; @@ -14,6 +15,13 @@ namespace LINGYUN.Abp.Dapr.Client.Tests _service = GetRequiredService(); } + protected override void BeforeAddApplication(IServiceCollection services) + { + services.AddDaprClientProxies( + typeof(AbpDaprTestModule).Assembly, + "TestDapr"); + } + [Fact] public async Task Get_Result_Items_Count_Should_5() { @@ -33,7 +41,7 @@ namespace LINGYUN.Abp.Dapr.Client.Tests [Fact] public async Task Update_Result_Value_Should_Value_Updated_1() { - var result = await _service.UpdateAsync(); + var result = await _service.UpdateAsync(1); result.Value.ShouldBe("value:updated:1"); } diff --git a/aspnet-core/tests/LINGYUN.Abp.Dapr.Tests/LINGYUN/Abp/Dapr/ServiceInvocation/ITestAppService.cs b/aspnet-core/tests/LINGYUN.Abp.Dapr.Tests/LINGYUN/Abp/Dapr/ServiceInvocation/ITestAppService.cs index c543aa5b3..4c453c2e1 100644 --- a/aspnet-core/tests/LINGYUN.Abp.Dapr.Tests/LINGYUN/Abp/Dapr/ServiceInvocation/ITestAppService.cs +++ b/aspnet-core/tests/LINGYUN.Abp.Dapr.Tests/LINGYUN/Abp/Dapr/ServiceInvocation/ITestAppService.cs @@ -9,7 +9,7 @@ namespace LINGYUN.Abp.Dapr.ServiceInvocation { Task> GetAsync(); - Task UpdateAsync(); + Task UpdateAsync(int inctement); Task GetWrapedAsync(string name); }