Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/merge-v8-05032021

# Conflicts:
#	src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs
#	src/Umbraco.Infrastructure/Search/ExamineComponent.cs
This commit is contained in:
Bjarke Berg
2021-03-09 09:28:48 +01:00
95 changed files with 1635 additions and 1477 deletions

View File

@@ -0,0 +1,261 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Umbraco.Cms.Core.Events;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Cache
{
/// <summary>
/// Implements a <see cref="IAppCache"/> on top of <see cref="IHttpContextAccessor"/>
/// </summary>
/// <remarks>
/// <para>The HttpContext is not thread safe and no part of it is which means we need to include our own thread
/// safety mechanisms. This relies on notifications: <see cref="UmbracoRequestBegin"/> and <see cref="UmbracoRequestEnd"/>
/// in order to facilitate the correct locking and releasing allocations.
/// </para>
/// </remarks>
public class HttpContextRequestAppCache : FastDictionaryAppCacheBase, IRequestCache
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
/// </summary>
public HttpContextRequestAppCache(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
public bool IsAvailable => TryGetContextItems(out _);
private bool TryGetContextItems(out IDictionary<object, object> items)
{
items = _httpContextAccessor.HttpContext?.Items;
return items != null;
}
/// <inheritdoc />
public override object Get(string key, Func<object> factory)
{
//no place to cache so just return the callback result
if (!TryGetContextItems(out var items))
{
return factory();
}
key = GetCacheKey(key);
Lazy<object> result;
try
{
EnterWriteLock();
result = items[key] as Lazy<object>; // null if key not found
// cannot create value within the lock, so if result.IsValueCreated is false, just
// do nothing here - means that if creation throws, a race condition could cause
// more than one thread to reach the return statement below and throw - accepted.
if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
{
result = SafeLazy.GetSafeLazy(factory);
items[key] = result;
}
}
finally
{
ExitWriteLock();
}
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
// exceptions (but try again and again) and silently eat them - however at
// some point we have to report them - so need to re-throw here
// this does not throw anymore
//return result.Value;
var value = result.Value; // will not throw (safe lazy)
if (value is SafeLazy.ExceptionHolder eh)
{
eh.Exception.Throw(); // throw once!
}
return value;
}
public bool Set(string key, object value)
{
//no place to cache so just return the callback result
if (!TryGetContextItems(out var items))
{
return false;
}
key = GetCacheKey(key);
try
{
EnterWriteLock();
items[key] = SafeLazy.GetSafeLazy(() => value);
}
finally
{
ExitWriteLock();
}
return true;
}
public bool Remove(string key)
{
//no place to cache so just return the callback result
if (!TryGetContextItems(out var items))
{
return false;
}
key = GetCacheKey(key);
try
{
EnterWriteLock();
items.Remove(key);
}
finally
{
ExitWriteLock();
}
return true;
}
#region Entries
protected override IEnumerable<KeyValuePair<object, object>> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
if (!TryGetContextItems(out var items))
return Enumerable.Empty<KeyValuePair<object, object>>();
return items.Cast<KeyValuePair<object, object>>()
.Where(x => x.Key is string s && s.StartsWith(prefix));
}
protected override void RemoveEntry(string key)
{
if (!TryGetContextItems(out var items))
return;
items.Remove(key);
}
protected override object GetEntry(string key)
{
return !TryGetContextItems(out var items) ? null : items[key];
}
#endregion
#region Lock
protected override void EnterReadLock()
{
object locker = GetLock();
if (locker == null)
{
return;
}
Monitor.Enter(locker);
}
protected override void EnterWriteLock()
{
object locker = GetLock();
if (locker == null)
{
return;
}
Monitor.Enter(locker);
}
protected override void ExitReadLock()
{
object locker = GetLock();
if (locker == null)
{
return;
}
if (Monitor.IsEntered(locker))
{
Monitor.Exit(locker);
}
}
protected override void ExitWriteLock()
{
object locker = GetLock();
if (locker == null)
{
return;
}
if (Monitor.IsEntered(locker))
{
Monitor.Exit(locker);
}
}
#endregion
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
if (!TryGetContextItems(out IDictionary<object, object> items))
{
yield break;
}
foreach (KeyValuePair<object, object> item in items)
{
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Ensures and returns the current lock
/// </summary>
/// <returns></returns>
private object GetLock()
{
HttpContext httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
return null;
}
RequestLock requestLock = httpContext.Features.Get<RequestLock>();
if (requestLock != null)
{
return requestLock.SyncRoot;
}
IFeatureCollection features = httpContext.Features;
lock (httpContext)
{
requestLock = new RequestLock();
features.Set(requestLock);
return requestLock.SyncRoot;
}
}
/// <summary>
/// Used as Scoped instance to allow locking within a request
/// </summary>
private class RequestLock
{
public object SyncRoot { get; } = new object();
}
}
}

View File

@@ -54,6 +54,7 @@ using Umbraco.Cms.Web.Common.Security;
using Umbraco.Cms.Web.Common.Templates;
using Umbraco.Cms.Web.Common.UmbracoContext;
using Umbraco.Core.Events;
using static Umbraco.Cms.Core.Cache.HttpContextRequestAppCache;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Extensions
@@ -86,15 +87,18 @@ namespace Umbraco.Extensions
IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config);
var loggingDir = tempHostingEnvironment.MapPathContentRoot(Cms.Core.Constants.SystemDirectories.LogFiles);
var loggingDir = tempHostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles);
var loggingConfig = new LoggingConfiguration(loggingDir);
services.AddLogger(tempHostingEnvironment, loggingConfig, config);
// Manually create and register the HttpContextAccessor. In theory this should not be registered
// again by the user but if that is the case it's not the end of the world since HttpContextAccessor
// is just based on AsyncLocal, see https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/HttpContextAccessor.cs
IHttpContextAccessor httpContextAccessor = new HttpContextAccessor();
services.AddSingleton(httpContextAccessor);
var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items);
var requestCache = new HttpContextRequestAppCache(httpContextAccessor);
var appCaches = AppCaches.Create(requestCache);
services.AddUnique(appCaches);
@@ -243,7 +247,6 @@ namespace Umbraco.Extensions
builder.Services.AddUmbracoImageSharp(builder.Config);
// AspNetCore specific services
builder.Services.AddUnique<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddUnique<IRequestAccessor, AspNetCoreRequestAccessor>();
builder.AddNotificationHandler<UmbracoRequestBegin, AspNetCoreRequestAccessor>();
@@ -267,10 +270,8 @@ namespace Umbraco.Extensions
// register the umbraco context factory
builder.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
builder.Services.AddUnique<IBackOfficeSecurityFactory, BackOfficeSecurityFactory>();
builder.Services.AddUnique<IBackOfficeSecurityAccessor, HybridBackofficeSecurityAccessor>();
builder.AddNotificationHandler<UmbracoRoutedRequest, UmbracoWebsiteSecurityFactory>();
builder.Services.AddUnique<IUmbracoWebsiteSecurityAccessor, HybridUmbracoWebsiteSecurityAccessor>();
builder.Services.AddUnique<IBackOfficeSecurityAccessor, BackOfficeSecurityAccessor>();
builder.Services.AddUnique<IUmbracoWebsiteSecurityAccessor, UmbracoWebsiteSecurityAccessor>();
var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList();
builder.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
@@ -290,6 +291,8 @@ namespace Umbraco.Extensions
builder.Services.AddSingleton<ContentModelBinder>();
builder.Services.AddScoped<UmbracoHelper>();
builder.Services.AddScoped<IBackOfficeSecurity, BackOfficeSecurity>();
builder.Services.AddScoped<IUmbracoWebsiteSecurity, UmbracoWebsiteSecurity>();
builder.AddHttpClients();

View File

@@ -1,13 +1,27 @@
using System;
using System;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Extensions
{
public static class HttpContextExtensions
{
/// <summary>
/// Get the value in the request form or query string for the key
/// </summary>
public static string GetRequestValue(this HttpContext context, string key)
{
HttpRequest request = context.Request;
if (!request.HasFormContentType)
{
return request.Query[key];
}
string value = request.Form[key];
return value ?? request.Query[key];
}
public static void SetPrincipalForRequest(this HttpContext context, ClaimsPrincipal principal)
{
context.User = principal;

View File

@@ -4,12 +4,14 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.PublishedCache;
@@ -36,7 +38,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly IRequestCache _requestCache;
private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory;
private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler;
private readonly IEventAggregator _eventAggregator;
private readonly IHostingEnvironment _hostingEnvironment;
@@ -52,7 +53,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
ILogger<UmbracoRequestMiddleware> logger,
IUmbracoContextFactory umbracoContextFactory,
IRequestCache requestCache,
IBackOfficeSecurityFactory backofficeSecurityFactory,
PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler,
IEventAggregator eventAggregator,
IProfiler profiler,
@@ -61,7 +61,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
_logger = logger;
_umbracoContextFactory = umbracoContextFactory;
_requestCache = requestCache;
_backofficeSecurityFactory = backofficeSecurityFactory;
_publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler;
_eventAggregator = eventAggregator;
_hostingEnvironment = hostingEnvironment;
@@ -84,11 +83,6 @@ namespace Umbraco.Cms.Web.Common.Middleware
EnsureContentCacheInitialized();
// TODO: This dependency chain is broken and needs to be fixed.
// This is required to be called before EnsureUmbracoContext else the UmbracoContext's IBackOfficeSecurity instance is null
// This is ugly Temporal Coupling which also means that developers can no longer just use IUmbracoContextFactory the
// way it was intended.
_backofficeSecurityFactory.EnsureBackOfficeSecurity();
UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext();
Uri currentApplicationUrl = GetApplicationUrlFromCurrentRequest(context.Request);
@@ -133,10 +127,11 @@ namespace Umbraco.Cms.Web.Common.Middleware
try
{
DisposeRequestCacheItems(_logger, _requestCache, context.Request);
DisposeHttpContextItems(context.Request);
}
finally
{
// Dispose the umbraco context reference which will in turn dispose the UmbracoContext itself.
umbracoContextReference.Dispose();
}
}
@@ -159,9 +154,9 @@ namespace Umbraco.Cms.Web.Common.Middleware
}
/// <summary>
/// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request
/// Dispose some request scoped objects that we are maintaining the lifecycle for.
/// </summary>
private static void DisposeRequestCacheItems(ILogger<UmbracoRequestMiddleware> logger, IRequestCache requestCache, HttpRequest request)
private void DisposeHttpContextItems(HttpRequest request)
{
// do not process if client-side request
if (request.IsClientSideRequest())
@@ -169,37 +164,9 @@ namespace Umbraco.Cms.Web.Common.Middleware
return;
}
// get a list of keys to dispose
var keys = new HashSet<string>();
foreach (var i in requestCache)
{
if (i.Value is IDisposeOnRequestEnd || i.Key is IDisposeOnRequestEnd)
{
keys.Add(i.Key);
}
}
// dispose each item and key that was found as disposable.
foreach (var k in keys)
{
try
{
requestCache.Get(k).DisposeIfDisposable();
}
catch (Exception ex)
{
logger.LogError("Could not dispose item with key " + k, ex);
}
try
{
k.DisposeIfDisposable();
}
catch (Exception ex)
{
logger.LogError("Could not dispose item key " + k, ex);
}
}
// ensure this is disposed by DI at the end of the request
IHttpScopeReference httpScopeReference = request.HttpContext.RequestServices.GetRequiredService<IHttpScopeReference>();
httpScopeReference.Register();
}
/// <summary>

View File

@@ -774,9 +774,10 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
}
public void Stop(bool immediate)
{
{
_watcher.EnableRaisingEvents = false;
_watcher.Dispose();
_locker.Dispose();
_hostingLifetime.UnregisterObject(this);
}

View File

@@ -69,9 +69,10 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
/// This is used so that when new PureLive models are built, the entire razor stack is re-constructed so all razor
/// caches and assembly references, etc... are cleared.
/// </remarks>
internal class RefreshingRazorViewEngine : IRazorViewEngine
internal class RefreshingRazorViewEngine : IRazorViewEngine, IDisposable
{
private IRazorViewEngine _current;
private bool _disposedValue;
private readonly PureLiveModelFactory _pureLiveModelFactory;
private readonly Func<IRazorViewEngine> _defaultRazorViewEngineFactory;
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
@@ -172,5 +173,24 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder
_locker.ExitReadLock();
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_locker.Dispose();
}
_disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Http;

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Web.Common.Security
{
public class BackOfficeSecurityAccessor : IBackOfficeSecurityAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeSecurityAccessor"/> class.
/// </summary>
public BackOfficeSecurityAccessor(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
/// <summary>
/// Gets the current <see cref="IBackOfficeSecurity"/> object.
/// </summary>
public IBackOfficeSecurity BackOfficeSecurity
=> _httpContextAccessor.HttpContext?.RequestServices?.GetService<IBackOfficeSecurity>();
}
}

View File

@@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Security;

View File

@@ -1,34 +0,0 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Web.Common.Security
{
// TODO: This is only for the back office, does it need to be in common? YES currently UmbracoContext has an transitive dependency on this which needs to be fixed/reviewed.
public class BackOfficeSecurityFactory: IBackOfficeSecurityFactory
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IUserService _userService;
private readonly IHttpContextAccessor _httpContextAccessor;
public BackOfficeSecurityFactory(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IUserService userService,
IHttpContextAccessor httpContextAccessor)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_userService = userService;
_httpContextAccessor = httpContextAccessor;
}
public void EnsureBackOfficeSecurity()
{
if (_backOfficeSecurityAccessor.BackOfficeSecurity is null)
{
_backOfficeSecurityAccessor.BackOfficeSecurity = new BackOfficeSecurity(_userService, _httpContextAccessor);
}
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Cms.Web.Common.Security
{
public class UmbracoWebsiteSecurityAccessor : IUmbracoWebsiteSecurityAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoWebsiteSecurityAccessor"/> class.
/// </summary>
public UmbracoWebsiteSecurityAccessor(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor;
/// <summary>
/// Gets or sets the <see cref="IUmbracoWebsiteSecurity"/> object.
/// </summary>
public IUmbracoWebsiteSecurity WebsiteSecurity
=> _httpContextAccessor.HttpContext?.RequestServices?.GetService<IUmbracoWebsiteSecurity>();
}
}

View File

@@ -1,46 +0,0 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
namespace Umbraco.Cms.Web.Common.Security
{
/// <summary>
/// Ensures that the <see cref="IUmbracoWebsiteSecurity"/> is populated on a front-end request
/// </summary>
internal sealed class UmbracoWebsiteSecurityFactory : INotificationHandler<UmbracoRoutedRequest>
{
private readonly IUmbracoWebsiteSecurityAccessor _umbracoWebsiteSecurityAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IMemberService _memberService;
private readonly IMemberTypeService _memberTypeService;
private readonly IShortStringHelper _shortStringHelper;
public UmbracoWebsiteSecurityFactory(
IUmbracoWebsiteSecurityAccessor umbracoWebsiteSecurityAccessor,
IHttpContextAccessor httpContextAccessor,
IMemberService memberService,
IMemberTypeService memberTypeService,
IShortStringHelper shortStringHelper)
{
_umbracoWebsiteSecurityAccessor = umbracoWebsiteSecurityAccessor;
_httpContextAccessor = httpContextAccessor;
_memberService = memberService;
_memberTypeService = memberTypeService;
_shortStringHelper = shortStringHelper;
}
public void Handle(UmbracoRoutedRequest notification)
{
if (_umbracoWebsiteSecurityAccessor.WebsiteSecurity is null)
{
_umbracoWebsiteSecurityAccessor.WebsiteSecurity = new UmbracoWebsiteSecurity(
_httpContextAccessor,
_memberService,
_memberTypeService,
_shortStringHelper);
}
}
}
}

View File

@@ -21,12 +21,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.3" />
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="SixLabors.ImageSharp.Web" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp.Web" Version="1.0.2" />
<PackageReference Include="Smidge" Version="3.2.0" />
<PackageReference Include="Smidge.Nuglify" Version="3.2.0" />
</ItemGroup>

View File

@@ -1,10 +1,11 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;
@@ -13,17 +14,17 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
/// <summary>
/// Class that encapsulates Umbraco information of a specific HTTP request
/// </summary>
public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext
public class UmbracoContext : DisposableObjectSlim, IUmbracoContext
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly UriUtility _uriUtility;
private readonly ICookieManager _cookieManager;
private readonly IRequestAccessor _requestAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Lazy<IPublishedSnapshot> _publishedSnapshot;
private string _previewToken;
private bool? _previewing;
private readonly IBackOfficeSecurity _backofficeSecurity;
private readonly UmbracoRequestPaths _umbracoRequestPaths;
private Uri _requestUrl;
private Uri _originalRequestUrl;
private Uri _cleanedUmbracoUrl;
@@ -33,13 +34,12 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
// warn: does *not* manage setting any IUmbracoContextAccessor
internal UmbracoContext(
IPublishedSnapshotService publishedSnapshotService,
IBackOfficeSecurity backofficeSecurity,
UmbracoRequestPaths umbracoRequestPaths,
IHostingEnvironment hostingEnvironment,
IVariationContextAccessor variationContextAccessor,
UriUtility uriUtility,
ICookieManager cookieManager,
IRequestAccessor requestAccessor)
IHttpContextAccessor httpContextAccessor)
{
if (publishedSnapshotService == null)
{
@@ -50,11 +50,9 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
_uriUtility = uriUtility;
_hostingEnvironment = hostingEnvironment;
_cookieManager = cookieManager;
_requestAccessor = requestAccessor;
_httpContextAccessor = httpContextAccessor;
ObjectCreated = DateTime.Now;
UmbracoRequestId = Guid.NewGuid();
_backofficeSecurity = backofficeSecurity ?? throw new ArgumentNullException(nameof(backofficeSecurity));
_umbracoRequestPaths = umbracoRequestPaths;
// beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing
@@ -72,6 +70,11 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
/// </remarks>
internal Guid UmbracoRequestId { get; }
// lazily get/create a Uri for the current request
private Uri RequestUrl => _requestUrl ??= _httpContextAccessor.HttpContext is null
? null
: new Uri(_httpContextAccessor.HttpContext.Request.GetEncodedUrl());
/// <inheritdoc/>
// set the urls lazily, no need to allocate until they are needed...
// NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this
@@ -79,7 +82,7 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
// 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get
// the current domain during application startup.
// see: http://issues.umbraco.org/issue/U4-1890
public Uri OriginalRequestUrl => _originalRequestUrl ?? (_originalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost"));
public Uri OriginalRequestUrl => _originalRequestUrl ?? (_originalRequestUrl = RequestUrl ?? new Uri("http://localhost"));
/// <inheritdoc/>
// set the urls lazily, no need to allocate until they are needed...
@@ -106,8 +109,8 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
/// <inheritdoc/>
public bool IsDebug => // NOTE: the request can be null during app startup!
_hostingEnvironment.IsDebugMode
&& (string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebugshowtrace")) == false
|| string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebug")) == false
&& (string.IsNullOrEmpty(_httpContextAccessor.HttpContext.GetRequestValue("umbdebugshowtrace")) == false
|| string.IsNullOrEmpty(_httpContextAccessor.HttpContext.GetRequestValue("umbdebug")) == false
|| string.IsNullOrEmpty(_cookieManager.GetCookieValue("UMB-DEBUG")) == false);
/// <inheritdoc/>
@@ -140,10 +143,9 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
private void DetectPreviewMode()
{
Uri requestUrl = _requestAccessor.GetRequestUrl();
if (requestUrl != null
&& _umbracoRequestPaths.IsBackOfficeRequest(requestUrl.AbsolutePath) == false
&& _backofficeSecurity.CurrentUser != null)
if (RequestUrl != null
&& _umbracoRequestPaths.IsBackOfficeRequest(RequestUrl.AbsolutePath) == false
&& _httpContextAccessor.HttpContext?.GetCurrentIdentity() != null)
{
var previewToken = _cookieManager.GetCookieValue(Core.Constants.Web.PreviewCookieName); // may be null or empty
_previewToken = previewToken.IsNullOrWhiteSpace() ? null : previewToken;

View File

@@ -1,10 +1,10 @@
using System;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Web;
namespace Umbraco.Cms.Web.Common.UmbracoContext
@@ -18,12 +18,10 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
private readonly IPublishedSnapshotService _publishedSnapshotService;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
private readonly UmbracoRequestPaths _umbracoRequestPaths;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ICookieManager _cookieManager;
private readonly IRequestAccessor _requestAccessor;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UriUtility _uriUtility;
/// <summary>
@@ -38,8 +36,7 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
IHostingEnvironment hostingEnvironment,
UriUtility uriUtility,
ICookieManager cookieManager,
IRequestAccessor requestAccessor,
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
IHttpContextAccessor httpContextAccessor)
{
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
_publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService));
@@ -49,8 +46,7 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_uriUtility = uriUtility ?? throw new ArgumentNullException(nameof(uriUtility));
_cookieManager = cookieManager ?? throw new ArgumentNullException(nameof(cookieManager));
_requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(requestAccessor));
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private IUmbracoContext CreateUmbracoContext()
@@ -75,13 +71,12 @@ namespace Umbraco.Cms.Web.Common.UmbracoContext
return new UmbracoContext(
_publishedSnapshotService,
_backofficeSecurityAccessor.BackOfficeSecurity,
_umbracoRequestPaths,
_hostingEnvironment,
_variationContextAccessor,
_uriUtility,
_cookieManager,
_requestAccessor);
_httpContextAccessor);
}
/// <inheritdoc />