Seyounth.Auto.Hs/share/Syc.Abp.HttpApi.Client/AppApiDescriptionFinder.cs
2025-06-16 17:24:23 +08:00

162 lines
5.9 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Threading;
using Volo.Abp.Tracing;
using Microsoft.Extensions.DependencyInjection;
namespace Syc.Abp.HttpApi.Client
{
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class AppApiDescriptionFinder : IApiDescriptionFinder
{
public ICancellationTokenProvider CancellationTokenProvider { get; set; }
protected IApiDescriptionCache Cache { get; }
protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; }
protected ICorrelationIdProvider CorrelationIdProvider { get; }
protected ICurrentTenant CurrentTenant { get; }
public AppApiDescriptionFinder(
IApiDescriptionCache cache,
IOptions<AbpCorrelationIdOptions> abpCorrelationIdOptions,
ICorrelationIdProvider correlationIdProvider,
ICurrentTenant currentTenant)
{
Cache = cache;
AbpCorrelationIdOptions = abpCorrelationIdOptions.Value;
CorrelationIdProvider = correlationIdProvider;
CurrentTenant = currentTenant;
CancellationTokenProvider = NullCancellationTokenProvider.Instance;
}
public async Task<ActionApiDescriptionModel> FindActionAsync(
HttpClient client,
string baseUrl,
Type serviceType,
MethodInfo method)
{
var apiDescription = await GetApiDescriptionAsync(client, baseUrl);
//TODO: Cache finding?
var methodParameters = method.GetParameters().ToArray();
foreach (var module in apiDescription.Modules.Values)
{
foreach (var controller in module.Controllers.Values)
{
if (!controller.Implements(serviceType))
{
continue;
}
foreach (var action in controller.Actions.Values)
{
if (action.Name == method.Name && action.ParametersOnMethod.Count == methodParameters.Length)
{
var found = true;
for (int i = 0; i < methodParameters.Length; i++)
{
if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i]))
{
found = false;
break;
}
}
if (found)
{
return action;
}
}
}
}
}
throw new AbpException($"Could not find remote action for method: {method} on the URL: {baseUrl}");
}
public virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionAsync(HttpClient client, string baseUrl)
{
return await Cache.GetAsync(baseUrl, () => GetApiDescriptionFromServerAsync(client, baseUrl));
}
public static JsonSerializerOptions DeserializeOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
protected virtual async Task<ApplicationApiDescriptionModel> GetApiDescriptionFromServerAsync(
HttpClient client,
string baseUrl)
{
var requestMessage = new HttpRequestMessage(
HttpMethod.Get,
baseUrl.EnsureEndsWith('/') + "api/abp/api-definition"
);
AddHeaders(requestMessage);
var response = await client.SendAsync(
requestMessage,
CancellationTokenProvider.Token
);
if (!response.IsSuccessStatusCode)
{
throw new AbpException("Remote service returns error! StatusCode = " + response.StatusCode);
}
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<Response<ApplicationApiDescriptionModel>>(content, DeserializeOptions)!;
return result.Data;
}
protected virtual void AddHeaders(HttpRequestMessage requestMessage)
{
//CorrelationId
requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get());
//TenantId
if (CurrentTenant.Id.HasValue)
{
//TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
}
//Culture
//TODO: Is that the way we want? Couldn't send the culture (not ui culture)
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name;
if (!currentCulture.IsNullOrEmpty())
{
requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture));
}
//X-Requested-With
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest");
}
protected virtual bool TypeMatches(MethodParameterApiDescriptionModel actionParameter, ParameterInfo methodParameter)
{
return actionParameter.Type.ToUpper() == TypeHelper.GetFullNameHandlingNullableAndGenerics(methodParameter.ParameterType).ToUpper();
}
}
}