diff --git a/Directory.Build.props b/Directory.Build.props
index 4e42bb2f8f..8ae2e0baec 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
all
- 3.5.103
+ 3.5.107
diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj
index 97608c756b..5ab8db1d99 100644
--- a/src/JsonSchema/JsonSchema.csproj
+++ b/src/JsonSchema/JsonSchema.csproj
@@ -7,13 +7,16 @@
-
-
+
+
-
+
+
+ 3.5.107
+
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
index 960ef4451c..0cb4cbcd92 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
@@ -38,6 +38,20 @@ public class SqliteSyntaxProvider : SqlSyntaxProviderBase
[typeof(Guid)] = new SqliteGuidScalarMapper(),
[typeof(Guid?)] = new SqliteNullableGuidScalarMapper(),
};
+
+ IntColumnDefinition = "INTEGER";
+ LongColumnDefinition = "INTEGER";
+ BoolColumnDefinition = "INTEGER";
+
+ GuidColumnDefinition = "TEXT";
+ DateTimeColumnDefinition = "TEXT";
+ DateTimeOffsetColumnDefinition = "TEXT";
+ TimeColumnDefinition = "TEXT";
+ DecimalColumnDefinition = "TEXT"; // REAL would be lossy. - https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite/types
+
+ RealColumnDefinition = "REAL";
+
+ BlobColumnDefinition = "BLOB";
}
///
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj
index 57055ec96b..5aa062df17 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj
index 0a811cf0b3..23e8febd18 100644
--- a/src/Umbraco.Cms/Umbraco.Cms.csproj
+++ b/src/Umbraco.Cms/Umbraco.Cms.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props b/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props
index ea0b013665..7266a05f6e 100644
--- a/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props
+++ b/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props
@@ -5,4 +5,8 @@
$(DefaultItemExcludes);umbraco/Logs/**
$(DefaultItemExcludes);wwwroot/media/**
+
+
+
+
diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs
index fdb7bac0ef..c791ec6168 100644
--- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs
@@ -41,19 +41,20 @@ public class ModelsBuilderSettings
///
public bool FlagOutOfDateModels
{
- get => _flagOutOfDateModels;
-
- set
+ get
{
- if (!ModelsMode.IsAuto())
+ if (ModelsMode == ModelsMode.Nothing ||ModelsMode.IsAuto())
{
- _flagOutOfDateModels = false;
- return;
+ return false;
+
}
- _flagOutOfDateModels = value;
+ return _flagOutOfDateModels;
+ }
+
+ set =>_flagOutOfDateModels = value;
}
- }
+
///
/// Gets or sets a value for the models directory.
diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs
index 409776b7e3..111d7e6dfa 100644
--- a/src/Umbraco.Core/Models/RelationItem.cs
+++ b/src/Umbraco.Core/Models/RelationItem.cs
@@ -1,4 +1,4 @@
-using System.Runtime.Serialization;
+using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models;
@@ -18,7 +18,7 @@ public class RelationItem
public string? NodeType { get; set; }
[DataMember(Name = "udi")]
- public Udi NodeUdi => Udi.Create(NodeType, NodeKey);
+ public Udi? NodeUdi => NodeType == Constants.UdiEntityType.Unknown ? null : Udi.Create(NodeType, NodeKey);
[DataMember(Name = "icon")]
public string? ContentTypeIcon { get; set; }
diff --git a/src/Umbraco.Core/Scoping/ICoreScope.cs b/src/Umbraco.Core/Scoping/ICoreScope.cs
index ef3cf91c4c..fe2a9489f3 100644
--- a/src/Umbraco.Core/Scoping/ICoreScope.cs
+++ b/src/Umbraco.Core/Scoping/ICoreScope.cs
@@ -8,6 +8,14 @@ namespace Umbraco.Cms.Core.Scoping;
///
public interface ICoreScope : IDisposable, IInstanceIdentifiable
{
+ ///
+ /// Gets the distance from the root scope.
+ ///
+ ///
+ /// A zero represents a root scope, any value greater than zero represents a child scope.
+ ///
+ public int Depth => -1;
+
///
/// Gets the scope notification publisher
///
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 8bcead30bb..01183739d5 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -16,10 +16,10 @@
-
+
-
+
diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj
index db5a303efd..833e01be50 100644
--- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj
+++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj
@@ -21,7 +21,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
index e9e1236a9e..6d583151ba 100644
--- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
+++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs
@@ -35,11 +35,6 @@ namespace Umbraco.Cms.Core.Configuration
public void RemoveConnectionString()
{
- // Update and reload configuration
- _configuration[UmbracoConnectionStringPath] = null;
- _configuration[UmbracoConnectionStringProviderNamePath] = null;
- (_configuration as IConfigurationRoot)?.Reload();
-
// Remove keys from JSON
var provider = GetJsonConfigurationProvider(UmbracoConnectionStringPath);
@@ -58,11 +53,6 @@ namespace Umbraco.Cms.Core.Configuration
public void SaveConnectionString(string connectionString, string? providerName)
{
- // Update and reload configuration
- _configuration[UmbracoConnectionStringPath] = connectionString;
- _configuration[UmbracoConnectionStringProviderNamePath] = providerName;
- (_configuration as IConfigurationRoot)?.Reload();
-
// Save keys to JSON
var provider = GetJsonConfigurationProvider();
@@ -84,10 +74,6 @@ namespace Umbraco.Cms.Core.Configuration
public void SaveConfigValue(string key, object value)
{
- // Update and reload configuration
- _configuration[key] = value?.ToString();
- (_configuration as IConfigurationRoot)?.Reload();
-
// Save key to JSON
var provider = GetJsonConfigurationProvider();
@@ -122,10 +108,6 @@ namespace Umbraco.Cms.Core.Configuration
public void SaveDisableRedirectUrlTracking(bool disable)
{
- // Update and reload configuration
- _configuration["Umbraco:CMS:WebRouting:DisableRedirectUrlTracking"] = disable.ToString();
- (_configuration as IConfigurationRoot)?.Reload();
-
// Save key to JSON
var provider = GetJsonConfigurationProvider();
@@ -147,10 +129,6 @@ namespace Umbraco.Cms.Core.Configuration
public void SetGlobalId(string id)
{
- // Update and reload configuration
- _configuration["Umbraco:CMS:Global:Id"] = id;
- (_configuration as IConfigurationRoot)?.Reload();
-
// Save key to JSON
var provider = GetJsonConfigurationProvider();
@@ -336,17 +314,21 @@ namespace Umbraco.Cms.Core.Configuration
{
if (token is JObject obj)
{
-
foreach (var property in obj.Properties())
{
if (name is null)
+ {
return property.Value;
+ }
+
if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase))
+ {
return property.Value;
+ }
}
}
+
return null;
}
-
}
}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 9252cb7111..3c05e56ac6 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -97,11 +97,14 @@ public static partial class UmbracoBuilderExtensions
builder.Mappers()?.AddCoreMappers();
// register the scope provider
- builder.Services.AddSingleton(); // implements IScopeProvider, IScopeAccessor
+ builder.Services.AddSingleton(sp => ActivatorUtilities.CreateInstance(sp, sp.GetRequiredService())); // implements IScopeProvider, IScopeAccessor
builder.Services.AddSingleton(f => f.GetRequiredService());
builder.Services.AddSingleton(f => f.GetRequiredService());
builder.Services.AddSingleton(f => f.GetRequiredService());
- builder.Services.AddSingleton(f => f.GetRequiredService());
+
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton(f => f.GetRequiredService());
+ builder.Services.AddSingleton();
builder.Services.AddScoped();
diff --git a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs
index f58d9d8854..e740ff1b26 100644
--- a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs
+++ b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs
@@ -1,4 +1,4 @@
-using Serilog;
+using Serilog;
using Serilog.Events;
using Serilog.Parsing;
@@ -27,14 +27,14 @@ public class MessageTemplates : IMessageTemplates
throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args.");
}
- var values = boundProperties.ToDictionary(x => x.Name, x => x.Value);
+ var values = boundProperties!.ToDictionary(x => x.Name, x => x.Value);
// this ends up putting every string parameter between quotes
// return parsedTemplate.Render(values);
// this does not
var tw = new StringWriter();
- foreach (MessageTemplateToken? t in parsedTemplate.Tokens)
+ foreach (MessageTemplateToken? t in parsedTemplate!.Tokens)
{
if (t is PropertyToken pt &&
values.TryGetValue(pt.PropertyName, out LogEventPropertyValue? propVal) &&
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
index 2f8bd3f4ec..b3188720b0 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs
@@ -163,7 +163,27 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
if (!isTrialRun)
{
+ // File configuration providers use a delay before reloading and triggering changes, so wait
+ using var isChanged = new ManualResetEvent(false);
+ using IDisposable? onChange = _connectionStrings.OnChange((options, name) =>
+ {
+ // Only watch default named option (CurrentValue)
+ if (name != Options.DefaultName)
+ {
+ return;
+ }
+
+ // Signal change
+ isChanged.Set();
+ });
+
+ // Update configuration and wait for change
_configManipulator.SaveConnectionString(connectionString, providerName);
+ if (!isChanged.WaitOne(10_000))
+ {
+ throw new InstallException("Didn't retrieve updated connection string within 10 seconds, try manual configuration instead.");
+ }
+
Configure(_globalSettings.CurrentValue.InstallMissingDatabase || providerMeta.ForceCreateDatabase);
}
diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
index fafdf0f346..e96856d0b8 100644
--- a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
+++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs
@@ -10,7 +10,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime;
internal class FileSystemMainDomLock : IMainDomLock
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
- private readonly IOptionsMonitor _globalSettings;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly IOptionsMonitor _globalSettings;
private readonly string _lockFilePath;
private readonly ILogger _logger;
private readonly string _releaseSignalFilePath;
@@ -25,7 +26,8 @@ internal class FileSystemMainDomLock : IMainDomLock
IOptionsMonitor globalSettings)
{
_logger = logger;
- _globalSettings = globalSettings;
+ _hostingEnvironment = hostingEnvironment;
+ _globalSettings = globalSettings;
var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock";
_lockFilePath = Path.Combine(hostingEnvironment.LocalTempPath, lockFileName);
@@ -41,7 +43,7 @@ internal class FileSystemMainDomLock : IMainDomLock
{
try
{
- _logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
+ Directory.CreateDirectory(_hostingEnvironment.LocalTempPath);_logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath);
_lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
DeleteLockReleaseSignalFile();
return Task.FromResult(true);
diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs
new file mode 100644
index 0000000000..18e68120a6
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs
@@ -0,0 +1,39 @@
+using System.Collections.Concurrent;
+using Umbraco.Cms.Core.Scoping;
+
+namespace Umbraco.Cms.Infrastructure.Scoping;
+
+internal class AmbientScopeContextStack : IAmbientScopeContextStack
+{
+ private static AsyncLocal> _stack = new();
+
+ public IScopeContext? AmbientContext
+ {
+ get
+ {
+ if (_stack.Value?.TryPeek(out IScopeContext? ambientContext) ?? false)
+ {
+ return ambientContext;
+ }
+
+ return null;
+ }
+ }
+
+ public IScopeContext Pop()
+ {
+ if (_stack.Value?.TryPop(out IScopeContext? ambientContext) ?? false)
+ {
+ return ambientContext;
+ }
+
+ throw new InvalidOperationException("No AmbientContext was found.");
+ }
+
+ public void Push(IScopeContext scope)
+ {
+ _stack.Value ??= new ConcurrentStack();
+
+ _stack.Value.Push(scope);
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs
new file mode 100644
index 0000000000..3ad5e89e51
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs
@@ -0,0 +1,39 @@
+using System.Collections.Concurrent;
+
+namespace Umbraco.Cms.Infrastructure.Scoping
+{
+ internal class AmbientScopeStack : IAmbientScopeStack
+ {
+ private static AsyncLocal> _stack = new ();
+
+ public IScope? AmbientScope
+ {
+ get
+ {
+ if (_stack.Value?.TryPeek(out IScope? ambientScope) ?? false)
+ {
+ return ambientScope;
+ }
+
+ return null;
+ }
+ }
+
+ public IScope Pop()
+ {
+ if (_stack.Value?.TryPop(out IScope? ambientScope) ?? false)
+ {
+ return ambientScope;
+ }
+
+ throw new InvalidOperationException("No AmbientScope was found.");
+ }
+
+ public void Push(IScope scope)
+ {
+ _stack.Value ??= new ConcurrentStack();
+
+ _stack.Value.Push(scope);
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs
new file mode 100644
index 0000000000..28da9a6427
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs
@@ -0,0 +1,10 @@
+using Umbraco.Cms.Core.Scoping;
+
+namespace Umbraco.Cms.Infrastructure.Scoping;
+
+internal interface IAmbientScopeContextStack
+{
+ IScopeContext? AmbientContext { get; }
+ IScopeContext Pop();
+ void Push(IScopeContext scope);
+}
diff --git a/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs
new file mode 100644
index 0000000000..71cfbf3a03
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs
@@ -0,0 +1,7 @@
+namespace Umbraco.Cms.Infrastructure.Scoping;
+
+internal interface IAmbientScopeStack : IScopeAccessor
+{
+ IScope Pop();
+ void Push(IScope scope);
+}
diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs
index 873e08fc99..000b6a602e 100644
--- a/src/Umbraco.Infrastructure/Scoping/Scope.cs
+++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs
@@ -33,7 +33,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private readonly bool? _scopeFileSystem;
private readonly ScopeProvider _scopeProvider;
- private bool _callContext;
private bool? _completed;
private IUmbracoDatabase? _database;
@@ -86,7 +85,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
_eventDispatcher = eventDispatcher;
_notificationPublisher = notificationPublisher;
_scopeFileSystem = scopeFileSystems;
- _callContext = callContext;
_autoComplete = autoComplete;
Detachable = detachable;
_dictionaryLocker = new object();
@@ -248,24 +246,15 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
}
+ [Obsolete("Scopes are never stored on HttpContext.Items anymore, so CallContext is always true.")]
// a value indicating whether to force call-context
public bool CallContext
{
- get
+ get => true;
+ set
{
- if (_callContext)
- {
- return true;
- }
-
- if (ParentScope != null)
- {
- return ParentScope.CallContext;
- }
-
- return false;
+ // NOOP - always true.
}
- set => _callContext = value;
}
public bool ScopedFileSystems
@@ -467,6 +456,19 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
}
+ public int Depth
+ {
+ get
+ {
+ if (ParentScope == null)
+ {
+ return 0;
+ }
+
+ return ParentScope.Depth + 1;
+ }
+ }
+
public IScopedNotificationPublisher Notifications
{
get
@@ -540,7 +542,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
}
- _scopeProvider.PopAmbientScope(this); // might be null = this is how scopes are removed from context objects
+ _scopeProvider.PopAmbientScope(); // might be null = this is how scopes are removed from context objects
#if DEBUG_SCOPES
_scopeProvider.Disposed(this);
@@ -885,7 +887,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
// by Deploy which I don't fully understand since there is limited tests on this in the CMS
if (OrigScope != _scopeProvider.AmbientScope)
{
- _scopeProvider.PopAmbientScope(_scopeProvider.AmbientScope);
+ _scopeProvider.PopAmbientScope();
}
if (OrigContext != _scopeProvider.AmbientContext)
diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs
index c8c58d9185..31249daa67 100644
--- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs
+++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs
@@ -1,15 +1,17 @@
-using System.Collections.Concurrent;
using System.Data;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Cache;
-using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DistributedLocking;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Persistence;
+using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
+using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings;
+
#if DEBUG_SCOPES
using System.Linq;
using System.Text;
@@ -18,26 +20,52 @@ using System.Text;
namespace Umbraco.Cms.Infrastructure.Scoping
{
///
- /// Implements .
+ /// Implements .
///
internal class ScopeProvider :
ICoreScopeProvider,
IScopeProvider,
Core.Scoping.IScopeProvider,
- IScopeAccessor
+ IScopeAccessor // TODO: No need to implement this here but literally hundreds of our tests cast ScopeProvider to ScopeAccessor
{
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
- private readonly IRequestCache _requestCache;
+ private readonly IEventAggregator _eventAggregator;
+ private readonly IAmbientScopeStack _ambientScopeStack;
+ private readonly IAmbientScopeContextStack _ambientContextStack;
+
private readonly FileSystems _fileSystems;
private CoreDebugSettings _coreDebugSettings;
private readonly MediaFileManager _mediaFileManager;
- private static readonly AsyncLocal> _scopeStack = new();
- private static readonly AsyncLocal> _scopeContextStack = new();
- private static readonly string _scopeItemKey = typeof(Scope).FullName!;
- private static readonly string _contextItemKey = typeof(ScopeProvider).FullName!;
- private readonly IEventAggregator _eventAggregator;
+ public ScopeProvider(
+ IAmbientScopeStack ambientScopeStack,
+ IAmbientScopeContextStack ambientContextStack,
+ IDistributedLockingMechanismFactory distributedLockingMechanismFactory,
+ IUmbracoDatabaseFactory databaseFactory,
+ FileSystems fileSystems,
+ IOptionsMonitor coreDebugSettings,
+ MediaFileManager mediaFileManager,
+ ILoggerFactory loggerFactory,
+ IEventAggregator eventAggregator)
+ {
+ DistributedLockingMechanismFactory = distributedLockingMechanismFactory;
+ DatabaseFactory = databaseFactory;
+ _ambientScopeStack = ambientScopeStack;
+ _ambientContextStack = ambientContextStack;
+ _fileSystems = fileSystems;
+ _coreDebugSettings = coreDebugSettings.CurrentValue;
+ _mediaFileManager = mediaFileManager;
+ _logger = loggerFactory.CreateLogger();
+ _loggerFactory = loggerFactory;
+ _eventAggregator = eventAggregator;
+ // take control of the FileSystems
+ _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
+
+ coreDebugSettings.OnChange(x => _coreDebugSettings = x);
+ }
+
+ [Obsolete("Please use an alternative constructor. This constructor is due for removal in v12.")]
public ScopeProvider(
IDistributedLockingMechanismFactory distributedLockingMechanismFactory,
IUmbracoDatabaseFactory databaseFactory,
@@ -47,21 +75,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping
ILoggerFactory loggerFactory,
IRequestCache requestCache,
IEventAggregator eventAggregator)
+ : this(
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService(),
+ distributedLockingMechanismFactory,
+ databaseFactory,
+ fileSystems,
+ coreDebugSettings,
+ mediaFileManager,
+ loggerFactory,
+ eventAggregator)
{
- DistributedLockingMechanismFactory = distributedLockingMechanismFactory;
- DatabaseFactory = databaseFactory;
- _fileSystems = fileSystems;
- _coreDebugSettings = coreDebugSettings.CurrentValue;
- _mediaFileManager = mediaFileManager;
- _logger = loggerFactory.CreateLogger();
- _loggerFactory = loggerFactory;
- _requestCache = requestCache;
- _eventAggregator = eventAggregator;
-
- // take control of the FileSystems
- _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
-
- coreDebugSettings.OnChange(x => _coreDebugSettings = x);
}
public IDistributedLockingMechanismFactory DistributedLockingMechanismFactory { get; }
@@ -70,282 +94,32 @@ namespace Umbraco.Cms.Infrastructure.Scoping
public ISqlContext SqlContext => DatabaseFactory.SqlContext;
- #region Context
-
- private void MoveHttpContextScopeToCallContext()
- {
- var source = (ConcurrentStack?)_requestCache.Get(_scopeItemKey);
- ConcurrentStack? stack = _scopeStack.Value;
- MoveContexts(_scopeItemKey, source, stack, (_, v) => _scopeStack.Value = v);
- }
-
- private void MoveHttpContextScopeContextToCallContext()
- {
- var source = (ConcurrentStack?)_requestCache.Get(_contextItemKey);
- ConcurrentStack? stack = _scopeContextStack.Value;
- MoveContexts(_contextItemKey, source, stack, (_, v) => _scopeContextStack.Value = v);
- }
-
- private void MoveCallContextScopeToHttpContext()
- {
- ConcurrentStack? source = _scopeStack.Value;
- var stack = (ConcurrentStack?)_requestCache.Get(_scopeItemKey);
- MoveContexts(_scopeItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
- }
-
- private void MoveCallContextScopeContextToHttpContext()
- {
- ConcurrentStack? source = _scopeContextStack.Value;
- var stack = (ConcurrentStack?)_requestCache.Get(_contextItemKey);
- MoveContexts(_contextItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
- }
-
- private void MoveContexts(string key, ConcurrentStack? source, ConcurrentStack? stack, Action> setter)
- where T : class, IInstanceIdentifiable
- {
- if (source == null)
- {
- return;
- }
-
- if (stack != null)
- {
- stack.Clear();
- }
- else
- {
- // TODO: This isn't going to copy it back up the execution context chain
- stack = new ConcurrentStack();
- setter(key, stack);
- }
-
- var arr = new T[source.Count];
- source.CopyTo(arr, 0);
- Array.Reverse(arr);
- foreach (T a in arr)
- {
- stack.Push(a);
- }
-
- source.Clear();
- }
-
- private void SetCallContextScope(IScope? value)
- {
- ConcurrentStack? stack = _scopeStack.Value;
-
-#if DEBUG_SCOPES
- // first, null-register the existing value
- if (stack != null && stack.TryPeek(out IScope ambientScope))
- {
- RegisterContext(ambientScope, null);
- }
-
- // then register the new value
- if (value != null)
- {
- RegisterContext(value, "call");
- }
-#endif
-
- if (value == null)
- {
- if (stack != null)
- {
- stack.TryPop(out _);
- }
- }
- else
- {
-#if DEBUG_SCOPES
- _logger.LogDebug("AddObject " + value.InstanceId.ToString("N").Substring(0, 8));
-#endif
- if (stack == null)
- {
- stack = new ConcurrentStack();
- }
-
- stack.Push(value);
- _scopeStack.Value = stack;
- }
- }
-
- private void SetCallContextScopeContext(IScopeContext? value)
- {
- ConcurrentStack? stack = _scopeContextStack.Value;
-
- if (value == null)
- {
- if (stack != null)
- {
- stack.TryPop(out _);
- }
- }
- else
- {
- if (stack == null)
- {
- stack = new ConcurrentStack();
- }
-
- stack.Push(value);
- _scopeContextStack.Value = stack;
- }
- }
-
- private T? GetHttpContextObject(string key, bool required = true)
- where T : class
- {
- if (!_requestCache.IsAvailable && required)
- {
- throw new Exception("Request cache is unavailable.");
- }
-
- var stack = (ConcurrentStack?)_requestCache.Get(key);
- return stack != null && stack.TryPeek(out T? peek) ? peek : null;
- }
-
- private bool SetHttpContextObject(string key, T? value, bool required = true)
- {
- if (!_requestCache.IsAvailable)
- {
- if (required)
- {
- throw new Exception("Request cache is unavailable.");
- }
-
- return false;
- }
-
-#if DEBUG_SCOPES
- // manage the 'context' that contains the scope (null, "http" or "call")
- // only for scopes of course!
- if (key == s_scopeItemKey)
- {
- // first, null-register the existing value
- var ambientScope = (IScope)_requestCache.Get(s_scopeItemKey);
- if (ambientScope != null)
- {
- RegisterContext(ambientScope, null);
- }
-
- // then register the new value
- if (value is IScope scope)
- {
- RegisterContext(scope, "http");
- }
- }
-#endif
- var stack = (ConcurrentStack?)_requestCache.Get(key);
-
- if (value == null)
- {
- if (stack != null)
- {
- stack.TryPop(out _);
- }
- }
- else
- {
- if (stack == null)
- {
- stack = new ConcurrentStack();
- }
-
- stack.Push(value);
- _requestCache.Set(key, stack);
- }
-
- return true;
- }
-
- #endregion
#region Ambient Context
///
- /// Get the Ambient (Current) for the current execution context.
+ /// Get the Ambient (Current) for the current execution context.
///
///
- /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
+ /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
///
- public IScopeContext? AmbientContext
- {
- get
- {
- // try http context, fallback onto call context
- IScopeContext? value = GetHttpContextObject(_contextItemKey, false);
- if (value != null)
- {
- return value;
- }
-
- ConcurrentStack? stack = _scopeContextStack.Value;
- if (stack == null || !stack.TryPeek(out IScopeContext? peek))
- {
- return null;
- }
-
- return peek;
- }
- }
+ public IScopeContext? AmbientContext => _ambientContextStack.AmbientContext;
#endregion
#region Ambient Scope
- IScope? IScopeAccessor.AmbientScope => AmbientScope;
-
///
- /// Gets or set the Ambient (Current) for the current execution context.
+ /// Gets or set the Ambient (Current) for the current execution context.
///
///
- /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
+ /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal)
///
- public Scope? AmbientScope
- {
- get
- {
- // try http context, fallback onto call context
- IScope? value = GetHttpContextObject(_scopeItemKey, false);
- if (value != null)
- {
- return (Scope)value;
- }
+ public Scope? AmbientScope => (Scope?)_ambientScopeStack.AmbientScope;
- ConcurrentStack? stack = _scopeStack.Value;
- if (stack == null || !stack.TryPeek(out IScope? peek))
- {
- return null;
- }
+ IScope? IScopeAccessor.AmbientScope => _ambientScopeStack.AmbientScope;
- return (Scope)peek;
- }
- }
-
- public void PopAmbientScope(Scope? scope)
- {
- // pop the stack from all contexts
- SetHttpContextObject(_scopeItemKey, null, false);
- SetCallContextScope(null);
-
- // We need to move the stack to a different context if the parent scope
- // is flagged with a different CallContext flag. This is required
- // if creating a child scope with callContext: true (thus forcing CallContext)
- // when there is actually a current HttpContext available.
- // It's weird but is required for Deploy somehow.
- var parentScopeCallContext = scope?.ParentScope?.CallContext ?? false;
- if ((scope?.CallContext ?? false) && !parentScopeCallContext)
- {
- MoveCallContextScopeToHttpContext();
- MoveCallContextScopeContextToHttpContext();
- }
- else if ((!scope?.CallContext ?? false) && parentScopeCallContext)
- {
- MoveHttpContextScopeToCallContext();
- MoveHttpContextScopeContextToCallContext();
- }
- }
+ public void PopAmbientScope() => _ambientScopeStack.Pop();
#endregion
@@ -356,21 +130,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
throw new ArgumentNullException(nameof(scope));
}
- if (scope.CallContext || !SetHttpContextObject(_scopeItemKey, scope, false))
- {
- // In this case, always ensure that the HttpContext items
- // is transfered to CallContext and then cleared since we
- // may be migrating context with the callContext = true flag.
- // This is a weird case when forcing callContext when HttpContext
- // is available. Required by Deploy.
- if (_requestCache.IsAvailable)
- {
- MoveHttpContextScopeToCallContext();
- MoveHttpContextScopeContextToCallContext();
- }
-
- SetCallContextScope(scope);
- }
+ _ambientScopeStack.Push(scope);
}
public void PushAmbientScopeContext(IScopeContext? scopeContext)
@@ -379,17 +139,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
throw new ArgumentNullException(nameof(scopeContext));
}
-
- SetHttpContextObject(_contextItemKey, scopeContext, false);
- SetCallContextScopeContext(scopeContext);
+ _ambientContextStack.Push(scopeContext);
}
- public void PopAmbientScopeContext()
- {
- // pop stack from all contexts
- SetHttpContextObject(_contextItemKey, null, false);
- SetCallContextScopeContext(null);
- }
+ public void PopAmbientScopeContext() => _ambientContextStack.Pop();
///
public IScope CreateDetachedScope(
@@ -398,21 +151,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
IEventDispatcher? eventDispatcher = null,
IScopedNotificationPublisher? scopedNotificationPublisher = null,
bool? scopeFileSystems = null)
- =>
- new Scope(
- this,
- _coreDebugSettings,
- _mediaFileManager,
- _eventAggregator,
- _loggerFactory.CreateLogger(),
- _fileSystems,
- true,
- null,
- isolationLevel,
- repositoryCacheMode,
- eventDispatcher,
- scopedNotificationPublisher,
- scopeFileSystems);
+ => new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems);
///
public void AttachScope(IScope other, bool callContext = false)
@@ -457,20 +196,18 @@ namespace Umbraco.Cms.Infrastructure.Scoping
throw new InvalidOperationException("Ambient scope is not detachable.");
}
- PopAmbientScope(ambientScope);
+ PopAmbientScope();
PopAmbientScopeContext();
Scope? originalScope = AmbientScope;
if (originalScope != ambientScope.OrigScope)
{
- throw new InvalidOperationException(
- $"The detatched scope ({ambientScope.GetDebugInfo()}) does not match the original ({originalScope?.GetDebugInfo()})");
+ throw new InvalidOperationException($"The detatched scope ({ambientScope.GetDebugInfo()}) does not match the original ({originalScope?.GetDebugInfo()})");
}
-
IScopeContext? originalScopeContext = AmbientContext;
if (originalScopeContext != ambientScope.OrigContext)
{
- throw new InvalidOperationException("The detatched scope context does not match the original");
+ throw new InvalidOperationException($"The detatched scope context does not match the original");
}
ambientScope.OrigScope = null;
@@ -494,22 +231,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
IScopeContext? ambientContext = AmbientContext;
ScopeContext? newContext = ambientContext == null ? new ScopeContext() : null;
- var scope = new Scope(
- this,
- _coreDebugSettings,
- _mediaFileManager,
- _eventAggregator,
- _loggerFactory.CreateLogger(),
- _fileSystems,
- false,
- newContext,
- isolationLevel,
- repositoryCacheMode,
- eventDispatcher,
- notificationPublisher,
- scopeFileSystems,
- callContext,
- autoComplete);
+ var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
// assign only if scope creation did not throw!
PushAmbientScope(scope);
@@ -517,34 +239,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
PushAmbientScopeContext(newContext);
}
-
return scope;
}
- var nested = new Scope(
- this,
- _coreDebugSettings,
- _mediaFileManager,
- _eventAggregator,
- _loggerFactory.CreateLogger(),
- _fileSystems,
- ambientScope,
- isolationLevel,
- repositoryCacheMode,
- eventDispatcher,
- notificationPublisher,
- scopeFileSystems,
- callContext,
- autoComplete);
+ var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
PushAmbientScope(nested);
return nested;
}
+ ///
public IScopeContext? Context => AmbientContext;
- // for testing
- internal ConcurrentStack? GetCallContextScopeValue() => _scopeStack.Value;
-
#if DEBUG_SCOPES
// this code needs TLC
//
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index c304f4e99a..fc41bec6c5 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -18,9 +18,9 @@
-
+
-
+
@@ -28,7 +28,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -37,10 +37,10 @@
-
+
-
+
@@ -48,15 +48,18 @@
-
+
-
+
all
+
+ 3.5.107
+
diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs
index 6b8afa6626..2d18905ee9 100644
--- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs
+++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs
@@ -1,110 +1,116 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
-using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
-namespace Umbraco.Extensions;
-
-///
-/// Extension methods for for the Umbraco back office
-///
-public static partial class UmbracoBuilderExtensions
+namespace Umbraco.Extensions
{
///
- /// Adds the supplementary localized texxt file sources from the various physical and virtual locations supported.
+ /// Extension methods for for the Umbraco back office
///
- private static IUmbracoBuilder AddSupplemenataryLocalizedTextFileSources(this IUmbracoBuilder builder)
+ public static partial class UmbracoBuilderExtensions
{
- builder.Services.AddTransient(sp =>
+
+ ///
+ /// Adds the supplementary localized texxt file sources from the various physical and virtual locations supported.
+ ///
+ private static IUmbracoBuilder AddSupplemenataryLocalizedTextFileSources(this IUmbracoBuilder builder)
{
- return GetSupplementaryFileSources(
- sp.GetRequiredService());
- });
-
- return builder;
- }
-
-
- ///
- /// Loads the suplimentary localization files from plugins and user config
- ///
- private static IEnumerable GetSupplementaryFileSources(
- IWebHostEnvironment webHostEnvironment)
- {
- IFileProvider webFileProvider = webHostEnvironment.WebRootFileProvider;
- IFileProvider contentFileProvider = webHostEnvironment.ContentRootFileProvider;
-
- // gets all langs files in /app_plugins real or virtual locations
- IEnumerable pluginLangFileSources =
- GetPluginLanguageFileSources(webFileProvider, Constants.SystemDirectories.AppPlugins, false);
-
- // user defined langs that overwrite the default, these should not be used by plugin creators
- var userConfigLangFolder = Constants.SystemDirectories.Config
- .TrimStart(Constants.CharArrays.Tilde);
-
- IEnumerable userLangFileSources = contentFileProvider
- .GetDirectoryContents(userConfigLangFolder)
- .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang"))
- .Select(x => new DirectoryInfo(x.PhysicalPath))
- .SelectMany(x => x.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly))
- .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true));
-
- return pluginLangFileSources
- .Concat(userLangFileSources);
- }
-
-
- ///
- /// Loads the suplimentary localaization files via the file provider.
- ///
- ///
- /// locates all *.xml files in the lang folder of any sub folder of the one provided.
- /// e.g /app_plugins/plugin-name/lang/*.xml
- ///
- private static IEnumerable GetPluginLanguageFileSources(
- IFileProvider fileProvider, string folder, bool overwriteCoreKeys)
- {
- // locate all the *.xml files inside Lang folders inside folders of the main folder
- // e.g. /app_plugins/plugin-name/lang/*.xml
- var fileSources = new List();
-
- var pluginFolders = fileProvider.GetDirectoryContents(folder)
- .Where(x => x.IsDirectory).ToList();
-
- foreach (IFileInfo pluginFolder in pluginFolders)
- {
- // get the full virtual path for the plugin folder
- var pluginFolderPath = WebPath.Combine(folder, pluginFolder.Name);
-
- // get any lang folders in this plugin
- IEnumerable langFolders = fileProvider.GetDirectoryContents(pluginFolderPath)
- .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang"));
-
- // loop through the lang folder(s)
- // - there could be multiple on case sensitive file system
- foreach (IFileInfo langFolder in langFolders)
+ builder.Services.AddTransient(sp =>
{
- // get the full 'virtual' path of the lang folder
- var langFolderPath = WebPath.Combine(pluginFolderPath, langFolder.Name);
+ return GetSupplementaryFileSources(
+ sp.GetRequiredService());
+ });
- // request all the files out of the path, these will have physicalPath set.
- var files = fileProvider.GetDirectoryContents(langFolderPath)
- .Where(x => x.Name.InvariantEndsWith(".xml") && !string.IsNullOrEmpty(x.PhysicalPath))
- .Select(x => new FileInfo(x.PhysicalPath))
- .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, overwriteCoreKeys))
- .ToList();
+ return builder;
+ }
- // add any to our results
- if (files.Count > 0)
+
+ ///
+ /// Loads the suplimentary localization files from plugins and user config
+ ///
+ private static IEnumerable GetSupplementaryFileSources(
+ IWebHostEnvironment webHostEnvironment)
+ {
+ IFileProvider webFileProvider = webHostEnvironment.WebRootFileProvider;
+ IFileProvider contentFileProvider = webHostEnvironment.ContentRootFileProvider;
+
+ // gets all langs files in /app_plugins real or virtual locations
+ IEnumerable pluginLangFileSources = GetPluginLanguageFileSources(webFileProvider, Cms.Core.Constants.SystemDirectories.AppPlugins, false);
+
+ // user defined langs that overwrite the default, these should not be used by plugin creators
+ var userConfigLangFolder = Cms.Core.Constants.SystemDirectories.Config
+ .TrimStart(Cms.Core.Constants.CharArrays.Tilde);
+
+ IEnumerable userLangFileSources = contentFileProvider.GetDirectoryContents(userConfigLangFolder)
+ .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang"))
+ .Select(x => new DirectoryInfo(x.PhysicalPath))
+ .SelectMany(x => x.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly))
+ .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true));
+
+ return pluginLangFileSources
+ .Concat(userLangFileSources);
+ }
+
+
+ ///
+ /// Loads the suplimentary localaization files via the file provider.
+ ///
+ ///
+ /// locates all *.xml files in the lang folder of any sub folder of the one provided.
+ /// e.g /app_plugins/plugin-name/lang/*.xml
+ ///
+ private static IEnumerable GetPluginLanguageFileSources(
+ IFileProvider fileProvider, string folder, bool overwriteCoreKeys)
+ {
+ IEnumerable pluginFolders = fileProvider
+ .GetDirectoryContents(folder)
+ .Where(x => x.IsDirectory);
+
+ foreach (IFileInfo pluginFolder in pluginFolders)
+ {
+ // get the full virtual path for the plugin folder
+ var pluginFolderPath = WebPath.Combine(folder, pluginFolder.Name);
+
+ // loop through the lang folder(s)
+ // - there could be multiple on case sensitive file system
+ foreach (var langFolder in GetLangFolderPaths(fileProvider, pluginFolderPath))
{
- fileSources.AddRange(files);
+ // request all the files out of the path, these will have physicalPath set.
+ IEnumerable localizationFiles = fileProvider
+ .GetDirectoryContents(langFolder)
+ .Where(x => !string.IsNullOrEmpty(x.PhysicalPath))
+ .Where(x => x.Name.InvariantEndsWith(".xml"))
+ .Select(x => new FileInfo(x.PhysicalPath));
+
+ foreach (FileInfo file in localizationFiles)
+ {
+ yield return new LocalizedTextServiceSupplementaryFileSource(file, overwriteCoreKeys);
+ }
}
}
}
- return fileSources;
+ private static IEnumerable GetLangFolderPaths(IFileProvider fileProvider, string path)
+ {
+ IEnumerable directories = fileProvider.GetDirectoryContents(path).Where(x => x.IsDirectory);
+
+ foreach (IFileInfo directory in directories)
+ {
+ var virtualPath = WebPath.Combine(path, directory.Name);
+
+ if (directory.Name.InvariantEquals("lang"))
+ {
+ yield return virtualPath;
+ }
+
+ foreach (var nested in GetLangFolderPaths(fileProvider, virtualPath))
+ {
+ yield return nested;
+ }
+ }
+ }
}
}
diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
index 13076bd50c..3f24e2717e 100644
--- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
+++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
@@ -28,6 +28,9 @@
all
+
+ 3.5.107
+
diff --git a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs
index 616611a224..3bf8d3a04a 100644
--- a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs
+++ b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs
@@ -11,5 +11,5 @@ public interface IVirtualPageController
///
/// Returns the to use as the current page for the request
///
- IPublishedContent FindContent(ActionExecutingContext actionExecutingContext);
+ IPublishedContent? FindContent(ActionExecutingContext actionExecutingContext);
}
diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
index a2c7dcb475..e08f45e5af 100644
--- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
+++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
@@ -35,14 +35,14 @@
-
-
+
+
-
-
-
+
+
+
diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
index b1ac11c77d..0b3132b795 100644
--- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
+++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs
@@ -128,7 +128,7 @@ public abstract class UmbracoViewPage : RazorPage
{
// filter / add preview banner
// ASP.NET default value is text/html
- if (Context.Response.ContentType.InvariantContains("text/html"))
+ if (Context.Response?.ContentType?.InvariantContains("text/html") ?? false)
{
if (((UmbracoContext?.IsDebug ?? false) || (UmbracoContext?.InPreviewMode ?? false))
&& tagHelperOutput.TagName != null
diff --git a/src/Umbraco.Web.UI/Program.cs b/src/Umbraco.Web.UI/Program.cs
index b41c61513a..cebb7b0370 100644
--- a/src/Umbraco.Web.UI/Program.cs
+++ b/src/Umbraco.Web.UI/Program.cs
@@ -1,6 +1,3 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Hosting;
-
namespace Umbraco.Cms.Web.UI
{
public class Program
diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs
index 0176974b6b..eba72b6924 100644
--- a/src/Umbraco.Web.UI/Startup.cs
+++ b/src/Umbraco.Web.UI/Startup.cs
@@ -1,12 +1,3 @@
-using System;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Umbraco.Cms.Core.DependencyInjection;
-using Umbraco.Extensions;
-
namespace Umbraco.Cms.Web.UI
{
public class Startup
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 2274c73327..42e894a7f3 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -24,7 +24,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj
index 0bb0b6c0ad..176564eaf8 100644
--- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj
+++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj
@@ -35,6 +35,9 @@
all
+
+ 3.5.107
+
diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj
index 182b220167..47b2246835 100644
--- a/templates/Umbraco.Templates.csproj
+++ b/templates/Umbraco.Templates.csproj
@@ -1,7 +1,7 @@
-
+
net6.0
Template
@@ -14,7 +14,7 @@
true
false
-
+
diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj
index e34e743acb..efaa0edbb0 100644
--- a/templates/UmbracoProject/UmbracoProject.csproj
+++ b/templates/UmbracoProject/UmbracoProject.csproj
@@ -1,38 +1,37 @@
+
+ net6.0
+ enable
+ enable
+ Umbraco.Cms.Web.UI
+
-
- net6.0
- enable
- Umbraco.Cms.Web.UI
-
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
+
+ true
+
-
- true
-
-
-
-
-
-
-
-
- false
- false
-
+
+
+
+
+
+ false
+ false
+
diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
index db34088068..29e0a36353 100644
--- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
+++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj
@@ -29,7 +29,7 @@
6.0.0
- 4.17.2
+ 4.18.1
diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj
index 705f3333f9..05a5554db2 100644
--- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj
+++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj
@@ -16,7 +16,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs
index cfd9e49583..5febb2819b 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs
@@ -226,56 +226,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping
Assert.IsNull(scopeProvider.AmbientScope);
}
- [Test]
- public void NestedMigrateScope()
- {
- // Get the request cache mock and re-configure it to be available and used
- var requestCacheDictionary = new Dictionary();
- IRequestCache requestCache = AppCaches.RequestCache;
- var requestCacheMock = Mock.Get(requestCache);
- requestCacheMock
- .Setup(x => x.IsAvailable)
- .Returns(true);
- requestCacheMock
- .Setup(x => x.Set(It.IsAny(), It.IsAny