V10: fix build warnings infrastructure (#12369)

* Run code cleanup

* Run dotnet format

* Start manual fixes

* Manual fixing of warnings

* Fix nullability in columnalias

* Fix tests

* Fix up after merge

* Start updating after review

* Update editorconfig to contain new static & const rules

* Fix up editorconfig to not contain duplicate rules

* Fix up static member names

* Fix up according to review

* Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/ExamineUmbracoIndexingHandler.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Examine/PublishedContentIndexPopulator.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Extensions/InstanceIdentifiableExtensions.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/HostedServices/RecurringHostedServiceBase.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/HostedServices/ReportSiteTask.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Macros/MacroTagParser.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Migrations/Expressions/Alter/Table/IAlterTableColumnOptionBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_0_0/AddMemberPropertiesAsColumns.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Dtos/ExternalLoginDto.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/AccessMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/AuditEntryMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/MediaMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/MemberMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapper.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/NPocoMapperCollectionBuilder.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Querying/ExpressionVisitorBase.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Fix [..] to substring

* Fix after merge with 10/dev

* Fox ContentValueSetValidator.cs

* Update LoggerConfigExtensions

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Nikolaj Geisle
2022-06-02 08:18:31 +02:00
committed by GitHub
parent adcc9a0e1f
commit f4e333c178
838 changed files with 64052 additions and 61173 deletions

View File

@@ -1,48 +1,49 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
namespace Umbraco.Cms.Core.Scoping
using Umbraco.Cms.Infrastructure.Scoping;
namespace Umbraco.Cms.Core.Scoping;
/// <summary>
/// Disposed at the end of the request to cleanup any orphaned Scopes.
/// </summary>
/// <remarks>Registered as Scoped in DI (per request)</remarks>
internal class HttpScopeReference : IHttpScopeReference
{
private readonly ScopeProvider _scopeProvider;
private bool _disposedValue;
private bool _registered;
/// <summary>
/// Disposed at the end of the request to cleanup any orphaned Scopes.
/// </summary>
/// <remarks>Registered as Scoped in DI (per request)</remarks>
internal class HttpScopeReference : IHttpScopeReference
public HttpScopeReference(ScopeProvider scopeProvider) => _scopeProvider = scopeProvider;
public void Dispose() =>
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(true);
public void Register() => _registered = true;
protected virtual void Dispose(bool disposing)
{
private readonly Infrastructure.Scoping.ScopeProvider _scopeProvider;
private bool _disposedValue;
private bool _registered = false;
public HttpScopeReference(Infrastructure.Scoping.ScopeProvider scopeProvider) => _scopeProvider = scopeProvider;
protected virtual void Dispose(bool disposing)
if (!_disposedValue)
{
if (!_disposedValue)
if (disposing)
{
if (disposing)
if (_registered)
{
if (_registered)
// dispose the entire chain (if any)
// reset (don't commit by default)
Scope? scope;
while ((scope = _scopeProvider.AmbientScope) != null)
{
// dispose the entire chain (if any)
// reset (don't commit by default)
Infrastructure.Scoping.Scope? scope;
while ((scope = _scopeProvider.AmbientScope) != null)
{
scope.Reset();
scope.Dispose();
}
scope.Reset();
scope.Dispose();
}
}
_disposedValue = true;
}
_disposedValue = true;
}
public void Dispose() =>
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
public void Register() => _registered = true;
}
}

View File

@@ -1,18 +1,15 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
namespace Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Core.Scoping
/// <summary>
/// Cleans up orphaned <see cref="IScope" /> references at the end of a request
/// </summary>
public interface IHttpScopeReference : IDisposable
{
/// <summary>
/// Cleans up orphaned <see cref="IScope"/> references at the end of a request
/// Register for cleanup in the request
/// </summary>
public interface IHttpScopeReference : IDisposable
{
/// <summary>
/// Register for cleanup in the request
/// </summary>
void Register();
}
void Register();
}

View File

@@ -6,12 +6,12 @@ namespace Umbraco.Cms.Infrastructure.Scoping;
public interface IScope : ICoreScope
{
/// <summary>
/// Gets the scope database.
/// Gets the scope database.
/// </summary>
IUmbracoDatabase Database { get; }
/// <summary>
/// Gets the Sql context.
/// Gets the Sql context.
/// </summary>
ISqlContext SqlContext { get; }
}

View File

@@ -1,11 +1,10 @@
namespace Umbraco.Cms.Infrastructure.Scoping
namespace Umbraco.Cms.Infrastructure.Scoping;
public interface IScopeAccessor
{
public interface IScopeAccessor
{
/// <summary>
/// Gets the ambient scope.
/// </summary>
/// <remarks>Returns <c>null</c> if there is no ambient scope.</remarks>
IScope? AmbientScope { get; }
}
/// <summary>
/// Gets the ambient scope.
/// </summary>
/// <remarks>Returns <c>null</c> if there is no ambient scope.</remarks>
IScope? AmbientScope { get; }
}

View File

@@ -7,10 +7,15 @@ using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Infrastructure.Scoping;
/// <summary>
/// Provides scopes.
/// Provides scopes.
/// </summary>
public interface IScopeProvider : ICoreScopeProvider
{
/// <summary>
/// Gets the scope context.
/// </summary>
IScopeContext? Context { get; }
/// <inheritdoc />
ICoreScope ICoreScopeProvider.CreateCoreScope(
IsolationLevel isolationLevel,
@@ -34,7 +39,7 @@ public interface IScopeProvider : ICoreScopeProvider
SqlContext.Query<T>();
/// <summary>
/// Creates an ambient scope.
/// Creates an ambient scope.
/// </summary>
/// <param name="isolationLevel">The transaction isolation level.</param>
/// <param name="repositoryCacheMode">The repositories cache mode.</param>
@@ -45,12 +50,14 @@ public interface IScopeProvider : ICoreScopeProvider
/// <param name="autoComplete">A value indicating whether this scope is auto-completed.</param>
/// <returns>The created ambient scope.</returns>
/// <remarks>
/// <para>The created scope becomes the ambient scope.</para>
/// <para>If an ambient scope already exists, it becomes the parent of the created scope.</para>
/// <para>When the created scope is disposed, the parent scope becomes the ambient scope again.</para>
/// <para>Parameters must be specified on the outermost scope, or must be compatible with the parents.</para>
/// <para>Auto-completed scopes should be used for read-only operations ONLY. Do not use them if you do not
/// understand the associated issues, such as the scope being completed even though an exception is thrown.</para>
/// <para>The created scope becomes the ambient scope.</para>
/// <para>If an ambient scope already exists, it becomes the parent of the created scope.</para>
/// <para>When the created scope is disposed, the parent scope becomes the ambient scope again.</para>
/// <para>Parameters must be specified on the outermost scope, or must be compatible with the parents.</para>
/// <para>
/// Auto-completed scopes should be used for read-only operations ONLY. Do not use them if you do not
/// understand the associated issues, such as the scope being completed even though an exception is thrown.
/// </para>
/// </remarks>
IScope CreateScope(
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
@@ -62,7 +69,7 @@ public interface IScopeProvider : ICoreScopeProvider
bool autoComplete = false);
/// <summary>
/// Creates a detached scope.
/// Creates a detached scope.
/// </summary>
/// <returns>A detached scope.</returns>
/// <param name="isolationLevel">The transaction isolation level.</param>
@@ -71,8 +78,8 @@ public interface IScopeProvider : ICoreScopeProvider
/// <param name="scopedNotificationPublisher">An option notification publisher.</param>
/// <param name="scopeFileSystems">A value indicating whether to scope the filesystems.</param>
/// <remarks>
/// <para>A detached scope is not ambient and has no parent.</para>
/// <para>It is meant to be attached by <see cref="AttachScope"/>.</para>
/// <para>A detached scope is not ambient and has no parent.</para>
/// <para>It is meant to be attached by <see cref="AttachScope" />.</para>
/// </remarks>
/// <remarks>
/// This is not used by CMS but is used by Umbraco Deploy.
@@ -85,36 +92,30 @@ public interface IScopeProvider : ICoreScopeProvider
bool? scopeFileSystems = null);
/// <summary>
/// Attaches a scope.
/// Attaches a scope.
/// </summary>
/// <param name="scope">The scope to attach.</param>
/// <param name="callContext">A value indicating whether to force usage of call context.</param>
/// <remarks>
/// <para>Only a scope created by <see cref="CreateDetachedScope"/> can be attached.</para>
/// <para>Only a scope created by <see cref="CreateDetachedScope" /> can be attached.</para>
/// </remarks>
void AttachScope(IScope scope, bool callContext = false);
/// <summary>
/// Detaches a scope.
/// Detaches a scope.
/// </summary>
/// <returns>The detached scope.</returns>
/// <remarks>
/// <para>Only a scope previously attached by <see cref="AttachScope"/> can be detached.</para>
/// <para>Only a scope previously attached by <see cref="AttachScope" /> can be detached.</para>
/// </remarks>
IScope DetachScope();
/// <summary>
/// Gets the scope context.
/// </summary>
IScopeContext? Context { get; }
/// <summary>
/// Gets the sql context.
/// Gets the sql context.
/// </summary>
ISqlContext SqlContext { get; }
#if DEBUG_SCOPES
IEnumerable<ScopeInfo> ScopeInfos { get; }
ScopeInfo GetScopeInfo(IScope scope);
#endif

View File

@@ -1,4 +1,3 @@
using System;
using Umbraco.Cms.Core.Events;
// ReSharper disable once CheckNamespace
@@ -8,12 +7,12 @@ namespace Umbraco.Cms.Core.Scoping;
public interface IScope : Infrastructure.Scoping.IScope
{
/// <summary>
/// Gets the scope event messages.
/// Gets the scope event messages.
/// </summary>
EventMessages Messages { get; }
/// <summary>
/// Gets the scope event dispatcher.
/// Gets the scope event dispatcher.
/// </summary>
IEventDispatcher Events { get; }
}

View File

@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
@@ -22,10 +18,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
/// Implements <see cref="IScope" />.
/// </summary>
/// <remarks>Not thread-safe obviously.</remarks>
internal class Scope :
ICoreScope,
IScope,
Core.Scoping.IScope
internal class Scope : ICoreScope, IScope, Core.Scoping.IScope
{
private readonly bool _autoComplete;
private readonly CoreDebugSettings _coreDebugSettings;
@@ -101,8 +94,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
#if DEBUG_SCOPES
_scopeProvider.RegisterScope(this);
#endif
logger.LogTrace("Create {InstanceId} on thread {ThreadId}", InstanceId.ToString("N").Substring(0, 8),
Thread.CurrentThread.ManagedThreadId);
logger.LogTrace("Create {InstanceId} on thread {ThreadId}", InstanceId.ToString("N").Substring(0, 8), Thread.CurrentThread.ManagedThreadId);
if (detachable)
{
@@ -146,8 +138,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
parent.RepositoryCacheMode > repositoryCacheMode)
{
throw new ArgumentException(
$"Value '{repositoryCacheMode}' cannot be lower than parent value '{parent.RepositoryCacheMode}'.",
nameof(repositoryCacheMode));
$"Value '{repositoryCacheMode}' cannot be lower than parent value '{parent.RepositoryCacheMode}'.", nameof(repositoryCacheMode));
}
// cannot specify a dispatcher!
@@ -159,8 +150,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
// Only the outermost scope can specify the notification publisher
if (_notificationPublisher != null)
{
throw new ArgumentException("Value cannot be specified on nested scope.",
nameof(notificationPublisher));
throw new ArgumentException("Value cannot be specified on nested scope.", nameof(notificationPublisher));
}
// cannot specify a different fs scope!
@@ -168,8 +158,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
if (scopeFileSystems != null && parent._scopeFileSystem != scopeFileSystems)
{
throw new ArgumentException(
$"Value '{scopeFileSystems.Value}' be different from parent value '{parent._scopeFileSystem}'.",
nameof(scopeFileSystems));
$"Value '{scopeFileSystems.Value}' be different from parent value '{parent._scopeFileSystem}'.", nameof(scopeFileSystems));
}
}
else
@@ -696,14 +685,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping
switch (currentType)
{
case DistributedLockType.ReadLock:
EagerReadLockInner(currentInstanceId,
currentTimeout == TimeSpan.Zero ? null : currentTimeout,
collectedIds.ToArray());
EagerReadLockInner(currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray());
break;
case DistributedLockType.WriteLock:
EagerWriteLockInner(currentInstanceId,
currentTimeout == TimeSpan.Zero ? null : currentTimeout,
collectedIds.ToArray());
EagerWriteLockInner(currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray());
break;
}
@@ -722,12 +707,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping
switch (currentType)
{
case DistributedLockType.ReadLock:
EagerReadLockInner(currentInstanceId,
currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray());
EagerReadLockInner(currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray());
break;
case DistributedLockType.WriteLock:
EagerWriteLockInner(currentInstanceId,
currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray());
EagerWriteLockInner(currentInstanceId, currentTimeout == TimeSpan.Zero ? null : currentTimeout, collectedIds.ToArray());
break;
}
}
@@ -776,8 +759,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
/// <param name="dict">Lock dictionary to report on.</param>
/// <param name="builder">String builder to write to.</param>
/// <param name="dictName">The name to report the dictionary as.</param>
private void WriteLockDictionaryToString(Dictionary<Guid, Dictionary<int, int>> dict, StringBuilder builder,
string dictName)
private void WriteLockDictionaryToString(Dictionary<Guid, Dictionary<int, int>> dict, StringBuilder builder, string dictName)
{
if (dict?.Count > 0)
{
@@ -921,8 +903,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
HandleScopedFileSystems,
HandleScopedNotifications,
HandleScopeContext,
HandleDetachedScopes
);
HandleDetachedScopes);
}
private static void TryFinally(params Action[] actions)

View File

@@ -1,115 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Core.Scoping
internal class ScopeContext : IScopeContext, IInstanceIdentifiable
{
internal class ScopeContext : IScopeContext, IInstanceIdentifiable
private Dictionary<string, IEnlistedObject>? _enlisted;
private interface IEnlistedObject
{
private Dictionary<string, IEnlistedObject>? _enlisted;
int Priority { get; }
public void ScopeExit(bool completed)
void Execute(bool completed);
}
public Guid InstanceId { get; } = Guid.NewGuid();
private IDictionary<string, IEnlistedObject> Enlisted => _enlisted ??= new Dictionary<string, IEnlistedObject>();
public void ScopeExit(bool completed)
{
if (_enlisted == null)
{
if (_enlisted == null)
return;
return;
}
// TODO: can we create infinite loops? - what about nested events? will they just be plainly ignored = really bad?
List<Exception>? exceptions = null;
List<IEnlistedObject> orderedEnlisted;
while ((orderedEnlisted = _enlisted.Values.OrderBy(x => x.Priority).ToList()).Count > 0)
// TODO: can we create infinite loops? - what about nested events? will they just be plainly ignored = really bad?
List<Exception>? exceptions = null;
List<IEnlistedObject> orderedEnlisted;
while ((orderedEnlisted = _enlisted.Values.OrderBy(x => x.Priority).ToList()).Count > 0)
{
_enlisted.Clear();
foreach (IEnlistedObject enlisted in orderedEnlisted)
{
_enlisted.Clear();
foreach (var enlisted in orderedEnlisted)
try
{
try
{
enlisted.Execute(completed);
}
catch (Exception e)
{
if (exceptions == null)
exceptions = new List<Exception>();
exceptions.Add(e);
}
enlisted.Execute(completed);
}
}
if (exceptions != null)
throw new AggregateException("Exceptions were thrown by listed actions.", exceptions);
}
public Guid InstanceId { get; } = Guid.NewGuid();
public int CreatedThreadId { get; } = Thread.CurrentThread.ManagedThreadId;
private IDictionary<string, IEnlistedObject> Enlisted => _enlisted
?? (_enlisted = new Dictionary<string, IEnlistedObject>());
private interface IEnlistedObject
{
void Execute(bool completed);
int Priority { get; }
}
private class EnlistedObject<T> : IEnlistedObject
{
private readonly Action<bool, T?>? _action;
public EnlistedObject(T? item, Action<bool, T?>? action, int priority)
{
Item = item;
Priority = priority;
_action = action;
}
public T? Item { get; }
public int Priority { get; }
public void Execute(bool completed)
{
if (_action is not null)
catch (Exception e)
{
_action(completed, Item);
if (exceptions == null)
{
exceptions = new List<Exception>();
}
exceptions.Add(e);
}
}
}
public void Enlist(string key, Action<bool> action, int priority = 100)
if (exceptions != null)
{
Enlist<object>(key, null, (completed, item) => action(completed), priority);
throw new AggregateException("Exceptions were thrown by listed actions.", exceptions);
}
}
public T? Enlist<T>(string key, Func<T>? creator, Action<bool, T?>? action = null, int priority = 100)
public int CreatedThreadId { get; } = Thread.CurrentThread.ManagedThreadId;
public void Enlist(string key, Action<bool> action, int priority = 100) =>
Enlist<object>(key, null, (completed, item) => action(completed), priority);
public T? Enlist<T>(string key, Func<T>? creator, Action<bool, T?>? action = null, int priority = 100)
{
Dictionary<string, IEnlistedObject> enlistedObjects =
_enlisted ??= new Dictionary<string, IEnlistedObject>();
if (enlistedObjects.TryGetValue(key, out IEnlistedObject? enlisted))
{
var enlistedObjects = _enlisted ?? (_enlisted = new Dictionary<string, IEnlistedObject>());
if (enlistedObjects.TryGetValue(key, out var enlisted))
{
if (!(enlisted is EnlistedObject<T> enlistedAs))
throw new InvalidOperationException("An item with the key already exists, but with a different type.");
if (enlistedAs.Priority != priority)
throw new InvalidOperationException("An item with the key already exits, but with a different priority.");
return enlistedAs.Item;
}
var enlistedOfT = new EnlistedObject<T>(creator == null ? default : creator(), action, priority);
Enlisted[key] = enlistedOfT;
return enlistedOfT.Item;
}
public T? GetEnlisted<T>(string key)
{
var enlistedObjects = _enlisted;
if (enlistedObjects == null) return default;
if (enlistedObjects.TryGetValue(key, out var enlisted) == false)
return default;
if (!(enlisted is EnlistedObject<T> enlistedAs))
throw new InvalidOperationException("An item with the key exists, but with a different type.");
{
throw new InvalidOperationException("An item with the key already exists, but with a different type.");
}
if (enlistedAs.Priority != priority)
{
throw new InvalidOperationException(
"An item with the key already exits, but with a different priority.");
}
return enlistedAs.Item;
}
var enlistedOfT = new EnlistedObject<T>(creator == null ? default : creator(), action, priority);
Enlisted[key] = enlistedOfT;
return enlistedOfT.Item;
}
public T? GetEnlisted<T>(string key)
{
Dictionary<string, IEnlistedObject>? enlistedObjects = _enlisted;
if (enlistedObjects == null)
{
return default;
}
if (enlistedObjects.TryGetValue(key, out IEnlistedObject? enlisted) == false)
{
return default;
}
if (!(enlisted is EnlistedObject<T> enlistedAs))
{
throw new InvalidOperationException("An item with the key exists, but with a different type.");
}
return enlistedAs.Item;
}
private class EnlistedObject<T> : IEnlistedObject
{
private readonly Action<bool, T?>? _action;
public EnlistedObject(T? item, Action<bool, T?>? action, int priority)
{
Item = item;
Priority = priority;
_action = action;
}
public T? Item { get; }
public int Priority { get; }
public void Execute(bool completed)
{
if (_action is not null)
{
_action(completed, Item);
}
}
}
}

View File

@@ -1,65 +1,69 @@
using System;
namespace Umbraco.Cms.Core.Scoping;
namespace Umbraco.Cms.Core.Scoping
/// <summary>
/// Provides a base class for scope contextual objects.
/// </summary>
/// <remarks>
/// <para>
/// A scope contextual object is enlisted in the current scope context,
/// if any, and released when the context exists. It must be used in a 'using'
/// block, and will be released when disposed, if not part of a scope.
/// </para>
/// </remarks>
public abstract class ScopeContextualBase : IDisposable
{
private bool _scoped;
/// <summary>
/// Provides a base class for scope contextual objects.
/// Gets a contextual object.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="key">A context key for the object.</param>
/// <param name="ctor">A function producing the contextual object.</param>
/// <returns>The contextual object.</returns>
/// <remarks>
/// <para>A scope contextual object is enlisted in the current scope context,
/// if any, and released when the context exists. It must be used in a 'using'
/// block, and will be released when disposed, if not part of a scope.</para>
/// <para></para>
/// </remarks>
public abstract class ScopeContextualBase : IDisposable
public static T? Get<T>(ICoreScopeProvider scopeProvider, string key, Func<bool, T> ctor)
where T : ScopeContextualBase
{
private bool _scoped;
/// <summary>
/// Gets a contextual object.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="scopeProvider">A scope provider.</param>
/// <param name="key">A context key for the object.</param>
/// <param name="ctor">A function producing the contextual object.</param>
/// <returns>The contextual object.</returns>
/// <remarks>
/// <para></para>
/// </remarks>
public static T? Get<T>(ICoreScopeProvider scopeProvider, string key, Func<bool, T> ctor)
where T : ScopeContextualBase
// no scope context = create a non-scoped object
IScopeContext? scopeContext = scopeProvider.Context;
if (scopeContext == null)
{
// no scope context = create a non-scoped object
var scopeContext = scopeProvider.Context;
if (scopeContext == null)
return ctor(false);
// create & enlist the scoped object
var w = scopeContext.Enlist("ScopeContextualBase_" + key,
() => ctor(true),
(completed, item) => { item?.Release(completed); });
if (w is not null)
{
w._scoped = true;
}
return w;
return ctor(false);
}
/// <inheritdoc />
/// <remarks>
/// <para>If not scoped, then this releases the contextual object.</para>
/// </remarks>
public void Dispose()
// create & enlist the scoped object
T? w = scopeContext.Enlist(
"ScopeContextualBase_" + key,
() => ctor(true),
(completed, item) => { item?.Release(completed); });
if (w is not null)
{
if (_scoped == false)
Release(true);
w._scoped = true;
}
/// <summary>
/// Releases the contextual object.
/// </summary>
/// <param name="completed">A value indicating whether the scoped operation completed.</param>
public abstract void Release(bool completed);
return w;
}
/// <inheritdoc />
/// <remarks>
/// <para>If not scoped, then this releases the contextual object.</para>
/// </remarks>
public void Dispose()
{
if (_scoped == false)
{
Release(true);
}
}
/// <summary>
/// Releases the contextual object.
/// </summary>
/// <param name="completed">A value indicating whether the scoped operation completed.</param>
public abstract void Release(bool completed);
}

View File

@@ -1,18 +1,15 @@
using System;
using System.Collections.Concurrent;
using System.Data;
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.Infrastructure.Persistence;
using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings;
using Umbraco.Extensions;
using System.Collections.Concurrent;
using System.Threading;
using Umbraco.Cms.Core.DistributedLocking;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Extensions;
#if DEBUG_SCOPES
using System.Linq;
using System.Text;
@@ -21,7 +18,7 @@ using System.Text;
namespace Umbraco.Cms.Infrastructure.Scoping
{
/// <summary>
/// Implements <see cref="IScopeProvider"/>.
/// Implements <see cref="IScopeProvider" />.
/// </summary>
internal class ScopeProvider :
ICoreScopeProvider,
@@ -35,10 +32,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private readonly FileSystems _fileSystems;
private CoreDebugSettings _coreDebugSettings;
private readonly MediaFileManager _mediaFileManager;
private static readonly AsyncLocal<ConcurrentStack<IScope>> s_scopeStack = new AsyncLocal<ConcurrentStack<IScope>>();
private static readonly AsyncLocal<ConcurrentStack<IScopeContext>> s_scopeContextStack = new AsyncLocal<ConcurrentStack<IScopeContext>>();
private static readonly string s_scopeItemKey = typeof(Scope).FullName!;
private static readonly string s_contextItemKey = typeof(ScopeProvider).FullName!;
private static readonly AsyncLocal<ConcurrentStack<IScope>> _scopeStack = new();
private static readonly AsyncLocal<ConcurrentStack<IScopeContext>> _scopeContextStack = new();
private static readonly string _scopeItemKey = typeof(Scope).FullName!;
private static readonly string _contextItemKey = typeof(ScopeProvider).FullName!;
private readonly IEventAggregator _eventAggregator;
public ScopeProvider(
@@ -60,6 +57,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
_loggerFactory = loggerFactory;
_requestCache = requestCache;
_eventAggregator = eventAggregator;
// take control of the FileSystems
_fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
@@ -76,30 +74,30 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private void MoveHttpContextScopeToCallContext()
{
var source = (ConcurrentStack<IScope>?)_requestCache.Get(s_scopeItemKey);
ConcurrentStack<IScope>? stack = s_scopeStack.Value;
MoveContexts(s_scopeItemKey, source, stack, (_, v) => s_scopeStack.Value = v);
var source = (ConcurrentStack<IScope>?)_requestCache.Get(_scopeItemKey);
ConcurrentStack<IScope>? stack = _scopeStack.Value;
MoveContexts(_scopeItemKey, source, stack, (_, v) => _scopeStack.Value = v);
}
private void MoveHttpContextScopeContextToCallContext()
{
var source = (ConcurrentStack<IScopeContext>?)_requestCache.Get(s_contextItemKey);
ConcurrentStack<IScopeContext>? stack = s_scopeContextStack.Value;
MoveContexts(s_contextItemKey, source, stack, (_, v) => s_scopeContextStack.Value = v);
var source = (ConcurrentStack<IScopeContext>?)_requestCache.Get(_contextItemKey);
ConcurrentStack<IScopeContext>? stack = _scopeContextStack.Value;
MoveContexts(_contextItemKey, source, stack, (_, v) => _scopeContextStack.Value = v);
}
private void MoveCallContextScopeToHttpContext()
{
ConcurrentStack<IScope>? source = s_scopeStack.Value;
var stack = (ConcurrentStack<IScope>?)_requestCache.Get(s_scopeItemKey);
MoveContexts(s_scopeItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
ConcurrentStack<IScope>? source = _scopeStack.Value;
var stack = (ConcurrentStack<IScope>?)_requestCache.Get(_scopeItemKey);
MoveContexts(_scopeItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
}
private void MoveCallContextScopeContextToHttpContext()
{
ConcurrentStack<IScopeContext>? source = s_scopeContextStack.Value;
var stack = (ConcurrentStack<IScopeContext>?)_requestCache.Get(s_contextItemKey);
MoveContexts(s_contextItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
ConcurrentStack<IScopeContext>? source = _scopeContextStack.Value;
var stack = (ConcurrentStack<IScopeContext>?)_requestCache.Get(_contextItemKey);
MoveContexts(_contextItemKey, source, stack, (k, v) => _requestCache.Set(k, v));
}
private void MoveContexts<T>(string key, ConcurrentStack<T>? source, ConcurrentStack<T>? stack, Action<string, ConcurrentStack<T>> setter)
@@ -134,7 +132,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
private void SetCallContextScope(IScope? value)
{
ConcurrentStack<IScope>? stack = s_scopeStack.Value;
ConcurrentStack<IScope>? stack = _scopeStack.Value;
#if DEBUG_SCOPES
// first, null-register the existing value
@@ -159,7 +157,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
}
else
{
#if DEBUG_SCOPES
_logger.LogDebug("AddObject " + value.InstanceId.ToString("N").Substring(0, 8));
#endif
@@ -167,14 +164,15 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
stack = new ConcurrentStack<IScope>();
}
stack.Push(value);
s_scopeStack.Value = stack;
_scopeStack.Value = stack;
}
}
private void SetCallContextScopeContext(IScopeContext? value)
{
ConcurrentStack<IScopeContext>? stack = s_scopeContextStack.Value;
ConcurrentStack<IScopeContext>? stack = _scopeContextStack.Value;
if (value == null)
{
@@ -189,12 +187,12 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
stack = new ConcurrentStack<IScopeContext>();
}
stack.Push(value);
s_scopeContextStack.Value = stack;
_scopeContextStack.Value = stack;
}
}
private T? GetHttpContextObject<T>(string key, bool required = true)
where T : class
{
@@ -253,6 +251,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
stack = new ConcurrentStack<T>();
}
stack.Push(value);
_requestCache.Set(key, stack);
}
@@ -265,23 +264,23 @@ namespace Umbraco.Cms.Infrastructure.Scoping
#region Ambient Context
/// <summary>
/// Get the Ambient (Current) <see cref="IScopeContext"/> for the current execution context.
/// Get the Ambient (Current) <see cref="IScopeContext" /> for the current execution context.
/// </summary>
/// <remarks>
/// 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)
/// </remarks>
public IScopeContext? AmbientContext
{
get
{
// try http context, fallback onto call context
IScopeContext? value = GetHttpContextObject<IScopeContext>(s_contextItemKey, false);
IScopeContext? value = GetHttpContextObject<IScopeContext>(_contextItemKey, false);
if (value != null)
{
return value;
}
ConcurrentStack<IScopeContext>? stack = s_scopeContextStack.Value;
ConcurrentStack<IScopeContext>? stack = _scopeContextStack.Value;
if (stack == null || !stack.TryPeek(out IScopeContext? peek))
{
return null;
@@ -298,23 +297,23 @@ namespace Umbraco.Cms.Infrastructure.Scoping
IScope? IScopeAccessor.AmbientScope => AmbientScope;
/// <summary>
/// Gets or set the Ambient (Current) <see cref="Scope"/> for the current execution context.
/// Gets or set the Ambient (Current) <see cref="Scope" /> for the current execution context.
/// </summary>
/// <remarks>
/// 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)
/// </remarks>
public Scope? AmbientScope
{
get
{
// try http context, fallback onto call context
IScope? value = GetHttpContextObject<IScope>(s_scopeItemKey, false);
IScope? value = GetHttpContextObject<IScope>(_scopeItemKey, false);
if (value != null)
{
return (Scope)value;
}
ConcurrentStack<IScope>? stack = s_scopeStack.Value;
ConcurrentStack<IScope>? stack = _scopeStack.Value;
if (stack == null || !stack.TryPeek(out IScope? peek))
{
return null;
@@ -327,7 +326,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
public void PopAmbientScope(Scope? scope)
{
// pop the stack from all contexts
SetHttpContextObject<IScope>(s_scopeItemKey, null, false);
SetHttpContextObject<IScope>(_scopeItemKey, null, false);
SetCallContextScope(null);
// We need to move the stack to a different context if the parent scope
@@ -335,7 +334,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
// 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.
bool parentScopeCallContext = (scope?.ParentScope?.CallContext ?? false);
var parentScopeCallContext = scope?.ParentScope?.CallContext ?? false;
if ((scope?.CallContext ?? false) && !parentScopeCallContext)
{
MoveCallContextScopeToHttpContext();
@@ -357,14 +356,13 @@ namespace Umbraco.Cms.Infrastructure.Scoping
throw new ArgumentNullException(nameof(scope));
}
if (scope.CallContext != false || !SetHttpContextObject<IScope>(s_scopeItemKey, scope, false))
if (scope.CallContext || !SetHttpContextObject<IScope>(_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();
@@ -382,14 +380,14 @@ namespace Umbraco.Cms.Infrastructure.Scoping
throw new ArgumentNullException(nameof(scopeContext));
}
SetHttpContextObject<IScopeContext>(s_contextItemKey, scopeContext, false);
SetHttpContextObject(_contextItemKey, scopeContext, false);
SetCallContextScopeContext(scopeContext);
}
public void PopAmbientScopeContext()
{
// pop stack from all contexts
SetHttpContextObject<IScopeContext>(s_contextItemKey, null, false);
SetHttpContextObject<IScopeContext>(_contextItemKey, null, false);
SetCallContextScopeContext(null);
}
@@ -400,7 +398,21 @@ namespace Umbraco.Cms.Infrastructure.Scoping
IEventDispatcher? eventDispatcher = null,
IScopedNotificationPublisher? scopedNotificationPublisher = null,
bool? scopeFileSystems = null)
=> new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems);
=>
new Scope(
this,
_coreDebugSettings,
_mediaFileManager,
_eventAggregator,
_loggerFactory.CreateLogger<Scope>(),
_fileSystems,
true,
null,
isolationLevel,
repositoryCacheMode,
eventDispatcher,
scopedNotificationPublisher,
scopeFileSystems);
/// <inheritdoc />
public void AttachScope(IScope other, bool callContext = false)
@@ -451,12 +463,14 @@ namespace Umbraco.Cms.Infrastructure.Scoping
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;
@@ -480,7 +494,22 @@ 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<Scope>(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
var scope = new Scope(
this,
_coreDebugSettings,
_mediaFileManager,
_eventAggregator,
_loggerFactory.CreateLogger<Scope>(),
_fileSystems,
false,
newContext,
isolationLevel,
repositoryCacheMode,
eventDispatcher,
notificationPublisher,
scopeFileSystems,
callContext,
autoComplete);
// assign only if scope creation did not throw!
PushAmbientScope(scope);
@@ -488,19 +517,33 @@ namespace Umbraco.Cms.Infrastructure.Scoping
{
PushAmbientScopeContext(newContext);
}
return scope;
}
var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger<Scope>(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete);
var nested = new Scope(
this,
_coreDebugSettings,
_mediaFileManager,
_eventAggregator,
_loggerFactory.CreateLogger<Scope>(),
_fileSystems,
ambientScope,
isolationLevel,
repositoryCacheMode,
eventDispatcher,
notificationPublisher,
scopeFileSystems,
callContext,
autoComplete);
PushAmbientScope(nested);
return nested;
}
/// <inheritdoc />
public IScopeContext? Context => AmbientContext;
// for testing
internal ConcurrentStack<IScope>? GetCallContextScopeValue() => s_scopeStack.Value;
internal ConcurrentStack<IScope>? GetCallContextScopeValue() => _scopeStack.Value;
#if DEBUG_SCOPES
// this code needs TLC