From 9bac0007efac5866057a187b5d20f732b923272f Mon Sep 17 00:00:00 2001 From: Vullnet Dyla Date: Fri, 5 Apr 2019 16:30:09 +0200 Subject: [PATCH 1/4] Implemented strongly typed JSON deserialization --- sample/Web/ServiceExtensions.cs | 7 +++-- sample/Web/Startup.cs | 2 ++ sample/Web/TraduoraService.cs | 7 +---- src/Traduora/Api/ApiCredentials.cs | 9 +----- src/Traduora/Api/AuthResponse.cs | 10 +++++++ src/Traduora/Api/ITraduoraApi.cs | 10 +++---- src/Traduora/Api/LocalesResponse.cs | 21 ++++++++++++++ src/Traduora/TraduoraProvider.cs | 44 +++++++++++++---------------- 8 files changed, 63 insertions(+), 47 deletions(-) create mode 100644 src/Traduora/Api/AuthResponse.cs create mode 100644 src/Traduora/Api/LocalesResponse.cs diff --git a/sample/Web/ServiceExtensions.cs b/sample/Web/ServiceExtensions.cs index da2b7f9..1640a8b 100644 --- a/sample/Web/ServiceExtensions.cs +++ b/sample/Web/ServiceExtensions.cs @@ -30,14 +30,15 @@ public static IServiceCollection AddTraduora(this IServiceCollection services) return new TraduoraProvider(httpClient); }); - services.AddTransient(); + services.AddTransient(); services.AddSingleton(sp => { - var traduoraApiSettings = sp.GetRequiredService>().Value; + TraduoraApiSettings traduoraApiSettings = + sp.GetRequiredService>().Value; var cache = new DictionaryPollingCache( - sp.GetRequiredService().GetTranslations, + sp.GetRequiredService().GetTranslations, traduoraApiSettings.RefreshIntervalSeconds); return new CachedDictionaryStringLocalizer(cache.GetData); diff --git a/sample/Web/Startup.cs b/sample/Web/Startup.cs index 1853fa0..bb9db92 100644 --- a/sample/Web/Startup.cs +++ b/sample/Web/Startup.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using Web.Config; namespace Web diff --git a/sample/Web/TraduoraService.cs b/sample/Web/TraduoraService.cs index 157656e..5462d89 100644 --- a/sample/Web/TraduoraService.cs +++ b/sample/Web/TraduoraService.cs @@ -6,7 +6,7 @@ namespace Web { - public class TraduoraService : ITraduoraService + public class TraduoraService { private readonly TraduoraSecretSettings _config; private readonly TraduoraProvider _traduora; @@ -24,9 +24,4 @@ public async Task>> GetTranslations(); - } } diff --git a/src/Traduora/Api/ApiCredentials.cs b/src/Traduora/Api/ApiCredentials.cs index 8a1b9a5..f6e2aa1 100644 --- a/src/Traduora/Api/ApiCredentials.cs +++ b/src/Traduora/Api/ApiCredentials.cs @@ -1,16 +1,9 @@ -using Newtonsoft.Json; - -namespace Traduora.Provider.Api +namespace Traduora.Provider.Api { internal class ApiCredentials { - [JsonProperty("clientId")] public string ClientId { get; set; } - - [JsonProperty("clientSecret")] public string ClientSecret { get; set; } - - [JsonProperty("grantType")] public string GrantType => "client_credentials"; } } \ No newline at end of file diff --git a/src/Traduora/Api/AuthResponse.cs b/src/Traduora/Api/AuthResponse.cs new file mode 100644 index 0000000..e9bf4e9 --- /dev/null +++ b/src/Traduora/Api/AuthResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json.Linq; + +namespace Traduora.Provider.Api +{ + internal class AuthResponse + { + public JObject Data { get; set; } + public string AccessToken => Data["accessToken"].Value(); + } +} diff --git a/src/Traduora/Api/ITraduoraApi.cs b/src/Traduora/Api/ITraduoraApi.cs index afbbbf9..2c6c6eb 100644 --- a/src/Traduora/Api/ITraduoraApi.cs +++ b/src/Traduora/Api/ITraduoraApi.cs @@ -1,5 +1,5 @@ -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; using Refit; namespace Traduora.Provider.Api @@ -7,13 +7,13 @@ namespace Traduora.Provider.Api internal interface ITraduoraApi { [Get("/projects/{projectId}/exports?format={format}&locale={locale}")] - Task GetExportedData(string projectId, string format, string locale, + Task> GetExportedData(string projectId, string format, string locale, [Header("Authorization")] string auth); [Get("/projects/{projectId}/translations")] - Task GetLocales(string projectId, [Header("Authorization")] string auth); + Task GetLocales(string projectId, [Header("Authorization")] string auth); [Post("/auth/token")] - Task GetToken([Body] ApiCredentials apiCredentials); + Task GetToken([Body] ApiCredentials apiCredentials); } } \ No newline at end of file diff --git a/src/Traduora/Api/LocalesResponse.cs b/src/Traduora/Api/LocalesResponse.cs new file mode 100644 index 0000000..4c165ed --- /dev/null +++ b/src/Traduora/Api/LocalesResponse.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace Traduora.Provider.Api +{ + internal class LocalesResponse + { + public IEnumerable Data { get; set; } + } + + internal class LocaleWrapper + { + public Locale Locale { get; set; } + } + + internal class Locale + { + public string Code { get; set; } + public string Culture => Code.Replace('_', '-'); + } +} diff --git a/src/Traduora/TraduoraProvider.cs b/src/Traduora/TraduoraProvider.cs index e022f60..cc3c714 100644 --- a/src/Traduora/TraduoraProvider.cs +++ b/src/Traduora/TraduoraProvider.cs @@ -2,7 +2,8 @@ using System.Collections.ObjectModel; using System.Net.Http; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using Refit; using Traduora.Provider.Api; @@ -15,16 +16,23 @@ public class TraduoraProvider public TraduoraProvider(HttpClient httpClient) { - _traduoraApi = RestService.For(httpClient); + var refitSettings = new RefitSettings + { + ContentSerializer = new JsonContentSerializer( + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }) + }; + + _traduoraApi = RestService.For(httpClient, refitSettings); } public async Task Authenticate(string clientId, string clientSecret) { var authBody = new ApiCredentials { ClientId = clientId, ClientSecret = clientSecret }; - JToken responseJson = await _traduoraApi.GetToken(authBody); - - return responseJson["data"]["accessToken"].Value(); + return (await _traduoraApi.GetToken(authBody)).AccessToken; } public async Task>> @@ -33,15 +41,11 @@ public async Task>(); - JObject localesJson = await _traduoraApi.GetLocales(projectId, $"Bearer {authToken}"); + LocalesResponse localesJson = await _traduoraApi.GetLocales(projectId, $"Bearer {authToken}"); - foreach (JToken localeObject in localesJson["data"].Value()) + foreach (LocaleWrapper locale in localesJson.Data) { - string localeCode = localeObject["locale"]["code"].Value(); - - string culture = localeCode.Replace('_', '-'); - - output[culture] = await GetTranslations(projectId, localeCode, authToken, format); + output[locale.Locale.Culture] = await GetTranslations(projectId, locale.Locale.Code, authToken, format); } return new ReadOnlyDictionary>(output); @@ -51,20 +55,10 @@ private async Task> GetTranslations(string projectId, string locale, string authToken, string format = DefaultFormat) { - JObject exportedJson = await _traduoraApi.GetExportedData(projectId, format, locale, $"Bearer {authToken}"); - - return ToReadOnlyDictionary(exportedJson); - } - - private static IReadOnlyDictionary ToReadOnlyDictionary(JObject exportedJson) - { - var output = new Dictionary(); - foreach (JProperty jProperty in exportedJson.Properties()) - { - output[jProperty.Name] = jProperty.Value.Value(); - } + Dictionary exportedResult = + await _traduoraApi.GetExportedData(projectId, format, locale, $"Bearer {authToken}"); - return new ReadOnlyDictionary(output); + return new ReadOnlyDictionary(exportedResult); } } } \ No newline at end of file From b84a78e1f7c3c4c191a2b437d2b88d02e81c1c14 Mon Sep 17 00:00:00 2001 From: Vullnet Dyla Date: Fri, 5 Apr 2019 16:52:48 +0200 Subject: [PATCH 2/4] Created Traduora.Localizer to glue the provider with the localizer Localizer renamed to DynamicDictionaryLocalizer Moved a lot of stuff into Traduora.Localizer so the Web project is slim and simple --- CMP.Localiztion.Traduora.sln | 8 +- .../Cache/AsyncPollingCache.cs | 2 +- .../Cache/DictionaryPollingCache.cs | 5 +- .../Config/ConfigurationService.cs | 2 +- .../Config/TraduoraApiSettings.cs | 2 +- .../Config/TraduoraSecretSettings.cs | 2 +- .../ServiceExtensions.cs | 17 +-- Traduora.Localizer/Traduora.Localizer.csproj | 31 ++++++ .../TraduoraService.cs | 4 +- sample/Web/Logging/HttpLoggingHandler.cs | 105 ------------------ sample/Web/Startup.cs | 22 ++-- sample/Web/Web.csproj | 4 +- ...proj => DynamicDictionaryLocalizer.csproj} | 2 + ...cs => DynamicDictionaryStringLocalizer.cs} | 10 +- 14 files changed, 74 insertions(+), 142 deletions(-) rename {src/Core => Traduora.Localizer}/Cache/AsyncPollingCache.cs (95%) rename {src/Core => Traduora.Localizer}/Cache/DictionaryPollingCache.cs (89%) rename {sample/Web => Traduora.Localizer}/Config/ConfigurationService.cs (93%) rename {sample/Web => Traduora.Localizer}/Config/TraduoraApiSettings.cs (87%) rename {sample/Web => Traduora.Localizer}/Config/TraduoraSecretSettings.cs (83%) rename {sample/Web => Traduora.Localizer}/ServiceExtensions.cs (74%) create mode 100644 Traduora.Localizer/Traduora.Localizer.csproj rename {sample/Web => Traduora.Localizer}/TraduoraService.cs (92%) delete mode 100644 sample/Web/Logging/HttpLoggingHandler.cs rename src/Core/{CachedLocalizer.csproj => DynamicDictionaryLocalizer.csproj} (68%) rename src/Core/{CachedDictionaryStringLocalizer.cs => DynamicDictionaryStringLocalizer.cs} (83%) diff --git a/CMP.Localiztion.Traduora.sln b/CMP.Localiztion.Traduora.sln index b246d0f..0429f53 100644 --- a/CMP.Localiztion.Traduora.sln +++ b/CMP.Localiztion.Traduora.sln @@ -5,10 +5,12 @@ VisualStudioVersion = 15.0.28307.329 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "sample\Web\Web.csproj", "{17E01B10-44A9-4896-96C4-F6D948DEC5F9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CachedLocalizer", "src\Core\CachedLocalizer.csproj", "{79B4E158-DADA-4A56-A3BC-E0CCD012FA10}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicDictionaryLocalizer", "src\Core\DynamicDictionaryLocalizer.csproj", "{79B4E158-DADA-4A56-A3BC-E0CCD012FA10}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Traduora.Provider", "src\Traduora\Traduora.Provider.csproj", "{A007D934-6807-49F7-B31F-34B9C6AFC5C9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Traduora.Localizer", "Traduora.Localizer\Traduora.Localizer.csproj", "{CD719FDF-97E1-48E1-9600-A1FD58DBCD97}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {A007D934-6807-49F7-B31F-34B9C6AFC5C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {A007D934-6807-49F7-B31F-34B9C6AFC5C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {A007D934-6807-49F7-B31F-34B9C6AFC5C9}.Release|Any CPU.Build.0 = Release|Any CPU + {CD719FDF-97E1-48E1-9600-A1FD58DBCD97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD719FDF-97E1-48E1-9600-A1FD58DBCD97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD719FDF-97E1-48E1-9600-A1FD58DBCD97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD719FDF-97E1-48E1-9600-A1FD58DBCD97}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Core/Cache/AsyncPollingCache.cs b/Traduora.Localizer/Cache/AsyncPollingCache.cs similarity index 95% rename from src/Core/Cache/AsyncPollingCache.cs rename to Traduora.Localizer/Cache/AsyncPollingCache.cs index b679fde..d2c8431 100644 --- a/src/Core/Cache/AsyncPollingCache.cs +++ b/Traduora.Localizer/Cache/AsyncPollingCache.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace CachedLocalizer.Cache +namespace Traduora.Localizer.Cache { public class AsyncPollingCache { diff --git a/src/Core/Cache/DictionaryPollingCache.cs b/Traduora.Localizer/Cache/DictionaryPollingCache.cs similarity index 89% rename from src/Core/Cache/DictionaryPollingCache.cs rename to Traduora.Localizer/Cache/DictionaryPollingCache.cs index bcdeb48..30b9284 100644 --- a/src/Core/Cache/DictionaryPollingCache.cs +++ b/Traduora.Localizer/Cache/DictionaryPollingCache.cs @@ -2,14 +2,13 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace CachedLocalizer.Cache +namespace Traduora.Localizer.Cache { public class DictionaryPollingCache : AsyncPollingCache>> { public DictionaryPollingCache( Func>>> asyncDataProvider, TimeSpan refreshInterval) : base(asyncDataProvider, refreshInterval) - { - } + {} } } diff --git a/sample/Web/Config/ConfigurationService.cs b/Traduora.Localizer/Config/ConfigurationService.cs similarity index 93% rename from sample/Web/Config/ConfigurationService.cs rename to Traduora.Localizer/Config/ConfigurationService.cs index 4ab14ba..8f194b2 100644 --- a/sample/Web/Config/ConfigurationService.cs +++ b/Traduora.Localizer/Config/ConfigurationService.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -namespace Web.Config +namespace Traduora.Localizer.Config { public static class ConfigurationService { diff --git a/sample/Web/Config/TraduoraApiSettings.cs b/Traduora.Localizer/Config/TraduoraApiSettings.cs similarity index 87% rename from sample/Web/Config/TraduoraApiSettings.cs rename to Traduora.Localizer/Config/TraduoraApiSettings.cs index a87f86c..3571f4d 100644 --- a/sample/Web/Config/TraduoraApiSettings.cs +++ b/Traduora.Localizer/Config/TraduoraApiSettings.cs @@ -1,6 +1,6 @@ using System; -namespace Web.Config +namespace Traduora.Localizer.Config { public class TraduoraApiSettings { diff --git a/sample/Web/Config/TraduoraSecretSettings.cs b/Traduora.Localizer/Config/TraduoraSecretSettings.cs similarity index 83% rename from sample/Web/Config/TraduoraSecretSettings.cs rename to Traduora.Localizer/Config/TraduoraSecretSettings.cs index c46e3bf..b078aed 100644 --- a/sample/Web/Config/TraduoraSecretSettings.cs +++ b/Traduora.Localizer/Config/TraduoraSecretSettings.cs @@ -1,4 +1,4 @@ -namespace Web.Config +namespace Traduora.Localizer.Config { public class TraduoraSecretSettings { diff --git a/sample/Web/ServiceExtensions.cs b/Traduora.Localizer/ServiceExtensions.cs similarity index 74% rename from sample/Web/ServiceExtensions.cs rename to Traduora.Localizer/ServiceExtensions.cs index 1640a8b..8bfbca2 100644 --- a/sample/Web/ServiceExtensions.cs +++ b/Traduora.Localizer/ServiceExtensions.cs @@ -1,24 +1,27 @@ using System.Net.Http; -using CachedLocalizer; -using CachedLocalizer.Cache; +using DynamicDictionaryLocalizer; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; +using Traduora.Localizer.Cache; +using Traduora.Localizer.Config; using Traduora.Provider; -using Web.Config; -namespace Web +namespace Traduora.Localizer { public static class ServiceExtensions { private const string HttpClientName = "traduora"; - public static IServiceCollection AddTraduora(this IServiceCollection services) + public static IServiceCollection AddTraduora(this IServiceCollection services, IConfiguration config) { + services.AddTypedConfiguration(config); + services .AddHttpClient(HttpClientName) .ConfigureHttpClient((sp, client) => { - var traduoraApiSettings = sp.GetRequiredService>().Value; + TraduoraApiSettings traduoraApiSettings = sp.GetRequiredService>().Value; client.BaseAddress = traduoraApiSettings.BaseUrl; }); @@ -41,7 +44,7 @@ public static IServiceCollection AddTraduora(this IServiceCollection services) sp.GetRequiredService().GetTranslations, traduoraApiSettings.RefreshIntervalSeconds); - return new CachedDictionaryStringLocalizer(cache.GetData); + return new DynamicDictionaryStringLocalizer(cache.GetData); }); return services; diff --git a/Traduora.Localizer/Traduora.Localizer.csproj b/Traduora.Localizer/Traduora.Localizer.csproj new file mode 100644 index 0000000..33a35c0 --- /dev/null +++ b/Traduora.Localizer/Traduora.Localizer.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.configuration.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.dependencyinjection.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.http\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Http.dll + + + ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.options\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + + diff --git a/sample/Web/TraduoraService.cs b/Traduora.Localizer/TraduoraService.cs similarity index 92% rename from sample/Web/TraduoraService.cs rename to Traduora.Localizer/TraduoraService.cs index 5462d89..7fa0b8d 100644 --- a/sample/Web/TraduoraService.cs +++ b/Traduora.Localizer/TraduoraService.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Options; +using Traduora.Localizer.Config; using Traduora.Provider; -using Web.Config; -namespace Web +namespace Traduora.Localizer { public class TraduoraService { diff --git a/sample/Web/Logging/HttpLoggingHandler.cs b/sample/Web/Logging/HttpLoggingHandler.cs deleted file mode 100644 index ca1bb2e..0000000 --- a/sample/Web/Logging/HttpLoggingHandler.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; - -namespace Web.Logging -{ - /// - /// To be used for debugging API calls - /// - public class HttpLoggingHandler : DelegatingHandler - { - public HttpLoggingHandler(HttpMessageHandler innerHandler = null) - : base(innerHandler ?? new HttpClientHandler()) - { - } - - protected override async Task SendAsync(HttpRequestMessage request, - CancellationToken cancellationToken) - { - var req = request; - var id = Guid.NewGuid().ToString(); - var msg = $"[{id} - Request]"; - - Debug.WriteLine($"{msg}========Start=========="); - Debug.WriteLine($"{msg} {req.Method} {req.RequestUri.PathAndQuery} {req.RequestUri.Scheme}/{req.Version}"); - Debug.WriteLine($"{msg} Full Endpoint: {req.RequestUri.AbsoluteUri}"); - - foreach (var header in req.Headers) - Debug.WriteLine($"{msg} {header.Key}: {string.Join(", ", header.Value)}"); - - if (req.Content != null) - { - foreach (var header in req.Content.Headers) - Debug.WriteLine($"{msg} {header.Key}: {string.Join(", ", header.Value)}"); - - if (req.Content is StringContent || this.IsTextBasedContentType(req.Headers) || - this.IsTextBasedContentType(req.Content.Headers)) - { - var result = await req.Content.ReadAsStringAsync(); - - Debug.WriteLine($"{msg} Content:"); - Debug.WriteLine($"{msg} {string.Join("", result)}"); - } - } - - var start = DateTime.Now; - - var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - - var end = DateTime.Now; - - Debug.WriteLine($"{msg} Duration: {end - start}"); - Debug.WriteLine($"{msg}==========End=========="); - - msg = $"[{id} - Response]"; - Debug.WriteLine($"{msg}=========Start========="); - - var resp = response; - - Debug.WriteLine( - $"{msg} {req.RequestUri.Scheme.ToUpper()}/{resp.Version} {(int) resp.StatusCode} {resp.ReasonPhrase}"); - - foreach (var header in resp.Headers) - Debug.WriteLine($"{msg} {header.Key}: {string.Join(", ", header.Value)}"); - - if (resp.Content != null) - { - foreach (var header in resp.Content.Headers) - Debug.WriteLine($"{msg} {header.Key}: {string.Join(", ", header.Value)}"); - - if (resp.Content is StringContent || this.IsTextBasedContentType(resp.Headers) || - this.IsTextBasedContentType(resp.Content.Headers)) - { - start = DateTime.Now; - var result = await resp.Content.ReadAsStringAsync(); - end = DateTime.Now; - - Debug.WriteLine($"{msg} Content:"); - Debug.WriteLine($"{msg} {string.Join("", result.Cast().Take(255))}..."); - Debug.WriteLine($"{msg} Duration: {end - start}"); - } - } - - Debug.WriteLine($"{msg}==========End=========="); - return response; - } - - readonly string[] types = new[] {"html", "text", "xml", "json", "txt", "x-www-form-urlencoded"}; - - bool IsTextBasedContentType(HttpHeaders headers) - { - IEnumerable values; - if (!headers.TryGetValues("Content-Type", out values)) - return false; - var header = string.Join(" ", values).ToLowerInvariant(); - - return types.Any(t => header.Contains(t)); - } - } -} \ No newline at end of file diff --git a/sample/Web/Startup.cs b/sample/Web/Startup.cs index bb9db92..199ada8 100644 --- a/sample/Web/Startup.cs +++ b/sample/Web/Startup.cs @@ -1,13 +1,11 @@ -using System; -using System.Globalization; +using System.Globalization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Web.Config; +using Traduora.Localizer; +using Traduora.Localizer.Config; namespace Web { @@ -24,14 +22,19 @@ public Startup(IConfiguration config) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.Configure(_config.GetSection("blla")); services.AddMvc(); - services.AddTypedConfiguration(_config); - services.AddTraduora(); + services.AddTraduora(_config); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + var supportedCultures = new[] { new CultureInfo("en"), @@ -40,11 +43,6 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) new CultureInfo("sv-SE"), }; - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - app.UseRequestLocalization(new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture(supportedCultures[0]), diff --git a/sample/Web/Web.csproj b/sample/Web/Web.csproj index 0ae5931..2e70113 100644 --- a/sample/Web/Web.csproj +++ b/sample/Web/Web.csproj @@ -10,13 +10,11 @@ - - - + diff --git a/src/Core/CachedLocalizer.csproj b/src/Core/DynamicDictionaryLocalizer.csproj similarity index 68% rename from src/Core/CachedLocalizer.csproj rename to src/Core/DynamicDictionaryLocalizer.csproj index 01046db..e69f788 100644 --- a/src/Core/CachedLocalizer.csproj +++ b/src/Core/DynamicDictionaryLocalizer.csproj @@ -2,6 +2,8 @@ netstandard2.0 + DynamicDictionaryLocalizer + DynamicDictionaryLocalizer diff --git a/src/Core/CachedDictionaryStringLocalizer.cs b/src/Core/DynamicDictionaryStringLocalizer.cs similarity index 83% rename from src/Core/CachedDictionaryStringLocalizer.cs rename to src/Core/DynamicDictionaryStringLocalizer.cs index 284478a..47fb950 100644 --- a/src/Core/CachedDictionaryStringLocalizer.cs +++ b/src/Core/DynamicDictionaryStringLocalizer.cs @@ -4,21 +4,21 @@ using System.Linq; using Microsoft.Extensions.Localization; -namespace CachedLocalizer +namespace DynamicDictionaryLocalizer { - public class CachedDictionaryStringLocalizer : IStringLocalizer + public class DynamicDictionaryStringLocalizer : IStringLocalizer { private readonly Func>> _modelProvider; private readonly CultureInfo _culture; private CultureInfo Culture => _culture ?? CultureInfo.CurrentUICulture; - public CachedDictionaryStringLocalizer(Func>> modelProvider) + public DynamicDictionaryStringLocalizer(Func>> modelProvider) { _modelProvider = modelProvider; } - public CachedDictionaryStringLocalizer( + public DynamicDictionaryStringLocalizer( CultureInfo culture, Func>> modelProvider) : this(modelProvider) { @@ -64,7 +64,7 @@ public IEnumerable GetAllStrings(bool includeParentCultures) public IStringLocalizer WithCulture(CultureInfo culture) { - return new CachedDictionaryStringLocalizer(culture, _modelProvider); + return new DynamicDictionaryStringLocalizer(culture, _modelProvider); } } } \ No newline at end of file From a024d3d7eab07e16f068c2fddf42a4f726619b0a Mon Sep 17 00:00:00 2001 From: Vullnet Dyla Date: Fri, 5 Apr 2019 17:02:06 +0200 Subject: [PATCH 3/4] Implemented exception handling for traduora call failure --- Traduora.Localizer/TraduoraService.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Traduora.Localizer/TraduoraService.cs b/Traduora.Localizer/TraduoraService.cs index 7fa0b8d..407b30c 100644 --- a/Traduora.Localizer/TraduoraService.cs +++ b/Traduora.Localizer/TraduoraService.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Traduora.Localizer.Config; @@ -19,9 +21,21 @@ public TraduoraService(IOptions config, TraduoraProvider public async Task>> GetTranslations() { - string key = await _traduora.Authenticate(_config.ClientId, _config.ClientSecret); + try + { + string key = await _traduora.Authenticate(_config.ClientId, _config.ClientSecret); - return await _traduora.GetData(_config.ProjectId, key); + return await _traduora.GetData(_config.ProjectId, key); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + + var emptyDictionary = new ReadOnlyDictionary> + (new Dictionary>()); + + return emptyDictionary; + } } } } From 5f189ba7bd08d6e08b91c001f2d158a91be3206b Mon Sep 17 00:00:00 2001 From: Vullnet Dyla Date: Mon, 8 Apr 2019 16:41:09 +0200 Subject: [PATCH 4/4] Used BackgroundService : IHostedService for async traduora refresh --- Traduora.Localizer/Cache/AsyncPollingCache.cs | 33 ----------------- .../Cache/DictionaryPollingCache.cs | 14 -------- .../Cache/TranslationPollingService.cs | 35 +++++++++++++++++++ Traduora.Localizer/ServiceExtensions.cs | 11 +++--- Traduora.Localizer/Traduora.Localizer.csproj | 3 ++ sample/Web/Startup.cs | 2 -- 6 files changed, 45 insertions(+), 53 deletions(-) delete mode 100644 Traduora.Localizer/Cache/AsyncPollingCache.cs delete mode 100644 Traduora.Localizer/Cache/DictionaryPollingCache.cs create mode 100644 Traduora.Localizer/Cache/TranslationPollingService.cs diff --git a/Traduora.Localizer/Cache/AsyncPollingCache.cs b/Traduora.Localizer/Cache/AsyncPollingCache.cs deleted file mode 100644 index d2c8431..0000000 --- a/Traduora.Localizer/Cache/AsyncPollingCache.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Traduora.Localizer.Cache -{ - public class AsyncPollingCache - { - private readonly Func> _asyncDataProvider; - private readonly Timer _refreshTimer; - private T _data; - - public AsyncPollingCache(Func> asyncDataProvider, TimeSpan refreshInterval) - { - _asyncDataProvider = asyncDataProvider; - - _refreshTimer = new Timer(RefreshData, null, refreshInterval, refreshInterval); - } - - public T GetData() - { - if (_data != null) return _data; - - _data = _asyncDataProvider().Result; - return _data; - } - - private async void RefreshData(object _) - { - _data = await _asyncDataProvider(); - } - } -} diff --git a/Traduora.Localizer/Cache/DictionaryPollingCache.cs b/Traduora.Localizer/Cache/DictionaryPollingCache.cs deleted file mode 100644 index 30b9284..0000000 --- a/Traduora.Localizer/Cache/DictionaryPollingCache.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Traduora.Localizer.Cache -{ - public class DictionaryPollingCache : AsyncPollingCache>> - { - public DictionaryPollingCache( - Func>>> asyncDataProvider, TimeSpan refreshInterval) : - base(asyncDataProvider, refreshInterval) - {} - } -} diff --git a/Traduora.Localizer/Cache/TranslationPollingService.cs b/Traduora.Localizer/Cache/TranslationPollingService.cs new file mode 100644 index 0000000..c0b9752 --- /dev/null +++ b/Traduora.Localizer/Cache/TranslationPollingService.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace Traduora.Localizer.Cache +{ + public class TranslationPollingService : BackgroundService + { + private readonly Func>>> _asyncDataProvider; + private readonly TimeSpan _refreshInterval; + + public TranslationPollingService( + Func>>> asyncDataProvider, + TimeSpan refreshInterval) + { + _asyncDataProvider = asyncDataProvider; + _refreshInterval = refreshInterval; + + Data = _asyncDataProvider().Result; + } + + public IReadOnlyDictionary> Data { get; set; } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + Data = await _asyncDataProvider(); + await Task.Delay(_refreshInterval, stoppingToken); + } + } + } +} diff --git a/Traduora.Localizer/ServiceExtensions.cs b/Traduora.Localizer/ServiceExtensions.cs index 8bfbca2..de5cfed 100644 --- a/Traduora.Localizer/ServiceExtensions.cs +++ b/Traduora.Localizer/ServiceExtensions.cs @@ -2,6 +2,7 @@ using DynamicDictionaryLocalizer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Traduora.Localizer.Cache; @@ -35,17 +36,19 @@ public static IServiceCollection AddTraduora(this IServiceCollection services, I services.AddTransient(); - services.AddSingleton(sp => + services.AddSingleton(sp => { TraduoraApiSettings traduoraApiSettings = sp.GetRequiredService>().Value; - var cache = new DictionaryPollingCache( + return new TranslationPollingService( sp.GetRequiredService().GetTranslations, traduoraApiSettings.RefreshIntervalSeconds); - - return new DynamicDictionaryStringLocalizer(cache.GetData); }); + services.AddSingleton(sp => sp.GetRequiredService()); + + services.AddSingleton(sp => + new DynamicDictionaryStringLocalizer(() => sp.GetRequiredService().Data)); return services; } diff --git a/Traduora.Localizer/Traduora.Localizer.csproj b/Traduora.Localizer/Traduora.Localizer.csproj index 33a35c0..645d88d 100644 --- a/Traduora.Localizer/Traduora.Localizer.csproj +++ b/Traduora.Localizer/Traduora.Localizer.csproj @@ -20,6 +20,9 @@ ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.dependencyinjection.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.hosting.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll + ..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.http\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Http.dll diff --git a/sample/Web/Startup.cs b/sample/Web/Startup.cs index 199ada8..0dc6503 100644 --- a/sample/Web/Startup.cs +++ b/sample/Web/Startup.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Traduora.Localizer; -using Traduora.Localizer.Config; namespace Web { @@ -22,7 +21,6 @@ public Startup(IConfiguration config) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.Configure(_config.GetSection("blla")); services.AddMvc(); services.AddTraduora(_config); }