Merge remote-tracking branch 'origin/netcore/dev' into netcore/bugfix/logging-fixing

# Conflicts:
#	src/Umbraco.Tests.Integration/RuntimeTests.cs
#	src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
#	src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
This commit is contained in:
Bjarke Berg
2020-04-22 10:29:21 +02:00
29 changed files with 555 additions and 142 deletions

View File

@@ -0,0 +1,190 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Implements a fast <see cref="IAppCache"/> on top of HttpContext.Items.
/// </summary>
/// <remarks>
/// <para>If no current HttpContext items can be found (no current HttpContext,
/// or no Items...) then this cache acts as a pass-through and does not cache
/// anything.</para>
/// </remarks>
public class GenericDictionaryRequestAppCache : FastDictionaryAppCacheBase, IRequestCache
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
/// </summary>
public GenericDictionaryRequestAppCache(Func<IDictionary<object, object>> requestItems) : base()
{
ContextItems = requestItems;
}
private Func<IDictionary<object, object>> ContextItems { get; }
public bool IsAvailable => TryGetContextItems(out _);
private bool TryGetContextItems(out IDictionary<object, object> items)
{
items = ContextItems?.Invoke();
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<DictionaryEntry> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
if (!TryGetContextItems(out var items)) return Enumerable.Empty<DictionaryEntry>();
return items.Cast<DictionaryEntry>()
.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
private const string ContextItemsLockKey = "Umbraco.Core.Cache.HttpRequestCache::LockEntered";
protected override void EnterReadLock() => EnterWriteLock();
protected override void EnterWriteLock()
{
if (!TryGetContextItems(out var items)) return;
// note: cannot keep 'entered' as a class variable here,
// since there is one per request - so storing it within
// ContextItems - which is locked, so this should be safe
var entered = false;
Monitor.Enter(items, ref entered);
items[ContextItemsLockKey] = entered;
}
protected override void ExitReadLock() => ExitWriteLock();
protected override void ExitWriteLock()
{
if (!TryGetContextItems(out var items)) return;
var entered = (bool?)items[ContextItemsLockKey] ?? false;
if (entered)
Monitor.Exit(items);
items.Remove(ContextItemsLockKey);
}
#endregion
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
if (!TryGetContextItems(out var items))
{
yield break;
}
foreach (var item in items)
{
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Net
{
public class NullSessionIdResolver : ISessionIdResolver
{
public string SessionId => null;
}
}

View File

@@ -29,6 +29,7 @@ using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Examine;
using Umbraco.Infrastructure.Media;
using Umbraco.Net;
using Umbraco.Web;
using Umbraco.Web.Actions;
using Umbraco.Web.Cache;
@@ -351,10 +352,6 @@ namespace Umbraco.Core.Runtime
// register accessors for cultures
composition.RegisterUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
}
}
}

View File

@@ -25,6 +25,7 @@ namespace Umbraco.Core.Runtime
private IFactory _factory;
private readonly RuntimeState _state;
private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker;
private readonly IRequestCache _requestCache;
private readonly IGlobalSettings _globalSettings;
private readonly IConnectionStrings _connectionStrings;
@@ -39,7 +40,8 @@ namespace Umbraco.Core.Runtime
IBackOfficeInfo backOfficeInfo,
IDbProviderFactoryCreator dbProviderFactoryCreator,
IMainDom mainDom,
ITypeFinder typeFinder)
ITypeFinder typeFinder,
IRequestCache requestCache)
{
IOHelper = ioHelper;
Configs = configs;
@@ -50,6 +52,7 @@ namespace Umbraco.Core.Runtime
DbProviderFactoryCreator = dbProviderFactoryCreator;
_umbracoBootPermissionChecker = umbracoBootPermissionChecker;
_requestCache = requestCache;
Logger = logger;
MainDom = mainDom;
@@ -110,6 +113,7 @@ namespace Umbraco.Core.Runtime
{
if (register is null) throw new ArgumentNullException(nameof(register));
// create and register the essential services
// ie the bare minimum required to boot
@@ -129,12 +133,19 @@ namespace Umbraco.Core.Runtime
"Booted.",
"Boot failed."))
{
Logger.Info<CoreRuntime>("Booting Core");
Logger.Info<CoreRuntime>("Booting site '{HostingSiteName}', app '{HostingApplicationId}', path '{HostingPhysicalPath}', server '{MachineName}'.",
HostingEnvironment?.SiteName,
HostingEnvironment?.ApplicationId,
HostingEnvironment?.ApplicationPhysicalPath,
NetworkHelper.MachineName);
Logger.Debug<CoreRuntime>("Runtime: {Runtime}", GetType().FullName);
// application environment
ConfigureUnhandledException();
return _factory = Configure(register, timer);
_factory = Configure(register, timer);
return _factory;
}
}
@@ -151,7 +162,7 @@ namespace Umbraco.Core.Runtime
try
{
// run handlers
RuntimeOptions.DoRuntimeBoot(ProfilingLogger);
@@ -244,6 +255,13 @@ namespace Umbraco.Core.Runtime
// create & initialize the components
_components = _factory.GetInstance<ComponentCollection>();
_components.Initialize();
// now (and only now) is the time to switch over to perWebRequest scopes.
// up until that point we may not have a request, and scoped services would
// fail to resolve - but we run Initialize within a factory scope - and then,
// here, we switch the factory to bind scopes to requests
_factory.EnablePerWebRequestScope();
}
protected virtual void ConfigureUnhandledException()
@@ -350,7 +368,7 @@ namespace Umbraco.Core.Runtime
return new AppCaches(
new DeepCloneAppCache(new ObjectCacheAppCache()),
NoAppCache.Instance,
_requestCache,
new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache())));
}

View File

@@ -1,90 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Runtime;
namespace Umbraco.Web.Runtime
{
/// <summary>
/// Represents the Web Umbraco runtime.
/// </summary>
/// <remarks>On top of CoreRuntime, handles all of the web-related runtime aspects of Umbraco.</remarks>
public class WebRuntime : CoreRuntime
{
private readonly IRequestCache _requestCache;
/// <summary>
/// Initializes a new instance of the <see cref="WebRuntime"/> class.
/// </summary>
public WebRuntime(
Configs configs,
IUmbracoVersion umbracoVersion,
IIOHelper ioHelper,
ILogger logger,
IProfiler profiler,
IHostingEnvironment hostingEnvironment,
IBackOfficeInfo backOfficeInfo,
IDbProviderFactoryCreator dbProviderFactoryCreator,
IMainDom mainDom,
ITypeFinder typeFinder,
IRequestCache requestCache,
IUmbracoBootPermissionChecker umbracoBootPermissionChecker):
base(configs, umbracoVersion, ioHelper, logger, profiler ,umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder)
{
_requestCache = requestCache;
}
/// <inheritdoc/>
public override IFactory Configure(IRegister register)
{
var profilingLogger = new ProfilingLogger(Logger, Profiler);
var umbracoVersion = new UmbracoVersion();
using (var timer = profilingLogger.TraceDuration<CoreRuntime>(
$"Booting Umbraco {umbracoVersion.SemanticVersion.ToSemanticString()}.",
"Booted.",
"Boot failed."))
{
Logger.Info<CoreRuntime>("Booting site '{HostingSiteName}', app '{HostingApplicationId}', path '{HostingPhysicalPath}', server '{MachineName}'.",
HostingEnvironment.SiteName,
HostingEnvironment.ApplicationId,
HostingEnvironment.ApplicationPhysicalPath,
NetworkHelper.MachineName);
Logger.Debug<CoreRuntime>("Runtime: {Runtime}", GetType().FullName);
var factory = base.Configure(register);
// now (and only now) is the time to switch over to perWebRequest scopes.
// up until that point we may not have a request, and scoped services would
// fail to resolve - but we run Initialize within a factory scope - and then,
// here, we switch the factory to bind scopes to requests
factory.EnablePerWebRequestScope();
return factory;
}
}
#region Getters
protected override AppCaches GetAppCaches() => new AppCaches(
// we need to have the dep clone runtime cache provider to ensure
// all entities are cached properly (cloned in and cloned out)
new DeepCloneAppCache(new ObjectCacheAppCache()),
// we need request based cache when running in web-based context
_requestCache,
new IsolatedCaches(type =>
// we need to have the dep clone runtime cache provider to ensure
// all entities are cached properly (cloned in and cloned out)
new DeepCloneAppCache(new ObjectCacheAppCache())));
#endregion
}
}

View File

@@ -4,8 +4,10 @@ using Moq;
using NUnit.Framework;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Smidge;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Runtime;
@@ -57,7 +59,7 @@ namespace Umbraco.Tests.Integration
var coreRuntime = new CoreRuntime(testHelper.GetConfigs(), testHelper.GetUmbracoVersion(),
testHelper.IOHelper, testHelper.Logger, testHelper.Profiler, testHelper.UmbracoBootPermissionChecker,
testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo(), testHelper.DbProviderFactoryCreator,
testHelper.MainDom, testHelper.GetTypeFinder());
testHelper.MainDom, testHelper.GetTypeFinder(), NoAppCache.Instance);
// boot it!
var factory = coreRuntime.Configure(umbracoContainer);
@@ -99,7 +101,7 @@ namespace Umbraco.Tests.Integration
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, testHelper.GetLoggingConfiguration(), out _);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
});
var host = await hostBuilder.StartAsync();
@@ -138,7 +140,7 @@ namespace Umbraco.Tests.Integration
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, testHelper.GetLoggingConfiguration(), out _);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
});
var host = await hostBuilder.StartAsync();

View File

@@ -108,7 +108,7 @@ namespace Umbraco.Tests.Integration.Testing
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, testHelper.GetLoggingConfiguration(), out _);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
});
var host = await hostBuilder.StartAsync();

View File

@@ -16,10 +16,10 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Core.Strings;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Runtime;
using Umbraco.Core.Services;
using Umbraco.Tests.PublishedContent;
using Umbraco.Tests.Testing;
@@ -47,10 +47,10 @@ namespace Umbraco.Tests.Routing
HostingEnvironment);
}
public class TestRuntime : WebRuntime
public class TestRuntime : CoreRuntime
{
public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo)
: base(configs, umbracoVersion, ioHelper, Mock.Of<ILogger>(), Mock.Of<IProfiler>(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), TestHelper.GetRequestCache(), new AspNetUmbracoBootPermissionChecker())
: base(configs, umbracoVersion, ioHelper, Mock.Of<ILogger>(), Mock.Of<IProfiler>(), new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), NoAppCache.Instance)
{
}

View File

@@ -5,6 +5,7 @@ using Examine;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
@@ -116,7 +117,7 @@ namespace Umbraco.Tests.Runtimes
public class TestRuntime : CoreRuntime
{
public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo)
:base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder())
:base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), NoAppCache.Instance)
{
}

View File

@@ -76,7 +76,7 @@ namespace Umbraco.Tests.Runtimes
var runtimeState = new RuntimeState(logger, null, umbracoVersion, backOfficeInfo);
var configs = TestHelper.GetConfigs();
var variationContextAccessor = TestHelper.VariationContextAccessor;
// create the register and the composition
var register = TestHelper.GetRegister();
@@ -84,7 +84,7 @@ namespace Umbraco.Tests.Runtimes
composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, hostingEnvironment, backOfficeInfo);
// create the core runtime and have it compose itself
var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder);
var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance);
// determine actual runtime level
runtimeState.DetermineRuntimeLevel(databaseFactory, logger);
@@ -278,7 +278,7 @@ namespace Umbraco.Tests.Runtimes
composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, hostingEnvironment, backOfficeInfo);
// create the core runtime and have it compose itself
var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder);
var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance);
// get the components
// all of them?
@@ -322,8 +322,8 @@ namespace Umbraco.Tests.Runtimes
Assert.AreEqual(0, results.Count);
}
}
}

View File

@@ -2,9 +2,7 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Core.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Runtime;
using Umbraco.Core.WebAssets;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.Common.ActionResults;
namespace Umbraco.Web.BackOffice.Filters
@@ -14,10 +12,11 @@ namespace Umbraco.Web.BackOffice.Filters
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// logic before action goes here
var hostingEnvironment = context.HttpContext.RequestServices.GetService<IHostingEnvironment>();
var serviceProvider = context.HttpContext.RequestServices;
var hostingEnvironment = serviceProvider.GetService<IHostingEnvironment>();
if (!hostingEnvironment.IsDebugMode)
{
var runtimeMinifier = context.HttpContext.RequestServices.GetService<IRuntimeMinifier>();
var runtimeMinifier = serviceProvider.GetService<IRuntimeMinifier>();
if (context.Result is JavaScriptResult jsResult)
{

View File

@@ -0,0 +1,12 @@
namespace Umbraco.Web.Common.Constants
{
/// <summary>
/// constants
/// </summary>
internal static class ViewConstants
{
internal const string ViewLocation = "~/Views";
internal const string DataTokenCurrentViewContext = "umbraco-current-view-context";
}
}

View File

@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Web.Common.Controllers
{
public abstract class RenderController : Controller
{
}
}

View File

@@ -0,0 +1,17 @@
using System;
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Web.Common.Events
{
public class ActionExecutedEventArgs : EventArgs
{
public Controller Controller { get; set; }
public object Model { get; set; }
public ActionExecutedEventArgs(Controller controller, object model)
{
Controller = controller;
Model = model;
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Reflection;
@@ -80,7 +82,17 @@ namespace Umbraco.Web.Common.Extensions
Path.Combine(webHostEnvironment.ContentRootPath, "config\\serilog.config"),
Path.Combine(webHostEnvironment.ContentRootPath, "config\\serilog.user.config"));
services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), loggingConfig, out factory);
IHttpContextAccessor httpContextAccessor = new HttpContextAccessor();
services.AddSingleton<IHttpContextAccessor>(httpContextAccessor);
var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext.Items);
services.AddUmbracoCore(webHostEnvironment,
umbContainer,
Assembly.GetEntryAssembly(),
requestCache,
loggingConfig,
out factory);
return services;
}
@@ -92,6 +104,8 @@ namespace Umbraco.Web.Common.Extensions
/// <param name="webHostEnvironment"></param>
/// <param name="umbContainer"></param>
/// <param name="entryAssembly"></param>
/// <param name="requestCache"></param>
/// <param name="httpContextAccessor"></param>
/// <param name="loggingConfiguration"></param>
/// <param name="factory"></param>
/// <returns></returns>
@@ -100,6 +114,7 @@ namespace Umbraco.Web.Common.Extensions
IWebHostEnvironment webHostEnvironment,
IRegister umbContainer,
Assembly entryAssembly,
IRequestCache requestCache,
ILoggingConfiguration loggingConfiguration,
out IFactory factory)
{
@@ -108,11 +123,15 @@ namespace Umbraco.Web.Common.Extensions
if (container is null) throw new ArgumentNullException(nameof(container));
if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly));
// Special case! The generic host adds a few default services but we need to manually add this one here NOW because
// we resolve it before the host finishes configuring in the call to CreateCompositionRoot
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var serviceProvider = services.BuildServiceProvider();
var configs = serviceProvider.GetService<Configs>();
CreateCompositionRoot(services, webHostEnvironment, loggingConfiguration, out var logger, out var configs, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler);
CreateCompositionRoot(services,
configs,
webHostEnvironment,
loggingConfiguration,
out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler);
var globalSettings = configs.Global();
var umbracoVersion = new UmbracoVersion(globalSettings);
@@ -125,7 +144,8 @@ namespace Umbraco.Web.Common.Extensions
profiler,
hostingEnvironment,
backOfficeInfo,
CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly));
CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly),
requestCache);
factory = coreRuntime.Configure(container);
@@ -142,11 +162,12 @@ namespace Umbraco.Web.Common.Extensions
return new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(entryAssembly), runtimeHash);
}
private static IRuntime GetCoreRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, Core.Logging.ILogger logger,
private static IRuntime GetCoreRuntime(
Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, Core.Logging.ILogger logger,
IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
ITypeFinder typeFinder)
ITypeFinder typeFinder, IRequestCache requestCache)
{
var connectionStringConfig = configs.ConnectionStrings()[Constants.System.UmbracoConnectionName];
var connectionStringConfig = configs.ConnectionStrings()[Core.Constants.System.UmbracoConnectionName];
var dbProviderFactoryCreator = new SqlServerDbProviderFactoryCreator(
connectionStringConfig?.ProviderName,
DbProviderFactories.GetFactory);
@@ -161,23 +182,34 @@ namespace Umbraco.Web.Common.Extensions
var mainDom = new MainDom(logger, mainDomLock);
var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetCoreBootPermissionsChecker(),
hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder);
var coreRuntime = new CoreRuntime(
configs,
umbracoVersion,
ioHelper,
logger,
profiler,
new AspNetCoreBootPermissionsChecker(),
hostingEnvironment,
backOfficeInfo,
dbProviderFactoryCreator,
mainDom,
typeFinder,
requestCache);
return coreRuntime;
}
private static IServiceCollection CreateCompositionRoot(
IServiceCollection services,
Configs configs,
IWebHostEnvironment webHostEnvironment,
ILoggingConfiguration loggingConfiguration,
out Core.Logging.ILogger logger, out Configs configs, out IIOHelper ioHelper, out Core.Hosting.IHostingEnvironment hostingEnvironment,
out IBackOfficeInfo backOfficeInfo, out IProfiler profiler)
ILoggingConfiguration loggingConfiguration,
out Core.Logging.ILogger logger,
out IIOHelper ioHelper,
out Core.Hosting.IHostingEnvironment hostingEnvironment,
out IBackOfficeInfo backOfficeInfo,
out IProfiler profiler)
{
// TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured
var serviceProvider = services.BuildServiceProvider();
configs = serviceProvider.GetService<Configs>();
if (configs == null)
throw new InvalidOperationException($"Could not resolve type {typeof(Configs)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}");
@@ -205,13 +237,13 @@ namespace Umbraco.Web.Common.Extensions
// Wire up all the bits that serilog needs. We need to use our own code since the Serilog ext methods don't cater to our needs since
// we don't want to use the global serilog `Log` object and we don't have our own ILogger implementation before the HostBuilder runs which
// is the only other option that these ext methods allow.
// is the only other option that these ext methods allow.
// I have created a PR to make this nicer https://github.com/serilog/serilog-extensions-hosting/pull/19 but we'll need to wait for that.
// Also see : https://github.com/serilog/serilog-extensions-hosting/blob/dev/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs
services.AddSingleton<ILoggerFactory>(services => new SerilogLoggerFactory(logger.SerilogLog, false));
// This won't (and shouldn't) take ownership of the logger.
// This won't (and shouldn't) take ownership of the logger.
services.AddSingleton(logger.SerilogLog);
// Registered to provide two services...
@@ -229,7 +261,7 @@ namespace Umbraco.Web.Common.Extensions
public static IServiceCollection AddUmbracoRuntimeMinifier(this IServiceCollection services,
IConfiguration configuration)
{
services.AddSmidge(configuration.GetSection(Constants.Configuration.ConfigRuntimeMinification));
services.AddSmidge(configuration.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification));
services.AddSmidgeNuglify();
return services;
@@ -260,4 +292,5 @@ namespace Umbraco.Web.Common.Extensions
}
}

View File

@@ -0,0 +1,35 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Net.Http.Headers;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Ensures that the request is not cached by the browser
/// </summary>
public class DisableBrowserCacheAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
var httpResponse = context.HttpContext.Response;
if (httpResponse.StatusCode != 200) return;
httpResponse.GetTypedHeaders().CacheControl =
new CacheControlHeaderValue()
{
NoCache = true,
MaxAge = TimeSpan.Zero,
MustRevalidate = true,
NoStore = true
};
httpResponse.Headers[HeaderNames.LastModified] = DateTime.Now.ToString("R"); // Format RFC1123
httpResponse.Headers[HeaderNames.Pragma] = "no-cache";
httpResponse.Headers[HeaderNames.Expires] = new DateTime(1990, 1, 1, 0, 0, 0).ToString("R");
}
}
}

View File

@@ -0,0 +1,88 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Umbraco.Web.Common.Constants;
using Umbraco.Web.Common.Controllers;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// This is a special filter which is required for the RTE to be able to render Partial View Macros that
/// contain forms when the RTE value is resolved outside of an MVC view being rendered
/// </summary>
/// <remarks>
/// The entire way that we support partial view macros that contain forms isn't really great, these forms
/// need to be executed as ChildActions so that the ModelState,ViewData,TempData get merged into that action
/// so the form can show errors, viewdata, etc...
/// Under normal circumstances, macros will be rendered after a ViewContext is created but in some cases
/// developers will resolve the RTE value in the controller, in this case the Form won't be rendered correctly
/// with merged ModelState from the controller because the special DataToken hasn't been set yet (which is
/// normally done in the UmbracoViewPageOfModel when a real ViewContext is available.
/// So we need to detect if the currently rendering controller is IRenderController and if so we'll ensure that
/// this DataToken exists before the action executes in case the developer resolves an RTE value that contains
/// a partial view macro form.
/// </remarks>
public class EnsurePartialViewMacroViewContextFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// Ensures the custom ViewContext datatoken is set before the RenderController action is invoked,
/// this ensures that any calls to GetPropertyValue with regards to RTE or Grid editors can still
/// render any PartialViewMacro with a form and maintain ModelState
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!(context.Controller is Controller controller)) return;
//ignore anything that is not IRenderController
if (!(controller is RenderController)) return;
SetViewContext(context, controller);
}
/// <summary>
/// Ensures that the custom ViewContext datatoken is set after the RenderController action is invoked,
/// this ensures that any custom ModelState that may have been added in the RenderController itself is
/// passed onwards in case it is required when rendering a PartialViewMacro with a form
/// </summary>
/// <param name="context">The filter context.</param>
public override void OnResultExecuting(ResultExecutingContext context)
{
if (!(context.Controller is Controller controller)) return;
//ignore anything that is not IRenderController
if (!(controller is RenderController)) return;
SetViewContext(context, controller);
}
private void SetViewContext(ActionContext context, Controller controller)
{
var viewCtx = new ViewContext(
context,
new DummyView(),
controller.ViewData,
controller.TempData,
new StringWriter(),
new HtmlHelperOptions());
//set the special data token
context.RouteData.DataTokens[ViewConstants.DataTokenCurrentViewContext] = viewCtx;
}
private class DummyView : IView
{
public Task RenderAsync(ViewContext context)
{
return Task.CompletedTask;
}
public string Path { get; }
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Web.Common.Events;
namespace Umbraco.Web.Common.Filters
{
public class PreRenderViewActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (!(context.Controller is Controller umbController) || !(context.Result is ViewResult result))
{
return;
}
var model = result.Model;
if (model == null)
{
return;
}
var args = new ActionExecutedEventArgs(umbController, model);
OnActionExecuted(args);
if (args.Model != model)
{
result.ViewData.Model = args.Model;
}
base.OnActionExecuted(context);
}
public static event EventHandler<ActionExecutedEventArgs> ActionExecuted;
private static void OnActionExecuted(ActionExecutedEventArgs e)
{
var handler = ActionExecuted;
handler?.Invoke(null, e);
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Net;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Forces the response to have a specific http status code
/// </summary>
public class StatusCodeResultAttribute : ActionFilterAttribute
{
private readonly HttpStatusCode _statusCode;
public StatusCodeResultAttribute(HttpStatusCode statusCode)
{
_statusCode = statusCode;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
var httpContext = context.HttpContext;
httpContext.Response.StatusCode = (int)_statusCode;
var disableIisCustomErrors = httpContext.RequestServices.GetService<IWebRoutingSettings>().TrySkipIisCustomErrors;
var statusCodePagesFeature = httpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
// if IIS Custom Errors are disabled, we won't enable the Status Code Pages
statusCodePagesFeature.Enabled = !disableIisCustomErrors;
}
}
}
}

View File

@@ -112,7 +112,7 @@ namespace Umbraco.Web.Common.RuntimeMinification
public void Reset()
{
var version = DateTime.UtcNow.Ticks.ToString();
_configManipulator.SaveConfigValue(Constants.Configuration.ConfigRuntimeMinificationVersion, version.ToString());
_configManipulator.SaveConfigValue(Core.Constants.Configuration.ConfigRuntimeMinificationVersion, version.ToString());
}

View File

@@ -76,6 +76,7 @@ namespace Umbraco.Web.UI.BackOffice
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePages();
app.UseUmbracoCore();
app.UseUmbracoRequestLogging();
app.UseUmbracoWebsite();

View File

@@ -3,6 +3,7 @@ using System.Web.Mvc;
namespace Umbraco.Web.Mvc
{
/// Migrated already to .Net Core
public class ActionExecutedEventArgs : EventArgs
{
public Controller Controller { get; set; }

View File

@@ -3,6 +3,7 @@
/// <summary>
/// constants
/// </summary>
/// Migrated already to .Net Core
internal static class Constants
{
internal const string ViewLocation = "~/Views";

View File

@@ -19,6 +19,7 @@ namespace Umbraco.Web.Mvc
/// this DataToken exists before the action executes in case the developer resolves an RTE value that contains
/// a partial view macro form.
/// </remarks>
/// Migrated already to .Net Core
internal class EnsurePartialViewMacroViewContextFilterAttribute : ActionFilterAttribute
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// A marker interface to designate that a controller will be used for Umbraco front-end requests and/or route hijacking
/// </summary>
/// Migrated already to .Net Core
public interface IRenderController : IController
{

View File

@@ -12,6 +12,7 @@ namespace Umbraco.Web.Mvc
/// <remarks>
/// Only minifies in release mode
/// </remarks>
/// Migrated already to .Net Core
public class MinifyJavaScriptResultAttribute : ActionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;

View File

@@ -3,6 +3,7 @@ using System.Web.Mvc;
namespace Umbraco.Web.Mvc
{
/// Migrated already to .Net Core
public class PreRenderViewActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Forces the response to have a specific http status code
/// </summary>
/// Migrated already to .Net Core
internal class StatusCodeResultAttribute : ActionFilterAttribute
{
private readonly HttpStatusCode _statusCode;

View File

@@ -1,4 +1,7 @@
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Cache;
@@ -34,10 +37,12 @@ namespace Umbraco.Web
var mainDom = new MainDom(logger, mainDomLock);
var requestCache = new HttpRequestAppCache(() => HttpContext.Current?.Items);
var requestCache = new HttpRequestAppCache(() => HttpContext.Current != null ? HttpContext.Current.Items : null);
var umbracoBootPermissionChecker = new AspNetUmbracoBootPermissionChecker();
return new WebRuntime(configs, umbracoVersion, ioHelper, logger, profiler, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom,
GetTypeFinder(hostingEnvironment, logger, profiler), requestCache, umbracoBootPermissionChecker);
return new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom,
GetTypeFinder(hostingEnvironment, logger, profiler), requestCache);
}
}
}