diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
index d0b4416eed..edc11bcac2 100644
--- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs
+++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
@@ -16,12 +16,22 @@ namespace Umbraco.Core.Security
///
///
public static void EnsureCulture(this IIdentity identity)
+ {
+ var culture = GetCulture(identity);
+ if (!(culture is null))
+ {
+ Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
+ }
+ }
+
+ public static CultureInfo GetCulture(this IIdentity identity)
{
if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated)
{
- Thread.CurrentThread.CurrentUICulture =
- Thread.CurrentThread.CurrentCulture = UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
+ return UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
}
+
+ return null;
}
diff --git a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs
new file mode 100644
index 0000000000..d7c9833c6f
--- /dev/null
+++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs
@@ -0,0 +1,58 @@
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Web.Common.Attributes;
+
+namespace Umbraco.Web.Common.ApplicationModels
+{
+ ///
+ /// An application model provider for all Umbraco Back Office controllers
+ ///
+ public class BackOfficeApplicationModelProvider : IApplicationModelProvider
+ {
+ public BackOfficeApplicationModelProvider(IModelMetadataProvider modelMetadataProvider)
+ {
+ ActionModelConventions = new List()
+ {
+ new BackOfficeIdentityCultureConvention()
+ };
+ }
+
+ ///
+ /// Will execute after
+ ///
+ public int Order => 0;
+
+ public List ActionModelConventions { get; }
+
+ public void OnProvidersExecuted(ApplicationModelProviderContext context)
+ {
+ }
+
+ public void OnProvidersExecuting(ApplicationModelProviderContext context)
+ {
+ foreach (var controller in context.Result.Controllers)
+ {
+ if (!IsBackOfficeController(controller))
+ continue;
+
+ foreach (var action in controller.Actions)
+ {
+ foreach (var convention in ActionModelConventions)
+ {
+ convention.Apply(action);
+ }
+ }
+
+ }
+ }
+
+ private bool IsBackOfficeController(ControllerModel controller)
+ {
+ var pluginControllerAttribute = controller.Attributes.OfType().FirstOrDefault();
+ return pluginControllerAttribute != null
+ && pluginControllerAttribute.AreaName == Core.Constants.Web.Mvc.BackOfficeArea;
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs
new file mode 100644
index 0000000000..d3e2096dd3
--- /dev/null
+++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Umbraco.Web.Common.Filters;
+
+namespace Umbraco.Web.Common.ApplicationModels
+{
+ public class BackOfficeIdentityCultureConvention : IActionModelConvention
+ {
+ public void Apply(ActionModel action)
+ {
+ action.Filters.Add(new BackOfficeCultureFilter());
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
index e76ae1ff6b..918bc3776f 100644
--- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
+++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs
@@ -10,7 +10,7 @@ namespace Umbraco.Web.Common.ApplicationModels
{
///
- /// A custom application model provider for Umbraco controllers
+ /// An application model provider for Umbraco API controllers to behave like WebApi controllers
///
///
///
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoBackOfficeIdentityCultureProvider.cs b/src/Umbraco.Web.Common/Extensions/UmbracoBackOfficeIdentityCultureProvider.cs
new file mode 100644
index 0000000000..a5af18fbda
--- /dev/null
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoBackOfficeIdentityCultureProvider.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Localization;
+using Umbraco.Core.Security;
+
+namespace Umbraco.Web.Common.Extensions
+{
+ public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider
+ {
+ public override Task DetermineProviderCultureResult(HttpContext httpContext)
+ {
+ var culture = httpContext.User.Identity.GetCulture();
+
+ if (culture is null)
+ {
+ return NullProviderCultureResult;
+ }
+
+ return Task.FromResult(new ProviderCultureResult(culture.Name, culture.Name));
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
index 358378ca35..91cee9672a 100644
--- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
@@ -1,10 +1,12 @@
using System;
-using System.Collections;
using System.Data.Common;
using System.Data.SqlClient;
+using System.Globalization;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
@@ -13,7 +15,6 @@ using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Extensions.Hosting;
using Serilog.Extensions.Logging;
-using Umbraco.Composing;
using Umbraco.Configuration;
using Umbraco.Core;
using Umbraco.Core.Cache;
@@ -26,6 +27,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Runtime;
using Umbraco.Web.Common.AspNetCore;
+using Umbraco.Web.Common.Extensions;
using Umbraco.Web.Common.Profiler;
namespace Umbraco.Extensions
@@ -154,7 +156,7 @@ namespace Umbraco.Extensions
out factory);
return services;
- }
+ }
///
/// Adds the Umbraco Back Core requirements
@@ -182,6 +184,7 @@ namespace Umbraco.Extensions
if (container is null) throw new ArgumentNullException(nameof(container));
if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly));
+ // Add supported databases
services.AddUmbracoSqlCeSupport();
services.AddUmbracoSqlServerSupport();
@@ -228,7 +231,7 @@ namespace Umbraco.Extensions
factory = coreRuntime.Configure(container);
return services;
- }
+ }
private static ITypeFinder CreateTypeFinder(Core.Logging.ILogger logger, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, ITypeFinderSettings typeFinderSettings)
{
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs
index a3e9b901dc..a795edf6cf 100644
--- a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs
@@ -36,6 +36,7 @@ namespace Umbraco.Extensions
{
services.ConfigureOptions();
services.TryAddEnumerable(ServiceDescriptor.Transient());
+ services.TryAddEnumerable(ServiceDescriptor.Transient());
// TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured
var serviceProvider = services.BuildServiceProvider();
diff --git a/src/Umbraco.Web.Common/Filters/BackOfficeCultureFilter.cs b/src/Umbraco.Web.Common/Filters/BackOfficeCultureFilter.cs
new file mode 100644
index 0000000000..99109fe230
--- /dev/null
+++ b/src/Umbraco.Web.Common/Filters/BackOfficeCultureFilter.cs
@@ -0,0 +1,34 @@
+using System;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Umbraco.Core.Security;
+using System.Globalization;
+
+namespace Umbraco.Web.Common.Filters
+{
+ ///
+ /// Applied to all Umbraco controllers to ensure the thread culture is set to the culture assigned to the back office identity
+ ///
+ public class BackOfficeCultureFilter : IActionFilter
+ {
+ public void OnActionExecuted(ActionExecutedContext context)
+ {
+ }
+
+ public void OnActionExecuting(ActionExecutingContext context)
+ {
+ var culture = context.HttpContext.User.Identity.GetCulture();
+ if (culture != null)
+ {
+ SetCurrentThreadCulture(culture);
+ }
+ }
+
+ private static void SetCurrentThreadCulture(CultureInfo culture)
+ {
+ CultureInfo.CurrentCulture = culture;
+ CultureInfo.CurrentUICulture = culture;
+ }
+ }
+
+
+}
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index cd4fa3eaac..7ccbdd6ab4 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -6,7 +6,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Umbraco.Extensions;
-using Umbraco.Web.Common.Middleware;
namespace Umbraco.Web.UI.BackOffice
{
@@ -80,6 +79,7 @@ namespace Umbraco.Web.UI.BackOffice
app.UseUmbracoCore();
app.UseUmbracoRouting();
+ app.UseRequestLocalization();
app.UseUmbracoRequestLogging();
app.UseUmbracoWebsite();
app.UseUmbracoBackOffice();