| File: RequestLocalizationMiddleware.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\Localization\src\Microsoft.AspNetCore.Localization.csproj (Microsoft.AspNetCore.Localization) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Localization; /// <summary> /// Enables automatic setting of the culture for <see cref="HttpRequest"/>s based on information /// sent by the client in headers and logic provided by the application. /// </summary> public class RequestLocalizationMiddleware { private const int MaxCultureFallbackDepth = 5; private readonly RequestDelegate _next; private readonly RequestLocalizationOptions _options; private readonly ILogger _logger; /// <summary> /// Creates a new <see cref="RequestLocalizationMiddleware"/>. /// </summary> /// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param> /// <param name="options">The <see cref="RequestLocalizationOptions"/> representing the options for the /// <see cref="RequestLocalizationMiddleware"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/> used for logging.</param> public RequestLocalizationMiddleware(RequestDelegate next, IOptions<RequestLocalizationOptions> options, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(options); _next = next ?? throw new ArgumentNullException(nameof(next)); _logger = loggerFactory?.CreateLogger<RequestLocalizationMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory)); _options = options.Value; } /// <summary> /// Invokes the logic of the middleware. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns> public async Task Invoke(HttpContext context) { ArgumentNullException.ThrowIfNull(context); var requestCulture = _options.DefaultRequestCulture; IRequestCultureProvider? winningProvider = null; if (_options.RequestCultureProviders != null) { foreach (var provider in _options.RequestCultureProviders) { var providerResultCulture = await provider.DetermineProviderCultureResult(context); if (providerResultCulture == null) { continue; } var cultures = providerResultCulture.Cultures; var uiCultures = providerResultCulture.UICultures; CultureInfo? cultureInfo = null; CultureInfo? uiCultureInfo = null; if (_options.SupportedCultures != null) { cultureInfo = GetCultureInfo( cultures, _options.SupportedCultures, _options.FallBackToParentCultures); if (cultureInfo == null) { _logger.UnsupportedCultures(provider.GetType().Name, cultures); } } if (_options.SupportedUICultures != null) { uiCultureInfo = GetCultureInfo( uiCultures, _options.SupportedUICultures, _options.FallBackToParentUICultures); if (uiCultureInfo == null) { _logger.UnsupportedUICultures(provider.GetType().Name, uiCultures); } } if (cultureInfo == null && uiCultureInfo == null) { continue; } cultureInfo ??= _options.DefaultRequestCulture.Culture; uiCultureInfo ??= _options.DefaultRequestCulture.UICulture; var result = new RequestCulture(cultureInfo, uiCultureInfo); requestCulture = result; winningProvider = provider; break; } } context.Features.Set<IRequestCultureFeature>(new RequestCultureFeature(requestCulture, winningProvider)); SetCurrentThreadCulture(requestCulture); if (_options.ApplyCurrentCultureToResponseHeaders) { var headers = context.Response.Headers; headers.ContentLanguage = requestCulture.UICulture.Name; } await _next(context); } private static void SetCurrentThreadCulture(RequestCulture requestCulture) { CultureInfo.CurrentCulture = requestCulture.Culture; CultureInfo.CurrentUICulture = requestCulture.UICulture; } private static CultureInfo? GetCultureInfo( IList<StringSegment> cultureNames, IList<CultureInfo> supportedCultures, bool fallbackToParentCultures) { foreach (var cultureName in cultureNames) { // Allow empty string values as they map to InvariantCulture, whereas null culture values will throw in // the CultureInfo ctor if (cultureName != null) { var cultureInfo = GetCultureInfo(cultureName, supportedCultures, fallbackToParentCultures, currentDepth: 0); if (cultureInfo != null) { return cultureInfo; } } } return null; } private static CultureInfo? GetCultureInfo( StringSegment cultureName, IList<CultureInfo>? supportedCultures, bool fallbackToParentCultures, int currentDepth) { // If the cultureName is an empty string there // is no chance we can resolve the culture info. if (cultureName.Equals(string.Empty)) { return null; } var culture = GetCultureInfo(cultureName, supportedCultures); if (culture == null && fallbackToParentCultures && currentDepth < MaxCultureFallbackDepth) { try { culture = CultureInfo.GetCultureInfo(cultureName.ToString()); culture = GetCultureInfo(culture.Parent.Name, supportedCultures, fallbackToParentCultures, currentDepth + 1); } catch (CultureNotFoundException) { } } return culture; } private static CultureInfo? GetCultureInfo(StringSegment name, IList<CultureInfo>? supportedCultures) { // Allow only known culture names as this API is called with input from users (HTTP requests) and // creating CultureInfo objects is expensive and we don't want it to throw either. if (name == null || supportedCultures == null) { return null; } var culture = supportedCultures.FirstOrDefault( supportedCulture => StringSegment.Equals(supportedCulture.Name, name, StringComparison.OrdinalIgnoreCase)); if (culture == null) { return null; } return CultureInfo.ReadOnly(culture); } }