[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add culture info manager #2067

Merged
merged 8 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/Microsoft.Azure.SignalR.Common/DefaultCultureFeatureManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

# if NETSTANDARD2_0 || NET6_0_OR_GREATER
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.AspNetCore.Localization;

namespace Microsoft.Azure.SignalR;

internal class DefaultCultureFeatureManager : ICultureFeatureManager
{
private readonly long _cacheTimeoutTicks;

private readonly ConcurrentDictionary<string, RequestCultureFeatureWithTimestamp> _cultures = new ConcurrentDictionary<string, RequestCultureFeatureWithTimestamp>();

public DefaultCultureFeatureManager(long cacheTimeoutInSecond = 30)
{
_cacheTimeoutTicks = cacheTimeoutInSecond * Stopwatch.Frequency;
}

public bool TryAddCultureFeature(string requestId, IRequestCultureFeature feature)
{
return _cultures.TryAdd(requestId, new RequestCultureFeatureWithTimestamp(feature, Stopwatch.GetTimestamp()));
}

public bool TryRemoveCultureFeature(string requestId, out IRequestCultureFeature feature)
{
if (_cultures.TryRemove(requestId, out var featureWithTimeout))
{
feature = featureWithTimeout.Feature;
return true;
}
feature = null;
return false;
}

public void Cleanup()
{
foreach (var item in _cultures)
{
if (_cultures.TryGetValue(item.Key, out var cultureWithTimestamp))
{
if (Stopwatch.GetTimestamp() - cultureWithTimestamp.Timestamp > _cacheTimeoutTicks)
{
_cultures.TryRemove(item.Key, out _);
}
}
}
}

private record RequestCultureFeatureWithTimestamp(IRequestCultureFeature Feature, long Timestamp)
{
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

// IRequestCultureFeature is unavailable in net462. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.localization.irequestculturefeature#applies-to
#if NETSTANDARD2_0 || NET6_0_OR_GREATER
using Microsoft.AspNetCore.Localization;

namespace Microsoft.Azure.SignalR;

internal interface ICultureFeatureManager
{
bool TryAddCultureFeature(string clientRequestId, IRequestCultureFeature cultureFeature);

bool TryRemoveCultureFeature(string clientRequestId, out IRequestCultureFeature feature);

public void Cleanup();
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="$(MicrosoftAspNetCoreSignalRPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="$(MicrosoftAspNetCoreLocalizationPackageVersion)" />
</ItemGroup>

<!-- New packages should be added to Directory.Build.props! -->
Expand Down
42 changes: 41 additions & 1 deletion test/Microsoft.Azure.SignalR.Tests/ServiceContextFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using System.Net;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.Azure.SignalR.Protocol;
using Microsoft.Extensions.Primitives;
using Xunit;
Expand Down Expand Up @@ -229,11 +231,49 @@ public void ServiceConnectionContextCultureTest(string cultureQuery, bool isCult
var originalUiCulture = CultureInfo.CurrentUICulture.Name;

_ = new ClientConnectionContext(new OpenConnectionMessage("1", new Claim[0], EmptyHeaders, queryString));

var expectedCulture = isCultureValid ? parsedCulture : originalCulture;
var expectedUiCulture = isUiCultureValid ? parsedUiCulture : originalUiCulture;

Assert.Equal(expectedCulture, CultureInfo.CurrentCulture.Name);
Assert.Equal(expectedUiCulture, CultureInfo.CurrentUICulture.Name);
}

public static IEnumerable<object[]> TestCultures => new object[][]
{
new object[]
{
new CultureInfo("zh-CN"), new CultureInfo("en-US")
},
new object[]
{
new CultureInfo("zh-CN") { DateTimeFormat = { ShortDatePattern = "MM#dd#yyyy" } },
new CultureInfo("fr-CA") { DateTimeFormat = { ShortDatePattern = "yyyy|MM|dd" } }
}
};

[Theory]
[MemberData(nameof(TestCultures))]
public async Task ServiceConnectionContextCultureManagerTest(CultureInfo expectCulture, CultureInfo expectUICulture)
{
var (originalCulture, originalUICulture) = (CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture);

var timeoutSeconds = 1;
var cultureManager = new DefaultCultureFeatureManager(timeoutSeconds);

var requestIdProvider = new DefaultConnectionRequestIdProvider();
var requestId1 = "1";
var requestId2 = "2";
var expectFeature = new RequestCultureFeature(new RequestCulture(expectCulture, expectUICulture), null);
cultureManager.TryAddCultureFeature(requestId1, expectFeature);
cultureManager.TryAddCultureFeature(requestId2, expectFeature);
cultureManager.Cleanup(); // should take no effect

Assert.True(cultureManager.TryRemoveCultureFeature(requestId1, out var actualFeature));
Assert.Equal(expectFeature, actualFeature);

await Task.Delay(timeoutSeconds * 1000 + 100);
cultureManager.Cleanup();
Assert.False(cultureManager.TryRemoveCultureFeature(requestId2, out var _));
}
}
Loading