Browse Source

Added localization aggregation

pull/235/head
Galip Tolga Erdem 3 years ago
parent
commit
202bbcc18c
  1. 11
      gateways/web/src/EShopOnAbp.WebGateway/Aggregations/ILocalizationAggregation.cs
  2. 152
      gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs
  3. 8
      gateways/web/src/EShopOnAbp.WebGateway/EShopOnAbpWebGatewayModule.cs
  4. 88
      gateways/web/src/EShopOnAbp.WebGateway/ReverseProxyBuilderExtensions.cs
  5. 6
      gateways/web/src/EShopOnAbp.WebGateway/yarp.json

11
gateways/web/src/EShopOnAbp.WebGateway/Aggregations/ILocalizationAggregation.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.DependencyInjection;
namespace EShopOnAbp.WebGateway.Aggregations;
public interface ILocalizationAggregation : ITransientDependency
{
public string LocalizationEndpoint { get; }
public Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input);
}

152
gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Caching;
namespace EShopOnAbp.WebGateway.Aggregations;
public class LocalizationAggregation : ILocalizationAggregation
{
//Yarp localization route name
public string LocalizationEndpoint { get; } = "EshopOnAbpLocalization";
private readonly ILogger<LocalizationAggregation> _logger;
private readonly IDistributedCache<LocalizationResponse> _localizationResponseCache;
public LocalizationAggregation(ILogger<LocalizationAggregation> logger, IDistributedCache<LocalizationResponse> localizationResponseCache)
{
_logger = logger;
_localizationResponseCache = localizationResponseCache;
}
public async Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input)
{
//Temp list
Dictionary<string, string> services = new Dictionary<string, string>()
{
{ "Administration", "https://localhost:44353/api/abp/application-localization?CultureName=en" },
{ "Catalog", "https://localhost:44354/api/abp/application-localization?CultureName=en" }
};
// Check the cache
Dictionary<string, ApplicationLocalizationDto> localizationResults = await GetCompletedLocalizationResultsAsync(services);
ApplicationLocalizationDto mergedResult = MergeLocalizations(localizationResults);
return mergedResult;
}
private ApplicationLocalizationDto MergeLocalizations(Dictionary<string, ApplicationLocalizationDto> localizationResults)
{
var localizationDto = new ApplicationLocalizationDto();
foreach (var localizationResult in localizationResults)
{
localizationDto.Resources.AddIfNotContains(localizationResult.Value.Resources);
}
return localizationDto;
}
private async Task<Dictionary<string, ApplicationLocalizationDto>> GetCompletedLocalizationResultsAsync(Dictionary<string, string> services)
{
Dictionary<string, Task<ApplicationLocalizationDto>> completedTasks = new Dictionary<string, Task<ApplicationLocalizationDto>>();
Dictionary<string, Task<ApplicationLocalizationDto>> runningTasks = new Dictionary<string, Task<ApplicationLocalizationDto>>();
Dictionary<string, ApplicationLocalizationDto> completedResult = new Dictionary<string, ApplicationLocalizationDto>();
using (HttpClient httpClient = new HttpClient())
{
foreach (var service in services)
{
Task<ApplicationLocalizationDto> requestTask = MakeRequestAsync<ApplicationLocalizationDto>(httpClient, service.Value);
runningTasks.Add(service.Key, requestTask);
}
while (runningTasks.Count > 0)
{
KeyValuePair<string, Task<ApplicationLocalizationDto>> completedTask = await WaitForAnyTaskAsync(runningTasks);
runningTasks.Remove(completedTask.Key);
try
{
ApplicationLocalizationDto result = await completedTask.Value;
completedTasks.Add(completedTask.Key, completedTask.Value);
completedResult.Add(completedTask.Key, result);
_logger.LogInformation($"Key: {completedTask.Key}, Value: {result}");
}
catch (Exception ex)
{
_logger.LogInformation($"Error for the {completedTask.Key}: {ex.Message}");
}
}
}
return completedResult;
}
private async Task<KeyValuePair<string, Task<ApplicationLocalizationDto>>> WaitForAnyTaskAsync(
Dictionary<string, Task<ApplicationLocalizationDto>> tasks)
{
Task<Task<ApplicationLocalizationDto>> completedTask = Task.WhenAny(tasks.Values);
Task<ApplicationLocalizationDto> result = await completedTask;
// Find the key corresponding to the completed task
KeyValuePair<string, Task<ApplicationLocalizationDto>>
completedTaskPair = tasks.First(kv => kv.Value == result);
return completedTaskPair;
}
private async Task<T> MakeRequestAsync<T>(HttpClient httpClient, string url)
{
try
{
HttpResponseMessage response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
return JsonSerializer.Deserialize<T>(content, options);
}
catch (Exception ex)
{
// Log or handle the exception, and return a default value or throw it again if needed
Console.WriteLine($"Error making request to {url}: {ex.Message}");
throw;
}
}
}
public class LocalizationRequest
{
// Localization clusterName - localization endpoint pair
public Dictionary<string, string> LocalizationEndpoints { get; }
public string CultureName { get; set; }
public bool? OnlyDynamics { get; set; }
public LocalizationRequest(string cultureName)
{
CultureName = cultureName;
LocalizationEndpoints = new Dictionary<string, string>();
}
}
public class LocalizationResponse
{
// Localization url - localization resource pair
public Dictionary<string, ApplicationLocalizationDto> Localization { get; }
public string CultureName { get; set; }
public bool? OnlyDynamics { get; set; }
public string ServiceName { get; set; }
}

8
gateways/web/src/EShopOnAbp.WebGateway/EShopOnAbpWebGatewayModule.cs

@ -7,7 +7,10 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using System; using System;
using System.Linq; using System.Linq;
using EShopOnAbp.WebGateway.Aggregations;
using Microsoft.AspNetCore.Http;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace EShopOnAbp.WebGateway; namespace EShopOnAbp.WebGateway;
@ -76,6 +79,9 @@ public class EShopOnAbpWebGatewayModule : AbpModule
// Regex for "", "/" and "" (whitespace) // Regex for "", "/" and "" (whitespace)
.AddRedirect("^(|\\|\\s+)$", "/swagger")); .AddRedirect("^(|\\|\\s+)$", "/swagger"));
app.UseEndpoints(endpoints => { endpoints.MapReverseProxy(); }); app.UseEndpoints(endpoints =>
{
endpoints.MapReverseProxyWithLocalization();
});
} }
} }

88
gateways/web/src/EShopOnAbp.WebGateway/ReverseProxyBuilderExtensions.cs

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using EShopOnAbp.WebGateway.Aggregations;
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 string LocalizationEndpoint = "api/abp/application-localization";
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>();
if (localizationAggregation.LocalizationEndpoint == endpoint?.DisplayName)
{
LocalizationRequest input = CreateLocalizationRequestInput(context);
var result = await localizationAggregation.GetLocalizationAsync(input);
await context.Response.WriteAsync(JsonSerializer.Serialize(result, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
}));
return;
}
await next();
});
proxyBuilder.UseLoadBalancing();
});
}
private static LocalizationRequest CreateLocalizationRequestInput(HttpContext context)
{
var proxyConfig = context.RequestServices.GetRequiredService<IProxyConfigProvider>();
context.Request.Query.TryGetValue("CultureName", out var cultureName);
context.Request.Query.TryGetValue("OnlyDynamics", out var onlyDynamics);
var input = new LocalizationRequest(cultureName);
StringBuilder stringBuilder = new StringBuilder(LocalizationEndpoint + $"?CultureName={cultureName}");
if (!string.IsNullOrEmpty(onlyDynamics))
{
input.OnlyDynamics = bool.Parse(onlyDynamics);
stringBuilder.Append($"&OnlyDynamics={onlyDynamics}");
}
var clusterList = GetClusters(proxyConfig);
foreach (var cluster in clusterList)
{
var hostUrl = new Uri(cluster.Value.Address) + $"{stringBuilder}";
input.LocalizationEndpoints.Add(cluster.Key, 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);
}
}

6
gateways/web/src/EShopOnAbp.WebGateway/yarp.json

@ -7,6 +7,12 @@
"Path": "/api/abp/{**catch-all}" "Path": "/api/abp/{**catch-all}"
} }
}, },
"EshopOnAbpLocalization": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/abp/application-localization"
}
},
"Identity Service": { "Identity Service": {
"ClusterId": "Identity", "ClusterId": "Identity",
"Match": { "Match": {

Loading…
Cancel
Save