Netcore: Health check notifier hosted service (#9295)
* Implemented health check notifier as a hosted service. Added validation to health check settings. * Registered health check notifier as a hosted service. Modified health check nested settings to use concrete classes to align with other configuration models. * Resolved issues with email sending using development server. * PR review comments and fixed failing unit test. * Changed period and delay millisecond and hourly values to TimeSpans. Changed configuration of first run time for health check notifications to use H:mm format. * Set up SecureSocketOptions as a locally defined enum. * Tightened up time format validation to verify input is an actual time (with hours and minutes only) and not a timespan. * Aligned naming and namespace of health check configuration related classes with other configuration classes. * Created constants for hex colors used in formatting health check results as HTML. * Revert "Tightened up time format validation to verify input is an actual time (with hours and minutes only) and not a timespan." This reverts commit f9bb8a7a825bcb58146879f18b47922e09453e2d. * Renamed method to be clear validation is of a TimeSpan and not a time. Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.HealthCheck.Checks
|
||||
namespace Umbraco.Core.Configuration.Models
|
||||
{
|
||||
public class DisabledHealthCheck
|
||||
public class DisabledHealthCheckSettings
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime DisabledOn { get; set; }
|
||||
|
||||
public int DisabledBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models.Extensions
|
||||
{
|
||||
public static class HealthCheckSettingsExtensions
|
||||
{
|
||||
public static TimeSpan GetNotificationDelay(this HealthChecksSettings settings, DateTime now, TimeSpan defaultDelay)
|
||||
{
|
||||
// If first run time not set, start with just small delay after application start.
|
||||
var firstRunTime = settings.Notification.FirstRunTime;
|
||||
if (string.IsNullOrEmpty(firstRunTime))
|
||||
{
|
||||
return defaultDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise start at scheduled time.
|
||||
var delay = TimeSpan.FromMinutes(now.PeriodicMinutesFrom(firstRunTime));
|
||||
return (delay < defaultDelay)
|
||||
? defaultDelay
|
||||
: delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.HealthCheck;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models
|
||||
{
|
||||
public class HealthChecksNotificationMethodSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public HealthCheckNotificationVerbosity Verbosity { get; set; } = HealthCheckNotificationVerbosity.Summary;
|
||||
|
||||
public bool FailureOnly { get; set; } = false;
|
||||
|
||||
public IDictionary<string, string> Settings { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models
|
||||
{
|
||||
public class HealthChecksNotificationSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public string FirstRunTime { get; set; } = string.Empty;
|
||||
|
||||
public TimeSpan Period { get; set; } = TimeSpan.FromHours(24);
|
||||
|
||||
public IDictionary<string, HealthChecksNotificationMethodSettings> NotificationMethods { get; set; } = new Dictionary<string, HealthChecksNotificationMethodSettings>();
|
||||
|
||||
public IEnumerable<DisabledHealthCheckSettings> DisabledChecks { get; set; } = Enumerable.Empty<DisabledHealthCheckSettings>();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.HealthCheck;
|
||||
using Umbraco.Core.HealthCheck.Checks;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models
|
||||
{
|
||||
public class HealthChecksSettings
|
||||
{
|
||||
public IEnumerable<DisabledHealthCheck> DisabledChecks { get; set; } = Enumerable.Empty<DisabledHealthCheck>();
|
||||
public IEnumerable<DisabledHealthCheckSettings> DisabledChecks { get; set; } = Enumerable.Empty<DisabledHealthCheckSettings>();
|
||||
|
||||
public HealthCheckNotificationSettings NotificationSettings { get; set; } = new HealthCheckNotificationSettings();
|
||||
|
||||
public class HealthCheckNotificationSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
public string FirstRunTime { get; set; }
|
||||
|
||||
public int PeriodInHours { get; set; } = 24;
|
||||
|
||||
public IReadOnlyDictionary<string, INotificationMethod> NotificationMethods { get; set; } = new Dictionary<string, INotificationMethod>();
|
||||
|
||||
public IEnumerable<DisabledHealthCheck> DisabledChecks { get; set; } = Enumerable.Empty<DisabledHealthCheck>();
|
||||
}
|
||||
public HealthChecksNotificationSettings Notification { get; set; } = new HealthChecksNotificationSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Net.Mail;
|
||||
using Umbraco.Core.Configuration.Models.Validation;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches MailKit.Security.SecureSocketOptions and defined locally to avoid having to take
|
||||
/// thi
|
||||
/// </summary>
|
||||
public enum SecureSocketOptions
|
||||
{
|
||||
None = 0,
|
||||
Auto = 1,
|
||||
SslOnConnect = 2,
|
||||
StartTls = 3,
|
||||
StartTlsWhenAvailable = 4
|
||||
}
|
||||
|
||||
public class SmtpSettings : ValidatableEntryBase
|
||||
{
|
||||
[Required]
|
||||
@@ -15,6 +27,8 @@ namespace Umbraco.Core.Configuration.Models
|
||||
|
||||
public int Port { get; set; }
|
||||
|
||||
public SecureSocketOptions SecureSocketOptions { get; set; } = SecureSocketOptions.Auto;
|
||||
|
||||
public string PickupDirectoryLocation { get; set; }
|
||||
|
||||
public SmtpDeliveryMethod DeliveryMethod { get; set; } = SmtpDeliveryMethod.Network;
|
||||
|
||||
@@ -41,5 +41,18 @@ namespace Umbraco.Core.Configuration.Models.Validation
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ValidateOptionalTime(string configPath, string value, out string message)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value) && !value.IsValidTimeSpan())
|
||||
{
|
||||
message = $"Configuration entry {configPath} contains an invalid time value.";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Macros;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models.Validation
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Umbraco.Core.Configuration.Models.Validation
|
||||
{
|
||||
public class HealthChecksSettingsValidator : ConfigurationValidatorBase, IValidateOptions<HealthChecksSettings>
|
||||
{
|
||||
public ValidateOptionsResult Validate(string name, HealthChecksSettings options)
|
||||
{
|
||||
if (!ValidateNotificationFirstRunTime(options.Notification.FirstRunTime, out var message))
|
||||
{
|
||||
return ValidateOptionsResult.Fail(message);
|
||||
}
|
||||
|
||||
return ValidateOptionsResult.Success;
|
||||
}
|
||||
|
||||
private bool ValidateNotificationFirstRunTime(string value, out string message)
|
||||
{
|
||||
return ValidateOptionalTime($"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", value, out message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ namespace Umbraco.Core
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the number of minutes from a date time, on a rolling daily basis (so if
|
||||
/// date time is before the time, calculate onto next day)
|
||||
/// date time is before the time, calculate onto next day).
|
||||
/// </summary>
|
||||
/// <param name="fromDateTime">Date to start from</param>
|
||||
/// <param name="scheduledTime">Time to compare against (in Hmm form, e.g. 330, 2200)</param>
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Umbraco.Core.HealthCheck
|
||||
{
|
||||
protected HealthCheck()
|
||||
{
|
||||
Type thisType = GetType();
|
||||
HealthCheckAttribute meta = thisType.GetCustomAttribute<HealthCheckAttribute>(false);
|
||||
var thisType = GetType();
|
||||
var meta = thisType.GetCustomAttribute<HealthCheckAttribute>(false);
|
||||
if (meta == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The health check {thisType} requires a {typeof(HealthCheckAttribute)}");
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.HealthCheck
|
||||
{
|
||||
public interface INotificationMethod
|
||||
{
|
||||
string Alias { get; }
|
||||
bool Enabled { get; }
|
||||
HealthCheckNotificationVerbosity Verbosity { get; }
|
||||
bool FailureOnly { get; }
|
||||
IReadOnlyDictionary<string, INotificationMethodSettings> Settings { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Umbraco.Core.HealthCheck
|
||||
{
|
||||
public interface INotificationMethodSettings
|
||||
{
|
||||
string Key { get; }
|
||||
string Value { get; }
|
||||
}
|
||||
}
|
||||
@@ -1478,5 +1478,20 @@ namespace Umbraco.Core
|
||||
{
|
||||
return shortStringHelper.CleanStringForSafeFileName(text, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a string matches a time stamp.
|
||||
/// </summary>
|
||||
/// <param name="input">String with timespan representation (in standard timespan format: https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings)</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidTimeSpan(this string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TimeSpan.TryParse(input, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,11 +128,17 @@ namespace Umbraco.Infrastructure.HealthCheck
|
||||
return html;
|
||||
}
|
||||
|
||||
internal Dictionary<string, IEnumerable<HealthCheckStatus>> ResultsAsDictionary => _results;
|
||||
|
||||
private string ApplyHtmlHighlighting(string html)
|
||||
{
|
||||
html = ApplyHtmlHighlightingForStatus(html, StatusResultType.Success, "5cb85c");
|
||||
html = ApplyHtmlHighlightingForStatus(html, StatusResultType.Warning, "f0ad4e");
|
||||
return ApplyHtmlHighlightingForStatus(html, StatusResultType.Error, "d9534f");
|
||||
const string SuccessHexColor = "5cb85c";
|
||||
const string WarningHexColor = "f0ad4e";
|
||||
const string ErrorHexColor = "d9534f";
|
||||
|
||||
html = ApplyHtmlHighlightingForStatus(html, StatusResultType.Success, SuccessHexColor);
|
||||
html = ApplyHtmlHighlightingForStatus(html, StatusResultType.Warning, WarningHexColor);
|
||||
return ApplyHtmlHighlightingForStatus(html, StatusResultType.Error, ErrorHexColor);
|
||||
}
|
||||
|
||||
private string ApplyHtmlHighlightingForStatus(string html, StatusResultType status, string color)
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
IOptions<ContentSettings> contentSettings)
|
||||
: base(healthChecksSettings)
|
||||
{
|
||||
var recipientEmail = Settings?["recipientEmail"]?.Value;
|
||||
var recipientEmail = Settings?["RecipientEmail"];
|
||||
if (string.IsNullOrWhiteSpace(recipientEmail))
|
||||
{
|
||||
Enabled = false;
|
||||
@@ -45,7 +45,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
|
||||
public string RecipientEmail { get; }
|
||||
|
||||
public override async Task SendAsync(HealthCheckResults results, CancellationToken token)
|
||||
public override async Task SendAsync(HealthCheckResults results)
|
||||
{
|
||||
if (ShouldSend(results) == false)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
public interface IHealthCheckNotificationMethod : IDiscoverable
|
||||
{
|
||||
bool Enabled { get; }
|
||||
Task SendAsync(HealthCheckResults results, CancellationToken token);
|
||||
|
||||
Task SendAsync(HealthCheckResults results);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.HealthCheck;
|
||||
using Umbraco.Core.HealthCheck.Checks;
|
||||
using Umbraco.Infrastructure.HealthCheck;
|
||||
|
||||
namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
@@ -22,8 +20,8 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
return;
|
||||
}
|
||||
|
||||
var notificationMethods = healthCheckSettings.Value.NotificationSettings.NotificationMethods;
|
||||
if(!notificationMethods.TryGetValue(attribute.Alias, out var notificationMethod))
|
||||
var notificationMethods = healthCheckSettings.Value.Notification.NotificationMethods;
|
||||
if (!notificationMethods.TryGetValue(attribute.Alias, out var notificationMethod))
|
||||
{
|
||||
Enabled = false;
|
||||
return;
|
||||
@@ -41,13 +39,13 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
|
||||
public HealthCheckNotificationVerbosity Verbosity { get; protected set; }
|
||||
|
||||
public IReadOnlyDictionary<string, INotificationMethodSettings> Settings { get; }
|
||||
public IDictionary<string, string> Settings { get; }
|
||||
|
||||
protected bool ShouldSend(HealthCheckResults results)
|
||||
{
|
||||
return Enabled && (!FailureOnly || !results.AllChecksSuccessful);
|
||||
}
|
||||
|
||||
public abstract Task SendAsync(HealthCheckResults results, CancellationToken token);
|
||||
public abstract Task SendAsync(HealthCheckResults results);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,86 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Extensions;
|
||||
using Umbraco.Core.HealthCheck;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Infrastructure.HealthCheck;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
namespace Umbraco.Infrastructure.HostedServices
|
||||
{
|
||||
public class HealthCheckNotifier : RecurringTaskBase
|
||||
/// <summary>
|
||||
/// Hosted service implementation for recurring health check notifications.
|
||||
/// </summary>
|
||||
public class HealthCheckNotifier : RecurringHostedServiceBase
|
||||
{
|
||||
private readonly IMainDom _mainDom;
|
||||
private readonly HealthChecksSettings _healthChecksSettings;
|
||||
private readonly HealthCheckCollection _healthChecks;
|
||||
private readonly HealthCheckNotificationMethodCollection _notifications;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
private readonly ILogger<HealthCheckNotifier> _logger;
|
||||
private readonly HealthChecksSettings _healthChecksSettings;
|
||||
private readonly IServerRegistrar _serverRegistrar;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly IServerRegistrar _serverRegistrar;
|
||||
private readonly IMainDom _mainDom;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly ILogger<HealthCheckNotifier> _logger;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
|
||||
public HealthCheckNotifier(
|
||||
IBackgroundTaskRunner<RecurringTaskBase> runner,
|
||||
int delayMilliseconds,
|
||||
int periodMilliseconds,
|
||||
IOptions<HealthChecksSettings> healthChecksSettings,
|
||||
HealthCheckCollection healthChecks,
|
||||
HealthCheckNotificationMethodCollection notifications,
|
||||
IMainDom mainDom,
|
||||
IProfilingLogger profilingLogger ,
|
||||
ILogger<HealthCheckNotifier> logger,
|
||||
HealthChecksSettings healthChecksSettings,
|
||||
IServerRegistrar serverRegistrar,
|
||||
IRuntimeState runtimeState,
|
||||
IScopeProvider scopeProvider)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
IServerRegistrar serverRegistrar,
|
||||
IMainDom mainDom,
|
||||
IScopeProvider scopeProvider,
|
||||
ILogger<HealthCheckNotifier> logger,
|
||||
IProfilingLogger profilingLogger)
|
||||
: base(healthChecksSettings.Value.Notification.Period,
|
||||
healthChecksSettings.Value.GetNotificationDelay(DateTime.Now, DefaultDelay))
|
||||
{
|
||||
_healthChecksSettings = healthChecksSettings.Value;
|
||||
_healthChecks = healthChecks;
|
||||
_notifications = notifications;
|
||||
_runtimeState = runtimeState;
|
||||
_serverRegistrar = serverRegistrar;
|
||||
_mainDom = mainDom;
|
||||
_scopeProvider = scopeProvider;
|
||||
_runtimeState = runtimeState;
|
||||
_profilingLogger = profilingLogger ;
|
||||
_logger = logger;
|
||||
_healthChecksSettings = healthChecksSettings;
|
||||
_serverRegistrar = serverRegistrar;
|
||||
_runtimeState = runtimeState;
|
||||
_profilingLogger = profilingLogger;
|
||||
}
|
||||
|
||||
public override async Task<bool> PerformRunAsync(CancellationToken token)
|
||||
|
||||
public override async void ExecuteAsync(object state)
|
||||
{
|
||||
if (_healthChecksSettings.Notification.Enabled == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
return true; // repeat...
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_serverRegistrar.GetCurrentServerRole())
|
||||
{
|
||||
case ServerRole.Replica:
|
||||
_logger.LogDebug("Does not run on replica servers.");
|
||||
return true; // DO repeat, server role can change
|
||||
return;
|
||||
case ServerRole.Unknown:
|
||||
_logger.LogDebug("Does not run on servers with unknown role.");
|
||||
return true; // DO repeat, server role can change
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure we do not run if not main domain, but do NOT lock it
|
||||
// Ensure we do not run if not main domain, but do NOT lock it
|
||||
if (_mainDom.IsMainDom == false)
|
||||
{
|
||||
_logger.LogDebug("Does not run if not MainDom.");
|
||||
return false; // do NOT repeat, going down
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we use an explicit scope since we are running on a background thread and plugin health
|
||||
@@ -80,13 +89,10 @@ namespace Umbraco.Web.Scheduling
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
using (_profilingLogger.DebugDuration<HealthCheckNotifier>("Health checks executing", "Health checks complete"))
|
||||
{
|
||||
var healthCheckConfig = _healthChecksSettings;
|
||||
|
||||
// Don't notify for any checks that are disabled, nor for any disabled
|
||||
// just for notifications
|
||||
var disabledCheckIds = healthCheckConfig.NotificationSettings.DisabledChecks
|
||||
// Don't notify for any checks that are disabled, nor for any disabled just for notifications.
|
||||
var disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks
|
||||
.Select(x => x.Id)
|
||||
.Union(healthCheckConfig.DisabledChecks
|
||||
.Union(_healthChecksSettings.DisabledChecks
|
||||
.Select(x => x.Id))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
@@ -97,14 +103,12 @@ namespace Umbraco.Web.Scheduling
|
||||
var results = new HealthCheckResults(checks);
|
||||
results.LogResults();
|
||||
|
||||
// Send using registered notification methods that are enabled
|
||||
// Send using registered notification methods that are enabled.
|
||||
foreach (var notificationMethod in _notifications.Where(x => x.Enabled))
|
||||
await notificationMethod.SendAsync(results, token);
|
||||
{
|
||||
await notificationMethod.SendAsync(results);
|
||||
}
|
||||
}
|
||||
|
||||
return true; // repeat
|
||||
}
|
||||
|
||||
public override bool IsAsync => true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Umbraco.Infrastructure.HostedServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for recurring background tasks implemented as hosted services.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#timed-background-tasks
|
||||
/// </remarks>
|
||||
public abstract class RecurringHostedServiceBase : IHostedService, IDisposable
|
||||
{
|
||||
protected static readonly TimeSpan DefaultDelay = TimeSpan.FromMinutes(3);
|
||||
|
||||
private readonly TimeSpan _period;
|
||||
private readonly TimeSpan _delay;
|
||||
private Timer _timer;
|
||||
|
||||
protected RecurringHostedServiceBase(TimeSpan period, TimeSpan delay)
|
||||
{
|
||||
_period = period;
|
||||
_delay = delay;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public abstract void ExecuteAsync(object state);
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_timer?.Change(Timeout.Infinite, 0);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,7 +303,6 @@ namespace Umbraco.Core.Runtime
|
||||
composition.HealthChecks()
|
||||
.Add(() => composition.TypeLoader.GetTypes<HealthCheck.HealthCheck>());
|
||||
|
||||
|
||||
composition.WithCollectionBuilder<HealthCheckNotificationMethodCollectionBuilder>()
|
||||
.Add(() => composition.TypeLoader.GetTypes<IHealthCheckNotificationMethod>());
|
||||
|
||||
|
||||
@@ -36,10 +36,7 @@ namespace Umbraco.Web.Scheduling
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly HealthCheckCollection _healthChecks;
|
||||
private readonly HealthCheckNotificationMethodCollection _notifications;
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
private readonly HealthChecksSettings _healthChecksSettings;
|
||||
private readonly IServerMessenger _serverMessenger;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly IBackofficeSecurityFactory _backofficeSecurityFactory;
|
||||
@@ -59,9 +56,8 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
public SchedulerComponent(IRuntimeState runtime, IMainDom mainDom, IServerRegistrar serverRegistrar,
|
||||
IContentService contentService, IAuditService auditService,
|
||||
HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications,
|
||||
IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, IProfilingLogger profilingLogger , ILoggerFactory loggerFactory,
|
||||
IApplicationShutdownRegistry applicationShutdownRegistry, IOptions<HealthChecksSettings> healthChecksSettings,
|
||||
IScopeProvider scopeProvider, IUmbracoContextFactory umbracoContextFactory, IProfilingLogger profilingLogger, ILoggerFactory loggerFactory,
|
||||
IApplicationShutdownRegistry applicationShutdownRegistry,
|
||||
IServerMessenger serverMessenger, IRequestAccessor requestAccessor,
|
||||
IOptions<LoggingSettings> loggingSettings, IOptions<KeepAliveSettings> keepAliveSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
@@ -73,15 +69,11 @@ namespace Umbraco.Web.Scheduling
|
||||
_contentService = contentService;
|
||||
_auditService = auditService;
|
||||
_scopeProvider = scopeProvider;
|
||||
_profilingLogger = profilingLogger ;
|
||||
_profilingLogger = profilingLogger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_logger = loggerFactory.CreateLogger<SchedulerComponent>();
|
||||
_applicationShutdownRegistry = applicationShutdownRegistry;
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
|
||||
_healthChecks = healthChecks;
|
||||
_notifications = notifications;
|
||||
_healthChecksSettings = healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings));
|
||||
_serverMessenger = serverMessenger;
|
||||
_requestAccessor = requestAccessor;
|
||||
_backofficeSecurityFactory = backofficeSecurityFactory;
|
||||
@@ -98,7 +90,6 @@ namespace Umbraco.Web.Scheduling
|
||||
_publishingRunner = new BackgroundTaskRunner<IBackgroundTask>("ScheduledPublishing", logger, _applicationShutdownRegistry);
|
||||
_scrubberRunner = new BackgroundTaskRunner<IBackgroundTask>("LogScrubber", logger, _applicationShutdownRegistry);
|
||||
_fileCleanupRunner = new BackgroundTaskRunner<IBackgroundTask>("TempFileCleanup", logger, _applicationShutdownRegistry);
|
||||
_healthCheckRunner = new BackgroundTaskRunner<IBackgroundTask>("HealthCheckNotifier", logger, _applicationShutdownRegistry);
|
||||
|
||||
// we will start the whole process when a successful request is made
|
||||
_requestAccessor.RouteAttempt += RegisterBackgroundTasksOnce;
|
||||
@@ -138,10 +129,6 @@ namespace Umbraco.Web.Scheduling
|
||||
tasks.Add(RegisterLogScrubber(_loggingSettings));
|
||||
tasks.Add(RegisterTempFileCleanup());
|
||||
|
||||
var healthCheckConfig = _healthChecksSettings;
|
||||
if (healthCheckConfig.NotificationSettings.Enabled)
|
||||
tasks.Add(RegisterHealthCheckNotifier(healthCheckConfig, _healthChecks, _notifications, _profilingLogger));
|
||||
|
||||
return tasks.ToArray();
|
||||
});
|
||||
}
|
||||
@@ -164,32 +151,6 @@ namespace Umbraco.Web.Scheduling
|
||||
return task;
|
||||
}
|
||||
|
||||
private IBackgroundTask RegisterHealthCheckNotifier(HealthChecksSettings healthCheckSettingsConfig,
|
||||
HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications,
|
||||
IProfilingLogger logger)
|
||||
{
|
||||
// If first run time not set, start with just small delay after application start
|
||||
int delayInMilliseconds;
|
||||
if (string.IsNullOrEmpty(healthCheckSettingsConfig.NotificationSettings.FirstRunTime))
|
||||
{
|
||||
delayInMilliseconds = DefaultDelayMilliseconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise start at scheduled time
|
||||
delayInMilliseconds = DateTime.Now.PeriodicMinutesFrom(healthCheckSettingsConfig.NotificationSettings.FirstRunTime) * 60 * 1000;
|
||||
if (delayInMilliseconds < DefaultDelayMilliseconds)
|
||||
{
|
||||
delayInMilliseconds = DefaultDelayMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
var periodInMilliseconds = healthCheckSettingsConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000;
|
||||
var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _mainDom, logger, _loggerFactory.CreateLogger<HealthCheckNotifier>(), _healthChecksSettings, _serverRegistrar, _runtime, _scopeProvider);
|
||||
_healthCheckRunner.TryAdd(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
private IBackgroundTask RegisterLogScrubber(LoggingSettings settings)
|
||||
{
|
||||
// log scrubbing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
using MailKit.Security;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using MimeKit.Text;
|
||||
@@ -45,15 +46,15 @@ namespace Umbraco.Core
|
||||
{
|
||||
OnSendEmail(new SendEmailEventArgs(message));
|
||||
}
|
||||
else
|
||||
else if (_smtpConfigured.Value == true)
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
client.Connect(_globalSettings.Smtp.Host,
|
||||
_globalSettings.Smtp.Port,
|
||||
(MailKit.Security.SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
|
||||
|
||||
client.Connect(_globalSettings.Smtp.Host, _globalSettings.Smtp.Port);
|
||||
|
||||
if (!(_globalSettings.Smtp.Username is null &&
|
||||
_globalSettings.Smtp.Password is null))
|
||||
if (!(_globalSettings.Smtp.Username is null && _globalSettings.Smtp.Password is null))
|
||||
{
|
||||
client.Authenticate(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
|
||||
}
|
||||
@@ -75,14 +76,15 @@ namespace Umbraco.Core
|
||||
{
|
||||
OnSendEmail(new SendEmailEventArgs(message));
|
||||
}
|
||||
else
|
||||
else if (_smtpConfigured.Value == true)
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
await client.ConnectAsync(_globalSettings.Smtp.Host, _globalSettings.Smtp.Port);
|
||||
await client.ConnectAsync(_globalSettings.Smtp.Host,
|
||||
_globalSettings.Smtp.Port,
|
||||
(MailKit.Security.SecureSocketOptions)(int)_globalSettings.Smtp.SecureSocketOptions);
|
||||
|
||||
if (!(_globalSettings.Smtp.Username is null &&
|
||||
_globalSettings.Smtp.Password is null))
|
||||
if (!(_globalSettings.Smtp.Username is null && _globalSettings.Smtp.Password is null))
|
||||
{
|
||||
await client.AuthenticateAsync(_globalSettings.Smtp.Username, _globalSettings.Smtp.Password);
|
||||
}
|
||||
@@ -125,8 +127,7 @@ namespace Umbraco.Core
|
||||
|
||||
private static void OnSendEmail(SendEmailEventArgs e)
|
||||
{
|
||||
var handler = SendEmail;
|
||||
if (handler != null) handler(null, e);
|
||||
SendEmail?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private MimeMessage ConstructEmailMessage(EmailMessage mailMessage)
|
||||
@@ -138,10 +139,10 @@ namespace Umbraco.Core
|
||||
var messageToSend = new MimeMessage
|
||||
{
|
||||
Subject = mailMessage.Subject,
|
||||
From = { new MailboxAddress(fromEmail)},
|
||||
From = { MailboxAddress.Parse(fromEmail) },
|
||||
Body = new TextPart(mailMessage.IsBodyHtml ? TextFormat.Html : TextFormat.Plain) { Text = mailMessage.Body }
|
||||
};
|
||||
messageToSend.To.Add(new MailboxAddress(mailMessage.To));
|
||||
messageToSend.To.Add(MailboxAddress.Parse(mailMessage.To));
|
||||
|
||||
return messageToSend;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Extensions
|
||||
{
|
||||
[TestFixture]
|
||||
public class HealthCheckSettingsExtensionsTests
|
||||
{
|
||||
[Test]
|
||||
public void Returns_Notification_Delay_From_Provided_Time()
|
||||
{
|
||||
var settings = new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
FirstRunTime = "1230",
|
||||
}
|
||||
};
|
||||
var now = DateTime.Now.Date.AddHours(12);
|
||||
var result = settings.GetNotificationDelay(now, TimeSpan.Zero);
|
||||
Assert.AreEqual(30, result.Minutes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_Notification_Delay_From_Default_When_Provided_Time_Too_Close_To_Current_Time()
|
||||
{
|
||||
var settings = new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
FirstRunTime = "1230",
|
||||
}
|
||||
};
|
||||
var now = DateTime.Now.Date.AddHours(12).AddMinutes(25);
|
||||
var result = settings.GetNotificationDelay(now, TimeSpan.FromMinutes(10));
|
||||
Assert.AreEqual(10, result.Minutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Validation;
|
||||
using Umbraco.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation
|
||||
{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Configuration.Models.Validation;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation
|
||||
{
|
||||
[TestFixture]
|
||||
public class HealthChecksSettingsValidationTests
|
||||
{
|
||||
[Test]
|
||||
public void Returns_Success_ForValid_Configuration()
|
||||
{
|
||||
var validator = new HealthChecksSettingsValidator();
|
||||
var options = BuildHealthChecksSettings();
|
||||
var result = validator.Validate("settings", options);
|
||||
Assert.True(result.Succeeded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_Fail_For_Configuration_With_Invalid_Notification_FirstRunTime()
|
||||
{
|
||||
var validator = new HealthChecksSettingsValidator();
|
||||
var options = BuildHealthChecksSettings(firstRunTime: "25:00");
|
||||
var result = validator.Validate("settings", options);
|
||||
Assert.False(result.Succeeded);
|
||||
}
|
||||
|
||||
private static HealthChecksSettings BuildHealthChecksSettings(string firstRunTime = "12:00")
|
||||
{
|
||||
return new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
Enabled = true,
|
||||
FirstRunTime = firstRunTime,
|
||||
Period = TimeSpan.FromHours(1),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,14 +293,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper
|
||||
Assert.AreEqual(expected, output);
|
||||
}
|
||||
|
||||
#region Cases
|
||||
[TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")]
|
||||
[TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")]
|
||||
#endregion
|
||||
public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected)
|
||||
{
|
||||
var output = input.ReplaceMany(toReplace.ToArray(), replacement);
|
||||
Assert.AreEqual(expected, output);
|
||||
}
|
||||
|
||||
[TestCase("", false)]
|
||||
[TestCase("12:34", true)]
|
||||
[TestCase("1:14:23", true)]
|
||||
[TestCase("25:03", false)]
|
||||
[TestCase("18:61", false)]
|
||||
public void IsValidTimeSpan(string input, bool expected)
|
||||
{
|
||||
var result = input.IsValidTimeSpan();
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.HealthCheck;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Infrastructure.HealthCheck;
|
||||
using Umbraco.Infrastructure.HostedServices;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Umbraco.Web.HealthCheck.NotificationMethods;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices
|
||||
{
|
||||
[TestFixture]
|
||||
public class HealthCheckNotifierTests
|
||||
{
|
||||
private Mock<IHealthCheckNotificationMethod> _mockNotificationMethod;
|
||||
|
||||
private const string Check1Id = "00000000-0000-0000-0000-000000000001";
|
||||
private const string Check2Id = "00000000-0000-0000-0000-000000000002";
|
||||
private const string Check3Id = "00000000-0000-0000-0000-000000000003";
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Not_Enabled()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(enabled: false);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Runtime_State_Is_Not_Run()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(runtimeLevel: RuntimeLevel.Boot);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Server_Role_Is_Replica()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Replica);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Server_Role_Is_Unknown()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(serverRole: ServerRole.Unknown);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_When_Not_Main_Dom()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(isMainDom: false);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Execute_With_No_Enabled_Notification_Methods()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier(notificationEnabled: false);
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Executes_With_Enabled_Notification_Methods()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier();
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.IsAny<HealthCheckResults>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Executes_Only_Enabled_Checks()
|
||||
{
|
||||
var sut = CreateHealthCheckNotifier();
|
||||
sut.ExecuteAsync(null);
|
||||
_mockNotificationMethod.Verify(x => x.SendAsync(It.Is<HealthCheckResults>(
|
||||
y => y.ResultsAsDictionary.Count == 1 && y.ResultsAsDictionary.ContainsKey("Check1"))), Times.Once);
|
||||
}
|
||||
|
||||
private HealthCheckNotifier CreateHealthCheckNotifier(
|
||||
bool enabled = true,
|
||||
RuntimeLevel runtimeLevel = RuntimeLevel.Run,
|
||||
ServerRole serverRole = ServerRole.Single,
|
||||
bool isMainDom = true,
|
||||
bool notificationEnabled = true)
|
||||
{
|
||||
var settings = new HealthChecksSettings
|
||||
{
|
||||
Notification = new HealthChecksNotificationSettings
|
||||
{
|
||||
Enabled = enabled,
|
||||
DisabledChecks = new List<DisabledHealthCheckSettings>
|
||||
{
|
||||
new DisabledHealthCheckSettings { Id = Guid.Parse(Check3Id) }
|
||||
}
|
||||
},
|
||||
DisabledChecks = new List<DisabledHealthCheckSettings>
|
||||
{
|
||||
new DisabledHealthCheckSettings { Id = Guid.Parse(Check2Id) }
|
||||
}
|
||||
};
|
||||
var checks = new HealthCheckCollection(new List<HealthCheck>
|
||||
{
|
||||
new TestHealthCheck1(),
|
||||
new TestHealthCheck2(),
|
||||
new TestHealthCheck3(),
|
||||
});
|
||||
|
||||
_mockNotificationMethod = new Mock<IHealthCheckNotificationMethod>();
|
||||
_mockNotificationMethod.SetupGet(x => x.Enabled).Returns(notificationEnabled);
|
||||
var notifications = new HealthCheckNotificationMethodCollection(new List<IHealthCheckNotificationMethod> { _mockNotificationMethod.Object });
|
||||
|
||||
var mockRunTimeState = new Mock<IRuntimeState>();
|
||||
mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel);
|
||||
|
||||
var mockServerRegistrar = new Mock<IServerRegistrar>();
|
||||
mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole);
|
||||
|
||||
var mockMainDom = new Mock<IMainDom>();
|
||||
mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom);
|
||||
|
||||
var mockScopeProvider = new Mock<IScopeProvider>();
|
||||
var mockLogger = new Mock<ILogger<HealthCheckNotifier>>();
|
||||
var mockProfilingLogger = new Mock<IProfilingLogger>();
|
||||
|
||||
return new HealthCheckNotifier(Options.Create(settings), checks, notifications,
|
||||
mockRunTimeState.Object, mockServerRegistrar.Object, mockMainDom.Object, mockScopeProvider.Object,
|
||||
mockLogger.Object, mockProfilingLogger.Object);
|
||||
}
|
||||
|
||||
[HealthCheck(Check1Id, "Check1")]
|
||||
private class TestHealthCheck1 : TestHealthCheck
|
||||
{
|
||||
}
|
||||
|
||||
[HealthCheck(Check2Id, "Check2")]
|
||||
private class TestHealthCheck2 : TestHealthCheck
|
||||
{
|
||||
}
|
||||
|
||||
[HealthCheck(Check3Id, "Check3")]
|
||||
private class TestHealthCheck3 : TestHealthCheck
|
||||
{
|
||||
}
|
||||
|
||||
private class TestHealthCheck : HealthCheck
|
||||
{
|
||||
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
|
||||
{
|
||||
return new HealthCheckStatus("Check message");
|
||||
}
|
||||
|
||||
public override IEnumerable<HealthCheckStatus> GetStatus()
|
||||
{
|
||||
return Enumerable.Empty<HealthCheckStatus>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ namespace Umbraco.Extensions
|
||||
.WithMiniProfiler()
|
||||
.WithMvcAndRazor()
|
||||
.WithWebServer()
|
||||
.WithPreview();
|
||||
.WithPreview()
|
||||
.WithHostedServices();
|
||||
}
|
||||
|
||||
public static IUmbracoBuilder WithBackOffice(this IUmbracoBuilder builder)
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace Umbraco.Web.Common.Builder
|
||||
public static IUmbracoBuilder WithCore(this IUmbracoBuilder builder)
|
||||
=> builder.AddWith(nameof(WithCore), () => builder.Services.AddUmbracoCore(builder.WebHostEnvironment, builder.Config));
|
||||
|
||||
public static IUmbracoBuilder WithHostedServices(this IUmbracoBuilder builder)
|
||||
=> builder.AddWith(nameof(WithHostedServices), () => builder.Services.AddUmbracoHostedServices());
|
||||
|
||||
public static IUmbracoBuilder WithMiniProfiler(this IUmbracoBuilder builder)
|
||||
=> builder.AddWith(nameof(WithMiniProfiler), () =>
|
||||
builder.Services.AddMiniProfiler(options =>
|
||||
|
||||
@@ -12,8 +12,6 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Hosting;
|
||||
using Serilog.Extensions.Logging;
|
||||
using Umbraco.Composing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -26,97 +24,17 @@ using Umbraco.Core.Logging.Serilog;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Infrastructure.HostedServices;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
using Umbraco.Web.Common.Profiler;
|
||||
using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings;
|
||||
using CoreDebugSettings = Umbraco.Core.Configuration.Models.CoreDebugSettings;
|
||||
using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class UmbracoCoreServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds SqlCe support for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoSqlCeSupport(this IServiceCollection services)
|
||||
{
|
||||
try
|
||||
{
|
||||
var binFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
if (binFolder != null)
|
||||
{
|
||||
var dllPath = Path.Combine(binFolder, "Umbraco.Persistance.SqlCe.dll");
|
||||
var umbSqlCeAssembly = Assembly.LoadFrom(dllPath);
|
||||
|
||||
var sqlCeSyntaxProviderType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeSyntaxProvider");
|
||||
var sqlCeBulkSqlInsertProviderType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeBulkSqlInsertProvider");
|
||||
var sqlCeEmbeddedDatabaseCreatorType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeEmbeddedDatabaseCreator");
|
||||
|
||||
if (!(sqlCeSyntaxProviderType is null || sqlCeBulkSqlInsertProviderType is null || sqlCeEmbeddedDatabaseCreatorType is null))
|
||||
{
|
||||
services.AddSingleton(typeof(ISqlSyntaxProvider), sqlCeSyntaxProviderType);
|
||||
services.AddSingleton(typeof(IBulkSqlInsertProvider), sqlCeBulkSqlInsertProviderType);
|
||||
services.AddSingleton(typeof(IEmbeddedDatabaseCreator), sqlCeEmbeddedDatabaseCreatorType);
|
||||
}
|
||||
|
||||
var sqlCeAssembly = Assembly.LoadFrom(Path.Combine(binFolder, "System.Data.SqlServerCe.dll"));
|
||||
|
||||
var sqlCe = sqlCeAssembly.GetType("System.Data.SqlServerCe.SqlCeProviderFactory");
|
||||
if (!(sqlCe is null))
|
||||
{
|
||||
DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlCe, sqlCe);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore if SqlCE is not available
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Sql Server support for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoSqlServerSupport(this IServiceCollection services)
|
||||
{
|
||||
DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
|
||||
|
||||
services.AddSingleton<ISqlSyntaxProvider, SqlServerSyntaxProvider>();
|
||||
services.AddSingleton<IBulkSqlInsertProvider, SqlServerBulkSqlInsertProvider>();
|
||||
services.AddSingleton<IEmbeddedDatabaseCreator, NoopEmbeddedDatabaseCreator>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Umbraco Back Core requirements
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="webHostEnvironment"></param>
|
||||
/// <param name="umbContainer"></param>
|
||||
/// <param name="entryAssembly"></param>
|
||||
/// <param name="appCaches"></param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoCore(this IServiceCollection services,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
Assembly entryAssembly,
|
||||
AppCaches appCaches,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration)
|
||||
=> services.AddUmbracoCore(webHostEnvironment, entryAssembly, appCaches, loggingConfiguration, configuration, GetCoreRuntime);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Umbraco Configuration requirements
|
||||
/// </summary>
|
||||
@@ -127,10 +45,13 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
// Register configuration validators.
|
||||
services.AddSingleton<IValidateOptions<ContentSettings>, ContentSettingsValidator>();
|
||||
services.AddSingleton<IValidateOptions<GlobalSettings>, GlobalSettingsValidator>();
|
||||
services.AddSingleton<IValidateOptions<HealthChecksSettings>, HealthChecksSettingsValidator >();
|
||||
services.AddSingleton<IValidateOptions<RequestHandlerSettings>, RequestHandlerSettingsValidator>();
|
||||
|
||||
// Register configuration sections.
|
||||
services.Configure<ActiveDirectorySettings>(configuration.GetSection(Constants.Configuration.ConfigActiveDirectory));
|
||||
services.Configure<ConnectionStrings>(configuration.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true);
|
||||
services.Configure<ContentSettings>(configuration.GetSection(Constants.Configuration.ConfigContent));
|
||||
@@ -157,6 +78,27 @@ namespace Umbraco.Extensions
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Umbraco Back Core requirements
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="webHostEnvironment"></param>
|
||||
/// <param name="umbContainer"></param>
|
||||
/// <param name="entryAssembly"></param>
|
||||
/// <param name="appCaches"></param>
|
||||
/// <param name="loggingConfiguration"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoCore(this IServiceCollection services,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
Assembly entryAssembly,
|
||||
AppCaches appCaches,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration)
|
||||
=> services.AddUmbracoCore(webHostEnvironment, entryAssembly, appCaches, loggingConfiguration, configuration, GetCoreRuntime);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Umbraco Back Core requirements
|
||||
/// </summary>
|
||||
@@ -284,6 +226,77 @@ namespace Umbraco.Extensions
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds SqlCe support for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
private static IServiceCollection AddUmbracoSqlCeSupport(this IServiceCollection services)
|
||||
{
|
||||
try
|
||||
{
|
||||
var binFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
if (binFolder != null)
|
||||
{
|
||||
var dllPath = Path.Combine(binFolder, "Umbraco.Persistance.SqlCe.dll");
|
||||
var umbSqlCeAssembly = Assembly.LoadFrom(dllPath);
|
||||
|
||||
var sqlCeSyntaxProviderType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeSyntaxProvider");
|
||||
var sqlCeBulkSqlInsertProviderType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeBulkSqlInsertProvider");
|
||||
var sqlCeEmbeddedDatabaseCreatorType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeEmbeddedDatabaseCreator");
|
||||
|
||||
if (!(sqlCeSyntaxProviderType is null || sqlCeBulkSqlInsertProviderType is null || sqlCeEmbeddedDatabaseCreatorType is null))
|
||||
{
|
||||
services.AddSingleton(typeof(ISqlSyntaxProvider), sqlCeSyntaxProviderType);
|
||||
services.AddSingleton(typeof(IBulkSqlInsertProvider), sqlCeBulkSqlInsertProviderType);
|
||||
services.AddSingleton(typeof(IEmbeddedDatabaseCreator), sqlCeEmbeddedDatabaseCreatorType);
|
||||
}
|
||||
|
||||
var sqlCeAssembly = Assembly.LoadFrom(Path.Combine(binFolder, "System.Data.SqlServerCe.dll"));
|
||||
|
||||
var sqlCe = sqlCeAssembly.GetType("System.Data.SqlServerCe.SqlCeProviderFactory");
|
||||
if (!(sqlCe is null))
|
||||
{
|
||||
DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlCe, sqlCe);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore if SqlCE is not available
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Sql Server support for Umbraco
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoSqlServerSupport(this IServiceCollection services)
|
||||
{
|
||||
DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
|
||||
|
||||
services.AddSingleton<ISqlSyntaxProvider, SqlServerSyntaxProvider>();
|
||||
services.AddSingleton<IBulkSqlInsertProvider, SqlServerBulkSqlInsertProvider>();
|
||||
services.AddSingleton<IEmbeddedDatabaseCreator, NoopEmbeddedDatabaseCreator>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds hosted services for Umbraco.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddUmbracoHostedServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddHostedService<HealthCheckNotifier>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static ITypeFinder CreateTypeFinder(ILoggerFactory loggerFactory, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, IOptionsMonitor<TypeFinderSettings> typeFinderSettings)
|
||||
{
|
||||
var runtimeHashPaths = new RuntimeHashPaths();
|
||||
@@ -355,7 +368,7 @@ namespace Umbraco.Extensions
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
private static void AddLogger(
|
||||
IServiceCollection services,
|
||||
Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user