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, ICorrelationIdProvider correlationIdProvider, ICurrentTenant currentTenant) { Cache = cache; AbpCorrelationIdOptions = abpCorrelationIdOptions.Value; CorrelationIdProvider = correlationIdProvider; CurrentTenant = currentTenant; CancellationTokenProvider = NullCancellationTokenProvider.Instance; } public async Task 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 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 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>(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(); } } }