Merge remote-tracking branch 'origin/netcore/netcore' into netcore/bugfix/azure-devops-tests

This commit is contained in:
Bjarke Berg
2020-10-26 16:26:59 +01:00
476 changed files with 4566 additions and 7698 deletions

6
.gitignore vendored
View File

@@ -189,3 +189,9 @@ cypress.env.json
/src/Umbraco.Web.UI.NetCore/App_Data/TEMP/*
/src/Umbraco.Web.UI.NetCore/App_Data/Smidge/Cache/*
/src/Umbraco.Web.UI.NetCore/umbraco/logs
src/Umbraco.Tests.Integration/umbraco/logs/
src/Umbraco.Tests.Integration/Views/
src/Umbraco.Tests/TEMP/

View File

@@ -41,7 +41,7 @@ namespace Umbraco.Core.Cache
public virtual IEnumerable<object> SearchByKey(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
IEnumerable<DictionaryEntry> entries;
IEnumerable<KeyValuePair<object, object>> entries;
try
{
EnterReadLock();
@@ -65,7 +65,7 @@ namespace Umbraco.Core.Cache
const string prefix = CacheItemPrefix + "-";
var compiled = new Regex(regex, RegexOptions.Compiled);
var plen = prefix.Length;
IEnumerable<DictionaryEntry> entries;
IEnumerable<KeyValuePair<object, object>> entries;
try
{
EnterReadLock();
@@ -249,7 +249,7 @@ namespace Umbraco.Core.Cache
// manipulate the underlying cache entries
// these *must* be called from within the appropriate locks
// and use the full prefixed cache keys
protected abstract IEnumerable<DictionaryEntry> GetDictionaryEntries();
protected abstract IEnumerable<KeyValuePair<object, object>> GetDictionaryEntries();
protected abstract void RemoveEntry(string key);
protected abstract object GetEntry(string key);

View File

@@ -115,13 +115,13 @@ namespace Umbraco.Core.Cache
#region Entries
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
protected override IEnumerable<KeyValuePair<object, object>> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
if (!TryGetContextItems(out var items)) return Enumerable.Empty<DictionaryEntry>();
if (!TryGetContextItems(out var items)) return Enumerable.Empty<KeyValuePair<object, object>>();
return items.Cast<DictionaryEntry>()
return items.Cast<KeyValuePair<object, object>>()
.Where(x => x.Key is string s && s.StartsWith(prefix));
}

View File

@@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
@@ -17,19 +16,23 @@ namespace Umbraco.Core.Cache
/// </remarks>
public class HttpRequestAppCache : FastDictionaryAppCacheBase, IRequestCache
{
private static object _syncRoot = new object(); // Using this for locking as the SyncRoot property is not available to us
// on the provided collection provided from .NET Core's HttpContext.Items dictionary,
// as it doesn't implement ICollection where SyncRoot is defined.
/// <summary>
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
/// </summary>
public HttpRequestAppCache(Func<IDictionary> requestItems) : base()
public HttpRequestAppCache(Func<IDictionary<object, object>> requestItems) : base()
{
ContextItems = requestItems;
}
private Func<IDictionary> ContextItems { get; }
private Func<IDictionary<object, object>> ContextItems { get; }
public bool IsAvailable => TryGetContextItems(out _);
private bool TryGetContextItems(out IDictionary items)
private bool TryGetContextItems(out IDictionary<object, object> items)
{
items = ContextItems?.Invoke();
return items != null;
@@ -115,13 +118,13 @@ namespace Umbraco.Core.Cache
#region Entries
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
protected override IEnumerable<KeyValuePair<object, object>> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
if (!TryGetContextItems(out var items)) return Enumerable.Empty<DictionaryEntry>();
if (!TryGetContextItems(out var items)) return Enumerable.Empty<KeyValuePair<object, object>>();
return items.Cast<DictionaryEntry>()
return items.Cast<KeyValuePair<object, object>>()
.Where(x => x.Key is string s && s.StartsWith(prefix));
}
@@ -154,7 +157,7 @@ namespace Umbraco.Core.Cache
// ContextItems - which is locked, so this should be safe
var entered = false;
Monitor.Enter(items.SyncRoot, ref entered);
Monitor.Enter(_syncRoot, ref entered);
items[ContextItemsLockKey] = entered;
}
@@ -166,7 +169,7 @@ namespace Umbraco.Core.Cache
var entered = (bool?)items[ContextItemsLockKey] ?? false;
if (entered)
Monitor.Exit(items.SyncRoot);
Monitor.Exit(_syncRoot);
items.Remove(ContextItemsLockKey);
}
@@ -179,7 +182,7 @@ namespace Umbraco.Core.Cache
yield break;
}
foreach (DictionaryEntry item in items)
foreach (var item in items)
{
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}

View File

@@ -64,10 +64,7 @@ namespace Umbraco.Core.Composing
#endregion
#region IRegister
/// <inheritdoc />
public object Concrete => _register.Concrete;
/// <inheritdoc />
public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient)
=> _register.Register(serviceType, lifetime);
@@ -85,33 +82,6 @@ namespace Umbraco.Core.Composing
public void Register(Type serviceType, object instance)
=> _register.Register(serviceType, instance);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.RegisterFor<TService, TTarget>(lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Type implementingType, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.RegisterFor<TService, TTarget>(implementingType, lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.RegisterFor<TService, TTarget>(factory, lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(TService instance)
where TService : class
=> _register.RegisterFor<TService, TTarget>(instance);
/// <inheritdoc />
public void RegisterAuto(Type serviceBaseType)
=> _register.RegisterAuto(serviceBaseType);
/// <inheritdoc />
public void ConfigureForWeb()
=> _register.ConfigureForWeb();
/// <inheritdoc />
public IFactory CreateFactory()
@@ -127,13 +97,7 @@ namespace Umbraco.Core.Composing
builder.RegisterWith(_register);
_builders.Clear(); // no point keep them around
IFactory factory = null;
// ReSharper disable once AccessToModifiedClosure -- on purpose
_register.Register(_ => factory, Lifetime.Singleton);
factory = _register.CreateFactory();
return factory;
return _register.CreateFactory();
}
/// <summary>
@@ -186,38 +150,6 @@ namespace Umbraco.Core.Composing
public void RegisterUnique(Type serviceType, object instance)
=> _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, instance);
/// <summary>
/// Registers a unique service for a target, as its own implementation.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>()
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(Lifetime.Singleton);
/// <summary>
/// Registers a unique service for a target, with an implementing type.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>(Type implementingType)
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(implementingType, Lifetime.Singleton);
/// <summary>
/// Registers a unique service for a target, with an implementation factory.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>(Func<IFactory, TService> factory)
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(factory, Lifetime.Singleton);
/// <summary>
/// Registers a unique service for a target, with an implementing instance.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>(TService instance)
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(instance);
#endregion
#region Collection Builders

View File

@@ -21,15 +21,6 @@ namespace Umbraco.Core.Composing
/// <remarks>Throws an exception if the container failed to get an instance of the specified type.</remarks>
object GetInstance(Type type);
/// <summary>
/// Gets a targeted instance of a service.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <returns>The instance of the specified type for the specified target.</returns>
/// <remarks>Throws an exception if the container failed to get an instance of the specified type.</remarks>
TService GetInstanceFor<TService, TTarget>();
/// <summary>
/// Tries to get an instance of a service.
/// </summary>
@@ -52,18 +43,7 @@ namespace Umbraco.Core.Composing
/// <typeparam name="TService">The type of the service.</typeparam>
IEnumerable<TService> GetAllInstances<TService>()
where TService : class;
/// <summary>
/// Releases an instance.
/// </summary>
/// <param name="instance">The instance.</param>
/// <remarks>
/// See https://stackoverflow.com/questions/14072208 and http://kozmic.net/2010/08/27/must-i-release-everything-when-using-windsor/,
/// you only need to release instances you specifically resolved, and even then, if done right, that might never be needed. For
/// instance, LightInject does not require this and does not support it - should work with scopes.
/// </remarks>
void Release(object instance);
/// <summary>
/// Begins a scope.
/// </summary>
@@ -72,13 +52,5 @@ namespace Umbraco.Core.Composing
/// <para>Scopes can be nested. Each instance is disposed individually.</para>
/// </remarks>
IDisposable BeginScope();
/// <summary>
/// Enables per-request scope.
/// </summary>
/// <remarks>
/// <para>Ties scopes to web requests.</para>
/// </remarks>
void EnablePerWebRequestScope();
}
}

View File

@@ -7,11 +7,6 @@ namespace Umbraco.Core.Composing
/// </summary>
public interface IRegister
{
/// <summary>
/// Gets the concrete container.
/// </summary>
object Concrete { get; }
/// <summary>
/// Registers a service as its own implementation.
/// </summary>
@@ -33,69 +28,8 @@ namespace Umbraco.Core.Composing
/// </summary>
void Register(Type serviceType, object instance);
/// <summary>
/// Registers a service for a target, as its own implementation.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service for a target, with an implementation type.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(Type implementingType, Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service for a target, with an implementation factory.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service for a target, with an implementing instance.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(TService instance)
where TService : class;
/// <summary>
/// Registers a base type for auto-registration.
/// </summary>
/// <remarks>
/// <para>Auto-registration means that anytime the container is asked to create an instance
/// of a type deriving from <paramref name="serviceBaseType"/>, it will first register that
/// type automatically.</para>
/// <para>This can be used for instance for views or controllers. Then, one just needs to
/// register a common base class or interface, and the container knows how to create instances.</para>
/// </remarks>
void RegisterAuto(Type serviceBaseType);
#region Control
/// <summary>
/// Configures the container for web support.
/// </summary>
/// <remarks>
/// <para>Enables support for MVC, WebAPI, but *not* per-request scope. This is used early in the boot
/// process, where anything "scoped" should not be linked to a web request.</para>
/// </remarks>
void ConfigureForWeb(); // TODO: Unsure if we need this anymore
/// <summary>
/// Creates the factory.
/// </summary>

View File

@@ -1,18 +0,0 @@
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides a base class for targeted service factories.
/// </summary>
/// <typeparam name="TService"></typeparam>
public abstract class TargetedServiceFactory<TService>
{
private readonly IFactory _factory;
protected TargetedServiceFactory(IFactory factory)
{
_factory = factory;
}
public TService For<TTarget>() => _factory.GetInstanceFor<TService, TTarget>();
}
}

View File

@@ -1,11 +1,11 @@
using Umbraco.Core.Composing;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.Manifest;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Actions;
using Umbraco.Web.ContentApps;
using Umbraco.Web.Dashboards;
using Umbraco.Web.Editors;
using Umbraco.Web.HealthCheck;
using Umbraco.Web.Routing;
using Umbraco.Web.Sections;
using Umbraco.Web.Tour;

View File

@@ -19,13 +19,6 @@ namespace Umbraco.Core
public static void RegisterUnique<TService, TImplementing>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementation type, for a target.
/// </summary>
public static void RegisterUniqueFor<TService, TTarget, TImplementing>(this Composition composition)
where TService : class
=> composition.RegisterUniqueFor<TService, TTarget>(typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementing instance.
/// </summary>

View File

@@ -1,234 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
{
public abstract class AbstractConfigCheck : HealthCheck
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ConfigurationService _configurationService;
protected ILocalizedTextService TextService { get; }
protected ILoggerFactory LoggerFactory { get; }
/// <summary>
/// Gets the config file path.
/// </summary>
public abstract string FilePath { get; }
/// <summary>
/// Gets XPath statement to the config element to check.
/// </summary>
public abstract string XPath { get; }
/// <summary>
/// Gets the values to compare against.
/// </summary>
public abstract IEnumerable<AcceptableConfiguration> Values { get; }
/// <summary>
/// Gets the current value
/// </summary>
public string CurrentValue { get; set; }
/// <summary>
/// Gets the provided value
/// </summary>
public string ProvidedValue { get; set; }
/// <summary>
/// Gets the comparison type for checking the value.
/// </summary>
public abstract ValueComparisonType ValueComparisonType { get; }
/// <summary>
/// Gets the flag indicating if the check is considered successful if the config value is missing (defaults to false - an error - if missing)
/// </summary>
public virtual bool ValidIfConfigMissing
{
get { return false; }
}
protected AbstractConfigCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
{
_hostingEnvironment = hostingEnvironment;
TextService = textService;
LoggerFactory = loggerFactory;
_configurationService = new ConfigurationService(AbsoluteFilePath, XPath, textService, loggerFactory.CreateLogger<ConfigurationService>());
}
/// <summary>
/// Gets the name of the file.
/// </summary>
private string FileName => Path.GetFileName(FilePath);
/// <summary>
/// Gets the absolute file path.
/// </summary>
private string AbsoluteFilePath => _hostingEnvironment.MapPathContentRoot(FilePath);
/// <summary>
/// Gets the message for when the check has succeeded.
/// </summary>
public virtual string CheckSuccessMessage
{
get
{
return TextService.Localize("healthcheck/checkSuccessMessage",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath });
}
}
/// <summary>
/// Gets the message for when the check has failed.
/// </summary>
public virtual string CheckErrorMessage
{
get
{
return ValueComparisonType == ValueComparisonType.ShouldEqual
? TextService.Localize("healthcheck/checkErrorMessageDifferentExpectedValue",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath })
: TextService.Localize("healthcheck/checkErrorMessageUnexpectedValue",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, XPath, AbsoluteFilePath });
}
}
/// <summary>
/// Gets the rectify success message.
/// </summary>
public virtual string RectifySuccessMessage
{
get
{
var recommendedValue = Values.FirstOrDefault(v => v.IsRecommended);
var rectifiedValue = recommendedValue != null
? recommendedValue.Value
: ProvidedValue;
return TextService.Localize("healthcheck/rectifySuccessMessage",
new[]
{
CurrentValue,
rectifiedValue,
XPath,
AbsoluteFilePath
});
}
}
/// <summary>
/// Gets a value indicating whether this check can be rectified automatically.
/// </summary>
public virtual bool CanRectify => ValueComparisonType == ValueComparisonType.ShouldEqual;
/// <summary>
/// Gets a value indicating whether this check can be rectified automatically if a value is provided.
/// </summary>
public virtual bool CanRectifyWithValue => ValueComparisonType == ValueComparisonType.ShouldNotEqual;
public override IEnumerable<HealthCheckStatus> GetStatus()
{
var successMessage = string.Format(CheckSuccessMessage, FileName, XPath, Values);
var configValue = _configurationService.GetConfigurationValue();
if (configValue.Success == false)
{
if (ValidIfConfigMissing)
{
return new[] { new HealthCheckStatus(successMessage) { ResultType = StatusResultType.Success } };
}
var errorMessage = configValue.Result;
return new[] { new HealthCheckStatus(errorMessage) { ResultType = StatusResultType.Error } };
}
CurrentValue = configValue.Result;
// need to update the successMessage with the CurrentValue
successMessage = string.Format(CheckSuccessMessage, FileName, XPath, Values, CurrentValue);
var valueFound = Values.Any(value => string.Equals(CurrentValue, value.Value, StringComparison.InvariantCultureIgnoreCase));
if (ValueComparisonType == ValueComparisonType.ShouldEqual && valueFound || ValueComparisonType == ValueComparisonType.ShouldNotEqual && valueFound == false)
{
return new[] { new HealthCheckStatus(successMessage) { ResultType = StatusResultType.Success } };
}
// Declare the action for rectifying the config value
var rectifyAction = new HealthCheckAction("rectify", Id)
{
Name = TextService.Localize("healthcheck/rectifyButton"),
ValueRequired = CanRectifyWithValue,
};
var resultMessage = string.Format(CheckErrorMessage, FileName, XPath, Values, CurrentValue);
return new[]
{
new HealthCheckStatus(resultMessage)
{
ResultType = StatusResultType.Error,
Actions = CanRectify || CanRectifyWithValue ? new[] { rectifyAction } : new HealthCheckAction[0]
}
};
}
/// <summary>
/// Rectifies this check.
/// </summary>
/// <returns></returns>
public virtual HealthCheckStatus Rectify()
{
if (ValueComparisonType == ValueComparisonType.ShouldNotEqual)
throw new InvalidOperationException(TextService.Localize("healthcheck/cannotRectifyShouldNotEqual"));
var recommendedValue = Values.First(v => v.IsRecommended).Value;
return UpdateConfigurationValue(recommendedValue);
}
/// <summary>
/// Rectifies this check with a provided value.
/// </summary>
/// <param name="value">Value provided</param>
/// <returns></returns>
public virtual HealthCheckStatus Rectify(string value)
{
if (ValueComparisonType == ValueComparisonType.ShouldEqual)
throw new InvalidOperationException(TextService.Localize("healthcheck/cannotRectifyShouldEqualWithValue"));
if (string.IsNullOrWhiteSpace(value))
throw new InvalidOperationException(TextService.Localize("healthcheck/valueToRectifyNotProvided"));
// Need to track provided value in order to correctly put together the rectify message
ProvidedValue = value;
return UpdateConfigurationValue(value);
}
private HealthCheckStatus UpdateConfigurationValue(string value)
{
var updateConfigFile = _configurationService.UpdateConfigFile(value);
if (updateConfigFile.Success == false)
{
var message = updateConfigFile.Result;
return new HealthCheckStatus(message) { ResultType = StatusResultType.Error };
}
var resultMessage = string.Format(RectifySuccessMessage, FileName, XPath, Values);
return new HealthCheckStatus(resultMessage) { ResultType = StatusResultType.Success };
}
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
{
return string.IsNullOrEmpty(action.ProvidedValue)
? Rectify()
: Rectify(action.ProvidedValue);
}
}
}

View File

@@ -1,33 +1,41 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.HealthCheck.Checks;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.Configuration.HealthChecks
{
[HealthCheck("61214FF3-FC57-4B31-B5CF-1D095C977D6D", "Debug Compilation Mode",
Description = "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.",
Group = "Live Environment")]
public class CompilationDebugCheck : AbstractConfigCheck
public class CompilationDebugCheck : AbstractSettingsCheck
{
public CompilationDebugCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
: base(textService, hostingEnvironment, loggerFactory)
{ }
private readonly IOptionsMonitor<HostingSettings> _hostingSettings;
public override string FilePath => "~/Web.config";
public CompilationDebugCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory, IOptionsMonitor<HostingSettings> hostingSettings)
: base(textService, loggerFactory)
{
_hostingSettings = hostingSettings;
}
public override string XPath => "/configuration/system.web/compilation/@debug";
public override string ItemPath => Constants.Configuration.ConfigHostingDebug;
public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual;
public override bool ValidIfConfigMissing => true;
public override IEnumerable<AcceptableConfiguration> Values => new List<AcceptableConfiguration>
{
new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() }
new AcceptableConfiguration
{
IsRecommended = true,
Value = bool.FalseString.ToLower()
}
};
public override string CurrentValue => _hostingSettings.CurrentValue.Debug.ToString();
public override string CheckSuccessMessage => TextService.Localize("healthcheck/compilationDebugCheckSuccessMessage");
public override string CheckErrorMessage => TextService.Localize("healthcheck/compilationDebugCheckErrorMessage");

View File

@@ -1,109 +1,61 @@
using System;
using System.IO;
using System.Xml;
using Microsoft.Extensions.Logging;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.Configuration.HealthChecks
{
// TODO: Add config transform for when config with specified XPath is not found
public class ConfigurationService
public class ConfigurationService : IConfigurationService
{
private readonly string _configFilePath;
private readonly string _xPath;
private readonly ILocalizedTextService _textService;
private readonly ILogger<ConfigurationService> _logger;
private readonly IConfigManipulator _configManipulator;
/// <param name="configFilePath">The absolute file location of the configuration file</param>
/// <param name="xPath">The XPath to select the value</param>
/// <param name="textService"></param>
/// <param name="logger"></param>
/// <param name="configManipulator"></param>
/// <returns></returns>
public ConfigurationService(string configFilePath, string xPath, ILocalizedTextService textService, ILogger<ConfigurationService> logger)
public ConfigurationService(ILocalizedTextService textService, ILogger<ConfigurationService> logger, IConfigManipulator configManipulator)
{
_configFilePath = configFilePath;
_xPath = xPath;
if (textService == null) HandleNullParameter(nameof(textService));
if (configManipulator == null) HandleNullParameter(nameof(configManipulator));
if (logger == null) HandleNullParameter(nameof(logger));
_configManipulator = configManipulator;
_textService = textService;
_logger = logger;
}
/// <summary>
/// Gets a value from a given configuration file with the given XPath
/// </summary>
public ConfigurationServiceResult GetConfigurationValue()
private void HandleNullParameter(string parameter)
{
try
{
if (File.Exists(_configFilePath) == false)
return new ConfigurationServiceResult
{
Success = false,
Result = _textService.Localize("healthcheck/configurationServiceFileNotFound", new[] { _configFilePath })
};
var xmlDocument = new XmlDocument();
xmlDocument.Load(_configFilePath);
var xmlNode = xmlDocument.SelectSingleNode(_xPath);
if (xmlNode == null)
return new ConfigurationServiceResult
{
Success = false,
Result = _textService.Localize("healthcheck/configurationServiceNodeNotFound", new[] { _xPath, _configFilePath })
};
return new ConfigurationServiceResult
{
Success = true,
Result = string.Format(xmlNode.Value ?? xmlNode.InnerText)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error trying to get configuration value");
return new ConfigurationServiceResult
{
Success = false,
Result = _textService.Localize("healthcheck/configurationServiceError", new[] { ex.Message })
};
}
_logger.LogError("Error trying to get configuration value", parameter);
throw new ArgumentNullException(parameter);
}
/// <summary>
/// Updates a value in a given configuration file with the given XPath
/// Updates a value in a given configuration file with the given path
/// </summary>
/// <param name="value"></param>
/// <param name="itemPath"></param>
/// <returns></returns>
public ConfigurationServiceResult UpdateConfigFile(string value)
public ConfigurationServiceResult UpdateConfigFile(string value, string itemPath)
{
try
{
if (File.Exists(_configFilePath) == false)
if (itemPath == null)
{
return new ConfigurationServiceResult
{
Success = false,
Result = _textService.Localize("healthcheck/configurationServiceFileNotFound", new[] { _configFilePath })
Result = _textService.Localize("healthcheck/configurationServiceNodeNotFound", new[] { itemPath, value })
};
}
var xmlDocument = new XmlDocument { PreserveWhitespace = true };
xmlDocument.Load(_configFilePath);
var node = xmlDocument.SelectSingleNode(_xPath);
if (node == null)
return new ConfigurationServiceResult
{
Success = false,
Result = _textService.Localize("healthcheck/configurationServiceNodeNotFound", new[] { _xPath, _configFilePath })
};
if (node.NodeType == XmlNodeType.Element)
node.InnerText = value;
else
node.Value = value;
xmlDocument.Save(_configFilePath);
return new ConfigurationServiceResult { Success = true };
_configManipulator.SaveConfigValue(itemPath, value);
return new ConfigurationServiceResult
{
Success = true
};
}
catch (Exception ex)
{

View File

@@ -1,27 +1,40 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck.Checks.Configuration
{
[HealthCheck("D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", "Macro errors",
Description = "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.",
Description =
"Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.",
Group = "Configuration")]
public class MacroErrorsCheck : AbstractConfigCheck
public class MacroErrorsCheck : AbstractSettingsCheck
{
public MacroErrorsCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
: base(textService, hostingEnvironment, loggerFactory)
{ }
private readonly ILocalizedTextService _textService;
private readonly ILoggerFactory _loggerFactory;
private readonly IOptionsMonitor<ContentSettings> _contentSettings;
public override string FilePath => "~/Config/umbracoSettings.config";
public override string XPath => "/settings/content/MacroErrors";
public MacroErrorsCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory,
IOptionsMonitor<ContentSettings> contentSettings)
: base(textService, loggerFactory)
{
_textService = textService;
_loggerFactory = loggerFactory;
_contentSettings = contentSettings;
}
public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual;
public override string ItemPath => Constants.Configuration.ConfigContentMacroErrors;
/// <summary>
/// Gets the values to compare against.
/// </summary>
public override IEnumerable<AcceptableConfiguration> Values
{
get
@@ -44,24 +57,35 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
}
}
public override string CurrentValue => _contentSettings.CurrentValue.MacroErrors.ToString();
/// <summary>
/// Gets the message for when the check has succeeded.
/// </summary>
public override string CheckSuccessMessage
{
get
{
return TextService.Localize("healthcheck/macroErrorModeCheckSuccessMessage",
return _textService.Localize("healthcheck/macroErrorModeCheckSuccessMessage",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value });
}
}
/// <summary>
/// Gets the message for when the check has failed.
/// </summary>
public override string CheckErrorMessage
{
get
{
return TextService.Localize("healthcheck/macroErrorModeCheckErrorMessage",
return _textService.Localize("healthcheck/macroErrorModeCheckErrorMessage",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value });
}
}
/// <summary>
/// Gets the rectify success message.
/// </summary>
public override string RectifySuccessMessage
{
get

View File

@@ -2,32 +2,40 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck.Checks.Configuration
{
[Obsolete("This is not currently in the appsettings.JSON and so can either be removed, or rewritten in .NET Core fashion")]
[HealthCheck("046A066C-4FB2-4937-B931-069964E16C66", "Try Skip IIS Custom Errors",
Description = "Starting with IIS 7.5, this must be set to true for Umbraco 404 pages to show. Otherwise, IIS will takeover and render its built-in error page.",
Group = "Configuration")]
public class TrySkipIisCustomErrorsCheck : AbstractConfigCheck
public class TrySkipIisCustomErrorsCheck : AbstractSettingsCheck
{
private readonly ILocalizedTextService _textService;
private readonly ILoggerFactory _loggerFactory;
private readonly Version _iisVersion;
private readonly GlobalSettings _globalSettings;
public TrySkipIisCustomErrorsCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory,
IHostingEnvironment hostingEnvironment)
: base(textService, hostingEnvironment, loggerFactory)
public TrySkipIisCustomErrorsCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory, IOptions<GlobalSettings> globalSettings)
: base(textService, loggerFactory)
{
_iisVersion = hostingEnvironment.IISVersion;
_textService = textService;
_loggerFactory = loggerFactory;
//TODO: detect if hosted in IIS, and then IIS version if we want to go this route
_iisVersion = new Version("7.5");
_globalSettings = globalSettings.Value;
}
public override string FilePath => "~/Config/umbracoSettings.config";
public override string XPath => "/settings/web.routing/@trySkipIisCustomErrors";
public override string ItemPath => "TBC";
public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual;
public override string CurrentValue => null;
public override IEnumerable<AcceptableConfiguration> Values
{
get
@@ -36,7 +44,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
var recommendedValue = _iisVersion >= new Version("7.5")
? bool.TrueString.ToLower()
: bool.FalseString.ToLower();
return new List<AcceptableConfiguration> { new AcceptableConfiguration { IsRecommended = true, Value = recommendedValue } };
return new List<AcceptableConfiguration> { new AcceptableConfiguration { IsRecommended = true, Value = recommendedValue } };
}
}
@@ -44,7 +52,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
{
get
{
return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage",
return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckSuccessMessage",
new[] { Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() });
}
}
@@ -53,7 +61,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
{
get
{
return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckErrorMessage",
return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckErrorMessage",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() });
}
}
@@ -62,8 +70,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
{
get
{
return TextService.Localize("healthcheck/trySkipIisCustomErrorsCheckRectifySuccessMessage",
new[] { Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() });
return _textService.Localize("healthcheck/trySkipIisCustomErrorsCheckRectifySuccessMessage",
new[] { "Not implemented" });
//new[] { Values.First(v => v.IsRecommended).Value, _iisVersion.ToString() });
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Configuration.HealthChecks;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.HealthCheck.Checks;
namespace Umbraco.Core.Configuration.Models
{

View File

@@ -12,6 +12,6 @@ namespace Umbraco.Core.Configuration.Models
public uint CachedNameLength { get; set; } = 8;
public string CacheFolder { get; set; } = Path.Combine("..", "Umbraco", "MediaCache");
public string CacheFolder { get; set; } = Path.Combine("..", "umbraco", "mediacache");
}
}

View File

@@ -5,10 +5,10 @@ namespace Umbraco.Core.Configuration.Models
/// <summary>
/// Represents the models builder configuration.
/// </summary>
public class ModelsBuilderSettings
public class ModelsBuilderSettings
{
// TODO: This should not go into App_Data - that folder isn't really a real thing anymore
public static string DefaultModelsDirectory => "~/App_Data/Models";
public static string DefaultModelsDirectory => "~/umbraco/models";
/// <summary>
/// Gets a value indicating whether the whole models experience is enabled.

View File

@@ -11,9 +11,23 @@
/// ":" is used as marker for nested objects in json. E.g. "Umbraco:CMS:" = {"Umbraco":{"CMS":{....}}
/// </remarks>
public const string ConfigPrefix = "Umbraco:CMS:";
public const string ConfigContentPrefix = ConfigPrefix + "Content:";
public const string ConfigContentNotificationsPrefix = ConfigContentPrefix + "Notifications:";
public const string ConfigCorePrefix = ConfigPrefix + "Core:";
public const string ConfigCustomErrorsPrefix = ConfigPrefix + "CustomErrors:";
public const string ConfigGlobalPrefix = ConfigPrefix + "Global:";
public const string ConfigHostingPrefix = ConfigPrefix + "Hosting:";
public const string ConfigModelsBuilderPrefix = ConfigPrefix + "ModelsBuilder:";
public const string ConfigSecurityPrefix = ConfigPrefix + "Security:";
public const string ConfigContentNotificationsEmail = ConfigContentNotificationsPrefix + "Email";
public const string ConfigContentMacroErrors = ConfigContentPrefix + "MacroErrors";
public const string ConfigGlobalUseHttps = ConfigGlobalPrefix + "UseHttps";
public const string ConfigHostingDebug = ConfigHostingPrefix + "Debug";
public const string ConfigCustomErrorsMode = ConfigCustomErrorsPrefix + "Mode";
public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory";
public const string ConfigContent = ConfigPrefix + "Content";
public const string ConfigCoreDebug = ConfigPrefix + "Core:Debug";
public const string ConfigCoreDebug = ConfigCorePrefix + "Debug";
public const string ConfigExceptionFilter = ConfigPrefix + "ExceptionFilter";
public const string ConfigGlobal = ConfigPrefix + "Global";
public const string ConfigHealthChecks = ConfigPrefix + "HealthChecks";
@@ -27,14 +41,13 @@
public const string ConfigNuCache = ConfigPrefix + "NuCache";
public const string ConfigRequestHandler = ConfigPrefix + "RequestHandler";
public const string ConfigRuntime = ConfigPrefix + "Runtime";
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";
public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version";
public const string ConfigSecurity = ConfigPrefix + "Security";
public const string ConfigTours = ConfigPrefix + "Tours";
public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder";
public const string ConfigWebRouting = ConfigPrefix + "WebRouting";
public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword";
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";
public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version";
}
}
}

View File

@@ -1,13 +1,13 @@
using System;
using System.Net.Mail;
using Umbraco.Core.Models;
namespace Umbraco.Core.Events
{
public class SendEmailEventArgs : EventArgs
{
public MailMessage Message { get; private set; }
public EmailMessage Message { get; }
public SendEmailEventArgs(MailMessage message)
public SendEmailEventArgs(EmailMessage message)
{
Message = message;
}

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck
{
public class AcceptableConfiguration
{

View File

@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Services;
namespace Umbraco.Core.HealthCheck.Checks
{
public abstract class AbstractSettingsCheck : HealthCheck
{
protected ILocalizedTextService TextService { get; }
protected ILoggerFactory LoggerFactory { get; }
/// <summary>
/// Gets key within the JSON to check, in the colon-delimited format
/// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1
/// </summary>
public abstract string ItemPath { get; }
/// <summary>
/// Gets the values to compare against.
/// </summary>
public abstract IEnumerable<AcceptableConfiguration> Values { get; }
/// <summary>
/// Gets the current value of the config setting
/// </summary>
public abstract string CurrentValue { get; }
/// <summary>
/// Gets the provided value
/// </summary>
public string ProvidedValue { get; set; }
/// <summary>
/// Gets the comparison type for checking the value.
/// </summary>
public abstract ValueComparisonType ValueComparisonType { get; }
protected AbstractSettingsCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory)
{
TextService = textService;
LoggerFactory = loggerFactory;
}
/// <summary>
/// Gets the message for when the check has succeeded.
/// </summary>
public virtual string CheckSuccessMessage
{
get
{
return TextService.Localize("healthcheck/checkSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath });
}
}
/// <summary>
/// Gets the message for when the check has failed.
/// </summary>
public virtual string CheckErrorMessage
{
get
{
return ValueComparisonType == ValueComparisonType.ShouldEqual
? TextService.Localize("healthcheck/checkErrorMessageDifferentExpectedValue",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath })
: TextService.Localize("healthcheck/checkErrorMessageUnexpectedValue",
new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath });
}
}
/// <summary>
/// Gets the rectify success message.
/// </summary>
public virtual string RectifySuccessMessage
{
get
{
AcceptableConfiguration recommendedValue = Values.FirstOrDefault(v => v.IsRecommended);
string rectifiedValue = recommendedValue != null ? recommendedValue.Value : ProvidedValue;
return TextService.Localize("healthcheck/rectifySuccessMessage",
new[]
{
CurrentValue,
rectifiedValue,
ItemPath
});
}
}
/// <summary>
/// Gets a value indicating whether this check can be rectified automatically.
/// </summary>
public virtual bool CanRectify => ValueComparisonType == ValueComparisonType.ShouldEqual;
/// <summary>
/// Gets a value indicating whether this check can be rectified automatically if a value is provided.
/// </summary>
public virtual bool CanRectifyWithValue => ValueComparisonType == ValueComparisonType.ShouldNotEqual;
public override IEnumerable<HealthCheckStatus> GetStatus()
{
// update the successMessage with the CurrentValue
var successMessage = string.Format(CheckSuccessMessage, ItemPath, Values, CurrentValue);
bool valueFound = Values.Any(value => string.Equals(CurrentValue, value.Value, StringComparison.InvariantCultureIgnoreCase));
if (ValueComparisonType == ValueComparisonType.ShouldEqual
&& valueFound || ValueComparisonType == ValueComparisonType.ShouldNotEqual
&& valueFound == false)
{
return new[]
{
new HealthCheckStatus(successMessage)
{
ResultType = StatusResultType.Success
}
};
}
// Declare the action for rectifying the config value
var rectifyAction = new HealthCheckAction("rectify", Id)
{
Name = TextService.Localize("healthcheck/rectifyButton"),
ValueRequired = CanRectifyWithValue
};
string resultMessage = string.Format(CheckErrorMessage, ItemPath, Values, CurrentValue);
return new[]
{
new HealthCheckStatus(resultMessage)
{
ResultType = StatusResultType.Error,
Actions = CanRectify || CanRectifyWithValue ? new[] { rectifyAction } : new HealthCheckAction[0]
}
};
}
/// <summary>
/// Rectifies this check.
/// </summary>
/// <returns></returns>
public virtual HealthCheckStatus Rectify(HealthCheckAction action)
{
if (ValueComparisonType == ValueComparisonType.ShouldNotEqual)
{
throw new InvalidOperationException(TextService.Localize("healthcheck/cannotRectifyShouldNotEqual"));
}
//TODO: show message instead of actually fixing config
string recommendedValue = Values.First(v => v.IsRecommended).Value;
string resultMessage = string.Format(RectifySuccessMessage, ItemPath, Values);
return new HealthCheckStatus(resultMessage) { ResultType = StatusResultType.Success };
}
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
{
return Rectify(action);
}
}
}

View File

@@ -1,25 +1,26 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck.Checks.Configuration
{
[HealthCheck("3E2F7B14-4B41-452B-9A30-E67FBC8E1206", "Notification Email Settings",
Description = "If notifications are used, the 'from' email address should be specified and changed from the default value.",
Group = "Configuration")]
public class NotificationEmailCheck : AbstractConfigCheck
public class NotificationEmailCheck : AbstractSettingsCheck
{
private readonly IOptionsMonitor<ContentSettings> _contentSettings;
private const string DefaultFromEmail = "your@email.here";
public NotificationEmailCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
: base(textService, hostingEnvironment, loggerFactory)
{ }
public NotificationEmailCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory, IOptionsMonitor<ContentSettings> contentSettings)
: base(textService, loggerFactory)
{
_contentSettings = contentSettings;
}
public override string FilePath => "~/Config/umbracoSettings.config";
public override string XPath => "/settings/content/notifications/email";
public override string ItemPath => Constants.Configuration.ConfigContentNotificationsEmail;
public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldNotEqual;
@@ -28,8 +29,11 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
new AcceptableConfiguration { IsRecommended = false, Value = DefaultFromEmail }
};
public override string CheckSuccessMessage => TextService.Localize("healthcheck/notificationEmailsCheckSuccessMessage", new [] { CurrentValue } );
public override string CurrentValue => _contentSettings.CurrentValue.Notifications.Email;
public override string CheckSuccessMessage => TextService.Localize("healthcheck/notificationEmailsCheckSuccessMessage", new[] { CurrentValue });
public override string CheckErrorMessage => TextService.Localize("healthcheck/notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail });
}
}

View File

@@ -5,7 +5,7 @@ using System.Text;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Data
namespace Umbraco.Core.HealthCheck.Checks.Data
{
[HealthCheck(
"73DD0C1C-E0CA-4C31-9564-1DCA509788AF",

View File

@@ -1,6 +1,6 @@
using System;
namespace Umbraco.Core.Configuration.HealthChecks
namespace Umbraco.Core.HealthCheck.Checks
{
public class DisabledHealthCheck
{

View File

@@ -1,24 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck.Checks.LiveEnvironment
{
[HealthCheck("4090C0A1-2C52-4124-92DD-F028FD066A64", "Custom Errors",
Description = "Leaving custom errors off will display a complete stack trace to your visitors if an exception occurs.",
Group = "Live Environment")]
public class CustomErrorsCheck : AbstractConfigCheck
public class CustomErrorsCheck : AbstractSettingsCheck
{
public CustomErrorsCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
: base(textService, hostingEnvironment, loggerFactory)
public CustomErrorsCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory)
: base(textService, loggerFactory)
{ }
public override string FilePath => "~/Web.config";
public override string XPath => "/configuration/system.web/customErrors/@mode";
public override string ItemPath => Constants.Configuration.ConfigCustomErrorsMode;
public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual;
@@ -28,6 +24,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
new AcceptableConfiguration { IsRecommended = false, Value = "On" }
};
public override string CurrentValue { get; }
public override string CheckSuccessMessage
{
get

View File

@@ -1,24 +1,19 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck.Checks.LiveEnvironment
{
[HealthCheck("9BED6EF4-A7F3-457A-8935-B64E9AA8BAB3", "Trace Mode",
Description = "Leaving trace mode enabled can make valuable information about your system available to hackers.",
Group = "Live Environment")]
public class TraceCheck : AbstractConfigCheck
public class TraceCheck : AbstractSettingsCheck
{
public TraceCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
: base(textService, hostingEnvironment, loggerFactory)
public TraceCheck(ILocalizedTextService textService, ILoggerFactory loggerFactory)
: base(textService, loggerFactory)
{ }
public override string FilePath => "~/Web.config";
public override string XPath => "/configuration/system.web/trace/@enabled";
public override string ItemPath => "/configuration/system.web/trace/@enabled";
public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual;
@@ -27,10 +22,13 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
new AcceptableConfiguration { IsRecommended = true, Value = bool.FalseString.ToLower() }
};
public override string CurrentValue { get; }
public override string CheckSuccessMessage => TextService.Localize("healthcheck/traceModeCheckSuccessMessage");
public override string CheckErrorMessage => TextService.Localize("healthcheck/traceModeCheckErrorMessage");
public override string RectifySuccessMessage => TextService.Localize("healthcheck/traceModeCheckRectifySuccessMessage");
}
}

View File

@@ -2,27 +2,13 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Install;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Permissions
namespace Umbraco.Core.HealthCheck.Checks.Permissions
{
internal enum PermissionCheckRequirement
{
Required,
Optional
}
internal enum PermissionCheckFor
{
Folder,
File
}
[HealthCheck(
"53DBA282-4A79-4B67-B958-B29EC40FCC23",
"Folder & File Permissions",
@@ -31,14 +17,14 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
public class FolderAndFilePermissionsCheck : HealthCheck
{
private readonly ILocalizedTextService _textService;
private readonly GlobalSettings _globalSettings;
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
private readonly IFilePermissionHelper _filePermissionHelper;
private readonly IIOHelper _ioHelper;
public FolderAndFilePermissionsCheck(ILocalizedTextService textService, IOptions<GlobalSettings> globalSettings, IFilePermissionHelper filePermissionHelper, IIOHelper ioHelper)
public FolderAndFilePermissionsCheck(ILocalizedTextService textService, IOptionsMonitor<GlobalSettings> globalSettings, IFilePermissionHelper filePermissionHelper, IIOHelper ioHelper)
{
_textService = textService;
_globalSettings = globalSettings.Value;
_globalSettings = globalSettings;
_filePermissionHelper = filePermissionHelper;
_ioHelper = ioHelper;
}
@@ -74,10 +60,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
{ Constants.SystemDirectories.Preview, PermissionCheckRequirement.Required },
{ Constants.SystemDirectories.AppPlugins, PermissionCheckRequirement.Required },
{ Constants.SystemDirectories.Config, PermissionCheckRequirement.Optional },
{ _globalSettings.UmbracoCssPath, PermissionCheckRequirement.Optional },
{ _globalSettings.UmbracoMediaPath, PermissionCheckRequirement.Optional },
{ _globalSettings.UmbracoScriptsPath, PermissionCheckRequirement.Optional },
{ _globalSettings.UmbracoPath, PermissionCheckRequirement.Optional },
{ _globalSettings.CurrentValue.UmbracoCssPath, PermissionCheckRequirement.Optional },
{ _globalSettings.CurrentValue.UmbracoMediaPath, PermissionCheckRequirement.Optional },
{ _globalSettings.CurrentValue.UmbracoScriptsPath, PermissionCheckRequirement.Optional },
{ _globalSettings.CurrentValue.UmbracoPath, PermissionCheckRequirement.Optional },
{ Constants.SystemDirectories.MvcViews, PermissionCheckRequirement.Optional }
};
@@ -97,7 +83,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
//now check the special folders
var requiredPathCheckResult2 = _filePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart:true);
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart: true);
var optionalPathCheckResult2 = _filePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Optional), out var optionalFailedPaths2, writeCausesRestart: true);
@@ -139,9 +125,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
.ToArray();
}
private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable<string> requiredFailedPaths,
bool optionalPathCheckResult, IEnumerable<string> optionalFailedPaths,
PermissionCheckFor checkingFor)
private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable<string> requiredFailedPaths, bool optionalPathCheckResult, IEnumerable<string> optionalFailedPaths, PermissionCheckFor checkingFor)
{
// Return error if any required paths fail the check, or warning if any optional ones do
var resultType = StatusResultType.Success;
@@ -164,12 +148,11 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
}
var actions = new List<HealthCheckAction>();
return
new HealthCheckStatus(message)
{
ResultType = resultType,
Actions = actions
};
return new HealthCheckStatus(message)
{
ResultType = resultType,
Actions = actions
};
}
private string GetMessageForPathCheckFailure(string messageKey, IEnumerable<string> failedPaths)

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.HealthCheck.Checks.Permissions
{
internal enum PermissionCheckFor
{
Folder,
File
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.HealthCheck.Checks.Permissions
{
internal enum PermissionCheckRequirement
{
Required,
Optional
}
}

View File

@@ -4,13 +4,11 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Xml.XPath;
using Umbraco.Core;
using Umbraco.Core.IO;
using Microsoft.Extensions.Configuration;
using Umbraco.Core.Services;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
public abstract class BaseHttpHeaderCheck : HealthCheck
{
@@ -23,16 +21,14 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
private readonly string _localizedTextPrefix;
private readonly bool _metaTagOptionAvailable;
private readonly IRequestAccessor _requestAccessor;
private readonly IIOHelper _ioHelper;
protected BaseHttpHeaderCheck(
IRequestAccessor requestAccessor,
ILocalizedTextService textService,
string header, string value, string localizedTextPrefix, bool metaTagOptionAvailable, IIOHelper ioHelper)
string header, string value, string localizedTextPrefix, bool metaTagOptionAvailable)
{
TextService = textService ?? throw new ArgumentNullException(nameof(textService));
_requestAccessor = requestAccessor;
_ioHelper = ioHelper;
_header = header;
_value = value;
_localizedTextPrefix = localizedTextPrefix;
@@ -72,7 +68,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
var success = false;
// Access the site home page and check for the click-jack protection header or meta tag
var url = _requestAccessor.GetApplicationUrl();
var url = _requestAccessor.GetApplicationUrl();
var request = WebRequest.Create(url);
request.Method = "GET";
try
@@ -146,7 +142,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
private HealthCheckStatus SetHeaderInConfig()
{
var errorMessage = string.Empty;
var success = SaveHeaderToConfigFile(out errorMessage);
//TODO: edit to show fix suggestion instead of making fix
var success = true;
if (success)
{
@@ -158,64 +155,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
}
return
new HealthCheckStatus(TextService.Localize("healthcheck/setHeaderInConfigError", new [] { errorMessage }))
new HealthCheckStatus(TextService.Localize("healthcheck/setHeaderInConfigError", new[] { errorMessage }))
{
ResultType = StatusResultType.Error
};
}
private bool SaveHeaderToConfigFile(out string errorMessage)
{
try
{
// There don't look to be any useful classes defined in https://msdn.microsoft.com/en-us/library/system.web.configuration(v=vs.110).aspx
// for working with the customHeaders section, so working with the XML directly.
var configFile = _ioHelper.MapPath("~/Web.config");
var doc = XDocument.Load(configFile);
var systemWebServerElement = doc.XPathSelectElement("/configuration/system.webServer");
var httpProtocolElement = systemWebServerElement.Element("httpProtocol");
if (httpProtocolElement == null)
{
httpProtocolElement = new XElement("httpProtocol");
systemWebServerElement.Add(httpProtocolElement);
}
var customHeadersElement = httpProtocolElement.Element("customHeaders");
if (customHeadersElement == null)
{
customHeadersElement = new XElement("customHeaders");
httpProtocolElement.Add(customHeadersElement);
}
var removeHeaderElement = customHeadersElement.Elements("remove")
.SingleOrDefault(x => x.Attribute("name")?.Value.Equals(_value, StringComparison.InvariantCultureIgnoreCase) == true);
if (removeHeaderElement == null)
{
customHeadersElement.Add(
new XElement("remove",
new XAttribute("name", _header)));
}
var addHeaderElement = customHeadersElement.Elements("add")
.SingleOrDefault(x => x.Attribute("name")?.Value.Equals(_header, StringComparison.InvariantCultureIgnoreCase) == true);
if (addHeaderElement == null)
{
customHeadersElement.Add(
new XElement("add",
new XAttribute("name", _header),
new XAttribute("value", _value)));
}
doc.Save(configFile);
errorMessage = string.Empty;
return true;
}
catch (Exception ex)
{
errorMessage = ex.Message;
return false;
}
}
}
}

View File

@@ -1,8 +1,7 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.Services;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
[HealthCheck(
"ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0",
@@ -11,8 +10,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
Group = "Security")]
public class ClickJackingCheck : BaseHttpHeaderCheck
{
public ClickJackingCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService, IIOHelper ioHelper)
: base(requestAccessor, textService, "X-Frame-Options", "sameorigin", "clickJacking", true, ioHelper)
public ClickJackingCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService)
: base(requestAccessor, textService, "X-Frame-Options", "sameorigin", "clickJacking", true)
{
}
}

View File

@@ -2,10 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
[HealthCheck(
"92ABBAA2-0586-4089-8AE2-9A843439D577",

View File

@@ -1,8 +1,7 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.Services;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
[HealthCheck(
"E2048C48-21C5-4BE1-A80B-8062162DF124",
@@ -16,8 +15,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/)
// If you want do to it perfectly, you have to submit it https://hstspreload.org/,
// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites.
public HstsCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService, IIOHelper ioHelper)
: base(requestAccessor, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true, ioHelper)
public HstsCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService)
: base(requestAccessor, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true)
{
}
}

View File

@@ -3,15 +3,14 @@ using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Web.HealthCheck.Checks.Config;
using Umbraco.Core.Configuration.Models;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.HealthChecks;
using Umbraco.Core.IO;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
[HealthCheck(
"EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7",
@@ -21,22 +20,20 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
public class HttpsCheck : HealthCheck
{
private readonly ILocalizedTextService _textService;
private readonly GlobalSettings _globalSettings;
private readonly IIOHelper _ioHelper;
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
private readonly IRequestAccessor _requestAccessor;
private readonly ILogger<ConfigurationService> _logger;
private readonly ILogger<HttpsCheck> _logger;
private const string FixHttpsSettingAction = "fixHttpsSetting";
string itemPath => Constants.Configuration.ConfigGlobalUseHttps;
public HttpsCheck(ILocalizedTextService textService,
IOptions<GlobalSettings> globalSettings,
IOptionsMonitor<GlobalSettings> globalSettings,
IIOHelper ioHelper,
IRequestAccessor requestAccessor,
ILogger<ConfigurationService> logger)
ILogger<HttpsCheck> logger)
{
_textService = textService;
_globalSettings = globalSettings.Value;
_ioHelper = ioHelper;
_globalSettings = globalSettings;
_requestAccessor = requestAccessor;
_logger = logger;
}
@@ -75,7 +72,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
// Attempt to access the site over HTTPS to see if it HTTPS is supported
// and a valid certificate has been configured
var url = _requestAccessor.GetApplicationUrl().ToString().Replace("http:", "https:");
var request = (HttpWebRequest) WebRequest.Create(url);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "HEAD";
try
@@ -119,8 +116,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
if (exception != null)
{
message = exception.Status == WebExceptionStatus.TrustFailure
? _textService.Localize("healthcheck/httpsCheckInvalidCertificate", new [] { exception.Message })
: _textService.Localize("healthcheck/healthCheckInvalidUrl", new [] { url, exception.Message });
? _textService.Localize("healthcheck/httpsCheckInvalidCertificate", new[] { exception.Message })
: _textService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url, exception.Message });
}
else
{
@@ -132,33 +129,31 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
var actions = new List<HealthCheckAction>();
return
new HealthCheckStatus(message)
{
ResultType = result,
Actions = actions
};
return new HealthCheckStatus(message)
{
ResultType = result,
Actions = actions
};
}
private HealthCheckStatus CheckIfCurrentSchemeIsHttps()
{
var uri = _requestAccessor.GetApplicationUrl();
var uri = _requestAccessor.GetApplicationUrl();
var success = uri.Scheme == "https";
var actions = new List<HealthCheckAction>();
return
new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" }))
{
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
Actions = actions
};
return new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" }))
{
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
Actions = actions
};
}
private HealthCheckStatus CheckHttpsConfigurationSetting()
{
var httpsSettingEnabled = _globalSettings.UseHttps;
var uri = _requestAccessor.GetApplicationUrl();
bool httpsSettingEnabled = _globalSettings.CurrentValue.UseHttps;
Uri uri = _requestAccessor.GetApplicationUrl();
var actions = new List<HealthCheckAction>();
string resultMessage;
@@ -171,46 +166,34 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
else
{
if (httpsSettingEnabled == false)
{
actions.Add(new HealthCheckAction(FixHttpsSettingAction, Id)
{
Name = _textService.Localize("healthcheck/httpsCheckEnableHttpsButton"),
Description = _textService.Localize("healthcheck/httpsCheckEnableHttpsDescription")
});
}
resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationCheckResult",
new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"});
resultType = httpsSettingEnabled ? StatusResultType.Success: StatusResultType.Error;
new[] { httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not" });
resultType = httpsSettingEnabled ? StatusResultType.Success : StatusResultType.Error;
}
return
new HealthCheckStatus(resultMessage)
{
ResultType = resultType,
Actions = actions
};
return new HealthCheckStatus(resultMessage)
{
ResultType = resultType,
Actions = actions
};
}
private HealthCheckStatus FixHttpsSetting()
{
var configFile = _ioHelper.MapPath("~/Web.config");
const string xPath = "/configuration/appSettings/add[@key='Umbraco.Core.UseHttps']/@value";
var configurationService = new ConfigurationService(configFile, xPath, _textService, _logger);
var updateConfigFile = configurationService.UpdateConfigFile("true");
//TODO: return message instead of actual fix
if (updateConfigFile.Success)
return new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsSuccess"))
{
return
new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsSuccess"))
{
ResultType = StatusResultType.Success
};
}
return
new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsError", new [] { updateConfigFile.Result }))
{
ResultType = StatusResultType.Error
};
ResultType = StatusResultType.Success
};
}
}
}

View File

@@ -1,8 +1,7 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.Services;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
[HealthCheck(
"1CF27DB3-EFC0-41D7-A1BB-EA912064E071",
@@ -11,8 +10,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
Group = "Security")]
public class NoSniffCheck : BaseHttpHeaderCheck
{
public NoSniffCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService, IIOHelper ioHelper)
: base(requestAccessor, textService, "X-Content-Type-Options", "nosniff", "noSniff", false, ioHelper)
public NoSniffCheck(IRequestAccessor requestAccessor, ILocalizedTextService textService)
: base(requestAccessor, textService, "X-Content-Type-Options", "nosniff", "noSniff", false)
{
}
}

View File

@@ -1,8 +1,7 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Core.Services;
using Umbraco.Web;
namespace Umbraco.Web.HealthCheck.Checks.Security
namespace Umbraco.Core.HealthCheck.Checks.Security
{
[HealthCheck(
"F4D2B02E-28C5-4999-8463-05759FA15C3A",
@@ -16,8 +15,8 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/)
// If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/,
// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites.
public XssProtectionCheck(IRequestAccessor requestAccessor,ILocalizedTextService textService, IIOHelper ioHelper)
: base(requestAccessor, textService, "X-XSS-Protection", "1; mode=block", "xssProtection", true, ioHelper)
public XssProtectionCheck(IRequestAccessor requestAccessor,ILocalizedTextService textService)
: base(requestAccessor, textService, "X-XSS-Protection", "1; mode=block", "xssProtection", true)
{
}
}

View File

@@ -3,12 +3,10 @@ using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Services;
namespace Umbraco.Web.HealthCheck.Checks.Services
namespace Umbraco.Core.HealthCheck.Checks.Services
{
[HealthCheck(
"1B5D221B-CE99-4193-97CB-5F3261EC73DF",
@@ -18,12 +16,12 @@ namespace Umbraco.Web.HealthCheck.Checks.Services
public class SmtpCheck : HealthCheck
{
private readonly ILocalizedTextService _textService;
private readonly GlobalSettings _globalSettings;
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
public SmtpCheck(ILocalizedTextService textService, IOptions<GlobalSettings> globalSettings)
public SmtpCheck(ILocalizedTextService textService, IOptionsMonitor<GlobalSettings> globalSettings)
{
_textService = textService;
_globalSettings = globalSettings.Value;
_globalSettings = globalSettings;
}
/// <summary>
@@ -50,7 +48,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Services
{
var success = false;
var smtpSettings = _globalSettings.Smtp;
var smtpSettings = _globalSettings.CurrentValue.Smtp;
string message;
if (smtpSettings == null)

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck
{
public class ConfigurationServiceResult
{

View File

@@ -1,24 +1,25 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
/// <summary>
/// Provides a base class for health checks.
/// Provides a base class for health checks, filling in the healthcheck metadata on construction
/// </summary>
[DataContract(Name = "healthCheck", Namespace = "")]
public abstract class HealthCheck : IDiscoverable
{
protected HealthCheck()
{
//Fill in the metadata
var thisType = GetType();
var meta = thisType.GetCustomAttribute<HealthCheckAttribute>(false);
Type thisType = GetType();
HealthCheckAttribute meta = thisType.GetCustomAttribute<HealthCheckAttribute>(false);
if (meta == null)
throw new InvalidOperationException($"The health check {thisType} requires a {typeof (HealthCheckAttribute)}");
{
throw new InvalidOperationException($"The health check {thisType} requires a {typeof(HealthCheckAttribute)}");
}
Name = meta.Name;
Description = meta.Description;
Group = meta.Group;
@@ -49,7 +50,5 @@ namespace Umbraco.Web.HealthCheck
/// <param name="action"></param>
/// <returns></returns>
public abstract HealthCheckStatus ExecuteAction(HealthCheckAction action);
// TODO: What else?
}
}

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
[DataContract(Name = "healthCheckAction", Namespace = "")]
public class HealthCheckAction

View File

@@ -1,6 +1,6 @@
using System;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
/// <summary>
/// Metadata attribute for Health checks

View File

@@ -1,11 +1,11 @@
using System.Collections.Generic;
using Umbraco.Core.Composing;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
public class HealthCheckCollection : BuilderCollectionBase<HealthCheck>
public class HealthCheckCollection : BuilderCollectionBase<Core.HealthCheck.HealthCheck>
{
public HealthCheckCollection(IEnumerable<HealthCheck> items)
public HealthCheckCollection(IEnumerable<Core.HealthCheck.HealthCheck> items)
: base(items)
{ }
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
[DataContract(Name = "healthCheckGroup", Namespace = "")]
public class HealthCheckGroup

View File

@@ -1,6 +1,6 @@
using System;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
/// <summary>
/// Metadata attribute for health check notification methods

View File

@@ -1,6 +1,4 @@
using System.Runtime.Serialization;
namespace Umbraco.Core.Configuration.HealthChecks
namespace Umbraco.Core.HealthCheck
{
public enum HealthCheckNotificationVerbosity
{

View File

@@ -2,7 +2,7 @@
using System.Linq;
using System.Runtime.Serialization;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
/// <summary>
/// The status returned for a health check when it performs it check

View File

@@ -1,6 +1,6 @@
using Umbraco.Core.Composing;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
public class HealthCheckCollectionBuilder : LazyCollectionBuilderBase<HealthCheckCollectionBuilder, HealthCheckCollection, HealthCheck>
{

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.HealthCheck
{
public interface IConfigurationService
{
ConfigurationServiceResult UpdateConfigFile(string value, string itemPath);
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Umbraco.Core.Configuration.HealthChecks
namespace Umbraco.Core.HealthCheck
{
public interface INotificationMethod
{

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Core.Configuration.HealthChecks
namespace Umbraco.Core.HealthCheck
{
public interface INotificationMethodSettings
{

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Core.HealthCheck
{
public enum StatusResultType
{

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.HealthCheck.Checks.Config
namespace Umbraco.Core.HealthCheck
{
public enum ValueComparisonType
{

View File

@@ -1,5 +1,5 @@
using System.Net.Mail;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Umbraco.Core.Models;
namespace Umbraco.Core
{
@@ -8,7 +8,6 @@ namespace Umbraco.Core
/// </summary>
public interface IEmailSender
{
// TODO: This would be better if MailMessage was our own abstraction!
Task SendAsync(MailMessage message);
Task SendAsync(EmailMessage message);
}
}

View File

@@ -282,7 +282,7 @@ namespace Umbraco.Core.IO
{
lock (_shadowLocker)
{
var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath, IsScoped);
var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath,() => IsScoped());
if (_shadowCurrentId != null)
wrapper.Shadow(_shadowCurrentId);
_shadowWrappers.Add(wrapper);

View File

@@ -122,7 +122,7 @@ namespace Umbraco.Core.IO
var mappedRoot = MapPath(_hostingEnvironment.ApplicationVirtualPath);
if (filePath.StartsWith(mappedRoot) == false)
filePath = MapPath(filePath);
filePath = _hostingEnvironment.MapPathContentRoot(filePath);
// yes we can (see above)
//// don't trust what we get, it may contain relative segments
@@ -132,7 +132,7 @@ namespace Umbraco.Core.IO
{
var validDir = dir;
if (validDir.StartsWith(mappedRoot) == false)
validDir = MapPath(validDir);
validDir = _hostingEnvironment.MapPathContentRoot(validDir);
if (PathStartsWith(filePath, validDir, Path.DirectorySeparatorChar))
return true;

View File

@@ -10,7 +10,8 @@ using Umbraco.Core.Hosting;
namespace Umbraco.Core.IO
{
public class PhysicalFileSystem : IFileSystem
public interface IPhysicalFileSystem : IFileSystem {}
public class PhysicalFileSystem : IPhysicalFileSystem
{
private readonly IIOHelper _ioHelper;
private readonly ILogger<PhysicalFileSystem> _logger;
@@ -56,7 +57,6 @@ namespace Umbraco.Core.IO
if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentException("Value can't be empty.", nameof(rootUrl));
if (rootPath.StartsWith("~/")) throw new ArgumentException("Value can't be a virtual path and start with '~/'.", nameof(rootPath));
// rootPath should be... rooted, as in, it's a root path!
if (Path.IsPathRooted(rootPath) == false)
{
@@ -65,6 +65,9 @@ namespace Umbraco.Core.IO
rootPath = Path.Combine(localRoot, rootPath);
}
// clean up root path
rootPath = Path.GetFullPath(rootPath);
_rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar);
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
_rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/');
@@ -328,7 +331,7 @@ namespace Umbraco.Core.IO
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
throw new UnauthorizedAccessException("File '" + opath + "' is outside this filesystem's root.");
throw new UnauthorizedAccessException($"File original: [{opath}] full: [{path}] is outside this filesystem's root.");
}
/// <summary>

View File

@@ -1,11 +0,0 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.IO
{
public class SupportingFileSystems : TargetedServiceFactory<IFileSystem>
{
public SupportingFileSystems(IFactory factory)
: base(factory)
{ }
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Umbraco.Core.Models
{
public class EmailMessage
{
public string From { get; }
public string To { get; }
public string Subject { get; }
public string Body { get; }
public bool IsBodyHtml { get; }
public EmailMessage(string from, string to, string subject, string body, bool isBodyHtml)
{
if (from == null) throw new ArgumentNullException(nameof(from));
if (from.Length == 0) throw new ArgumentException("Value cannot be empty.", nameof(from));
if (to == null) throw new ArgumentNullException(nameof(to));
if (to.Length == 0) throw new ArgumentException("Value cannot be empty.", nameof(to));
if (subject == null) throw new ArgumentNullException(nameof(subject));
if (subject.Length == 0) throw new ArgumentException("Value cannot be empty.", nameof(subject));
if (body == null) throw new ArgumentNullException(nameof(body));
if (body.Length == 0) throw new ArgumentException("Value cannot be empty.", nameof(body));
From = from;
To = to;
Subject = subject;
Body = body;
IsBodyHtml = isBodyHtml;
}
}
}

View File

@@ -13,13 +13,6 @@ namespace Umbraco.Core
public static void Register<TService, TImplementing>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
=> register.Register(typeof(TService), typeof(TImplementing), lifetime);
/// <summary>
/// Registers a service with an implementation type, for a target.
/// </summary>
public static void RegisterFor<TService, TImplementing, TTarget>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> register.RegisterFor<TService, TTarget>(typeof(TImplementing), lifetime);
/// <summary>
/// Registers a service as its own implementation.
/// </summary>
@@ -33,12 +26,5 @@ namespace Umbraco.Core
public static void Register<TService>(this IRegister register, TService instance)
where TService : class
=> register.Register(typeof(TService), instance);
/// <summary>
/// Registers a base type for auto-registration.
/// </summary>
public static void RegisterAuto<TServiceBase>(this IRegister register)
where TServiceBase : class
=> register.RegisterAuto(typeof(TServiceBase));
}
}

View File

@@ -15,6 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />

View File

@@ -0,0 +1,39 @@
using System;
using System.Reflection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.BackOffice;
namespace Umbraco.Infrastructure.BackOffice
{
public class BackOfficeIdentityBuilder : IdentityBuilder
{
public BackOfficeIdentityBuilder(IServiceCollection services) : base(typeof(BackOfficeIdentityUser), services)
{
}
public BackOfficeIdentityBuilder(Type role, IServiceCollection services) : base(typeof(BackOfficeIdentityUser), role, services)
{
}
/// <summary>
/// Adds a token provider for the <seealso cref="BackOfficeIdentityUser"/>.
/// </summary>
/// <param name="providerName">The name of the provider to add.</param>
/// <param name="provider">The type of the <see cref="IUserTwoFactorTokenProvider{BackOfficeIdentityUser}"/> to add.</param>
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
public override IdentityBuilder AddTokenProvider(string providerName, Type provider)
{
if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
{
throw new InvalidOperationException($"Invalid Type for TokenProvider: {provider.FullName}");
}
Services.Configure<BackOfficeIdentityOptions>(options =>
{
options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider);
});
Services.AddTransient(provider);
return this;
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Extensions.Options;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
@@ -23,13 +24,13 @@ namespace Umbraco.Core.Compose
private readonly IIpResolver _ipResolver;
private readonly GlobalSettings _globalSettings;
public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService, IIpResolver ipResolver, GlobalSettings globalSettings)
public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService, IIpResolver ipResolver, IOptions<GlobalSettings> globalSettings)
{
_auditService = auditService;
_userService = userService;
_entityService = entityService;
_ipResolver = ipResolver;
_globalSettings = globalSettings;
_globalSettings = globalSettings.Value;
}
public void Initialize()

View File

@@ -1,10 +1,10 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.IO.MediaPathSchemes;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Composing.CompositionExtensions
{
@@ -14,50 +14,9 @@ namespace Umbraco.Core.Composing.CompositionExtensions
* HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM
* ----------------------------------------------
*
* Create a component and use it to modify the composition by adding something like:
* composition.RegisterUnique<IMediaFileSystem>(factoryMethod);
*
* composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(...);
*
* and register whatever supporting filesystem you like.
*
*
* HOW TO IMPLEMENT MY OWN FILESYSTEM
* ----------------------------------
*
* Create your filesystem class:
*
* public class MyFileSystem : FileSystemWrapper
* {
* public MyFileSystem(IFileSystem innerFileSystem)
* : base(innerFileSystem)
* { }
* }
*
* The ctor can have more parameters, that will be resolved by the container.
*
* Register your filesystem, in a component:
*
* composition.RegisterFileSystem<MyFileSystem>();
*
* Register the underlying filesystem:
*
* composition.RegisterUniqueFor<IFileSystem, MyFileSystem>(...);
*
* And that's it, you can inject MyFileSystem wherever it's needed.
*
*
* You can also declare a filesystem interface:
*
* public interface IMyFileSystem : IFileSystem
* { }
*
* Make the class implement the interface, then
* register your filesystem, in a component:
*
* composition.RegisterFileSystem<IMyFileSystem, MyFileSystem>();
* composition.RegisterUniqueFor<IFileSystem, IMyFileSystem>(...);
*
* And that's it, you can inject IMyFileSystem wherever it's needed.
* composition.RegisterUnique<IMediaFileSystem, TImplementation>();
*
*
* WHAT IS SHADOWING
@@ -85,16 +44,8 @@ namespace Umbraco.Core.Composing.CompositionExtensions
// register the scheme for media paths
composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
// register the IMediaFileSystem implementation
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>();
// register the supporting filesystems provider
composition.Register(factory => new SupportingFileSystems(factory), Lifetime.Singleton);
// register the IFileSystem supporting the IMediaFileSystem
// THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
// and, SupportingFileSystem.For<IMediaFileSystem>() returns the underlying filesystem
composition.SetMediaFileSystem(factory =>
// register the default IMediaFileSystem implementation
composition.RegisterUnique<IMediaFileSystem>(factory =>
{
var ioHelper = factory.GetInstance<IIOHelper>();
var hostingEnvironment = factory.GetInstance<IHostingEnvironment>();
@@ -103,7 +54,10 @@ namespace Umbraco.Core.Composing.CompositionExtensions
var rootPath = hostingEnvironment.MapPathWebRoot(globalSettings.UmbracoMediaPath);
var rootUrl = hostingEnvironment.ToAbsolute(globalSettings.UmbracoMediaPath);
return new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl);
var inner = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, rootPath, rootUrl);
var fileSystems = factory.GetInstance<IO.FileSystems>();
return fileSystems.GetFileSystem<MediaFileSystem>(inner);
});
return composition;

View File

@@ -12,19 +12,19 @@ namespace Umbraco.Web.Composing.CompositionExtensions
{
// register the installer steps
composition.Register<NewInstallStep>(Lifetime.Scope);
composition.Register<UpgradeStep>(Lifetime.Scope);
composition.Register<FilePermissionsStep>(Lifetime.Scope);
composition.Register<DatabaseConfigureStep>(Lifetime.Scope);
composition.Register<DatabaseInstallStep>(Lifetime.Scope);
composition.Register<DatabaseUpgradeStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,NewInstallStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,UpgradeStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,FilePermissionsStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,DatabaseConfigureStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,DatabaseInstallStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,DatabaseUpgradeStep>(Lifetime.Scope);
// TODO: Add these back once we have a compatible Starter kit
// composition.Register<StarterKitDownloadStep>(Lifetime.Scope);
// composition.Register<StarterKitInstallStep>(Lifetime.Scope);
// composition.Register<StarterKitCleanupStep>(Lifetime.Scope);
// composition.Register<InstallSetupStep,StarterKitDownloadStep>(Lifetime.Scope);
// composition.Register<InstallSetupStep,StarterKitInstallStep>(Lifetime.Scope);
// composition.Register<InstallSetupStep,StarterKitCleanupStep>(Lifetime.Scope);
composition.Register<CompleteInstallStep>(Lifetime.Scope);
composition.Register<InstallSetupStep,CompleteInstallStep>(Lifetime.Scope);
composition.Register<InstallStepCollection>();
composition.RegisterUnique<InstallHelper>();

View File

@@ -1,5 +1,4 @@
using Microsoft.Extensions.Hosting;
using Serilog;
namespace Umbraco.Core.Composing
{
@@ -15,17 +14,7 @@ namespace Umbraco.Core.Composing
/// <returns></returns>
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
{
return builder
.UseUmbraco(new UmbracoServiceProviderFactory());
return builder;
}
/// <summary>
/// Assigns a custom service provider factory to use Umbraco's container
/// </summary>
/// <param name="builder"></param>
/// <param name="umbracoServiceProviderFactory"></param>
/// <returns></returns>
public static IHostBuilder UseUmbraco(this IHostBuilder builder, UmbracoServiceProviderFactory umbracoServiceProviderFactory)
=> builder.UseServiceProviderFactory(umbracoServiceProviderFactory);
}
}

View File

@@ -183,32 +183,6 @@ namespace Umbraco.Core.Composing.LightInject
public void Register(Type serviceType, object instance)
=> Container.RegisterInstance(serviceType, instance);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Lifetime lifetime = Lifetime.Transient)
where TService : class
=> RegisterFor<TService, TTarget>(typeof(TService), lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Type implementingType, Lifetime lifetime = Lifetime.Transient)
where TService : class
{
// note that there can only be one implementation or instance registered "for" a service
Container.Register(typeof(TService), implementingType, GetTargetedServiceName<TTarget>(), GetLifetime(lifetime));
}
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class
{
// note that there can only be one implementation or instance registered "for" a service
Container.Register(f => factory(this), GetTargetedServiceName<TTarget>(), GetLifetime(lifetime));
}
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(TService instance)
where TService : class
=> Container.RegisterInstance(typeof(TService), instance, GetTargetedServiceName<TTarget>());
private ILifetime GetLifetime(Lifetime lifetime)
{
switch (lifetime)

View File

@@ -0,0 +1,92 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Composing;
namespace Umbraco.Infrastructure.Composing
{
public class ServiceCollectionRegistryAdapter : IRegister
{
private readonly IServiceCollection _services;
public ServiceCollectionRegistryAdapter(IServiceCollection services)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_services.AddTransient(typeof(Lazy<>), typeof(LazyResolve<>));
}
public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient)
{
switch (lifetime)
{
case Lifetime.Request:
case Lifetime.Scope:
_services.AddScoped(serviceType);
break;
case Lifetime.Transient:
_services.AddTransient(serviceType);
break;
case Lifetime.Singleton:
_services.AddSingleton(serviceType);
break;
default:
throw new NotImplementedException($"Unhandled Lifetime: {lifetime}");
}
}
public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient)
{
switch (lifetime)
{
case Lifetime.Request:
case Lifetime.Scope:
_services.AddScoped(serviceType, implementingType);
break;
case Lifetime.Transient:
_services.AddTransient(serviceType, implementingType);
break;
case Lifetime.Singleton:
_services.AddSingleton(serviceType, implementingType);
break;
default:
throw new NotImplementedException($"Unhandled Lifetime: {lifetime}");
}
}
public void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient) where TService : class
{
switch (lifetime)
{
case Lifetime.Request:
case Lifetime.Scope:
_services.AddScoped(sp => factory(ServiceProviderFactoryAdapter.Wrap(sp)));
break;
case Lifetime.Transient:
_services.AddTransient(sp => factory(ServiceProviderFactoryAdapter.Wrap(sp)));
break;
case Lifetime.Singleton:
_services.AddSingleton(sp => factory(ServiceProviderFactoryAdapter.Wrap(sp)));
break;
default:
throw new NotImplementedException($"Unhandled Lifetime: {lifetime}");
}
}
public void Register(Type serviceType, object instance)
{
_services.AddSingleton(serviceType, instance);
}
public IFactory CreateFactory()
{
return ServiceProviderFactoryAdapter.Wrap(_services.BuildServiceProvider());
}
}
public class LazyResolve<T> : Lazy<T>
where T : class
{
public LazyResolve(IServiceProvider serviceProvider) : base(serviceProvider.GetRequiredService<T>)
{
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Composing;
namespace Umbraco.Infrastructure.Composing
{
internal class ServiceProviderFactoryAdapter : IFactory
{
private readonly IServiceProvider _serviceProvider;
private ServiceProviderFactoryAdapter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public object Concrete => _serviceProvider;
public object GetInstance(Type type)
{
return _serviceProvider.GetRequiredService(type);
}
public object TryGetInstance(Type type)
{
return _serviceProvider.GetService(type);
}
public IEnumerable<object> GetAllInstances(Type serviceType)
{
return _serviceProvider.GetServices(serviceType);
}
public IEnumerable<TService> GetAllInstances<TService>() where TService : class
{
return _serviceProvider.GetServices<TService>();
}
public IDisposable BeginScope()
{
return _serviceProvider.CreateScope();
}
public static IFactory Wrap(IServiceProvider serviceProvider)
{
return new ServiceProviderFactoryAdapter(serviceProvider);
}
}
}

View File

@@ -1,105 +0,0 @@
using LightInject;
using LightInject.Microsoft.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Composing;
using Umbraco.Core.Composing.LightInject;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.IO;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Used to create Umbraco's container and cross-wire it up before the applicaton starts
/// </summary>
public class UmbracoServiceProviderFactory : IServiceProviderFactory<IServiceContainer>
{
public UmbracoServiceProviderFactory(ServiceContainer container, bool initializeCurrent)
{
_container = new LightInjectContainer(container);
_initializeCurrent = initializeCurrent;
}
/// <summary>
/// Creates an ASP.NET Core compatible service container
/// </summary>
/// <returns></returns>
public static ServiceContainer CreateServiceContainer() => new ServiceContainer(
ContainerOptions.Default.Clone()
.WithMicrosoftSettings()
//.WithAspNetCoreSettings() //TODO WithAspNetCoreSettings changes behavior that we need to discuss
);
/// <summary>
/// Default ctor for use in Host Builder configuration
/// </summary>
public UmbracoServiceProviderFactory()
{
var container = CreateServiceContainer();
UmbracoContainer = _container = new LightInjectContainer(container);
IsActive = true;
_initializeCurrent = true;
}
// see here for orig lightinject version https://github.com/seesharper/LightInject.Microsoft.DependencyInjection/blob/412566e3f70625e6b96471db5e1f7cd9e3e1eb18/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs#L263
// we don't really need all that, we're manually creating our container with the correct options and that
// is what we'll return in CreateBuilder
IServiceCollection _services;
readonly LightInjectContainer _container;
private readonly bool _initializeCurrent;
internal LightInjectContainer GetContainer() => _container;
/// <summary>
/// When the empty ctor is used this returns if this factory is active
/// </summary>
public static bool IsActive { get; private set; }
/// <summary>
/// When the empty ctor is used this returns the created IRegister
/// </summary>
public static IRegister UmbracoContainer { get; private set; }
/// <summary>
/// Create the container with the required settings for aspnetcore3
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public IServiceContainer CreateBuilder(IServiceCollection services)
{
_services = services;
return _container.Container;
}
/// <summary>
/// This cross-wires the container just before the application calls "Configure"
/// </summary>
/// <param name="containerBuilder"></param>
/// <returns></returns>
public IServiceProvider CreateServiceProvider(IServiceContainer containerBuilder)
{
var provider = containerBuilder.CreateServiceProvider(_services);
if (_initializeCurrent)
{
// after cross wiring, configure "Current"
Current.Initialize(
_container.GetInstance<ILogger<object>>(),
_container.GetInstance<IOptions<SecuritySettings>>().Value,
_container.GetInstance<IOptions<GlobalSettings>>().Value,
_container.GetInstance<IIOHelper>(),
_container.GetInstance<Umbraco.Core.Hosting.IHostingEnvironment>(),
_container.GetInstance<IBackOfficeInfo>(),
_container.GetInstance<Umbraco.Core.Logging.IProfiler>());
}
return provider;
}
}
}

View File

@@ -282,22 +282,6 @@ namespace Umbraco.Core
composition.RegisterUnique(_ => helper);
}
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
public static void SetMediaFileSystem(this Composition composition, Func<IFactory, IFileSystem> filesystemFactory)
=> composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(filesystemFactory);
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
public static void SetMediaFileSystem(this Composition composition, Func<IFileSystem> filesystemFactory)
=> composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(_ => filesystemFactory());
/// <summary>
/// Sets the log viewer.
/// </summary>

View File

@@ -1,47 +0,0 @@
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition)
where TImplementing : FileSystemWrapper, TFileSystem
where TFileSystem : class
{
composition.RegisterUnique<TFileSystem>(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
}
}

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using HeyRed.MarkdownSharp;
using Umbraco.Composing;
using Umbraco.Core.Configuration.HealthChecks;
using Microsoft.Extensions.Logging;
using Umbraco.Composing;
using Umbraco.Core.HealthCheck;
namespace Umbraco.Web.HealthCheck
namespace Umbraco.Infrastructure.HealthCheck
{
public class HealthCheckResults
{
@@ -16,7 +16,7 @@ namespace Umbraco.Web.HealthCheck
private ILogger Logger => Current.Logger; // TODO: inject
public HealthCheckResults(IEnumerable<HealthCheck> checks)
public HealthCheckResults(IEnumerable<Core.HealthCheck.HealthCheck> checks)
{
_results = checks.ToDictionary(
t => t.Name,

View File

@@ -1,13 +1,13 @@
using System;
using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.HealthChecks;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Infrastructure.HealthCheck;
namespace Umbraco.Web.HealthCheck.NotificationMethods
{
@@ -71,23 +71,19 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject", new[] { host.ToString() });
var mailSender = new EmailSender(Options.Create(_globalSettings));
using (var mailMessage = CreateMailMessage(subject, message))
{
await mailSender.SendAsync(mailMessage);
}
var mailMessage = CreateMailMessage(subject, message);
await mailSender.SendAsync(mailMessage);
}
private MailMessage CreateMailMessage(string subject, string message)
private EmailMessage CreateMailMessage(string subject, string message)
{
var to = _contentSettings.Notifications.Email;
if (string.IsNullOrWhiteSpace(subject))
subject = "Umbraco Health Check Status";
return new MailMessage(to, RecipientEmail, subject, message)
{
IsBodyHtml = message.IsNullOrWhiteSpace() == false && message.Contains("<") && message.Contains("</")
};
var isBodyHtml = message.IsNullOrWhiteSpace() == false && message.Contains("<") && message.Contains("</");
return new EmailMessage(to, RecipientEmail, subject, message, isBodyHtml);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Composing;
using Umbraco.Infrastructure.HealthCheck;
namespace Umbraco.Web.HealthCheck.NotificationMethods
{

View File

@@ -3,8 +3,10 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.HealthChecks;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.HealthCheck.Checks;
using Umbraco.Infrastructure.HealthCheck;
namespace Umbraco.Web.HealthCheck.NotificationMethods
{

View File

@@ -2,13 +2,14 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
[MapperFor(typeof(PublicAccessEntry))]
public sealed class AccessMapper : BaseMapper
{
public AccessMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public AccessMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -12,7 +13,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(AuditEntry))]
public sealed class AuditEntryMapper : BaseMapper
{
public AuditEntryMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public AuditEntryMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -9,7 +10,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IAuditItem))]
public sealed class AuditItemMapper : BaseMapper
{
public AuditItemMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public AuditItemMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using NPoco;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Composing;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -15,12 +16,12 @@ namespace Umbraco.Core.Persistence.Mappers
private readonly Lazy<ISqlContext> _sqlContext;
private readonly object _definedLock = new object();
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> _maps;
private readonly MapperConfigurationStore _maps;
private ISqlSyntaxProvider _sqlSyntax;
private bool _defined;
protected BaseMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
protected BaseMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
{
_sqlContext = sqlContext;
_maps = maps;

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -12,7 +13,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(Consent))]
public sealed class ConsentMapper : BaseMapper
{
public ConsentMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public ConsentMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IContent))]
public sealed class ContentMapper : BaseMapper
{
public ContentMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public ContentMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IContentType))]
public sealed class ContentTypeMapper : BaseMapper
{
public ContentTypeMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public ContentTypeMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IDataType))]
public sealed class DataTypeMapper : BaseMapper
{
public DataTypeMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public DataTypeMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IDictionaryItem))]
public sealed class DictionaryMapper : BaseMapper
{
public DictionaryMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public DictionaryMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IDictionaryTranslation))]
public sealed class DictionaryTranslationMapper : BaseMapper
{
public DictionaryTranslationMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public DictionaryTranslationMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -9,7 +10,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(UmbracoDomain))]
public sealed class DomainMapper : BaseMapper
{
public DomainMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public DomainMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -9,7 +10,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IdentityUserLogin))]
public sealed class ExternalLoginMapper : BaseMapper
{
public ExternalLoginMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public ExternalLoginMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(Language))]
public sealed class LanguageMapper : BaseMapper
{
public LanguageMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public LanguageMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -9,7 +10,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(IMacro))]
internal sealed class MacroMapper : BaseMapper
{
public MacroMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public MacroMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -1,14 +1,12 @@
using System;
using System.Collections.Concurrent;
using Umbraco.Core.Composing;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
public class MapperCollectionBuilder : SetCollectionBuilderBase<MapperCollectionBuilder, MapperCollection, BaseMapper>
{
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> _maps
= new ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>();
protected override MapperCollectionBuilder This => this;
public override void RegisterWith(IRegister register)
@@ -21,14 +19,10 @@ namespace Umbraco.Core.Persistence.Mappers
// we want to register extra
// - service IMapperCollection, returns MappersCollectionBuilder's collection
register.Register<MapperConfigurationStore>(Lifetime.Singleton);
register.Register<IMapperCollection>(factory => factory.GetInstance<MapperCollection>());
}
protected override BaseMapper CreateItem(IFactory factory, Type itemType)
{
return (BaseMapper) factory.CreateInstance(itemType, _maps);
}
public MapperCollectionBuilder AddCoreMappers()
{
Add<AccessMapper>();

View File

@@ -0,0 +1,8 @@
using System;
using System.Collections.Concurrent;
namespace Umbraco.Infrastructure.Persistence.Mappers
{
public class MapperConfigurationStore : ConcurrentDictionary<Type, ConcurrentDictionary<string, string>>
{ }
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(Umbraco.Core.Models.Media))]
public sealed class MediaMapper : BaseMapper
{
public MediaMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public MediaMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(MediaType))]
public sealed class MediaTypeMapper : BaseMapper
{
public MediaTypeMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public MediaTypeMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -9,7 +10,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof (MemberGroup))]
public sealed class MemberGroupMapper : BaseMapper
{
public MemberGroupMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public MemberGroupMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -14,7 +15,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(Member))]
public sealed class MemberMapper : BaseMapper
{
public MemberMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public MemberMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof (IMemberType))]
public sealed class MemberTypeMapper : BaseMapper
{
public MemberTypeMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public MemberTypeMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -12,7 +13,7 @@ namespace Umbraco.Core.Persistence.Mappers
[MapperFor(typeof(PropertyGroup))]
public sealed class PropertyGroupMapper : BaseMapper
{
public PropertyGroupMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public PropertyGroupMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

View File

@@ -2,13 +2,14 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Infrastructure.Persistence.Mappers;
namespace Umbraco.Core.Persistence.Mappers
{
[MapperFor(typeof(Property))]
public sealed class PropertyMapper : BaseMapper
{
public PropertyMapper(Lazy<ISqlContext> sqlContext, ConcurrentDictionary<Type, ConcurrentDictionary<string, string>> maps)
public PropertyMapper(Lazy<ISqlContext> sqlContext, MapperConfigurationStore maps)
: base(sqlContext, maps)
{ }

Some files were not shown because too many files have changed in this diff Show More