committed by
GitHub
29 changed files with 1081 additions and 314 deletions
@ -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<CSharpServiceProxyGenerator>(); |
||||
|
|
||||
|
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 <module-name> (default: 'app') The name of the backend module you wish to generate proxies for."); |
||||
|
sb.AppendLine("-wd|--working-directory <directory-path> Execution directory."); |
||||
|
sb.AppendLine("-u|--url <url> API definition URL from."); |
||||
|
sb.AppendLine("-p|--provider <client-proxy-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"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<CSharpServiceProxyGenerator>, ITransientDependency |
||||
|
{ |
||||
|
public const string Name = "CSHARP"; |
||||
|
|
||||
|
private const string UsingPlaceholder = "<using placeholder>"; |
||||
|
private const string MethodPlaceholder = "<method placeholder>"; |
||||
|
private const string ClassName = "<className>"; |
||||
|
private const string ServiceInterface = "<serviceInterface>"; |
||||
|
private readonly static string[] ServicePostfixes = { "AppService", "ApplicationService" , "Service"}; |
||||
|
private const string DefaultNamespace = "ClientProxies"; |
||||
|
private const string Namespace = "<namespace>"; |
||||
|
private const string DefaultProvider = "ClientProxyBase"; |
||||
|
private const string Provider = "<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}<using placeholder>" + |
||||
|
$"{Environment.NewLine}" + |
||||
|
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" + |
||||
|
$"{Environment.NewLine}namespace <namespace>;" + |
||||
|
$"{Environment.NewLine}" + |
||||
|
$"{Environment.NewLine}[Dependency(ReplaceServices = true)]" + |
||||
|
$"{Environment.NewLine}[ExposeServices(typeof(<serviceInterface>), typeof(<className>))]" + |
||||
|
$"{Environment.NewLine}public partial class <className> : <provider><<serviceInterface>>, <serviceInterface>" + |
||||
|
$"{Environment.NewLine}{{" + |
||||
|
$"{Environment.NewLine} <method placeholder>" + |
||||
|
$"{Environment.NewLine}}}" + |
||||
|
$"{Environment.NewLine}"; |
||||
|
private readonly string _clientProxyTemplate = "// This file is part of <className>, you can customize it here" + |
||||
|
$"{Environment.NewLine}// ReSharper disable once CheckNamespace" + |
||||
|
$"{Environment.NewLine}namespace <namespace>;" + |
||||
|
$"{Environment.NewLine}" + |
||||
|
$"{Environment.NewLine}public partial class <className>" + |
||||
|
$"{Environment.NewLine}{{" + |
||||
|
$"{Environment.NewLine}}}" + |
||||
|
$"{Environment.NewLine}"; |
||||
|
private readonly List<string> _usingNamespaceList = new() |
||||
|
{ |
||||
|
"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<GenerateProxyArgs>().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<string>(_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<string> 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<string> usingNamespaceList) |
||||
|
{ |
||||
|
methodBuilder.AppendLine($"public virtual {returnTypeName} {action.Name}(<args>)"); |
||||
|
|
||||
|
foreach (var parameter in action.Parameters.GroupBy(x => x.Name).Select(x => x.First())) |
||||
|
{ |
||||
|
methodBuilder.Replace("<args>", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, <args>"); |
||||
|
} |
||||
|
|
||||
|
methodBuilder.Replace("<args>", 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<string> usingNamespaceList) |
||||
|
{ |
||||
|
var returnSign = returnTypeName == "void" ? "Task" : $"Task<{returnTypeName}>"; |
||||
|
|
||||
|
methodBuilder.AppendLine($"public virtual async {returnSign} {action.Name}(<args>)"); |
||||
|
|
||||
|
foreach (var parameter in action.ParametersOnMethod) |
||||
|
{ |
||||
|
methodBuilder.Replace("<args>", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, <args>"); |
||||
|
} |
||||
|
|
||||
|
methodBuilder.Replace("<args>", string.Empty); |
||||
|
methodBuilder.Replace(", )", ")"); |
||||
|
|
||||
|
methodBuilder.AppendLine(" {"); |
||||
|
|
||||
|
var argsTemplate = "new ClientProxyRequestTypeValue" + |
||||
|
$"{Environment.NewLine} {{<args>" + |
||||
|
$"{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("<args>", $"{Environment.NewLine} {{ typeof({GetRealTypeName(parameter.Type)}), {parameter.Name} }},<args>"); |
||||
|
} |
||||
|
|
||||
|
methodBuilder.Replace(",<args>", 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<string> 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<string> 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."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<string, string> extraProperties = null) |
||||
|
: base(commandName, workDirectory, module, url, output, target, apiName, source, folder, extraProperties) |
||||
|
{ |
||||
|
Provider = provider; |
||||
|
} |
||||
|
} |
||||
@ -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<AbpHttpClientBuilderOptions>(options => |
||||
|
{ |
||||
|
options.ProxyClientBuildActions.Add( |
||||
|
(_, builder) => |
||||
|
{ |
||||
|
builder.ConfigureHttpClient((provider, client) => |
||||
|
{ |
||||
|
var wrapperOptions = provider.GetRequiredService<IOptions<AbpWrapperOptions>>(); |
||||
|
var wrapperHeader = wrapperOptions.Value.IsEnabled |
||||
|
? AbpHttpWrapConsts.AbpWrapResult |
||||
|
: AbpHttpWrapConsts.AbpDontWrapResult; |
||||
|
|
||||
|
client.DefaultRequestHeaders.TryAddWithoutValidation(wrapperHeader, "true"); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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 |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -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<TService> |
|
||||
: DynamicHttpProxyInterceptorClientProxy<TService>, ITransientDependency |
|
||||
{ |
|
||||
protected IOptions<AbpWrapperOptions> WrapperOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpWrapperOptions>>(); |
|
||||
|
|
||||
protected override async Task<T> RequestAsync<T>(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<WrapResult<T>>(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<T>(stringContent); |
|
||||
} |
|
||||
|
|
||||
public override async Task<HttpContent> CallRequestAsync(ClientProxyRequestContext requestContext) |
|
||||
{ |
|
||||
var response = await RequestAndGetResponseAsync(requestContext); |
|
||||
// 对于包装后的结果需要处理
|
|
||||
if (response.Headers.Contains(AbpHttpWrapConsts.AbpWrapResult)) |
|
||||
{ |
|
||||
var stringContent = await response.Content.ReadAsStringAsync(); |
|
||||
var wrapResult = JsonSerializer.Deserialize<WrapResult>(stringContent); |
|
||||
|
|
||||
ThrowExceptionForResponse(wrapResult); |
|
||||
} |
|
||||
|
|
||||
return response.Content; |
|
||||
} |
|
||||
|
|
||||
protected virtual void ThrowExceptionForResponse<T>(WrapResult<T> 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<HttpResponseMessage> 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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +1,11 @@ |
|||||
using System; |
using LINGYUN.Abp.Dapr.Client.DynamicProxying; |
||||
|
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
using System.Net.Http; |
using System.Net.Http; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
namespace LINGYUN.Abp.Dapr.Client.DynamicProxying |
namespace LINGYUN.Abp.Dapr.Client.ClientProxying |
||||
{ |
{ |
||||
public class AbpDaprClientProxyOptions |
public class AbpDaprClientProxyOptions |
||||
{ |
{ |
||||
@ -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<TService> : ClientProxyBase<TService> |
||||
|
{ |
||||
|
protected IOptions<AbpDaprClientProxyOptions> DaprClientProxyOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDaprClientProxyOptions>>(); |
||||
|
protected IDaprClientFactory DaprClientFactory => LazyServiceProvider.LazyGetRequiredService<IDaprClientFactory>(); |
||||
|
|
||||
|
protected async override Task<T> RequestAsync<T>(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<T>(stringContent); |
||||
|
} |
||||
|
|
||||
|
protected async override Task<string> 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<HttpResponseMessage> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,134 +1,20 @@ |
|||||
using Dapr.Client; |
using LINGYUN.Abp.Dapr.Client.ClientProxying; |
||||
using Microsoft.Extensions.Options; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Net.Http; |
using System.Net.Http; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Volo.Abp; |
|
||||
using Volo.Abp.Content; |
|
||||
using Volo.Abp.Http.Client.Authentication; |
|
||||
using Volo.Abp.Http.Client.ClientProxying; |
using Volo.Abp.Http.Client.ClientProxying; |
||||
|
|
||||
namespace LINGYUN.Abp.Dapr.Client.DynamicProxying |
namespace LINGYUN.Abp.Dapr.Client.DynamicProxying |
||||
{ |
{ |
||||
public class DynamicDaprProxyInterceptorClientProxy<TService> : ClientProxyBase<TService> |
public class DynamicDaprProxyInterceptorClientProxy<TService> : DaprClientProxyBase<TService> |
||||
{ |
{ |
||||
protected IOptions<AbpDaprClientProxyOptions> DaprClientProxyOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDaprClientProxyOptions>>(); |
public async virtual Task<T> CallRequestAsync<T>(ClientProxyRequestContext requestContext) |
||||
protected IDaprClientFactory DaprClientFactory => LazyServiceProvider.LazyGetRequiredService<IDaprClientFactory>(); |
|
||||
|
|
||||
public virtual async Task<T> CallRequestAsync<T>(ClientProxyRequestContext requestContext) |
|
||||
{ |
{ |
||||
return await RequestAsync<T>(requestContext); |
return await RequestAsync<T>(requestContext); |
||||
} |
} |
||||
|
|
||||
public virtual async Task<HttpContent> CallRequestAsync(ClientProxyRequestContext requestContext) |
public async virtual Task<HttpContent> CallRequestAsync(ClientProxyRequestContext requestContext) |
||||
{ |
{ |
||||
return await RequestAsync(requestContext); |
return await RequestAsync(requestContext); |
||||
} |
} |
||||
|
|
||||
protected override async Task<T> RequestAsync<T>(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<T>(stringContent); |
|
||||
} |
|
||||
|
|
||||
protected override async Task<string> 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<HttpResponseMessage> 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; |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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>, ITestAppService |
||||
|
{ |
||||
|
public virtual async Task<ListResultDto<NameValue>> GetAsync() |
||||
|
{ |
||||
|
return await RequestAsync<ListResultDto<NameValue>>(nameof(GetAsync)); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<NameValue> UpdateAsync(int inctement) |
||||
|
{ |
||||
|
return await RequestAsync<NameValue>(nameof(UpdateAsync), new ClientProxyRequestTypeValue |
||||
|
{ |
||||
|
{ typeof(int), inctement } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<TestNeedWrapObject> GetWrapedAsync(string name) |
||||
|
{ |
||||
|
return await RequestAsync<TestNeedWrapObject>(nameof(GetWrapedAsync), new ClientProxyRequestTypeValue |
||||
|
{ |
||||
|
{ typeof(string), name } |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
} |
||||
@ -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<LINGYUN.Abp.Dapr.NameValue>", |
||||
|
"typeSimple": "Volo.Abp.Application.Dtos.ListResultDto<LINGYUN.Abp.Dapr.NameValue>" |
||||
|
}, |
||||
|
"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": {} |
||||
|
} |
||||
@ -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<ITestAppService>(); |
||||
|
} |
||||
|
|
||||
|
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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue