Ensures the culture is set for requests for both front-end and back office

This commit is contained in:
Shannon
2021-01-08 11:29:07 +11:00
parent b4922d2685
commit c15b416e88
10 changed files with 94 additions and 24 deletions

View File

@@ -123,10 +123,6 @@ namespace Umbraco.Web.Routing
private void SetVariationContext(CultureInfo culture)
{
// set the culture on the thread - once, so it's set when running document lookups
// TODO: Set this on HttpContext!
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
VariationContext variationContext = _variationContextAccessor.VariationContext;
if (variationContext != null && variationContext.Culture == culture?.Name)
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using Microsoft.Extensions.Logging;
using Moq;
@@ -35,8 +35,6 @@ namespace Umbraco.Tests.Routing
null, // FIXME: PublishedRouter complexities...
Mock.Of<IUmbracoContextFactory>(),
new RoutableDocumentFilter(globalSettings, IOHelper),
UriUtility,
AppCaches.RequestCache,
globalSettings,
HostingEnvironment
);

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Common.ActionsResults;
@@ -70,12 +71,13 @@ namespace Umbraco.Web.Common.Controllers
return _umbracoRouteValues;
}
if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def))
_umbracoRouteValues = HttpContext.GetRouteValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken) as UmbracoRouteValues;
if (_umbracoRouteValues == null)
{
throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}");
}
_umbracoRouteValues = (UmbracoRouteValues)def;
return _umbracoRouteValues;
}
}
@@ -126,10 +128,10 @@ namespace Umbraco.Web.Common.Controllers
IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest;
_logger.LogDebug(
"Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}",
pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none",
pcr.Is404() ? "true" : "false",
pcr.ResponseStatusCode);
"Response status: Content={Content}, StatusCode={ResponseStatusCode}, Culture={Culture}",
pcr.PublishedContent?.Id ?? -1,
pcr.ResponseStatusCode,
pcr.Culture);
UmbracoRouteResult routeStatus = pcr.GetRouteResult();
switch (routeStatus)

View File

@@ -48,9 +48,11 @@ using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.DependencyInjection;
using Umbraco.Web.Common.Install;
using Umbraco.Web.Common.Lifetime;
using Umbraco.Web.Common.Localization;
using Umbraco.Web.Common.Macros;
using Umbraco.Web.Common.Middleware;
using Umbraco.Web.Common.ModelBinders;
using Umbraco.Web.Common.Mvc;
using Umbraco.Web.Common.Profiler;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Common.Security;
@@ -226,6 +228,7 @@ namespace Umbraco.Web.Common.DependencyInjection
});
builder.Services.ConfigureOptions<UmbracoMvcConfigureOptions>();
builder.Services.ConfigureOptions<UmbracoRequestLocalizationOptions>();
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, UmbracoApiBehaviorApplicationModelProvider>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, BackOfficeApplicationModelProvider>());
builder.Services.AddUmbracoImageSharp(builder.Config);

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Serilog.Context;
@@ -57,9 +58,10 @@ namespace Umbraco.Extensions
// where we need to have UseAuthentication and UseAuthorization proceeding this call but before
// endpoints are defined.
app.UseRouting();
app.UseRequestLocalization();
app.UseAuthentication();
app.UseAuthorization();
// This must come after auth because the culture is based on the auth'd user
app.UseRequestLocalization();
// Must be called after UseRouting and before UseEndpoints
app.UseSession();

View File

@@ -1,22 +1,28 @@
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Umbraco.Core.Security;
namespace Umbraco.Web.Common.Extensions
namespace Umbraco.Web.Common.Localization
{
/// <summary>
/// Sets the request culture to the culture of the back office user if one is determined to be in the request
/// </summary>
public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider
{
/// <inheritdoc/>
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var culture = httpContext.User.Identity.GetCulture();
CultureInfo culture = httpContext.User.Identity.GetCulture();
if (culture is null)
{
return NullProviderCultureResult;
}
return Task.FromResult(new ProviderCultureResult(culture.Name, culture.Name));
return Task.FromResult(new ProviderCultureResult(culture.Name));
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Routing;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Routing;
namespace Umbraco.Web.Common.Localization
{
/// <summary>
/// Sets the request culture to the culture of the <see cref="IPublishedRequest"/> if one is found in the request
/// </summary>
public class UmbracoPublishedContentCultureProvider : RequestCultureProvider
{
/// <inheritdoc/>
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext.GetRouteValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken) is UmbracoRouteValues routeValues)
{
CultureInfo culture = routeValues.PublishedRequest?.Culture;
if (culture != null)
{
return Task.FromResult(new ProviderCultureResult(culture.Name));
}
}
return NullProviderCultureResult;
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
namespace Umbraco.Web.Common.Localization
{
/// <summary>
/// Custom Umbraco options configuration for <see cref="RequestLocalizationOptions"/>
/// </summary>
public class UmbracoRequestLocalizationOptions : IConfigureOptions<RequestLocalizationOptions>
{
private readonly IOptions<GlobalSettings> _globalSettings;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRequestLocalizationOptions"/> class.
/// </summary>
public UmbracoRequestLocalizationOptions(IOptions<GlobalSettings> globalSettings) => _globalSettings = globalSettings;
/// <inheritdoc/>
public void Configure(RequestLocalizationOptions options)
{
// set the default culture to what is in config
options.DefaultRequestCulture = new RequestCulture(_globalSettings.Value.DefaultUILanguage);
// add a custom provider
if (options.RequestCultureProviders == null)
{
options.RequestCultureProviders = new List<IRequestCultureProvider>();
}
options.RequestCultureProviders.Insert(0, new UmbracoBackOfficeIdentityCultureProvider());
options.RequestCultureProviders.Insert(1, new UmbracoPublishedContentCultureProvider());
}
}
}

View File

@@ -3,8 +3,9 @@ using Microsoft.Extensions.Options;
using Umbraco.Web.Common.Filters;
using Umbraco.Web.Common.ModelBinders;
namespace Umbraco.Web.Common.AspNetCore
namespace Umbraco.Web.Common.Mvc
{
/// <summary>
/// Options for globally configuring MVC for Umbraco
/// </summary>

View File

@@ -37,10 +37,8 @@ namespace Umbraco.Web
private readonly IPublishedRouter _publishedRouter;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly RoutableDocumentFilter _routableDocumentLookup;
private readonly IRequestCache _requestCache;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly UriUtility _uriUtility;
public UmbracoInjectedModule(
IRuntimeState runtime,
@@ -48,8 +46,6 @@ namespace Umbraco.Web
IPublishedRouter publishedRouter,
IUmbracoContextFactory umbracoContextFactory,
RoutableDocumentFilter routableDocumentLookup,
UriUtility uriUtility,
IRequestCache requestCache,
GlobalSettings globalSettings,
IHostingEnvironment hostingEnvironment)
{
@@ -58,8 +54,6 @@ namespace Umbraco.Web
_publishedRouter = publishedRouter;
_umbracoContextFactory = umbracoContextFactory;
_routableDocumentLookup = routableDocumentLookup;
_uriUtility = uriUtility;
_requestCache = requestCache;
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnvironment;
}