23 changed files with 636 additions and 10 deletions
@ -0,0 +1,94 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
|
||||
|
public class AppConfigurationAggregation : AggregateServiceBase<ApplicationConfigurationDto>, |
||||
|
IAppConfigurationAggregation, ITransientDependency |
||||
|
{ |
||||
|
public string AppConfigRouteName => "EshopOnAbpApplicationConfiguration"; |
||||
|
public string AppConfigEndpoint => "api/abp/application-configuration"; |
||||
|
|
||||
|
protected IAppConfigurationRemoteService AppConfigurationRemoteService { get; } |
||||
|
|
||||
|
public AppConfigurationAggregation( |
||||
|
IAppConfigurationRemoteService appConfigurationRemoteService) : base( |
||||
|
appConfigurationRemoteService) |
||||
|
{ |
||||
|
AppConfigurationRemoteService = appConfigurationRemoteService; |
||||
|
} |
||||
|
|
||||
|
public async Task<ApplicationConfigurationDto> GetAppConfigurationAsync(AppConfigurationRequest input) |
||||
|
{ |
||||
|
var remoteAppConfigurationResults = |
||||
|
await AppConfigurationRemoteService.GetMultipleAsync(input.Endpoints); |
||||
|
|
||||
|
//merge only application configuration settings data
|
||||
|
var mergedResult = MergeAppConfigurationSettingsData(remoteAppConfigurationResults); |
||||
|
|
||||
|
//return result
|
||||
|
return mergedResult; |
||||
|
} |
||||
|
|
||||
|
private static ApplicationConfigurationDto MergeAppConfigurationSettingsData( |
||||
|
IDictionary<string, ApplicationConfigurationDto> appConfigurations) |
||||
|
{ |
||||
|
var appConfigurationDto = CreateInitialAppConfigDto(appConfigurations); |
||||
|
|
||||
|
foreach (var (_, appConfig) in appConfigurations) |
||||
|
{ |
||||
|
foreach (var resource in appConfig.Setting.Values) |
||||
|
{ |
||||
|
appConfigurationDto.Setting.Values.TryAdd(resource.Key, resource.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return appConfigurationDto; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks "Administration" clusterId to set the initial data from the AdministrationService.
|
||||
|
/// Otherwise uses the first available service for the initial application configuration data
|
||||
|
/// </summary>
|
||||
|
/// <param name="appConfigurations"></param>
|
||||
|
/// <returns></returns>
|
||||
|
private static ApplicationConfigurationDto CreateInitialAppConfigDto( |
||||
|
IDictionary<string, ApplicationConfigurationDto> appConfigurations |
||||
|
) |
||||
|
{ |
||||
|
if (appConfigurations.Count == 0) |
||||
|
{ |
||||
|
return new ApplicationConfigurationDto(); |
||||
|
} |
||||
|
|
||||
|
if (appConfigurations.TryGetValue("Administration_AppConfig", out var administrationServiceData)) |
||||
|
{ |
||||
|
return MapServiceData(administrationServiceData); |
||||
|
} |
||||
|
|
||||
|
return MapServiceData(appConfigurations.First().Value); |
||||
|
} |
||||
|
|
||||
|
private static ApplicationConfigurationDto MapServiceData(ApplicationConfigurationDto appConfiguration) |
||||
|
{ |
||||
|
return new ApplicationConfigurationDto |
||||
|
{ |
||||
|
Localization = appConfiguration.Localization, |
||||
|
Auth = appConfiguration.Auth, |
||||
|
Clock = appConfiguration.Clock, |
||||
|
Setting = appConfiguration.Setting, |
||||
|
Features = appConfiguration.Features, |
||||
|
Timing = appConfiguration.Timing, |
||||
|
CurrentTenant = appConfiguration.CurrentTenant, |
||||
|
CurrentUser = appConfiguration.CurrentUser, |
||||
|
ExtraProperties = appConfiguration.ExtraProperties, |
||||
|
GlobalFeatures = appConfiguration.GlobalFeatures, |
||||
|
MultiTenancy = appConfiguration.MultiTenancy, |
||||
|
ObjectExtensions = appConfiguration.ObjectExtensions |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
|
||||
|
public class AppConfigurationCachedService(IMemoryCache applicationConfigurationCache) |
||||
|
: CachedServiceBase<ApplicationConfigurationDto>(applicationConfigurationCache), ISingletonDependency; |
||||
@ -0,0 +1,15 @@ |
|||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
|
||||
|
public class AppConfigurationRemoteService( |
||||
|
IHttpContextAccessor httpContextAccessor, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
ILogger<AggregateRemoteServiceBase<ApplicationConfigurationDto>> logger) |
||||
|
: AggregateRemoteServiceBase<ApplicationConfigurationDto>(httpContextAccessor, jsonSerializer, logger), |
||||
|
IAppConfigurationRemoteService, ITransientDependency; |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
|
||||
|
public class AppConfigurationRequest : IRequestInput |
||||
|
{ |
||||
|
public Dictionary<string, string> Endpoints { get; } = new(); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
|
||||
|
public interface IAppConfigurationAggregation |
||||
|
{ |
||||
|
string AppConfigRouteName { get; } |
||||
|
string AppConfigEndpoint { get; } |
||||
|
Task<ApplicationConfigurationDto> GetAppConfigurationAsync(AppConfigurationRequest input); |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
|
||||
|
public interface IAppConfigurationRemoteService : IAggregateRemoteService<ApplicationConfigurationDto>; |
||||
@ -0,0 +1,110 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
public abstract class AggregateRemoteServiceBase<TDto> : IAggregateRemoteService<TDto> |
||||
|
{ |
||||
|
private readonly IHttpContextAccessor _httpContextAccessor; |
||||
|
private readonly ILogger<AggregateRemoteServiceBase<TDto>> _logger; |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
|
||||
|
protected AggregateRemoteServiceBase(IHttpContextAccessor httpContextAccessor, IJsonSerializer jsonSerializer, |
||||
|
ILogger<AggregateRemoteServiceBase<TDto>> logger) |
||||
|
{ |
||||
|
_httpContextAccessor = httpContextAccessor; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
_logger = logger; |
||||
|
} |
||||
|
|
||||
|
public async Task<Dictionary<string, TDto>> GetMultipleAsync( |
||||
|
Dictionary<string, string> serviceNameWithUrlDictionary) |
||||
|
{ |
||||
|
Dictionary<string, Task<TDto>> completedTasks = new Dictionary<string, Task<TDto>>(); |
||||
|
Dictionary<string, Task<TDto>> runningTasks = new Dictionary<string, Task<TDto>>(); |
||||
|
Dictionary<string, TDto> completedResult = new Dictionary<string, TDto>(); |
||||
|
|
||||
|
using (HttpClient httpClient = CreateHttpClient()) |
||||
|
{ |
||||
|
foreach (var service in serviceNameWithUrlDictionary) |
||||
|
{ |
||||
|
Task<TDto> requestTask = |
||||
|
MakeRequestAsync<TDto>(httpClient, service.Value); |
||||
|
runningTasks.Add(service.Key, requestTask); |
||||
|
} |
||||
|
|
||||
|
while (runningTasks.Count > 0) |
||||
|
{ |
||||
|
KeyValuePair<string, Task<TDto>> completedTask = await WaitForAnyTaskAsync(runningTasks); |
||||
|
|
||||
|
runningTasks.Remove(completedTask.Key); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
TDto result = await completedTask.Value; |
||||
|
|
||||
|
completedTasks.Add(completedTask.Key, completedTask.Value); |
||||
|
completedResult.Add(completedTask.Key, result); |
||||
|
|
||||
|
_logger.LogInformation("Localization Key: {0}, Value: {1}", completedTask.Key, result); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
_logger.LogInformation("Error for the {0}: {1}", completedTask.Key, ex.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return completedResult; |
||||
|
} |
||||
|
|
||||
|
private HttpClient CreateHttpClient() |
||||
|
{ |
||||
|
var httpClient = new HttpClient(); |
||||
|
|
||||
|
var headers = _httpContextAccessor.HttpContext?.Request.Headers; |
||||
|
if (headers != null) |
||||
|
{ |
||||
|
foreach (var header in headers) |
||||
|
{ |
||||
|
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToArray()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return httpClient; |
||||
|
} |
||||
|
|
||||
|
public async Task<T> MakeRequestAsync<T>(HttpClient httpClient, string url) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
HttpResponseMessage response = await httpClient.GetAsync(url); |
||||
|
response.EnsureSuccessStatusCode(); |
||||
|
|
||||
|
var content = await response.Content.ReadAsStringAsync(); |
||||
|
return JsonSerializer.Deserialize<T>(content); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
_logger.LogInformation("Error making request to {0}: {1}", url, e.Message); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<KeyValuePair<TKey, Task<TValue>>> WaitForAnyTaskAsync<TKey, TValue>( |
||||
|
Dictionary<TKey, Task<TValue>> tasks) |
||||
|
{ |
||||
|
var completedTask = Task.WhenAny(tasks.Values); |
||||
|
var result = await completedTask; |
||||
|
|
||||
|
var completedTaskPair = tasks.First(kv => kv.Value == result); |
||||
|
|
||||
|
return completedTaskPair; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
public abstract class AggregateServiceBase<TDto> |
||||
|
{ |
||||
|
private readonly IAggregateRemoteService<TDto> _remoteService; |
||||
|
|
||||
|
public AggregateServiceBase(IAggregateRemoteService<TDto> remoteService) |
||||
|
{ |
||||
|
_remoteService = remoteService; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<Dictionary<string, TDto>> GetMultipleFromRemoteAsync(List<string> missingKeys, |
||||
|
Dictionary<string, string> endpoints) |
||||
|
{ |
||||
|
return await _remoteService |
||||
|
.GetMultipleAsync(endpoints |
||||
|
.Where(kv => missingKeys.Contains(kv.Key)) |
||||
|
.ToDictionary(k => k.Key, v => v.Value)); |
||||
|
} |
||||
|
|
||||
|
public List<string> GetMissingServiceKeys( |
||||
|
IDictionary<string, TDto> serviceNamesWithData, |
||||
|
Dictionary<string, string> serviceNamesWithUrls) |
||||
|
{ |
||||
|
List<string> missingKeysInCache = serviceNamesWithUrls.Keys.Except(serviceNamesWithData.Keys).ToList(); |
||||
|
List<string> missingKeysInUrls = serviceNamesWithData.Keys.Except(serviceNamesWithUrls.Keys).ToList(); |
||||
|
|
||||
|
return missingKeysInCache.Concat(missingKeysInUrls).ToList(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
public abstract class CachedServiceBase<TCacheValue> : ICachedServiceBase<TCacheValue> |
||||
|
{ |
||||
|
private readonly IMemoryCache _cache; |
||||
|
|
||||
|
protected MemoryCacheEntryOptions CacheEntryOptions { get; } = new() |
||||
|
{ |
||||
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24), |
||||
|
SlidingExpiration = TimeSpan.FromHours(4) |
||||
|
}; |
||||
|
|
||||
|
protected CachedServiceBase(IMemoryCache cache) |
||||
|
{ |
||||
|
_cache = cache ?? throw new ArgumentNullException(nameof(cache)); |
||||
|
} |
||||
|
|
||||
|
public void Add(string serviceName, TCacheValue data) |
||||
|
{ |
||||
|
_cache.Set(serviceName, data, CacheEntryOptions); |
||||
|
} |
||||
|
|
||||
|
public IDictionary<string, TCacheValue> GetManyAsync(IEnumerable<string> serviceNames) |
||||
|
{ |
||||
|
var result = new Dictionary<string, TCacheValue>(); |
||||
|
|
||||
|
foreach (var serviceName in serviceNames) |
||||
|
{ |
||||
|
if (_cache.TryGetValue(serviceName, out TCacheValue data)) |
||||
|
{ |
||||
|
result.Add(serviceName, data); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
public interface IAggregateRemoteService<TDto> |
||||
|
{ |
||||
|
Task<Dictionary<string, TDto>> GetMultipleAsync(Dictionary<string, string> serviceNameWithUrlDictionary); |
||||
|
Task<T> MakeRequestAsync<T>(HttpClient httpClient, string url); |
||||
|
Task<KeyValuePair<TKey, Task<TValue>>> WaitForAnyTaskAsync<TKey, TValue>(Dictionary<TKey, Task<TValue>> tasks); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
public interface ICachedServiceBase<TValue> |
||||
|
{ |
||||
|
void Add(string serviceName, TValue data); |
||||
|
IDictionary<string, TValue> GetManyAsync(IEnumerable<string> serviceNames); |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
public interface IRequestInput |
||||
|
{ |
||||
|
Dictionary<string, string> Endpoints { get; } |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
|
||||
|
public interface ILocalizationAggregation |
||||
|
{ |
||||
|
string LocalizationRouteName { get; } |
||||
|
string LocalizationEndpoint { get; } |
||||
|
Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input); |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
|
||||
|
public interface ILocalizationRemoteService : IAggregateRemoteService<ApplicationLocalizationDto>; |
||||
@ -0,0 +1,72 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
|
||||
|
public class LocalizationAggregation : AggregateServiceBase<ApplicationLocalizationDto>, ILocalizationAggregation, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
public string LocalizationRouteName => "EshopOnAbpLocalization"; |
||||
|
public string LocalizationEndpoint => "api/abp/application-localization"; |
||||
|
protected LocalizationCachedService LocalizationCachedService { get; } |
||||
|
|
||||
|
public LocalizationAggregation( |
||||
|
LocalizationCachedService localizationCachedService, |
||||
|
ILocalizationRemoteService localizationRemoteService) |
||||
|
: base(localizationRemoteService) |
||||
|
{ |
||||
|
LocalizationCachedService = localizationCachedService; |
||||
|
} |
||||
|
|
||||
|
public async Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input) |
||||
|
{ |
||||
|
// Check the cache service
|
||||
|
var cachedLocalization = LocalizationCachedService |
||||
|
.GetManyAsync(input.Endpoints.Keys.ToArray()); |
||||
|
|
||||
|
// Compare cache with input service list
|
||||
|
var missingLocalizationKeys = GetMissingServiceKeys(cachedLocalization, input.Endpoints); |
||||
|
|
||||
|
if (missingLocalizationKeys.Count != 0) |
||||
|
{ |
||||
|
// Make request to remote localization service to get missing localizations
|
||||
|
var remoteLocalizationResults = |
||||
|
await GetMultipleFromRemoteAsync(missingLocalizationKeys, input.Endpoints); |
||||
|
|
||||
|
// Update localization cache
|
||||
|
foreach (var result in remoteLocalizationResults) |
||||
|
{ |
||||
|
LocalizationCachedService.Add(result.Key, result.Value); |
||||
|
} |
||||
|
|
||||
|
cachedLocalization = LocalizationCachedService |
||||
|
.GetManyAsync(input.Endpoints.Keys.ToArray()); |
||||
|
} |
||||
|
|
||||
|
//merge result
|
||||
|
var mergedResult = MergeLocalizationData(cachedLocalization); |
||||
|
|
||||
|
//return result
|
||||
|
return mergedResult; |
||||
|
} |
||||
|
|
||||
|
private static ApplicationLocalizationDto MergeLocalizationData( |
||||
|
IDictionary<string, ApplicationLocalizationDto> resourceDictionary) |
||||
|
{ |
||||
|
var localizationDto = new ApplicationLocalizationDto(); |
||||
|
|
||||
|
foreach (var localization in resourceDictionary) |
||||
|
{ |
||||
|
foreach (var resource in localization.Value.Resources) |
||||
|
{ |
||||
|
localizationDto.Resources.TryAdd(resource.Key, resource.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return localizationDto; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
|
||||
|
public class LocalizationCachedService(IMemoryCache localizationCache) |
||||
|
: CachedServiceBase<ApplicationLocalizationDto>(localizationCache), ISingletonDependency; |
||||
@ -0,0 +1,20 @@ |
|||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
|
||||
|
public class LocalizationRemoteService : AggregateRemoteServiceBase<ApplicationLocalizationDto>, |
||||
|
ILocalizationRemoteService, ITransientDependency |
||||
|
{ |
||||
|
public LocalizationRemoteService( |
||||
|
IHttpContextAccessor httpContextAccessor, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
ILogger<AggregateRemoteServiceBase<ApplicationLocalizationDto>> logger) |
||||
|
: base(httpContextAccessor, jsonSerializer, logger) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using EShopOnAbp.WebGateway.Aggregations.Base; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
|
||||
|
public class LocalizationRequest : IRequestInput |
||||
|
{ |
||||
|
public Dictionary<string, string> Endpoints { get; } = new(); |
||||
|
public string CultureName { get; set; } |
||||
|
|
||||
|
public LocalizationRequest(string cultureName) |
||||
|
{ |
||||
|
CultureName = cultureName; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,113 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using EShopOnAbp.WebGateway.Aggregations.ApplicationConfiguration; |
||||
|
using EShopOnAbp.WebGateway.Aggregations.Localization; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.Routing; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Yarp.ReverseProxy.Configuration; |
||||
|
|
||||
|
namespace EShopOnAbp.WebGateway; |
||||
|
|
||||
|
public static class ReverseProxyBuilderExtensions |
||||
|
{ |
||||
|
public static ReverseProxyConventionBuilder MapReverseProxyWithLocalization(this IEndpointRouteBuilder endpoints) |
||||
|
{ |
||||
|
return endpoints.MapReverseProxy(proxyBuilder => |
||||
|
{ |
||||
|
proxyBuilder.Use(async (context, next) => |
||||
|
{ |
||||
|
var endpoint = context.GetEndpoint(); |
||||
|
|
||||
|
var localizationAggregation = context.RequestServices |
||||
|
.GetRequiredService<ILocalizationAggregation>(); |
||||
|
|
||||
|
var appConfigurationAggregation = context.RequestServices |
||||
|
.GetRequiredService<IAppConfigurationAggregation>(); |
||||
|
|
||||
|
// The "/api/abp/application-localization" endpoint
|
||||
|
if (localizationAggregation.LocalizationRouteName == endpoint?.DisplayName) |
||||
|
{ |
||||
|
var localizationRequestInput = |
||||
|
CreateLocalizationRequestInput(context, localizationAggregation.LocalizationEndpoint); |
||||
|
|
||||
|
var result = await localizationAggregation.GetLocalizationAsync(localizationRequestInput); |
||||
|
await context.Response.WriteAsJsonAsync(result); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// The "/api/abp/application-configuration" endpoint
|
||||
|
if (appConfigurationAggregation.AppConfigRouteName == endpoint?.DisplayName) |
||||
|
{ |
||||
|
var appConfigurationRequestInput = |
||||
|
CreateAppConfigurationRequestInput(context, appConfigurationAggregation.AppConfigEndpoint); |
||||
|
|
||||
|
var result = |
||||
|
await appConfigurationAggregation.GetAppConfigurationAsync(appConfigurationRequestInput); |
||||
|
await context.Response.WriteAsJsonAsync(result); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await next(); |
||||
|
}); |
||||
|
|
||||
|
proxyBuilder.UseLoadBalancing(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static AppConfigurationRequest CreateAppConfigurationRequestInput(HttpContext context, |
||||
|
string appConfigurationPath) |
||||
|
{ |
||||
|
var proxyConfig = context.RequestServices.GetRequiredService<IProxyConfigProvider>(); |
||||
|
|
||||
|
var input = new AppConfigurationRequest(); |
||||
|
string path = $"{appConfigurationPath}?includeLocalizationResources=false"; |
||||
|
|
||||
|
var clusterList = GetClusters(proxyConfig); |
||||
|
foreach (var cluster in clusterList) |
||||
|
{ |
||||
|
var hostUrl = new Uri(cluster.Value.Address) + $"{path}"; |
||||
|
// CacheKey/Endpoint dictionary key -> ex: ("Administration_AppConfig")
|
||||
|
input.Endpoints.Add($"{cluster.Key}_AppConfig", hostUrl); |
||||
|
} |
||||
|
|
||||
|
return input; |
||||
|
} |
||||
|
|
||||
|
private static LocalizationRequest CreateLocalizationRequestInput(HttpContext context, string localizationPath) |
||||
|
{ |
||||
|
var proxyConfig = context.RequestServices.GetRequiredService<IProxyConfigProvider>(); |
||||
|
|
||||
|
context.Request.Query.TryGetValue("CultureName", out var cultureName); |
||||
|
|
||||
|
var input = new LocalizationRequest(cultureName); |
||||
|
string path = $"{localizationPath}?cultureName={cultureName}&onlyDynamics=false"; |
||||
|
|
||||
|
var clusterList = GetClusters(proxyConfig); |
||||
|
foreach (var cluster in clusterList) |
||||
|
{ |
||||
|
var hostUrl = new Uri(cluster.Value.Address) + $"{path}"; |
||||
|
// Endpoint dictionary key -> ex: ("Administration_en")
|
||||
|
input.Endpoints.Add($"{cluster.Key}_{cultureName}", hostUrl); |
||||
|
} |
||||
|
|
||||
|
return input; |
||||
|
} |
||||
|
|
||||
|
private static Dictionary<string, DestinationConfig> GetClusters(IProxyConfigProvider proxyConfig) |
||||
|
{ |
||||
|
var yarpConfig = proxyConfig.GetConfig(); |
||||
|
|
||||
|
var routedClusters = yarpConfig.Clusters |
||||
|
.SelectMany(t => t.Destinations, |
||||
|
(clusterId, destination) => new { clusterId.ClusterId, destination.Value }); |
||||
|
|
||||
|
return routedClusters |
||||
|
.GroupBy(q => q.Value.Address) |
||||
|
.Select(t => t.First()) |
||||
|
.Distinct() |
||||
|
.ToDictionary(k => k.ClusterId, v => v.Value); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue