Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/remove-httpresponseexception

# Conflicts:
#	src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
#	src/Umbraco.Web.Common/Install/InstallApiController.cs
This commit is contained in:
Bjarke Berg
2021-01-05 14:15:56 +01:00
205 changed files with 2294 additions and 2693 deletions

View File

@@ -1,13 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<!-- <!--
this is Umbraco's NuGet configuration, this is Umbraco's NuGet configuration,
content of this file is merged with the system-wide configuration, content of this file is merged with the system-wide configuration,
at %APPDATA%\NuGet\NuGet.config at %APPDATA%\NuGet\NuGet.config
--> -->
<packageSources> <packageSources>
<add key="UmbracoCoreMyGet" value="https://www.myget.org/F/umbracocore/api/v3/index.json" /> <clear />
<add key="ExamineAzurePipelines" value="https://shazwazza.pkgs.visualstudio.com/Examine/_packaging/Examine-Beta/nuget/v3/index.json" /> <add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="SmidgeAppVeyor" value="https://ci.appveyor.com/nuget/smidge" /> <add key="UmbracoCoreMyGet" value="https://www.myget.org/F/umbracocore/api/v3/index.json" />
</packageSources> <add key="ExamineAzurePipelines" value="https://shazwazza.pkgs.visualstudio.com/Examine/_packaging/Examine-Beta/nuget/v3/index.json" />
<add key="SmidgeAppVeyor" value="https://ci.appveyor.com/nuget/smidge" />
</packageSources>
</configuration> </configuration>

View File

@@ -46,7 +46,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return; if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return;
_serverMessenger.PerformRefresh( _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
getNumericId, getNumericId,
instances); instances);
@@ -61,7 +61,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty || id == default(int)) return; if (refresherGuid == Guid.Empty || id == default(int)) return;
_serverMessenger.PerformRefresh( _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
id); id);
} }
@@ -75,7 +75,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty || id == Guid.Empty) return; if (refresherGuid == Guid.Empty || id == Guid.Empty) return;
_serverMessenger.PerformRefresh( _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
id); id);
} }
@@ -86,7 +86,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty || payload == null) return; if (refresherGuid == Guid.Empty || payload == null) return;
_serverMessenger.PerformRefresh( _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
payload); payload);
} }
@@ -97,7 +97,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty || payloads == null) return; if (refresherGuid == Guid.Empty || payloads == null) return;
_serverMessenger.PerformRefresh( _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
payloads.ToArray()); payloads.ToArray());
} }
@@ -125,7 +125,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty) return; if (refresherGuid == Guid.Empty) return;
_serverMessenger.PerformRefreshAll( _serverMessenger.QueueRefreshAll(
GetRefresherById(refresherGuid)); GetRefresherById(refresherGuid));
} }
@@ -138,7 +138,7 @@ namespace Umbraco.Web.Cache
{ {
if (refresherGuid == Guid.Empty || id == default(int)) return; if (refresherGuid == Guid.Empty || id == default(int)) return;
_serverMessenger.PerformRemove( _serverMessenger.QueueRemove(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
id); id);
} }
@@ -155,7 +155,7 @@ namespace Umbraco.Web.Cache
/// </remarks> /// </remarks>
public void Remove<T>(Guid refresherGuid, Func<T, int> getNumericId, params T[] instances) public void Remove<T>(Guid refresherGuid, Func<T, int> getNumericId, params T[] instances)
{ {
_serverMessenger.PerformRemove( _serverMessenger.QueueRemove(
GetRefresherById(refresherGuid), GetRefresherById(refresherGuid),
getNumericId, getNumericId,
instances); instances);
@@ -164,9 +164,15 @@ namespace Umbraco.Web.Cache
#endregion #endregion
// helper method to get an ICacheRefresher by its unique identifier // helper method to get an ICacheRefresher by its unique identifier
private ICacheRefresher GetRefresherById(Guid refresherGuid) private ICacheRefresher GetRefresherById(Guid refresherGuid)
{ {
return _cacheRefreshers[refresherGuid]; ICacheRefresher refresher = _cacheRefreshers[refresherGuid];
if (refresher == null)
{
throw new InvalidOperationException($"No cache refresher found with id {refresherGuid}");
}
return refresher;
} }
} }
} }

View File

@@ -1,22 +0,0 @@
using Umbraco.Core.Composing;
namespace Umbraco.Web.Cache
{
public class DistributedCacheBinderComponent : IComponent
{
private readonly IDistributedCacheBinder _binder;
public DistributedCacheBinderComponent(IDistributedCacheBinder distributedCacheBinder)
{
_binder = distributedCacheBinder;
}
public void Initialize()
{
_binder.BindEvents();
}
public void Terminate()
{ }
}
}

View File

@@ -1,7 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Composing namespace Umbraco.Core.Composing
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -70,7 +70,6 @@ namespace Umbraco.Core.Composing
foreach (var composer in composers) foreach (var composer in composers)
{ {
var componentType = composer.GetType();
composer.Compose(_builder); composer.Compose(_builder);
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection; using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache;

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Composing namespace Umbraco.Core.Composing
{ {

View File

@@ -1,123 +0,0 @@
using Umbraco.Core.DependencyInjection;
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.Routing;
using Umbraco.Web.Sections;
using Umbraco.Web.Tour;
namespace Umbraco.Core
{
public static partial class CompositionExtensions
{
#region Collection Builders
/// <summary>
/// Gets the actions collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ActionCollectionBuilder>();
/// <summary>
/// Gets the content apps collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ContentAppFactoryCollectionBuilder>();
/// <summary>
/// Gets the content finders collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static ContentFinderCollectionBuilder ContentFinders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ContentFinderCollectionBuilder>();
/// <summary>
/// Gets the editor validators collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static EditorValidatorCollectionBuilder EditorValidators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<EditorValidatorCollectionBuilder>();
/// <summary>
/// Gets the health checks collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static HealthCheckCollectionBuilder HealthChecks(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<HealthCheckCollectionBuilder>();
/// <summary>
/// Gets the TourFilters collection builder.
/// </summary>
public static TourFilterCollectionBuilder TourFilters(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<TourFilterCollectionBuilder>();
/// <summary>
/// Gets the URL providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static UrlProviderCollectionBuilder UrlProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<UrlProviderCollectionBuilder>();
/// <summary>
/// Gets the media url providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MediaUrlProviderCollectionBuilder>();
/// <summary>
/// Gets the backoffice sections/applications collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static SectionCollectionBuilder Sections(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<SectionCollectionBuilder>();
/// <summary>
/// Gets the components collection builder.
/// </summary>
public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ComponentCollectionBuilder>();
/// <summary>
/// Gets the backoffice dashboards collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<DashboardCollectionBuilder>()
.Add<ContentDashboard>()
.Add<ExamineDashboard>()
.Add<FormsDashboard>()
.Add<HealthCheckDashboard>()
.Add<ManifestDashboard>()
.Add<MediaDashboard>()
.Add<MembersDashboard>()
.Add<ProfilerDashboard>()
.Add<PublishedStatusDashboard>()
.Add<RedirectUrlDashboard>()
.Add<SettingsDashboard>();
/// <summary>
/// Gets the content finders collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns></returns>
public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MediaUrlGeneratorCollectionBuilder>();
#endregion
}
}

View File

@@ -1,9 +1,10 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Core;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
namespace Umbraco.Core namespace Umbraco.Core.DependencyInjection
{ {
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
@@ -21,7 +22,7 @@ namespace Umbraco.Core
where TImplementing : class, TService1, TService2 where TImplementing : class, TService1, TService2
{ {
services.AddUnique<TService1, TImplementing>(); services.AddUnique<TService1, TImplementing>();
services.AddUnique<TService2>(factory => (TImplementing) factory.GetRequiredService<TService1>()); services.AddUnique<TService2>(factory => (TImplementing)factory.GetRequiredService<TService1>());
} }
public static void AddUnique<TImplementing>(this IServiceCollection services) public static void AddUnique<TImplementing>(this IServiceCollection services)
@@ -48,9 +49,9 @@ namespace Umbraco.Core
/// </summary> /// </summary>
public static void AddUnique<TService>(this IServiceCollection services, TService instance) public static void AddUnique<TService>(this IServiceCollection services, TService instance)
where TService : class where TService : class
=> services.Replace(ServiceDescriptor.Singleton<TService>(instance)); => services.Replace(ServiceDescriptor.Singleton(instance));
public static IServiceCollection AddLazySupport(this IServiceCollection services) internal static IServiceCollection AddLazySupport(this IServiceCollection services)
{ {
services.Replace(ServiceDescriptor.Transient(typeof(Lazy<>), typeof(LazyResolve<>))); services.Replace(ServiceDescriptor.Transient(typeof(Lazy<>), typeof(LazyResolve<>)));
return services; return services;

View File

@@ -1,11 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Composing;
namespace Umbraco.Core namespace Umbraco.Core.DependencyInjection
{ {
/// <summary> /// <summary>
/// Provides extension methods to the <see cref="IFactory"/> class. /// Provides extension methods to the <see cref="IFactory"/> class.

View File

@@ -0,0 +1,275 @@
using System.Security.Cryptography;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Dashboards;
using Umbraco.Core.HealthCheck;
using Umbraco.Core.Manifest;
using Umbraco.Core.PackageActions;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
using Umbraco.Core.Strings;
using Umbraco.Core.Trees;
using Umbraco.Web.Actions;
using Umbraco.Web.ContentApps;
using Umbraco.Web.Dashboards;
using Umbraco.Web.Editors;
using Umbraco.Web.HealthCheck;
using Umbraco.Web.HealthCheck.NotificationMethods;
using Umbraco.Web.Media.EmbedProviders;
using Umbraco.Web.Routing;
using Umbraco.Web.Sections;
using Umbraco.Web.Tour;
namespace Umbraco.Core.DependencyInjection
{
/// <summary>
/// Extension methods for <see cref="IUmbracoBuilder"/>
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Adds all core collection builders
/// </summary>
internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder)
{
builder.CacheRefreshers().Add(() => builder.TypeLoader.GetCacheRefreshers());
builder.DataEditors().Add(() => builder.TypeLoader.GetDataEditors());
builder.Actions().Add(() => builder.TypeLoader.GetTypes<IAction>());
// register known content apps
builder.ContentApps()
.Append<ListViewContentAppFactory>()
.Append<ContentEditorContentAppFactory>()
.Append<ContentInfoContentAppFactory>()
.Append<ContentTypeDesignContentAppFactory>()
.Append<ContentTypeListViewContentAppFactory>()
.Append<ContentTypePermissionsContentAppFactory>()
.Append<ContentTypeTemplatesContentAppFactory>();
// all built-in finders in the correct order,
// devs can then modify this list on application startup
builder.ContentFinders()
.Append<ContentFinderByPageIdQuery>()
.Append<ContentFinderByUrl>()
.Append<ContentFinderByIdPath>()
/*.Append<ContentFinderByUrlAndTemplate>() // disabled, this is an odd finder */
.Append<ContentFinderByUrlAlias>()
.Append<ContentFinderByRedirectUrl>();
builder.EditorValidators().Add(() => builder.TypeLoader.GetTypes<IEditorValidator>());
builder.HealthChecks().Add(() => builder.TypeLoader.GetTypes<Core.HealthCheck.HealthCheck>());
builder.HealthCheckNotificationMethods().Add(() => builder.TypeLoader.GetTypes<IHealthCheckNotificationMethod>());
builder.TourFilters();
builder.UrlProviders()
.Append<AliasUrlProvider>()
.Append<DefaultUrlProvider>();
builder.MediaUrlProviders()
.Append<DefaultMediaUrlProvider>();
// register back office sections in the order we want them rendered
builder.Sections()
.Append<ContentSection>()
.Append<MediaSection>()
.Append<SettingsSection>()
.Append<PackagesSection>()
.Append<UsersSection>()
.Append<MembersSection>()
.Append<FormsSection>()
.Append<TranslationSection>();
builder.Components();
// register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards
builder.Dashboards()
.Add<ContentDashboard>()
.Add<ExamineDashboard>()
.Add<FormsDashboard>()
.Add<HealthCheckDashboard>()
.Add<ManifestDashboard>()
.Add<MediaDashboard>()
.Add<MembersDashboard>()
.Add<ProfilerDashboard>()
.Add<PublishedStatusDashboard>()
.Add<RedirectUrlDashboard>()
.Add<SettingsDashboard>()
.Add(builder.TypeLoader.GetTypes<IDashboard>());
builder.PackageActions().Add(() => builder.TypeLoader.GetPackageActions());
builder.DataValueReferenceFactories();
builder.PropertyValueConverters().Append(builder.TypeLoader.GetTypes<IPropertyValueConverter>());
builder.UrlSegmentProviders().Append<DefaultUrlSegmentProvider>();
builder.ManifestValueValidators()
.Add<RequiredValidator>()
.Add<RegexValidator>()
.Add<DelimitedValueValidator>()
.Add<EmailValidator>()
.Add<IntegerValidator>()
.Add<DecimalValidator>();
builder.ManifestFilters();
builder.MediaUrlGenerators();
// register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable
builder.OEmbedProviders()
.Append<YouTube>()
.Append<Twitter>()
.Append<Vimeo>()
.Append<DailyMotion>()
.Append<Flickr>()
.Append<Slideshare>()
.Append<Kickstarter>()
.Append<GettyImages>()
.Append<Ted>()
.Append<Soundcloud>()
.Append<Issuu>()
.Append<Hulu>()
.Append<Giphy>();
builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes<ISearchableTree>());
}
/// <summary>
/// Gets the actions collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ActionCollectionBuilder>();
/// <summary>
/// Gets the content apps collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ContentAppFactoryCollectionBuilder>();
/// <summary>
/// Gets the content finders collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static ContentFinderCollectionBuilder ContentFinders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ContentFinderCollectionBuilder>();
/// <summary>
/// Gets the editor validators collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static EditorValidatorCollectionBuilder EditorValidators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<EditorValidatorCollectionBuilder>();
/// <summary>
/// Gets the health checks collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static HealthCheckCollectionBuilder HealthChecks(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<HealthCheckCollectionBuilder>();
public static HealthCheckNotificationMethodCollectionBuilder HealthCheckNotificationMethods(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<HealthCheckNotificationMethodCollectionBuilder>();
/// <summary>
/// Gets the TourFilters collection builder.
/// </summary>
public static TourFilterCollectionBuilder TourFilters(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<TourFilterCollectionBuilder>();
/// <summary>
/// Gets the URL providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static UrlProviderCollectionBuilder UrlProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<UrlProviderCollectionBuilder>();
/// <summary>
/// Gets the media url providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MediaUrlProviderCollectionBuilder>();
/// <summary>
/// Gets the backoffice sections/applications collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static SectionCollectionBuilder Sections(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<SectionCollectionBuilder>();
/// <summary>
/// Gets the components collection builder.
/// </summary>
public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ComponentCollectionBuilder>();
/// <summary>
/// Gets the backoffice dashboards collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<DashboardCollectionBuilder>();
/// <summary>
/// Gets the cache refreshers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<CacheRefresherCollectionBuilder>();
/// <summary>
/// Gets the package actions collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<PackageActionCollectionBuilder>();
/// <summary>
/// Gets the data editor collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<DataEditorCollectionBuilder>();
/// <summary>
/// Gets the data value reference factory collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<DataValueReferenceFactoryCollectionBuilder>();
/// <summary>
/// Gets the property value converters collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
/// <summary>
/// Gets the url segment providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>();
/// <summary>
/// Gets the validators collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ManifestValueValidatorCollectionBuilder>();
/// <summary>
/// Gets the manifest filter collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ManifestFilterCollectionBuilder>();
/// <summary>
/// Gets the content finders collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MediaUrlGeneratorCollectionBuilder>();
/// <summary>
/// Gets the backoffice OEmbed Providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<EmbedProvidersCollectionBuilder>();
/// <summary>
/// Gets the back office searchable tree collection builder
/// </summary>
public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<SearchableTreeCollectionBuilder>();
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Composing;
namespace Umbraco.Core.DependencyInjection
{
/// <summary>
/// Extension methods for <see cref="IUmbracoBuilder"/>
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Adds Umbraco composers for plugins
/// </summary>
public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder)
{
// TODO: Should have a better name
IEnumerable<Type> composerTypes = builder.TypeLoader.GetTypes<IComposer>();
IEnumerable<Attribute> enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute));
new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger<Composers>()).Compose();
return builder;
}
}
}

View File

@@ -0,0 +1,52 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Configuration.Models.Validation;
namespace Umbraco.Core.DependencyInjection
{
/// <summary>
/// Extension methods for <see cref="IUmbracoBuilder"/>
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Add Umbraco configuration services and options
/// </summary>
public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
{
// Register configuration validators.
builder.Services.AddSingleton<IValidateOptions<ContentSettings>, ContentSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<GlobalSettings>, GlobalSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<HealthChecksSettings>, HealthChecksSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<RequestHandlerSettings>, RequestHandlerSettingsValidator>();
// Register configuration sections.
builder.Services.Configure<ActiveDirectorySettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigActiveDirectory));
builder.Services.Configure<ConnectionStrings>(builder.Config.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true);
builder.Services.Configure<ContentSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigContent));
builder.Services.Configure<CoreDebugSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigCoreDebug));
builder.Services.Configure<ExceptionFilterSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigExceptionFilter));
builder.Services.Configure<GlobalSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigGlobal));
builder.Services.Configure<HealthChecksSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigHealthChecks));
builder.Services.Configure<HostingSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigHosting));
builder.Services.Configure<ImagingSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigImaging));
builder.Services.Configure<IndexCreatorSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigExamine));
builder.Services.Configure<KeepAliveSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigKeepAlive));
builder.Services.Configure<LoggingSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigLogging));
builder.Services.Configure<MemberPasswordConfigurationSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigMemberPassword));
builder.Services.Configure<ModelsBuilderSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true);
builder.Services.Configure<NuCacheSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigNuCache));
builder.Services.Configure<RequestHandlerSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigRequestHandler));
builder.Services.Configure<RuntimeSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntime));
builder.Services.Configure<SecuritySettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigSecurity));
builder.Services.Configure<TourSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigTours));
builder.Services.Configure<TypeFinderSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder));
builder.Services.Configure<UserPasswordConfigurationSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigUserPassword));
builder.Services.Configure<WebRoutingSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigWebRouting));
builder.Services.Configure<UmbracoPluginSettings>(builder.Config.GetSection(Core.Constants.Configuration.ConfigPlugins));
return builder;
}
}
}

View File

@@ -2,6 +2,7 @@
// See LICENSE for more details. // See LICENSE for more details.
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Core.Events; using Umbraco.Core.Events;
namespace Umbraco.Core.DependencyInjection namespace Umbraco.Core.DependencyInjection
@@ -23,7 +24,15 @@ namespace Umbraco.Core.DependencyInjection
where TNotification : INotification where TNotification : INotification
{ {
// Register the handler as transient. This ensures that anything can be injected into it. // Register the handler as transient. This ensures that anything can be injected into it.
builder.Services.AddTransient(typeof(INotificationHandler<TNotification>), typeof(TNotificationHandler)); var descriptor = new ServiceDescriptor(typeof(INotificationHandler<TNotification>), typeof(TNotificationHandler), ServiceLifetime.Transient);
// TODO: Waiting on feedback here https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 about whether
// we perform this duplicate check or not.
if (!builder.Services.Contains(descriptor))
{
builder.Services.Add(descriptor);
}
return builder; return builder;
} }
} }

View File

@@ -3,28 +3,65 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Diagnostics;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Mail;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Runtime;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Web;
using Umbraco.Web.Cache;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Install;
using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.Routing;
using Umbraco.Web.Services;
using Umbraco.Web.Templates;
namespace Umbraco.Core.DependencyInjection namespace Umbraco.Core.DependencyInjection
{ {
public class UmbracoBuilder : IUmbracoBuilder public class UmbracoBuilder : IUmbracoBuilder
{ {
public IServiceCollection Services { get; }
public IConfiguration Config { get; }
public TypeLoader TypeLoader { get; }
public ILoggerFactory BuilderLoggerFactory { get; }
private readonly Dictionary<Type, ICollectionBuilder> _builders = new Dictionary<Type, ICollectionBuilder>(); private readonly Dictionary<Type, ICollectionBuilder> _builders = new Dictionary<Type, ICollectionBuilder>();
public IServiceCollection Services { get; }
public IConfiguration Config { get; }
public TypeLoader TypeLoader { get; }
public ILoggerFactory BuilderLoggerFactory { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoBuilder"/> class.
/// </summary>
public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader) public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader)
: this(services, config, typeLoader, NullLoggerFactory.Instance) : this(services, config, typeLoader, NullLoggerFactory.Instance)
{ } { }
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoBuilder"/> class.
/// </summary>
public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader, ILoggerFactory loggerFactory) public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader, ILoggerFactory loggerFactory)
{ {
Services = services; Services = services;
@@ -43,10 +80,12 @@ namespace Umbraco.Core.DependencyInjection
public TBuilder WithCollectionBuilder<TBuilder>() public TBuilder WithCollectionBuilder<TBuilder>()
where TBuilder : ICollectionBuilder, new() where TBuilder : ICollectionBuilder, new()
{ {
var typeOfBuilder = typeof(TBuilder); Type typeOfBuilder = typeof(TBuilder);
if (_builders.TryGetValue(typeOfBuilder, out var o)) if (_builders.TryGetValue(typeOfBuilder, out ICollectionBuilder o))
{
return (TBuilder)o; return (TBuilder)o;
}
var builder = new TBuilder(); var builder = new TBuilder();
_builders[typeOfBuilder] = builder; _builders[typeOfBuilder] = builder;
@@ -55,8 +94,10 @@ namespace Umbraco.Core.DependencyInjection
public void Build() public void Build()
{ {
foreach (var builder in _builders.Values) foreach (ICollectionBuilder builder in _builders.Values)
{
builder.RegisterWith(Services); builder.RegisterWith(Services);
}
_builders.Clear(); _builders.Clear();
} }
@@ -66,6 +107,115 @@ namespace Umbraco.Core.DependencyInjection
// Register as singleton to allow injection everywhere. // Register as singleton to allow injection everywhere.
Services.AddSingleton<ServiceFactory>(p => p.GetService); Services.AddSingleton<ServiceFactory>(p => p.GetService);
Services.AddSingleton<IEventAggregator, EventAggregator>(); Services.AddSingleton<IEventAggregator, EventAggregator>();
Services.AddLazySupport();
// Adds no-op registrations as many core services require these dependencies but these
// dependencies cannot be fulfilled in the Core project
Services.AddUnique<IMarchal, NoopMarchal>();
Services.AddUnique<IProfiler, NoopProfiler>();
Services.AddUnique<IApplicationShutdownRegistry, NoopApplicationShutdownRegistry>();
Services.AddUnique<IUmbracoApplicationLifetimeManager, NoopUmbracoApplicationLifetimeManager>();
Services.AddUnique<IMainDom, MainDom>();
Services.AddUnique<IMainDomLock, MainDomSemaphoreLock>();
Services.AddUnique<IIOHelper>(factory =>
{
IHostingEnvironment hostingEnvironment = factory.GetRequiredService<IHostingEnvironment>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return new IOHelperLinux(hostingEnvironment);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return new IOHelperOSX(hostingEnvironment);
}
return new IOHelperWindows(hostingEnvironment);
});
Services.AddUnique(factory => factory.GetRequiredService<AppCaches>().RuntimeCache);
Services.AddUnique(factory => factory.GetRequiredService<AppCaches>().RequestCache);
Services.AddUnique<IProfilingLogger, ProfilingLogger>();
Services.AddUnique<IUmbracoVersion, UmbracoVersion>();
this.AddAllCoreCollectionBuilders();
this.AddNotificationHandler<UmbracoApplicationStarting, EssentialDirectoryCreator>();
Services.AddSingleton<ManifestWatcher>();
this.AddNotificationHandler<UmbracoApplicationStarting, AppPluginsManifestWatcherNotificationHandler>();
Services.AddUnique<InstallStatusTracker>();
// by default, register a noop factory
Services.AddUnique<IPublishedModelFactory, NoopPublishedModelFactory>();
Services.AddUnique<ICultureDictionaryFactory, DefaultCultureDictionaryFactory>();
Services.AddSingleton(f => f.GetRequiredService<ICultureDictionaryFactory>().CreateDictionary());
Services.AddUnique<UriUtility>();
Services.AddUnique<IDashboardService, DashboardService>();
// will be injected in controllers when needed to invoke rest endpoints on Our
Services.AddUnique<IInstallationService, InstallationService>();
Services.AddUnique<IUpgradeService, UpgradeService>();
// Grid config is not a real config file as we know them
Services.AddUnique<IGridConfig, GridConfig>();
Services.AddUnique<IPublishedUrlProvider, UrlProvider>();
Services.AddUnique<ISiteDomainHelper, SiteDomainHelper>();
Services.AddUnique<HtmlLocalLinkParser>();
Services.AddUnique<HtmlImageSourceParser>();
Services.AddUnique<HtmlUrlParser>();
// register properties fallback
Services.AddUnique<IPublishedValueFallback, PublishedValueFallback>();
Services.AddUnique<UmbracoFeatures>();
// register published router
Services.AddUnique<IPublishedRouter, PublishedRouter>();
Services.AddUnique<IEventMessagesFactory, DefaultEventMessagesFactory>();
Services.AddUnique<IEventMessagesAccessor, HybridEventMessagesAccessor>();
Services.AddUnique<ITreeService, TreeService>();
Services.AddUnique<ISectionService, SectionService>();
Services.AddUnique<ISmsSender, NotImplementedSmsSender>();
Services.AddUnique<IEmailSender, NotImplementedEmailSender>();
// register distributed cache
Services.AddUnique(f => new DistributedCache(f.GetRequiredService<IServerMessenger>(), f.GetRequiredService<CacheRefresherCollection>()));
// register the http context and umbraco context accessors
// we *should* use the HttpContextUmbracoContextAccessor, however there are cases when
// we have no http context, eg when booting Umbraco or in background threads, so instead
// let's use an hybrid accessor that can fall back to a ThreadStatic context.
Services.AddUnique<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();
Services.AddUnique<LegacyPasswordSecurity>();
Services.AddUnique<UserEditorAuthorizationHelper>();
Services.AddUnique<ContentPermissions>();
Services.AddUnique<MediaPermissions>();
Services.AddUnique<PropertyEditorCollection>();
Services.AddUnique<ParameterEditorCollection>();
// register a server registrar, by default it's the db registrar
Services.AddUnique<IServerRoleAccessor>(f =>
{
GlobalSettings globalSettings = f.GetRequiredService<IOptions<GlobalSettings>>().Value;
var singleServer = globalSettings.DisableElectionForSingleServer;
return singleServer
? (IServerRoleAccessor)new SingleServerRoleAccessor()
: new ElectedServerRoleAccessor(f.GetRequiredService<IServerRegistrationService>());
});
} }
} }
} }

View File

@@ -0,0 +1,9 @@
using System;
namespace Umbraco.Core.Diagnostics
{
internal class NoopMarchal : IMarchal
{
public IntPtr GetExceptionPointers() => IntPtr.Zero;
}
}

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Core; using Umbraco.Core;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
using Umbraco.Core.HealthCheck; using Umbraco.Core.HealthCheck;
using Umbraco.Core.Mail;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Services; using Umbraco.Core.Services;
using Umbraco.Infrastructure.HealthCheck; using Umbraco.Infrastructure.HealthCheck;

View File

@@ -1,25 +1,20 @@
using System; using System;
namespace Umbraco.Net namespace Umbraco.Core.Hosting
{ {
// TODO: This shouldn't be in this namespace?
public interface IUmbracoApplicationLifetime public interface IUmbracoApplicationLifetime
{ {
/// <summary> /// <summary>
/// A value indicating whether the application is restarting after the current request. /// A value indicating whether the application is restarting after the current request.
/// </summary> /// </summary>
bool IsRestarting { get; } bool IsRestarting { get; }
/// <summary> /// <summary>
/// Terminates the current application. The application restarts the next time a request is received for it. /// Terminates the current application. The application restarts the next time a request is received for it.
/// </summary> /// </summary>
void Restart(); void Restart();
// TODO: Should be killed and replaced with UmbracoApplicationStarting notifications
event EventHandler ApplicationInit; event EventHandler ApplicationInit;
} }
public interface IUmbracoApplicationLifetimeManager
{
void InvokeApplicationInit();
}
} }

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Hosting
{
// TODO: Should be killed and replaced with UmbracoApplicationStarting notifications
public interface IUmbracoApplicationLifetimeManager
{
void InvokeApplicationInit();
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Hosting
{
internal class NoopApplicationShutdownRegistry : IApplicationShutdownRegistry
{
public void RegisterObject(IRegisteredObject registeredObject) { }
public void UnregisterObject(IRegisteredObject registeredObject) { }
}
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Core.Hosting
{
internal class NoopUmbracoApplicationLifetimeManager : IUmbracoApplicationLifetimeManager
{
public void InvokeApplicationInit() { }
}
}

View File

@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting; using Umbraco.Core.Hosting;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.IO namespace Umbraco.Core.IO
{ {

View File

@@ -2,9 +2,9 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Umbraco.Core.Hosting;
using Umbraco.Core.Security; using Umbraco.Core.Security;
using Umbraco.Core.Services; using Umbraco.Core.Services;
using Umbraco.Net;
using Umbraco.Web.Install.Models; using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps namespace Umbraco.Web.Install.InstallSteps

View File

@@ -1,8 +1,8 @@
using System; using System;
namespace Umbraco.Core.Logging namespace Umbraco.Core.Logging
{ {
public class VoidProfiler : IProfiler public class NoopProfiler : IProfiler
{ {
private readonly VoidDisposable _disposable = new VoidDisposable(); private readonly VoidDisposable _disposable = new VoidDisposable();

View File

@@ -1,7 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Umbraco.Core.Models; using Umbraco.Core.Models;
namespace Umbraco.Core namespace Umbraco.Core.Mail
{ {
/// <summary> /// <summary>
/// Simple abstraction to send an email message /// Simple abstraction to send an email message

View File

@@ -1,6 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Umbraco.Core namespace Umbraco.Core.Mail
{ {
/// <summary> /// <summary>
/// Service to send an SMS /// Service to send an SMS

View File

@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
using Umbraco.Core.Models;
namespace Umbraco.Core.Mail
{
internal class NotImplementedEmailSender : IEmailSender
{
public Task SendAsync(EmailMessage message)
=> throw new NotImplementedException("To send an Email ensure IEmailSender is implemented with a custom implementation");
}
}

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Umbraco.Core namespace Umbraco.Core.Mail
{ {
/// <summary> /// <summary>
/// An <see cref="ISmsSender"/> that throws <see cref="NotImplementedException"/> /// An <see cref="ISmsSender"/> that throws <see cref="NotImplementedException"/>

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Umbraco.Net; using Umbraco.Core.Hosting;
namespace Umbraco.Core.Manifest namespace Umbraco.Core.Manifest
{ {

View File

@@ -1,11 +1,9 @@
using System.Linq; using System.Linq;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Core.Manifest; using Umbraco.Core.Manifest;
namespace Umbraco.Core.PropertyEditors namespace Umbraco.Core.PropertyEditors
{ {
public class PropertyEditorCollection : BuilderCollectionBase<IDataEditor> public class PropertyEditorCollection : BuilderCollectionBase<IDataEditor>
{ {
public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser)

View File

@@ -1,4 +1,4 @@
using System; using System;
using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Strings; using Umbraco.Core.Strings;
@@ -8,7 +8,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
/// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used.
/// </summary> /// </summary>
[DefaultPropertyValueConverter] [DefaultPropertyValueConverter]
public class TinyMceValueConverter : PropertyValueConverterBase public class SimpleTinyMceValueConverter : PropertyValueConverterBase
{ {
public override bool IsConverter(IPublishedPropertyType propertyType) public override bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce;

View File

@@ -1,45 +0,0 @@
using System;
using System.Linq;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.PropertyEditors.ValueConverters
{
[DefaultPropertyValueConverter]
public class TextStringValueConverter : PropertyValueConverterBase
{
private static readonly string[] PropertyTypeAliases =
{
Constants.PropertyEditors.Aliases.TextBox,
Constants.PropertyEditors.Aliases.TextArea
};
public override bool IsConverter(IPublishedPropertyType propertyType)
=> PropertyTypeAliases.Contains(propertyType.EditorAlias);
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (string);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Element;
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
{
// in xml a string is: string
// in the database a string is: string
// default value is: null
return source;
}
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
// source should come from ConvertSource and be a string (or null) already
return inter ?? string.Empty;
}
public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview)
{
// source should come from ConvertSource and be a string (or null) already
return inter;
}
}
}

View File

@@ -2,12 +2,11 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Hosting; using Umbraco.Core.Hosting;
using Umbraco.Core.Manifest; using Umbraco.Core.Manifest;
namespace Umbraco.Infrastructure.Runtime namespace Umbraco.Core.Runtime
{ {
/// <summary> /// <summary>
/// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes. /// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes.

View File

@@ -1,13 +1,12 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Hosting; using Umbraco.Core.Hosting;
using Umbraco.Core.IO; using Umbraco.Core.IO;
namespace Umbraco.Infrastructure.Runtime namespace Umbraco.Core.Runtime
{ {
public class EssentialDirectoryCreator : INotificationHandler<UmbracoApplicationStarting> public class EssentialDirectoryCreator : INotificationHandler<UmbracoApplicationStarting>
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Runtime
_logger = logger; _logger = logger;
} }
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread // WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
public Task ListenAsync() => _signal.WaitOneAsync(); public Task ListenAsync() => _signal.WaitOneAsync();
public Task<bool> AcquireLockAsync(int millisecondsTimeout) public Task<bool> AcquireLockAsync(int millisecondsTimeout)
@@ -44,7 +44,7 @@ namespace Umbraco.Core.Runtime
// if more than 1 instance reach that point, one will get the lock // if more than 1 instance reach that point, one will get the lock
// and the other one will timeout, which is accepted // and the other one will timeout, which is accepted
//This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. // This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset.
try try
{ {
_lockRelease = _systemLock.Lock(millisecondsTimeout); _lockRelease = _systemLock.Lock(millisecondsTimeout);

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Sync; using Umbraco.Core.Sync;
@@ -11,9 +11,8 @@ namespace Umbraco.Core.Services
/// Touches a server to mark it as active; deactivate stale servers. /// Touches a server to mark it as active; deactivate stale servers.
/// </summary> /// </summary>
/// <param name="serverAddress">The server URL.</param> /// <param name="serverAddress">The server URL.</param>
/// <param name="serverIdentity">The server unique identity.</param>
/// <param name="staleTimeout">The time after which a server is considered stale.</param> /// <param name="staleTimeout">The time after which a server is considered stale.</param>
void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout); void TouchServer(string serverAddress, TimeSpan staleTimeout);
/// <summary> /// <summary>
/// Deactivates a server. /// Deactivates a server.
@@ -38,11 +37,6 @@ namespace Umbraco.Core.Services
/// from the database.</remarks> /// from the database.</remarks>
IEnumerable<IServerRegistration> GetActiveServers(bool refresh = false); IEnumerable<IServerRegistration> GetActiveServers(bool refresh = false);
/// <summary>
/// Gets the current server identity.
/// </summary>
string CurrentServerIdentity { get; }
/// <summary> /// <summary>
/// Gets the role of the current server. /// Gets the role of the current server.
/// </summary> /// </summary>

View File

@@ -1,8 +1,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories;
namespace Umbraco.Core.Services.Implement namespace Umbraco.Core.Services
{ {
public class InstallationService : IInstallationService public class InstallationService : IInstallationService
{ {

View File

@@ -1,10 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Semver; using Semver;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Services;
namespace Umbraco.Core namespace Umbraco.Core.Services
{ {
public class UpgradeService : IUpgradeService public class UpgradeService : IUpgradeService
{ {

View File

@@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Umbraco.Core.Sync namespace Umbraco.Core.Sync
{ {
/// <summary> /// <summary>
/// Holds a list of callbacks associated with implementations of <see cref="IBatchedDatabaseServerMessenger"/>. /// Holds a list of callbacks associated with implementations of <see cref="IServerMessenger"/>.
/// </summary> /// </summary>
public class DatabaseServerMessengerCallbacks public class DatabaseServerMessengerCallbacks
{ {

View File

@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Services;
namespace Umbraco.Core.Sync
{
/// <summary>
/// A registrar that stores registered server nodes in the database.
/// </summary>
/// <remarks>
/// This is the default registrar which determines a server's role by using a master election process.
/// The master election process doesn't occur until just after startup so this election process doesn't really affect the primary startup phase.
/// </remarks>
public sealed class DatabaseServerRegistrar : IServerRegistrar
{
private readonly Lazy<IServerRegistrationService> _registrationService;
/// <summary>
/// Initializes a new instance of the <see cref="DatabaseServerRegistrar"/> class.
/// </summary>
/// <param name="registrationService">The registration service.</param>
/// <param name="options">Some options.</param>
public DatabaseServerRegistrar(Lazy<IServerRegistrationService> registrationService)
{
_registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService));
}
/// <summary>
/// Gets the registered servers.
/// </summary>
public IEnumerable<IServerAddress> Registrations => _registrationService.Value.GetActiveServers();
/// <summary>
/// Gets the role of the current server in the application environment.
/// </summary>
public ServerRole GetCurrentServerRole()
{
var service = _registrationService.Value;
return service.GetCurrentServerRole();
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using Umbraco.Core.Services;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Gets the current server's <see cref="ServerRole"/> based on active servers registered with <see cref="IServerRegistrationService"/>
/// </summary>
/// <remarks>
/// This is the default service which determines a server's role by using a master election process.
/// The master election process doesn't occur until just after startup so this election process doesn't really affect the primary startup phase.
/// </remarks>
public sealed class ElectedServerRoleAccessor : IServerRoleAccessor
{
private readonly IServerRegistrationService _registrationService;
/// <summary>
/// Initializes a new instance of the <see cref="ElectedServerRoleAccessor"/> class.
/// </summary>
/// <param name="registrationService">The registration service.</param>
/// <param name="options">Some options.</param>
public ElectedServerRoleAccessor(IServerRegistrationService registrationService) => _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService));
/// <summary>
/// Gets the role of the current server in the application environment.
/// </summary>
public ServerRole CurrentServerRole => _registrationService.GetCurrentServerRole();
}
}

View File

@@ -1,12 +0,0 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// An <see cref="IServerMessenger"/> implementation that works by storing messages in the database.
/// </summary>
public interface IBatchedDatabaseServerMessenger : IDatabaseServerMessenger
{
void FlushBatch();
DatabaseServerMessengerCallbacks Callbacks { get; }
void Startup();
}
}

View File

@@ -1,7 +0,0 @@
namespace Umbraco.Core.Sync
{
public interface IDatabaseServerMessenger: IServerMessenger
{
void Sync();
}
}

View File

@@ -1,21 +1,30 @@
using System; using System;
using System.Collections.Generic;
using Umbraco.Core.Cache; using Umbraco.Core.Cache;
namespace Umbraco.Core.Sync namespace Umbraco.Core.Sync
{ {
/// <summary> /// <summary>
/// Broadcasts distributed cache notifications to all servers of a load balanced environment. /// Transmits distributed cache notifications for all servers of a load balanced environment.
/// </summary> /// </summary>
/// <remarks>Also ensures that the notification is processed on the local environment.</remarks> /// <remarks>Also ensures that the notification is processed on the local environment.</remarks>
public interface IServerMessenger public interface IServerMessenger
{ {
/// <summary>
/// Called to synchronize a server with queued notifications
/// </summary>
void Sync();
/// <summary>
/// Called to send/commit the queued messages created with the Perform methods
/// </summary>
void SendMessages();
/// <summary> /// <summary>
/// Notifies the distributed cache, for a specified <see cref="ICacheRefresher"/>. /// Notifies the distributed cache, for a specified <see cref="ICacheRefresher"/>.
/// </summary> /// </summary>
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="payload">The notification content.</param> /// <param name="payload">The notification content.</param>
void PerformRefresh<TPayload>(ICacheRefresher refresher, TPayload[] payload); void QueueRefresh<TPayload>(ICacheRefresher refresher, TPayload[] payload);
/// <summary> /// <summary>
/// Notifies the distributed cache of specified item invalidation, for a specified <see cref="ICacheRefresher"/>. /// Notifies the distributed cache of specified item invalidation, for a specified <see cref="ICacheRefresher"/>.
@@ -24,7 +33,7 @@ namespace Umbraco.Core.Sync
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="getNumericId">A function returning the unique identifier of items.</param> /// <param name="getNumericId">A function returning the unique identifier of items.</param>
/// <param name="instances">The invalidated items.</param> /// <param name="instances">The invalidated items.</param>
void PerformRefresh<T>(ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances); void QueueRefresh<T>(ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances);
/// <summary> /// <summary>
/// Notifies the distributed cache of specified item invalidation, for a specified <see cref="ICacheRefresher"/>. /// Notifies the distributed cache of specified item invalidation, for a specified <see cref="ICacheRefresher"/>.
@@ -33,7 +42,7 @@ namespace Umbraco.Core.Sync
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="getGuidId">A function returning the unique identifier of items.</param> /// <param name="getGuidId">A function returning the unique identifier of items.</param>
/// <param name="instances">The invalidated items.</param> /// <param name="instances">The invalidated items.</param>
void PerformRefresh<T>(ICacheRefresher refresher, Func<T, Guid> getGuidId, params T[] instances); void QueueRefresh<T>(ICacheRefresher refresher, Func<T, Guid> getGuidId, params T[] instances);
/// <summary> /// <summary>
/// Notifies all servers of specified items removal, for a specified <see cref="ICacheRefresher"/>. /// Notifies all servers of specified items removal, for a specified <see cref="ICacheRefresher"/>.
@@ -42,33 +51,33 @@ namespace Umbraco.Core.Sync
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="getNumericId">A function returning the unique identifier of items.</param> /// <param name="getNumericId">A function returning the unique identifier of items.</param>
/// <param name="instances">The removed items.</param> /// <param name="instances">The removed items.</param>
void PerformRemove<T>(ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances); void QueueRemove<T>(ICacheRefresher refresher, Func<T, int> getNumericId, params T[] instances);
/// <summary> /// <summary>
/// Notifies all servers of specified items removal, for a specified <see cref="ICacheRefresher"/>. /// Notifies all servers of specified items removal, for a specified <see cref="ICacheRefresher"/>.
/// </summary> /// </summary>
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="numericIds">The unique identifiers of the removed items.</param> /// <param name="numericIds">The unique identifiers of the removed items.</param>
void PerformRemove(ICacheRefresher refresher, params int[] numericIds); void QueueRemove(ICacheRefresher refresher, params int[] numericIds);
/// <summary> /// <summary>
/// Notifies all servers of specified items invalidation, for a specified <see cref="ICacheRefresher"/>. /// Notifies all servers of specified items invalidation, for a specified <see cref="ICacheRefresher"/>.
/// </summary> /// </summary>
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="numericIds">The unique identifiers of the invalidated items.</param> /// <param name="numericIds">The unique identifiers of the invalidated items.</param>
void PerformRefresh(ICacheRefresher refresher, params int[] numericIds); void QueueRefresh(ICacheRefresher refresher, params int[] numericIds);
/// <summary> /// <summary>
/// Notifies all servers of specified items invalidation, for a specified <see cref="ICacheRefresher"/>. /// Notifies all servers of specified items invalidation, for a specified <see cref="ICacheRefresher"/>.
/// </summary> /// </summary>
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
/// <param name="guidIds">The unique identifiers of the invalidated items.</param> /// <param name="guidIds">The unique identifiers of the invalidated items.</param>
void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds); void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds);
/// <summary> /// <summary>
/// Notifies all servers of a global invalidation for a specified <see cref="ICacheRefresher"/>. /// Notifies all servers of a global invalidation for a specified <see cref="ICacheRefresher"/>.
/// </summary> /// </summary>
/// <param name="refresher">The ICacheRefresher.</param> /// <param name="refresher">The ICacheRefresher.</param>
void PerformRefreshAll(ICacheRefresher refresher); void QueueRefreshAll(ICacheRefresher refresher);
} }
} }

View File

@@ -1,21 +0,0 @@
using System.Collections.Generic;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Provides server registrations to the distributed cache.
/// </summary>
public interface IServerRegistrar
{
/// <summary>
/// Gets the server registrations.
/// </summary>
IEnumerable<IServerAddress> Registrations { get; }
/// <summary>
/// Gets the role of the current server in the application environment.
/// </summary>
ServerRole GetCurrentServerRole();
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Gets the current server's <see cref="ServerRole"/>
/// </summary>
public interface IServerRoleAccessor
{
/// <summary>
/// Gets the role of the current server in the application environment.
/// </summary>
ServerRole CurrentServerRole { get; }
}
}

View File

@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using Umbraco.Web;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance
/// </summary>
/// <remarks>
/// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the <see cref="ServerRole"/>
/// which by default is done with master election by a database query. The master election process doesn't occur until just after startup
/// so this micro optimization doesn't really affect the primary startup phase.
/// </remarks>
public class SingleServerRegistrar : IServerRegistrar
{
private readonly IRequestAccessor _requestAccessor;
private readonly Lazy<IServerAddress[]> _registrations;
public IEnumerable<IServerAddress> Registrations => _registrations.Value;
public SingleServerRegistrar(IRequestAccessor requestAccessor)
{
_requestAccessor = requestAccessor;
_registrations = new Lazy<IServerAddress[]>(() => new IServerAddress[] { new ServerAddressImpl(_requestAccessor.GetApplicationUrl().ToString()) });
}
public ServerRole GetCurrentServerRole()
{
return ServerRole.Single;
}
private class ServerAddressImpl : IServerAddress
{
public ServerAddressImpl(string serverAddress)
{
ServerAddress = serverAddress;
}
public string ServerAddress { get; }
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using Umbraco.Web;
namespace Umbraco.Core.Sync
{
/// <summary>
/// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance
/// </summary>
/// <remarks>
/// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the <see cref="ServerRole"/>
/// which by default is done with master election by a database query. The master election process doesn't occur until just after startup
/// so this micro optimization doesn't really affect the primary startup phase.
/// </remarks>
public class SingleServerRoleAccessor : IServerRoleAccessor
{
public ServerRole CurrentServerRole => ServerRole.Single;
}
}

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Trees namespace Umbraco.Core.Trees
{ {
public interface ISearchableTree : IDiscoverable public interface ISearchableTree : IDiscoverable
{ {

View File

@@ -1,6 +1,4 @@
using Umbraco.Web.Trees; namespace Umbraco.Core.Trees
namespace Umbraco.Web.Search
{ {
public class SearchableApplicationTree public class SearchableApplicationTree
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Umbraco.Core; using Umbraco.Core;
@@ -6,7 +6,7 @@ using Umbraco.Core.Composing;
using Umbraco.Web.Services; using Umbraco.Web.Services;
using Umbraco.Web.Trees; using Umbraco.Web.Trees;
namespace Umbraco.Web.Search namespace Umbraco.Core.Trees
{ {
public class SearchableTreeCollection : BuilderCollectionBase<ISearchableTree> public class SearchableTreeCollection : BuilderCollectionBase<ISearchableTree>
{ {

View File

@@ -1,8 +1,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Search namespace Umbraco.Core.Trees
{ {
public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase<SearchableTreeCollectionBuilder, SearchableTreeCollection, ISearchableTree> public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase<SearchableTreeCollectionBuilder, SearchableTreeCollection, ISearchableTree>
{ {

View File

@@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.1.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.8" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" /> <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.8" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.7.0" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.7.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />

View File

@@ -1,5 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection; using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;

View File

@@ -0,0 +1,96 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Events;
using Umbraco.Core.Persistence;
using Umbraco.Core.Sync;
using Umbraco.Web;
using Umbraco.Web.Cache;
using Umbraco.Web.Routing;
namespace Umbraco.Infrastructure.Cache
{
/// <summary>
/// Ensures that distributed cache events are setup and the <see cref="IServerMessenger"/> is initialized
/// </summary>
public sealed class DatabaseServerMessengerNotificationHandler : INotificationHandler<UmbracoApplicationStarting>
{
private readonly IServerMessenger _messenger;
private readonly IRequestAccessor _requestAccessor;
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IDistributedCacheBinder _distributedCacheBinder;
private readonly ILogger<DatabaseServerMessengerNotificationHandler> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="DatabaseServerMessengerNotificationHandler"/> class.
/// </summary>
public DatabaseServerMessengerNotificationHandler(
IServerMessenger serverMessenger,
IRequestAccessor requestAccessor,
IUmbracoDatabaseFactory databaseFactory,
IDistributedCacheBinder distributedCacheBinder,
ILogger<DatabaseServerMessengerNotificationHandler> logger)
{
_requestAccessor = requestAccessor;
_databaseFactory = databaseFactory;
_distributedCacheBinder = distributedCacheBinder;
_logger = logger;
_messenger = serverMessenger;
}
/// <inheritdoc/>
public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
{
// The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services.
// The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL
// being available (via IRequestAccessor), which can only be retrieved within an HTTP request (unless it's explicitly configured).
// Hence we hook up a one-off task on an HTTP request to ensure this is retrieved, which caches the value and makes it available
// for the hosted services to use when the HTTP request is not available.
_requestAccessor.RouteAttempt += EnsureApplicationUrlOnce;
_requestAccessor.EndRequest += EndRequest;
Startup();
return Task.CompletedTask;
}
private void Startup()
{
if (_databaseFactory.CanConnect == false)
{
_logger.LogWarning("Cannot connect to the database, distributed calls will not be enabled for this server.");
}
else
{
_distributedCacheBinder.BindEvents();
// Sync on startup, this will run through the messenger's initialization sequence
_messenger?.Sync();
}
}
// TODO: I don't really know or think that the Application Url plays a role anymore with the DB dist cache,
// this might be really old stuff. I 'think' all this is doing is ensuring that the IRequestAccessor.GetApplicationUrl
// is definitely called during the first request. If that is still required, that logic doesn't belong here. That logic
// should be part of it's own service/middleware. There's also TODO notes within IRequestAccessor.GetApplicationUrl directly
// mentioning that the property doesn't belong on that service either. This should be investigated and resolved in a separate task.
private void EnsureApplicationUrlOnce(object sender, RoutableAttemptEventArgs e)
{
if (e.Outcome == EnsureRoutableOutcome.IsRoutable || e.Outcome == EnsureRoutableOutcome.NotDocumentRequest)
{
_requestAccessor.RouteAttempt -= EnsureApplicationUrlOnce;
EnsureApplicationUrl();
}
}
// By retrieving the application URL within the context of a request (as we are here in responding
// to the IRequestAccessor's RouteAttempt event), we'll get it from the HTTP context and save it for
// future requests that may not be within an HTTP request (e.g. from hosted services).
private void EnsureApplicationUrl() => _requestAccessor.GetApplicationUrl();
/// <summary>
/// Clear the batch on end request
/// </summary>
private void EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.SendMessages();
}
}

View File

@@ -1,20 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Cache
{
/// <summary>
/// Installs listeners on service events in order to refresh our caches.
/// </summary>
[ComposeBefore(typeof(ICoreComposer))] // runs before every other IUmbracoCoreComponent!
public sealed class DistributedCacheBinderComposer : ComponentComposer<DistributedCacheBinderComponent>, ICoreComposer
{
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.Services.AddUnique<IDistributedCacheBinder, DistributedCacheBinder>();
}
}
}

View File

@@ -1,121 +0,0 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Sync;
using Umbraco.Web.Cache;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Search;
namespace Umbraco.Web.Compose
{
/// <summary>
/// Ensures that servers are automatically registered in the database, when using the database server registrar.
/// </summary>
/// <remarks>
/// <para>At the moment servers are automatically registered upon first request and then on every
/// request but not more than once per (configurable) period. This really is "for information & debug" purposes so
/// we can look at the table and see what servers are registered - but the info is not used anywhere.</para>
/// <para>Should we actually want to use this, we would need a better and more deterministic way of figuring
/// out the "server address" ie the address to which server-to-server requests should be sent - because it
/// probably is not the "current request address" - especially in multi-domains configurations.</para>
/// </remarks>
// during Initialize / Startup, we end up checking Examine, which needs to be initialized beforehand
// TODO: should not be a strong dependency on "examine" but on an "indexing component"
[ComposeAfter(typeof(ExamineComposer))]
public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer<DatabaseServerRegistrarAndMessengerComponent>, ICoreComposer
{
public static DatabaseServerMessengerCallbacks GetCallbacks(IServiceProvider factory)
{
return new DatabaseServerMessengerCallbacks
{
//These callbacks will be executed if the server has not been synced
// (i.e. it is a new server or the lastsynced.txt file has been removed)
InitializingCallbacks = new Action[]
{
//rebuild the xml cache file if the server is not synced
() =>
{
var publishedSnapshotService = factory.GetRequiredService<IPublishedSnapshotService>();
// rebuild the published snapshot caches entirely, if the server is not synced
// this is equivalent to DistributedCache RefreshAll... but local only
// (we really should have a way to reuse RefreshAll... locally)
// note: refresh all content & media caches does refresh content types too
publishedSnapshotService.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
publishedSnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
publishedSnapshotService.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _);
},
//rebuild indexes if the server is not synced
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
// indexes then they can adjust this logic themselves.
() =>
{
var indexRebuilder = factory.GetRequiredService<BackgroundIndexRebuilder>();
indexRebuilder.RebuildIndexes(false, 5000);
}
}
};
}
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.SetDatabaseServerMessengerCallbacks(GetCallbacks);
builder.SetServerMessenger<BatchedDatabaseServerMessenger>();
}
}
public sealed class DatabaseServerRegistrarAndMessengerComponent : IComponent
{
private readonly IBatchedDatabaseServerMessenger _messenger;
private readonly IRequestAccessor _requestAccessor;
public DatabaseServerRegistrarAndMessengerComponent(
IServerMessenger serverMessenger,
IRequestAccessor requestAccessor)
{
_requestAccessor = requestAccessor;
_messenger = serverMessenger as IBatchedDatabaseServerMessenger;
}
public void Initialize()
{
// The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services.
// The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL
// being available (via IRequestAccessor), which can only be retrieved within an HTTP request (unless it's explicitly configured).
// Hence we hook up a one-off task on an HTTP request to ensure this is retrieved, which caches the value and makes it available
// for the hosted services to use when the HTTP request is not available.
_requestAccessor.RouteAttempt += EnsureApplicationUrlOnce;
// Must come last, as it references some _variables
_messenger?.Startup();
}
public void Terminate()
{ }
private void EnsureApplicationUrlOnce(object sender, RoutableAttemptEventArgs e)
{
if (e.Outcome == EnsureRoutableOutcome.IsRoutable || e.Outcome == EnsureRoutableOutcome.NotDocumentRequest)
{
_requestAccessor.RouteAttempt -= EnsureApplicationUrlOnce;
EnsureApplicationUrl();
}
}
private void EnsureApplicationUrl()
{
// By retrieving the application URL within the context of a request (as we are here in responding
// to the IRequestAccessor's RouteAttempt event), we'll get it from the HTTP context and save it for
// future requests that may not be within an HTTP request (e.g. from hosted services).
_requestAccessor.GetApplicationUrl();
}
}
}

View File

@@ -1,5 +1,4 @@
using Umbraco.Core; using Umbraco.Core.DependencyInjection;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
namespace Umbraco.Web.Compose namespace Umbraco.Web.Compose

View File

@@ -1,37 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
using Umbraco.Web.Install;
using Umbraco.Web.Install.InstallSteps;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Composing.CompositionExtensions
{
public static class Installer
{
public static IUmbracoBuilder ComposeInstaller(this IUmbracoBuilder builder)
{
// register the installer steps
builder.Services.AddScoped<InstallSetupStep,NewInstallStep>();
builder.Services.AddScoped<InstallSetupStep,UpgradeStep>();
builder.Services.AddScoped<InstallSetupStep,FilePermissionsStep>();
builder.Services.AddScoped<InstallSetupStep,DatabaseConfigureStep>();
builder.Services.AddScoped<InstallSetupStep,DatabaseInstallStep>();
builder.Services.AddScoped<InstallSetupStep,DatabaseUpgradeStep>();
// TODO: Add these back once we have a compatible Starter kit
// composition.Services.AddScoped<InstallSetupStep,StarterKitDownloadStep>();
// composition.Services.AddScoped<InstallSetupStep,StarterKitInstallStep>();
// composition.Services.AddScoped<InstallSetupStep,StarterKitCleanupStep>();
builder.Services.AddScoped<InstallSetupStep,CompleteInstallStep>();
builder.Services.AddTransient<InstallStepCollection>();
builder.Services.AddUnique<InstallHelper>();
return builder;
}
}
}

View File

@@ -1,335 +0,0 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging.Viewer;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PackageActions;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Web.Media.EmbedProviders;
using Umbraco.Web.Search;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
#region Collection Builders
/// <summary>
/// Gets the cache refreshers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<CacheRefresherCollectionBuilder>();
/// <summary>
/// Gets the mappers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MapperCollectionBuilder>();
/// <summary>
/// Gets the package actions collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<PackageActionCollectionBuilder>();
/// <summary>
/// Gets the data editor collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<DataEditorCollectionBuilder>();
/// <summary>
/// Gets the data value reference factory collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<DataValueReferenceFactoryCollectionBuilder>();
/// <summary>
/// Gets the property value converters collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
/// <summary>
/// Gets the url segment providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>();
/// <summary>
/// Gets the validators collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ManifestValueValidatorCollectionBuilder>();
/// <summary>
/// Gets the manifest filter collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<ManifestFilterCollectionBuilder>();
/// <summary>
/// Gets the backoffice OEmbed Providers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<EmbedProvidersCollectionBuilder>();
/// <summary>
/// Gets the back office searchable tree collection builder
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<SearchableTreeCollectionBuilder>();
#endregion
#region Uniques
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetCultureDictionaryFactory<T>(this IUmbracoBuilder builder)
where T : class, ICultureDictionaryFactory
{
builder.Services.AddUnique<ICultureDictionaryFactory, T>();
}
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a culture dictionary factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func<IServiceProvider, ICultureDictionaryFactory> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetPublishedContentModelFactory<T>(this IUmbracoBuilder builder)
where T : class, IPublishedModelFactory
{
builder.Services.AddUnique<IPublishedModelFactory, T>();
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedModelFactory> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerRegistrar<T>(this IUmbracoBuilder builder)
where T : class, IServerRegistrar
{
builder.Services.AddUnique<IServerRegistrar, T>();
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, Func<IServiceProvider, IServerRegistrar> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRegistrar registrar)
{
builder.Services.AddUnique(registrar);
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerMessenger<T>(this IUmbracoBuilder builder)
where T : class, IServerMessenger
{
builder.Services.AddUnique<IServerMessenger, T>();
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, Func<IServiceProvider, IServerMessenger> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
{
builder.Services.AddUnique(registrar);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating the options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerCallbacks(this IUmbracoBuilder builder, Func<IServiceProvider, DatabaseServerMessengerCallbacks> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="options">Options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this IUmbracoBuilder builder, DatabaseServerMessengerCallbacks options)
{
builder.Services.AddUnique(options);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <typeparam name="T">The type of the short string helper.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetShortStringHelper<T>(this IUmbracoBuilder builder)
where T : class, IShortStringHelper
{
builder.Services.AddUnique<IShortStringHelper, T>();
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, Func<IServiceProvider, IShortStringHelper> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
{
builder.Services.AddUnique(helper);
}
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
/// <remarks>
/// Using this helper will ensure that your IFileSystem implementation is wrapped by the ShadowWrapper
/// </remarks>
public static void SetMediaFileSystem(this IUmbracoBuilder builder, Func<IServiceProvider, IFileSystem> filesystemFactory)
=> builder.Services.AddUnique<IMediaFileSystem>(factory =>
{
var fileSystems = factory.GetRequiredService<IO.FileSystems>();
return fileSystems.GetFileSystem<MediaFileSystem>(filesystemFactory(factory));
});
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <typeparam name="T">The type of the log viewer.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetLogViewer<T>(this IUmbracoBuilder builder)
where T : class, ILogViewer
{
builder.Services.AddUnique<ILogViewer, T>();
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, Func<IServiceProvider, ILogViewer> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
{
builder.Services.AddUnique(viewer);
}
#endregion
}
}

View File

@@ -0,0 +1,25 @@
using Umbraco.Core.Cache;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Manifest;
using Umbraco.Core.PackageActions;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Strings;
using Umbraco.Core.Trees;
using Umbraco.Web.Media.EmbedProviders;
namespace Umbraco.Infrastructure.DependencyInjection
{
/// <summary>
/// Provides extension methods to the <see cref="IUmbracoBuilder"/> class.
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Gets the mappers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<MapperCollectionBuilder>();
}
}

View File

@@ -0,0 +1,208 @@
using System.Runtime.InteropServices;
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Hosting;
using Umbraco.Core.Install;
using Umbraco.Core.Logging.Serilog.Enrichers;
using Umbraco.Core.Mail;
using Umbraco.Core.Manifest;
using Umbraco.Core.Media;
using Umbraco.Core.Migrations;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Migrations.PostMigrations;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Runtime;
using Umbraco.Core.Scoping;
using Umbraco.Core.Serialization;
using Umbraco.Core.Strings;
using Umbraco.Core.Templates;
using Umbraco.Examine;
using Umbraco.Infrastructure.Examine;
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
using Umbraco.Infrastructure.Media;
using Umbraco.Infrastructure.Runtime;
using Umbraco.Web;
using Umbraco.Web.HealthCheck;
using Umbraco.Web.HealthCheck.NotificationMethods;
using Umbraco.Web.Install;
using Umbraco.Web.Media;
using Umbraco.Web.Migrations.PostMigrations;
using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.PropertyEditors.ValueConverters;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Search;
using Umbraco.Web.Trees;
namespace Umbraco.Infrastructure.DependencyInjection
{
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Adds all core Umbraco services required to run which may be replaced later in the pipeline
/// </summary>
public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder)
{
builder
.AddMainDom()
.AddLogging();
builder.Services.AddUnique<IUmbracoDatabaseFactory, UmbracoDatabaseFactory>();
builder.Services.AddUnique(factory => factory.GetRequiredService<IUmbracoDatabaseFactory>().CreateDatabase());
builder.Services.AddUnique(factory => factory.GetRequiredService<IUmbracoDatabaseFactory>().SqlContext);
builder.Services.AddUnique<IRuntimeState, RuntimeState>();
builder.Services.AddUnique<IRuntime, CoreRuntime>();
// composers
builder
.AddRepositories()
.AddServices()
.AddCoreMappingProfiles()
.AddFileSystems();
// register persistence mappers - required by database factory so needs to be done here
// means the only place the collection can be modified is in a runtime - afterwards it
// has been frozen and it is too late
builder.Mappers().AddCoreMappers();
// register the scope provider
builder.Services.AddUnique<ScopeProvider>(); // implements both IScopeProvider and IScopeAccessor
builder.Services.AddUnique<IScopeProvider>(f => f.GetRequiredService<ScopeProvider>());
builder.Services.AddUnique<IScopeAccessor>(f => f.GetRequiredService<ScopeProvider>());
builder.Services.AddUnique<IJsonSerializer, JsonNetSerializer>();
builder.Services.AddUnique<IConfigurationEditorJsonSerializer, ConfigurationEditorJsonSerializer>();
builder.Services.AddUnique<IMenuItemCollectionFactory, MenuItemCollectionFactory>();
// register database builder
// *not* a singleton, don't want to keep it around
builder.Services.AddTransient<DatabaseBuilder>();
// register manifest parser, will be injected in collection builders where needed
builder.Services.AddUnique<IManifestParser, ManifestParser>();
// register the manifest filter collection builder (collection is empty by default)
builder.ManifestFilters();
builder.MediaUrlGenerators()
.Add<FileUploadPropertyEditor>()
.Add<ImageCropperPropertyEditor>();
builder.Services.AddUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
builder.Services.AddUnique<IShortStringHelper>(factory
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetRequiredService<IOptions<RequestHandlerSettings>>().Value)));
builder.Services.AddUnique<IMigrationBuilder>(factory => new MigrationBuilder(factory));
builder.Services.AddUnique<IPublishedSnapshotRebuilder, PublishedSnapshotRebuilder>();
// register the published snapshot accessor - the "current" published snapshot is in the umbraco context
builder.Services.AddUnique<IPublishedSnapshotAccessor, UmbracoContextPublishedSnapshotAccessor>();
builder.Services.AddUnique<IVariationContextAccessor, HybridVariationContextAccessor>();
// Config manipulator
builder.Services.AddUnique<IConfigManipulator, JsonConfigManipulator>();
builder.Services.AddUnique<RichTextEditorPastedImages>();
builder.Services.AddUnique<BlockEditorConverter>();
// both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be
// discovered when CoreBootManager configures the converters. We will remove the basic one defined
// in core so that the more enhanced version is active.
builder.PropertyValueConverters()
.Remove<SimpleTinyMceValueConverter>();
builder.Services.AddUnique<IImageUrlGenerator, ImageSharpImageUrlGenerator>();
builder.Services.AddUnique<IPublishedSnapshotRebuilder, PublishedSnapshotRebuilder>();
// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddUnique<IMarkdownToHtmlConverter, MarkdownToHtmlConverter>();
builder.Services.AddUnique<IContentLastChanceFinder, ContentFinderByConfigured404>();
builder.Services.AddScoped<UmbracoTreeSearcher>();
// replace
builder.Services.AddUnique<IEmailSender, EmailSender>();
builder.Services.AddUnique<IExamineManager, ExamineManager>();
builder.Services.AddScoped<ITagQuery, TagQuery>();
builder.Services.AddUnique<IUmbracoTreeSearcherFields, UmbracoTreeSearcherFields>();
builder.Services.AddScoped<IPublishedContentQuery>(factory =>
{
var umbCtx = factory.GetRequiredService<IUmbracoContextAccessor>();
return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetRequiredService<IVariationContextAccessor>(), factory.GetRequiredService<IExamineManager>());
});
// register accessors for cultures
builder.Services.AddUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
builder.Services.AddSingleton<IFilePermissionHelper, FilePermissionHelper>();
builder.Services.AddUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
// Register noop versions for examine to be overridden by examine
builder.Services.AddUnique<IUmbracoIndexesCreator, NoopUmbracoIndexesCreator>();
builder.Services.AddUnique<IBackOfficeExamineSearcher, NoopBackOfficeExamineSearcher>();
builder.Services.AddUnique<UploadAutoFillProperties>();
builder.Services.AddUnique<ICronTabParser, NCronTabParser>();
builder.Services.AddUnique<IImageDimensionExtractor, ImageDimensionExtractor>();
builder.Services.AddUnique<PackageDataInstallation>();
builder.AddInstaller();
return builder;
}
/// <summary>
/// Adds logging requirements for Umbraco
/// </summary>
private static IUmbracoBuilder AddLogging(this IUmbracoBuilder builder)
{
builder.Services.AddUnique<ThreadAbortExceptionEnricher>();
builder.Services.AddUnique<HttpSessionIdEnricher>();
builder.Services.AddUnique<HttpRequestNumberEnricher>();
builder.Services.AddUnique<HttpRequestIdEnricher>();
return builder;
}
private static IUmbracoBuilder AddMainDom(this IUmbracoBuilder builder)
{
builder.Services.AddUnique<IMainDomLock>(factory =>
{
var globalSettings = factory.GetRequiredService<IOptions<GlobalSettings>>().Value;
var connectionStrings = factory.GetRequiredService<IOptions<ConnectionStrings>>().Value;
var hostingEnvironment = factory.GetRequiredService<IHostingEnvironment>();
var dbCreator = factory.GetRequiredService<IDbProviderFactoryCreator>();
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var loggerFactory = factory.GetRequiredService<ILoggerFactory>();
return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger<SqlMainDomLock>(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment)
: new MainDomSemaphoreLock(loggerFactory.CreateLogger<MainDomSemaphoreLock>(), hostingEnvironment);
});
return builder;
}
}
}

View File

@@ -0,0 +1,131 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Events;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Sync;
using Umbraco.Infrastructure.Cache;
using Umbraco.Web.Cache;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Search;
namespace Umbraco.Infrastructure.DependencyInjection
{
/// <summary>
/// Provides extension methods to the <see cref="IUmbracoBuilder"/> class.
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Adds distributed cache support
/// </summary>
public static IUmbracoBuilder AddDistributedCache(this IUmbracoBuilder builder)
{
builder.SetDatabaseServerMessengerCallbacks(GetCallbacks);
builder.SetServerMessenger<BatchedDatabaseServerMessenger>();
builder.AddNotificationHandler<UmbracoApplicationStarting, DatabaseServerMessengerNotificationHandler>();
builder.Services.AddUnique<IDistributedCacheBinder, DistributedCacheBinder>();
return builder;
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerRegistrar<T>(this IUmbracoBuilder builder)
where T : class, IServerRoleAccessor
=> builder.Services.AddUnique<IServerRoleAccessor, T>();
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, Func<IServiceProvider, IServerRoleAccessor> factory)
=> builder.Services.AddUnique(factory);
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar)
=> builder.Services.AddUnique(registrar);
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating the options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerCallbacks(this IUmbracoBuilder builder, Func<IServiceProvider, DatabaseServerMessengerCallbacks> factory)
=> builder.Services.AddUnique(factory);
/// <summary>
/// Sets the database server messenger options.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="options">Options.</param>
/// <remarks>Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.</remarks>
public static void SetDatabaseServerMessengerOptions(this IUmbracoBuilder builder, DatabaseServerMessengerCallbacks options)
=> builder.Services.AddUnique(options);
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerMessenger<T>(this IUmbracoBuilder builder)
where T : class, IServerMessenger
=> builder.Services.AddUnique<IServerMessenger, T>();
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, Func<IServiceProvider, IServerMessenger> factory)
=> builder.Services.AddUnique(factory);
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
=> builder.Services.AddUnique(registrar);
private static DatabaseServerMessengerCallbacks GetCallbacks(IServiceProvider factory) => new DatabaseServerMessengerCallbacks
{
// These callbacks will be executed if the server has not been synced
// (i.e. it is a new server or the lastsynced.txt file has been removed)
InitializingCallbacks = new Action[]
{
// rebuild the xml cache file if the server is not synced
() =>
{
IPublishedSnapshotService publishedSnapshotService = factory.GetRequiredService<IPublishedSnapshotService>();
// rebuild the published snapshot caches entirely, if the server is not synced
// this is equivalent to DistributedCache RefreshAll... but local only
// (we really should have a way to reuse RefreshAll... locally)
// note: refresh all content & media caches does refresh content types too
publishedSnapshotService.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
publishedSnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
publishedSnapshotService.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _);
},
// rebuild indexes if the server is not synced
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
// indexes then they can adjust this logic themselves.
() =>
{
var indexRebuilder = factory.GetRequiredService<BackgroundIndexRebuilder>();
indexRebuilder.RebuildIndexes(false, 5000);
}
}
};
}
}

View File

@@ -1,16 +1,15 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Hosting; using Umbraco.Core.Hosting;
using Umbraco.Core.IO; using Umbraco.Core.IO;
using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.IO.MediaPathSchemes;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Composing.CompositionExtensions namespace Umbraco.Infrastructure.DependencyInjection
{ {
internal static class FileSystems public static partial class UmbracoBuilderExtensions
{ {
/* /*
* HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM * HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM
@@ -34,16 +33,16 @@ namespace Umbraco.Core.Composing.CompositionExtensions
* *
*/ */
public static IUmbracoBuilder ComposeFileSystems(this IUmbracoBuilder builder) internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder)
{ {
// register FileSystems, which manages all filesystems // register FileSystems, which manages all filesystems
// it needs to be registered (not only the interface) because it provides additional // it needs to be registered (not only the interface) because it provides additional
// functionality eg for scoping, and is injected in the scope provider - whereas the // functionality eg for scoping, and is injected in the scope provider - whereas the
// interface is really for end-users to get access to filesystems. // interface is really for end-users to get access to filesystems.
builder.Services.AddUnique(factory => factory.CreateInstance<Core.IO.FileSystems>(factory)); builder.Services.AddUnique(factory => factory.CreateInstance<FileSystems>(factory));
// register IFileSystems, which gives access too all filesystems // register IFileSystems, which gives access too all filesystems
builder.Services.AddUnique<IFileSystems>(factory => factory.GetRequiredService<Core.IO.FileSystems>()); builder.Services.AddUnique<IFileSystems>(factory => factory.GetRequiredService<FileSystems>());
// register the scheme for media paths // register the scheme for media paths
builder.Services.AddUnique<IMediaPathScheme, UniqueMediaPathScheme>(); builder.Services.AddUnique<IMediaPathScheme, UniqueMediaPathScheme>();

View File

@@ -0,0 +1,36 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.DependencyInjection;
using Umbraco.Web.Install;
using Umbraco.Web.Install.InstallSteps;
using Umbraco.Web.Install.Models;
namespace Umbraco.Infrastructure.DependencyInjection
{
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Adds the services for the Umbraco installer
/// </summary>
internal static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder)
{
// register the installer steps
builder.Services.AddScoped<InstallSetupStep, NewInstallStep>();
builder.Services.AddScoped<InstallSetupStep, UpgradeStep>();
builder.Services.AddScoped<InstallSetupStep, FilePermissionsStep>();
builder.Services.AddScoped<InstallSetupStep, DatabaseConfigureStep>();
builder.Services.AddScoped<InstallSetupStep, DatabaseInstallStep>();
builder.Services.AddScoped<InstallSetupStep, DatabaseUpgradeStep>();
// TODO: Add these back once we have a compatible Starter kit
// composition.Services.AddScoped<InstallSetupStep,StarterKitDownloadStep>();
// composition.Services.AddScoped<InstallSetupStep,StarterKitInstallStep>();
// composition.Services.AddScoped<InstallSetupStep,StarterKitCleanupStep>();
builder.Services.AddScoped<InstallSetupStep, CompleteInstallStep>();
builder.Services.AddTransient<InstallStepCollection>();
builder.Services.AddUnique<InstallHelper>();
return builder;
}
}
}

View File

@@ -4,17 +4,14 @@ using Umbraco.Core.Mapping;
using Umbraco.Core.Security; using Umbraco.Core.Security;
using Umbraco.Web.Models.Mapping; using Umbraco.Web.Models.Mapping;
namespace Umbraco.Core.Composing.CompositionExtensions namespace Umbraco.Infrastructure.DependencyInjection
{ {
public static class CoreMappingProfiles public static partial class UmbracoBuilderExtensions
{ {
/// <summary> /// <summary>
/// Registers the core Umbraco mapper definitions /// Registers the core Umbraco mapper definitions
/// </summary> /// </summary>
/// <param name="builder"></param> public static IUmbracoBuilder AddCoreMappingProfiles(this IUmbracoBuilder builder)
/// <returns></returns>
public static IUmbracoBuilder ComposeCoreMappingProfiles(this IUmbracoBuilder builder)
{ {
builder.Services.AddUnique<UmbracoMapper>(); builder.Services.AddUnique<UmbracoMapper>();
@@ -34,8 +31,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
.Add<TemplateMapDefinition>() .Add<TemplateMapDefinition>()
.Add<UserMapDefinition>() .Add<UserMapDefinition>()
.Add<LanguageMapDefinition>() .Add<LanguageMapDefinition>()
.Add<IdentityMapDefinition>() .Add<IdentityMapDefinition>();
;
builder.Services.AddTransient<CommonMapper>(); builder.Services.AddTransient<CommonMapper>();
builder.Services.AddTransient<MemberTabsAndPropertiesMapper>(); builder.Services.AddTransient<MemberTabsAndPropertiesMapper>();

View File

@@ -1,15 +1,18 @@
using Umbraco.Core.DependencyInjection; using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Persistence.Repositories.Implement;
namespace Umbraco.Core.Composing.CompositionExtensions namespace Umbraco.Infrastructure.DependencyInjection
{ {
/// <summary> /// <summary>
/// Composes repositories. /// Composes repositories.
/// </summary> /// </summary>
internal static class Repositories public static partial class UmbracoBuilderExtensions
{ {
public static IUmbracoBuilder ComposeRepositories(this IUmbracoBuilder builder) /// <summary>
/// Adds the Umbraco repositories
/// </summary>
internal static IUmbracoBuilder AddRepositories(this IUmbracoBuilder builder)
{ {
// repositories // repositories
builder.Services.AddUnique<IAuditRepository, AuditRepository>(); builder.Services.AddUnique<IAuditRepository, AuditRepository>();

View File

@@ -1,9 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Cache; using Umbraco.Core.Cache;
using Umbraco.Core.Configuration; using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
@@ -15,16 +16,15 @@ using Umbraco.Core.Routing;
using Umbraco.Core.Services; using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement; using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Composing.CompositionExtensions namespace Umbraco.Infrastructure.DependencyInjection
{ {
internal static class Services public static partial class UmbracoBuilderExtensions
{ {
public static IUmbracoBuilder ComposeServices(this IUmbracoBuilder builder) /// <summary>
/// Adds Umbraco services
/// </summary>
internal static IUmbracoBuilder AddServices(this IUmbracoBuilder builder)
{ {
// register a transient messages factory, which will be replaced by the web
// boot manager when running in a web context
builder.Services.AddUnique<IEventMessagesFactory, TransientEventMessagesFactory>();
// register the service context // register the service context
builder.Services.AddUnique<ServiceContext>(); builder.Services.AddUnique<ServiceContext>();
@@ -59,7 +59,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
builder.Services.AddUnique<IExternalLoginService, ExternalLoginService>(); builder.Services.AddUnique<IExternalLoginService, ExternalLoginService>();
builder.Services.AddUnique<IRedirectUrlService, RedirectUrlService>(); builder.Services.AddUnique<IRedirectUrlService, RedirectUrlService>();
builder.Services.AddUnique<IConsentService, ConsentService>(); builder.Services.AddUnique<IConsentService, ConsentService>();
builder.Services.AddTransient<LocalizedTextServiceFileSources>(SourcesFactory); builder.Services.AddTransient(SourcesFactory);
builder.Services.AddUnique<ILocalizedTextService>(factory => new LocalizedTextService( builder.Services.AddUnique<ILocalizedTextService>(factory => new LocalizedTextService(
factory.GetRequiredService<Lazy<LocalizedTextServiceFileSources>>(), factory.GetRequiredService<Lazy<LocalizedTextServiceFileSources>>(),
factory.GetRequiredService<ILogger<LocalizedTextService>>())); factory.GetRequiredService<ILogger<LocalizedTextService>>()));
@@ -82,9 +82,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions
/// <summary> /// <summary>
/// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository /// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository
/// </summary> /// </summary>
/// <param name="factory"></param>
/// <param name="packageRepoFileName"></param>
/// <returns></returns>
private static PackagesRepository CreatePackageRepository(IServiceProvider factory, string packageRepoFileName) private static PackagesRepository CreatePackageRepository(IServiceProvider factory, string packageRepoFileName)
=> new PackagesRepository( => new PackagesRepository(
factory.GetRequiredService<IContentService>(), factory.GetRequiredService<IContentService>(),
@@ -106,9 +103,9 @@ namespace Umbraco.Core.Composing.CompositionExtensions
{ {
var hostingEnvironment = container.GetRequiredService<IHostingEnvironment>(); var hostingEnvironment = container.GetRequiredService<IHostingEnvironment>();
var globalSettings = container.GetRequiredService<IOptions<GlobalSettings>>().Value; var globalSettings = container.GetRequiredService<IOptions<GlobalSettings>>().Value;
var mainLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(globalSettings.UmbracoPath , "config","lang"))); var mainLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(globalSettings.UmbracoPath, "config", "lang")));
var appPlugins = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins)); var appPlugins = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins));
var configLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(Constants.SystemDirectories.Config ,"lang"))); var configLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(Constants.SystemDirectories.Config, "lang")));
var pluginLangFolders = appPlugins.Exists == false var pluginLangFolders = appPlugins.Exists == false
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>() ? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
@@ -117,7 +114,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
.SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
.Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
//user defined langs that overwrite the default, these should not be used by plugin creators // user defined langs that overwrite the default, these should not be used by plugin creators
var userLangFolders = configLangFolder.Exists == false var userLangFolders = configLangFolder.Exists == false
? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>() ? Enumerable.Empty<LocalizedTextServiceSupplementaryFileSource>()
: configLangFolder : configLangFolder

View File

@@ -0,0 +1,157 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging.Viewer;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
namespace Umbraco.Infrastructure.DependencyInjection
{
/// <summary>
/// Provides extension methods to the <see cref="IUmbracoBuilder"/> class.
/// </summary>
public static partial class UmbracoBuilderExtensions
{
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetCultureDictionaryFactory<T>(this IUmbracoBuilder builder)
where T : class, ICultureDictionaryFactory
{
builder.Services.AddUnique<ICultureDictionaryFactory, T>();
}
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a culture dictionary factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func<IServiceProvider, ICultureDictionaryFactory> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the culture dictionary factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetPublishedContentModelFactory<T>(this IUmbracoBuilder builder)
where T : class, IPublishedModelFactory
{
builder.Services.AddUnique<IPublishedModelFactory, T>();
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedModelFactory> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the published content model factory.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <typeparam name="T">The type of the short string helper.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetShortStringHelper<T>(this IUmbracoBuilder builder)
where T : class, IShortStringHelper
{
builder.Services.AddUnique<IShortStringHelper, T>();
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, Func<IServiceProvider, IShortStringHelper> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the short string helper.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
{
builder.Services.AddUnique(helper);
}
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
/// <remarks>
/// Using this helper will ensure that your IFileSystem implementation is wrapped by the ShadowWrapper
/// </remarks>
public static void SetMediaFileSystem(this IUmbracoBuilder builder, Func<IServiceProvider, IFileSystem> filesystemFactory)
=> builder.Services.AddUnique<IMediaFileSystem>(factory =>
{
var fileSystems = factory.GetRequiredService<FileSystems>();
return fileSystems.GetFileSystem<MediaFileSystem>(filesystemFactory(factory));
});
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <typeparam name="T">The type of the log viewer.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetLogViewer<T>(this IUmbracoBuilder builder)
where T : class, ILogViewer
{
builder.Services.AddUnique<ILogViewer, T>();
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, Func<IServiceProvider, ILogViewer> factory)
{
builder.Services.AddUnique(factory);
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
{
builder.Services.AddUnique(viewer);
}
}
}

View File

@@ -7,6 +7,7 @@ using MimeKit;
using MimeKit.Text; using MimeKit.Text;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Mail;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using SmtpClient = MailKit.Net.Smtp.SmtpClient; using SmtpClient = MailKit.Net.Smtp.SmtpClient;

View File

@@ -30,7 +30,7 @@ namespace Umbraco.Infrastructure.HostedServices
private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckCollection _healthChecks;
private readonly HealthCheckNotificationMethodCollection _notifications; private readonly HealthCheckNotificationMethodCollection _notifications;
private readonly IRuntimeState _runtimeState; private readonly IRuntimeState _runtimeState;
private readonly IServerRegistrar _serverRegistrar; private readonly IServerRoleAccessor _serverRegistrar;
private readonly IMainDom _mainDom; private readonly IMainDom _mainDom;
private readonly IScopeProvider _scopeProvider; private readonly IScopeProvider _scopeProvider;
private readonly ILogger<HealthCheckNotifier> _logger; private readonly ILogger<HealthCheckNotifier> _logger;
@@ -54,7 +54,7 @@ namespace Umbraco.Infrastructure.HostedServices
HealthCheckCollection healthChecks, HealthCheckCollection healthChecks,
HealthCheckNotificationMethodCollection notifications, HealthCheckNotificationMethodCollection notifications,
IRuntimeState runtimeState, IRuntimeState runtimeState,
IServerRegistrar serverRegistrar, IServerRoleAccessor serverRegistrar,
IMainDom mainDom, IMainDom mainDom,
IScopeProvider scopeProvider, IScopeProvider scopeProvider,
ILogger<HealthCheckNotifier> logger, ILogger<HealthCheckNotifier> logger,
@@ -87,7 +87,7 @@ namespace Umbraco.Infrastructure.HostedServices
return; return;
} }
switch (_serverRegistrar.GetCurrentServerRole()) switch (_serverRegistrar.CurrentServerRole)
{ {
case ServerRole.Replica: case ServerRole.Replica:
_logger.LogDebug("Does not run on replica servers."); _logger.LogDebug("Does not run on replica servers.");

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Infrastructure.HostedServices
private readonly KeepAliveSettings _keepAliveSettings; private readonly KeepAliveSettings _keepAliveSettings;
private readonly ILogger<KeepAlive> _logger; private readonly ILogger<KeepAlive> _logger;
private readonly IProfilingLogger _profilingLogger; private readonly IProfilingLogger _profilingLogger;
private readonly IServerRegistrar _serverRegistrar; private readonly IServerRoleAccessor _serverRegistrar;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
/// <summary> /// <summary>
@@ -43,7 +43,7 @@ namespace Umbraco.Infrastructure.HostedServices
IOptions<KeepAliveSettings> keepAliveSettings, IOptions<KeepAliveSettings> keepAliveSettings,
ILogger<KeepAlive> logger, ILogger<KeepAlive> logger,
IProfilingLogger profilingLogger, IProfilingLogger profilingLogger,
IServerRegistrar serverRegistrar, IServerRoleAccessor serverRegistrar,
IHttpClientFactory httpClientFactory) IHttpClientFactory httpClientFactory)
: base(TimeSpan.FromMinutes(5), DefaultDelay) : base(TimeSpan.FromMinutes(5), DefaultDelay)
{ {
@@ -64,7 +64,7 @@ namespace Umbraco.Infrastructure.HostedServices
} }
// Don't run on replicas nor unknown role servers // Don't run on replicas nor unknown role servers
switch (_serverRegistrar.GetCurrentServerRole()) switch (_serverRegistrar.CurrentServerRole)
{ {
case ServerRole.Replica: case ServerRole.Replica:
_logger.LogDebug("Does not run on replica servers."); _logger.LogDebug("Does not run on replica servers.");

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Infrastructure.HostedServices
public class LogScrubber : RecurringHostedServiceBase public class LogScrubber : RecurringHostedServiceBase
{ {
private readonly IMainDom _mainDom; private readonly IMainDom _mainDom;
private readonly IServerRegistrar _serverRegistrar; private readonly IServerRoleAccessor _serverRegistrar;
private readonly IAuditService _auditService; private readonly IAuditService _auditService;
private readonly LoggingSettings _settings; private readonly LoggingSettings _settings;
private readonly IProfilingLogger _profilingLogger; private readonly IProfilingLogger _profilingLogger;
@@ -42,7 +42,7 @@ namespace Umbraco.Infrastructure.HostedServices
/// <param name="profilingLogger">The profiling logger.</param> /// <param name="profilingLogger">The profiling logger.</param>
public LogScrubber( public LogScrubber(
IMainDom mainDom, IMainDom mainDom,
IServerRegistrar serverRegistrar, IServerRoleAccessor serverRegistrar,
IAuditService auditService, IAuditService auditService,
IOptions<LoggingSettings> settings, IOptions<LoggingSettings> settings,
IScopeProvider scopeProvider, IScopeProvider scopeProvider,
@@ -61,7 +61,7 @@ namespace Umbraco.Infrastructure.HostedServices
internal override Task PerformExecuteAsync(object state) internal override Task PerformExecuteAsync(object state)
{ {
switch (_serverRegistrar.GetCurrentServerRole()) switch (_serverRegistrar.CurrentServerRole)
{ {
case ServerRole.Replica: case ServerRole.Replica:
_logger.LogDebug("Does not run on replica servers."); _logger.LogDebug("Does not run on replica servers.");

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Infrastructure.HostedServices
private readonly IRuntimeState _runtimeState; private readonly IRuntimeState _runtimeState;
private readonly IServerMessenger _serverMessenger; private readonly IServerMessenger _serverMessenger;
private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory;
private readonly IServerRegistrar _serverRegistrar; private readonly IServerRoleAccessor _serverRegistrar;
private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly IUmbracoContextFactory _umbracoContextFactory;
/// <summary> /// <summary>
@@ -44,7 +44,7 @@ namespace Umbraco.Infrastructure.HostedServices
public ScheduledPublishing( public ScheduledPublishing(
IRuntimeState runtimeState, IRuntimeState runtimeState,
IMainDom mainDom, IMainDom mainDom,
IServerRegistrar serverRegistrar, IServerRoleAccessor serverRegistrar,
IContentService contentService, IContentService contentService,
IUmbracoContextFactory umbracoContextFactory, IUmbracoContextFactory umbracoContextFactory,
ILogger<ScheduledPublishing> logger, ILogger<ScheduledPublishing> logger,
@@ -69,7 +69,7 @@ namespace Umbraco.Infrastructure.HostedServices
return Task.CompletedTask; return Task.CompletedTask;
} }
switch (_serverRegistrar.GetCurrentServerRole()) switch (_serverRegistrar.CurrentServerRole)
{ {
case ServerRole.Replica: case ServerRole.Replica:
_logger.LogDebug("Does not run on replica servers."); _logger.LogDebug("Does not run on replica servers.");
@@ -123,9 +123,9 @@ namespace Umbraco.Infrastructure.HostedServices
finally finally
{ {
// If running on a temp context, we have to flush the messenger // If running on a temp context, we have to flush the messenger
if (contextReference.IsRoot && _serverMessenger is IBatchedDatabaseServerMessenger m) if (contextReference.IsRoot)
{ {
m.FlushBatch(); _serverMessenger.SendMessages();
} }
} }
} }

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
public class InstructionProcessTask : RecurringHostedServiceBase public class InstructionProcessTask : RecurringHostedServiceBase
{ {
private readonly IRuntimeState _runtimeState; private readonly IRuntimeState _runtimeState;
private readonly IDatabaseServerMessenger _messenger; private readonly IServerMessenger _messenger;
private readonly ILogger<InstructionProcessTask> _logger; private readonly ILogger<InstructionProcessTask> _logger;
/// <summary> /// <summary>
@@ -31,7 +31,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
: base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1))
{ {
_runtimeState = runtimeState; _runtimeState = runtimeState;
_messenger = messenger as IDatabaseServerMessenger ?? throw new ArgumentNullException(nameof(messenger)); _messenger = messenger;
_logger = logger; _logger = logger;
} }

View File

@@ -57,9 +57,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration
try try
{ {
// TouchServer uses a proper unit of work etc underneath so even in a _serverRegistrationService.TouchServer(serverAddress, _globalSettings.DatabaseServerRegistrar.StaleServerTimeout);
// background task it is safe to call it without dealing with any scope.
_serverRegistrationService.TouchServer(serverAddress, _serverRegistrationService.CurrentServerIdentity, _globalSettings.DatabaseServerRegistrar.StaleServerTimeout);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -6,9 +6,9 @@ using Umbraco.Core.Services;
using Umbraco.Core.Configuration; using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Packaging; using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Security; using Umbraco.Core.Security;
using Umbraco.Net;
using Umbraco.Web.Install.Models; using Umbraco.Web.Install.Models;
using Umbraco.Web.Security; using Umbraco.Web.Security;
using Umbraco.Core.Hosting;
namespace Umbraco.Web.Install.InstallSteps namespace Umbraco.Web.Install.InstallSteps
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -55,6 +55,7 @@ namespace Umbraco.Infrastructure.Logging.Serilog.Enrichers
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message)); logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
} }
else else
{
try try
{ {
var dumped = MiniDump.Dump(_marchal, _hostingEnvironment, withException: true); var dumped = MiniDump.Dump(_marchal, _hostingEnvironment, withException: true);
@@ -68,6 +69,7 @@ namespace Umbraco.Infrastructure.Logging.Serilog.Enrichers
message = "Failed to create a minidump. " + ex; message = "Failed to create a minidump. " + ex;
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message)); logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message));
} }
}
} }
private static bool IsTimeoutThreadAbortException(Exception exception) private static bool IsTimeoutThreadAbortException(Exception exception)

View File

@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging.Serilog.Enrichers;
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
namespace Umbraco.Infrastructure.Logging.Serilog
{
public class SerilogComposer : ICoreComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Services.AddUnique<ThreadAbortExceptionEnricher>();
builder.Services.AddUnique<HttpSessionIdEnricher>();
builder.Services.AddUnique<HttpRequestNumberEnricher>();
builder.Services.AddUnique<HttpRequestIdEnricher>();
}
}
}

View File

@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using Serilog; using Serilog;
using Umbraco.Core.DependencyInjection; using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Infrastructure.DependencyInjection;
namespace Umbraco.Core.Logging.Viewer namespace Umbraco.Core.Logging.Viewer
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using Umbraco.Core; using Umbraco.Core;
@@ -18,7 +18,7 @@ namespace Umbraco.Web.Media
/// use potentially large amounts of memory.</remarks> /// use potentially large amounts of memory.</remarks>
public ImageSize GetDimensions(Stream stream) public ImageSize GetDimensions(Stream stream)
{ {
//Try to load with exif // Try to load with exif
try try
{ {
if (ExifImageDimensionExtractor.TryGetDimensions(stream, out var width, out var height)) if (ExifImageDimensionExtractor.TryGetDimensions(stream, out var width, out var height))
@@ -28,12 +28,13 @@ namespace Umbraco.Web.Media
} }
catch catch
{ {
//We will just swallow, just means we can't read exif data, we don't want to log an error either // We will just swallow, just means we can't read exif data, we don't want to log an error either
} }
//we have no choice but to try to read in via GDI // we have no choice but to try to read in via GDI
try try
{ {
// TODO: We should be using ImageSharp for this
using (var image = Image.FromStream(stream)) using (var image = Image.FromStream(stream))
{ {
var fileWidth = image.Width; var fileWidth = image.Width;
@@ -43,7 +44,7 @@ namespace Umbraco.Web.Media
} }
catch (Exception) catch (Exception)
{ {
//We will just swallow, just means we can't read via GDI, we don't want to log an error either // We will just swallow, just means we can't read via GDI, we don't want to log an error either
} }
return new ImageSize(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize); return new ImageSize(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize);

View File

@@ -1,5 +1,6 @@
using System; using System;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Migrations namespace Umbraco.Core.Migrations
{ {

View File

@@ -1,12 +0,0 @@
namespace Umbraco.Core.Migrations.PostMigrations
{
/// <summary>
/// Implements <see cref="IPublishedSnapshotRebuilder"/> in Umbraco.Core (doing nothing).
/// </summary>
public class NoopPublishedSnapshotRebuilder : IPublishedSnapshotRebuilder
{
/// <inheritdoc />
public void Rebuild()
{ }
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -17,16 +17,11 @@ namespace Umbraco.Core.Packaging
private readonly IPackageActionRunner _packageActionRunner; private readonly IPackageActionRunner _packageActionRunner;
private readonly DirectoryInfo _applicationRootFolder; private readonly DirectoryInfo _applicationRootFolder;
/// <summary> /// <summary>
/// Constructor /// Initializes a new instance of the <see cref="PackageInstallation"/> class.
/// </summary> /// </summary>
/// <param name="packageDataInstallation"></param> public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner, IHostingEnvironment hostingEnvironment)
/// <param name="packageFileInstallation"></param>
/// <param name="parser"></param>
/// <param name="packageActionRunner"></param>
/// <param name="hostingEnvironment"></param>
public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner,
IHostingEnvironment hostingEnvironment)
{ {
_packageExtraction = new PackageExtraction(); _packageExtraction = new PackageExtraction();
_packageFileInstallation = packageFileInstallation ?? throw new ArgumentNullException(nameof(packageFileInstallation)); _packageFileInstallation = packageFileInstallation ?? throw new ArgumentNullException(nameof(packageFileInstallation));

View File

@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using HtmlAgilityPack; using HtmlAgilityPack;
using Umbraco.Core; using Umbraco.Core;
@@ -16,7 +16,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
/// used dynamically. /// used dynamically.
/// </summary> /// </summary>
[DefaultPropertyValueConverter] [DefaultPropertyValueConverter]
public class RteMacroRenderingValueConverter : TinyMceValueConverter public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter
{ {
private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IMacroRenderer _macroRenderer; private readonly IMacroRenderer _macroRenderer;

View File

@@ -1,392 +0,0 @@
using System;
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing.CompositionExtensions;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Dashboards;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.Install;
using Umbraco.Core.Logging;
using Umbraco.Core.Manifest;
using Umbraco.Core.Media;
using Umbraco.Core.Migrations;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Migrations.PostMigrations;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.Validators;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Scoping;
using Umbraco.Core.Security;
using Umbraco.Core.Serialization;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Core.Templates;
using Umbraco.Examine;
using Umbraco.Infrastructure.Examine;
using Umbraco.Infrastructure.Media;
using Umbraco.Web;
using Umbraco.Web.Actions;
using Umbraco.Web.Cache;
using Umbraco.Web.ContentApps;
using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.HealthCheck;
using Umbraco.Web.HealthCheck.NotificationMethods;
using Umbraco.Web.Install;
using Umbraco.Web.Media;
using Umbraco.Web.Media.EmbedProviders;
using Umbraco.Web.Migrations.PostMigrations;
using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.PropertyEditors.ValueConverters;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Search;
using Umbraco.Web.Sections;
using Umbraco.Web.Services;
using Umbraco.Web.Templates;
using Umbraco.Web.Trees;
using TextStringValueConverter = Umbraco.Core.PropertyEditors.ValueConverters.TextStringValueConverter;
namespace Umbraco.Infrastructure.Runtime
{
public static class CoreInitialServices
{
public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder)
{
builder.AddNotificationHandler<UmbracoApplicationStarting, EssentialDirectoryCreator>();
builder.Services.AddSingleton<ManifestWatcher>();
builder.AddNotificationHandler<UmbracoApplicationStarting, AppPluginsManifestWatcherNotificationHandler>();
// composers
builder
.ComposeRepositories()
.ComposeServices()
.ComposeCoreMappingProfiles()
.ComposeFileSystems();
// register persistence mappers - required by database factory so needs to be done here
// means the only place the collection can be modified is in a runtime - afterwards it
// has been frozen and it is too late
builder.Mappers().AddCoreMappers();
// register the scope provider
builder.Services.AddUnique<ScopeProvider>(); // implements both IScopeProvider and IScopeAccessor
builder.Services.AddUnique<IScopeProvider>(f => f.GetRequiredService<ScopeProvider>());
builder.Services.AddUnique<IScopeAccessor>(f => f.GetRequiredService<ScopeProvider>());
builder.Services.AddUnique<IJsonSerializer, JsonNetSerializer>();
builder.Services.AddUnique<IConfigurationEditorJsonSerializer, ConfigurationEditorJsonSerializer>();
builder.Services.AddUnique<IMenuItemCollectionFactory, MenuItemCollectionFactory>();
builder.Services.AddUnique<InstallStatusTracker>();
// register database builder
// *not* a singleton, don't want to keep it around
builder.Services.AddTransient<DatabaseBuilder>();
// register manifest parser, will be injected in collection builders where needed
builder.Services.AddUnique<IManifestParser, ManifestParser>();
// register our predefined validators
builder.ManifestValueValidators()
.Add<RequiredValidator>()
.Add<RegexValidator>()
.Add<DelimitedValueValidator>()
.Add<EmailValidator>()
.Add<IntegerValidator>()
.Add<DecimalValidator>();
// register the manifest filter collection builder (collection is empty by default)
builder.ManifestFilters();
// properties and parameters derive from data editors
builder.DataEditors()
.Add(() => builder.TypeLoader.GetDataEditors());
builder.MediaUrlGenerators()
.Add<FileUploadPropertyEditor>()
.Add<ImageCropperPropertyEditor>();
builder.Services.AddUnique<PropertyEditorCollection>();
builder.Services.AddUnique<ParameterEditorCollection>();
// Used to determine if a datatype/editor should be storing/tracking
// references to media item/s
builder.DataValueReferenceFactories();
// register a server registrar, by default it's the db registrar
builder.Services.AddUnique<IServerRegistrar>(f =>
{
var globalSettings = f.GetRequiredService<IOptions<GlobalSettings>>().Value;
// TODO: we still register the full IServerMessenger because
// even on 1 single server we can have 2 concurrent app domains
var singleServer = globalSettings.DisableElectionForSingleServer;
return singleServer
? (IServerRegistrar) new SingleServerRegistrar(f.GetRequiredService<IRequestAccessor>())
: new DatabaseServerRegistrar(
new Lazy<IServerRegistrationService>(f.GetRequiredService<IServerRegistrationService>));
});
// by default we'll use the database server messenger with default options (no callbacks),
// this will be overridden by the db thing in the corresponding components in the web
// project
builder.Services.AddUnique<IServerMessenger>(factory
=> new DatabaseServerMessenger(
factory.GetRequiredService<IMainDom>(),
factory.GetRequiredService<IScopeProvider>(),
factory.GetRequiredService<IUmbracoDatabaseFactory>(),
factory.GetRequiredService<IProfilingLogger>(),
factory.GetRequiredService<ILogger<DatabaseServerMessenger>>(),
factory.GetRequiredService<IServerRegistrar>(),
true,
new DatabaseServerMessengerCallbacks(),
factory.GetRequiredService<IHostingEnvironment>(),
factory.GetRequiredService<CacheRefresherCollection>(),
factory.GetRequiredService<IOptions<GlobalSettings>>()
));
builder.CacheRefreshers()
.Add(() => builder.TypeLoader.GetCacheRefreshers());
builder.PackageActions()
.Add(() => builder.TypeLoader.GetPackageActions());
builder.PropertyValueConverters()
.Append(builder.TypeLoader.GetTypes<IPropertyValueConverter>());
builder.Services.AddUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
builder.Services.AddUnique<IShortStringHelper>(factory
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetRequiredService<IOptions<RequestHandlerSettings>>().Value)));
builder.UrlSegmentProviders()
.Append<DefaultUrlSegmentProvider>();
builder.Services.AddUnique<IMigrationBuilder>(factory => new MigrationBuilder(factory));
// by default, register a noop factory
builder.Services.AddUnique<IPublishedModelFactory, NoopPublishedModelFactory>();
// by default
builder.Services.AddUnique<IPublishedSnapshotRebuilder, PublishedSnapshotRebuilder>();
builder.SetCultureDictionaryFactory<DefaultCultureDictionaryFactory>();
builder.Services.AddSingleton(f => f.GetRequiredService<ICultureDictionaryFactory>().CreateDictionary());
builder.Services.AddUnique<UriUtility>();
// register the published snapshot accessor - the "current" published snapshot is in the umbraco context
builder.Services.AddUnique<IPublishedSnapshotAccessor, UmbracoContextPublishedSnapshotAccessor>();
builder.Services.AddUnique<IVariationContextAccessor, HybridVariationContextAccessor>();
builder.Services.AddUnique<IDashboardService, DashboardService>();
// register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards
builder.Dashboards()
.Add(builder.TypeLoader.GetTypes<IDashboard>());
// will be injected in controllers when needed to invoke rest endpoints on Our
builder.Services.AddUnique<IInstallationService, InstallationService>();
builder.Services.AddUnique<IUpgradeService, UpgradeService>();
// Grid config is not a real config file as we know them
builder.Services.AddUnique<IGridConfig, GridConfig>();
// Config manipulator
builder.Services.AddUnique<IConfigManipulator, JsonConfigManipulator>();
// register the umbraco context factory
// composition.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
builder.Services.AddUnique<IPublishedUrlProvider, UrlProvider>();
builder.Services.AddUnique<HtmlLocalLinkParser>();
builder.Services.AddUnique<HtmlImageSourceParser>();
builder.Services.AddUnique<HtmlUrlParser>();
builder.Services.AddUnique<RichTextEditorPastedImages>();
builder.Services.AddUnique<BlockEditorConverter>();
// both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be
// discovered when CoreBootManager configures the converters. We HAVE to remove one of them
// here because there cannot be two converters for one property editor - and we want the full
// RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter.
// (the limited one, defined in Core, is there for tests) - same for others
builder.PropertyValueConverters()
.Remove<TinyMceValueConverter>()
.Remove<TextStringValueConverter>()
.Remove<MarkdownEditorValueConverter>();
builder.UrlProviders()
.Append<AliasUrlProvider>()
.Append<DefaultUrlProvider>();
builder.MediaUrlProviders()
.Append<DefaultMediaUrlProvider>();
builder.Services.AddUnique<ISiteDomainHelper, SiteDomainHelper>();
// register properties fallback
builder.Services.AddUnique<IPublishedValueFallback, PublishedValueFallback>();
builder.Services.AddUnique<IImageUrlGenerator, ImageSharpImageUrlGenerator>();
builder.Services.AddUnique<UmbracoFeatures>();
builder.Actions()
.Add(() => builder.TypeLoader.GetTypes<IAction>());
builder.EditorValidators()
.Add(() => builder.TypeLoader.GetTypes<IEditorValidator>());
builder.TourFilters();
// replace with web implementation
builder.Services.AddUnique<IPublishedSnapshotRebuilder, PublishedSnapshotRebuilder>();
// register OEmbed providers - no type scanning - all explicit opt-in of adding types
// note: IEmbedProvider is not IDiscoverable - think about it if going for type scanning
builder.OEmbedProviders()
.Append<YouTube>()
.Append<Twitter>()
.Append<Vimeo>()
.Append<DailyMotion>()
.Append<Flickr>()
.Append<Slideshare>()
.Append<Kickstarter>()
.Append<GettyImages>()
.Append<Ted>()
.Append<Soundcloud>()
.Append<Issuu>()
.Append<Hulu>()
.Append<Giphy>();
// register back office sections in the order we want them rendered
builder.Sections()
.Append<ContentSection>()
.Append<MediaSection>()
.Append<SettingsSection>()
.Append<PackagesSection>()
.Append<UsersSection>()
.Append<MembersSection>()
.Append<FormsSection>()
.Append<TranslationSection>();
// register known content apps
builder.ContentApps()
.Append<ListViewContentAppFactory>()
.Append<ContentEditorContentAppFactory>()
.Append<ContentInfoContentAppFactory>()
.Append<ContentTypeDesignContentAppFactory>()
.Append<ContentTypeListViewContentAppFactory>()
.Append<ContentTypePermissionsContentAppFactory>()
.Append<ContentTypeTemplatesContentAppFactory>();
// register published router
builder.Services.AddUnique<IPublishedRouter, PublishedRouter>();
// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddUnique<IMarkdownToHtmlConverter, MarkdownToHtmlConverter>();
builder.HealthChecks()
.Add(() => builder.TypeLoader.GetTypes<Core.HealthCheck.HealthCheck>());
builder.WithCollectionBuilder<HealthCheckNotificationMethodCollectionBuilder>()
.Add(() => builder.TypeLoader.GetTypes<IHealthCheckNotificationMethod>());
builder.Services.AddUnique<IContentLastChanceFinder, ContentFinderByConfigured404>();
builder.ContentFinders()
// all built-in finders in the correct order,
// devs can then modify this list on application startup
.Append<ContentFinderByPageIdQuery>()
.Append<ContentFinderByUrl>()
.Append<ContentFinderByIdPath>()
//.Append<ContentFinderByUrlAndTemplate>() // disabled, this is an odd finder
.Append<ContentFinderByUrlAlias>()
.Append<ContentFinderByRedirectUrl>();
builder.Services.AddScoped<UmbracoTreeSearcher>();
builder.SearchableTrees()
.Add(() => builder.TypeLoader.GetTypes<ISearchableTree>());
// replace some services
builder.Services.AddUnique<IEventMessagesFactory, DefaultEventMessagesFactory>();
builder.Services.AddUnique<IEventMessagesAccessor, HybridEventMessagesAccessor>();
builder.Services.AddUnique<ITreeService, TreeService>();
builder.Services.AddUnique<ISectionService, SectionService>();
builder.Services.AddUnique<IEmailSender, EmailSender>();
builder.Services.AddUnique<ISmsSender, NotImplementedSmsSender>();
builder.Services.AddUnique<IExamineManager, ExamineManager>();
// register distributed cache
builder.Services.AddUnique(f => new DistributedCache(f.GetRequiredService<IServerMessenger>(), f.GetRequiredService<CacheRefresherCollection>()));
builder.Services.AddScoped<ITagQuery, TagQuery>();
builder.Services.AddUnique<HtmlLocalLinkParser>();
builder.Services.AddUnique<HtmlUrlParser>();
builder.Services.AddUnique<HtmlImageSourceParser>();
builder.Services.AddUnique<RichTextEditorPastedImages>();
builder.Services.AddUnique<IUmbracoTreeSearcherFields, UmbracoTreeSearcherFields>();
builder.Services.AddScoped<IPublishedContentQuery>(factory =>
{
var umbCtx = factory.GetRequiredService<IUmbracoContextAccessor>();
return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetRequiredService<IVariationContextAccessor>(), factory.GetRequiredService<IExamineManager>());
});
builder.Services.AddUnique<IPublishedUrlProvider, UrlProvider>();
// register the http context and umbraco context accessors
// we *should* use the HttpContextUmbracoContextAccessor, however there are cases when
// we have no http context, eg when booting Umbraco or in background threads, so instead
// let's use an hybrid accessor that can fall back to a ThreadStatic context.
builder.Services.AddUnique<IUmbracoContextAccessor, HybridUmbracoContextAccessor>();
// register accessors for cultures
builder.Services.AddUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
builder.Services.AddSingleton<IFilePermissionHelper, FilePermissionHelper>();
builder.Services.AddUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
// Register noop versions for examine to be overridden by examine
builder.Services.AddUnique<IUmbracoIndexesCreator, NoopUmbracoIndexesCreator>();
builder.Services.AddUnique<IBackOfficeExamineSearcher, NoopBackOfficeExamineSearcher>();
builder.Services.AddUnique<UploadAutoFillProperties>();
builder.Services.AddUnique<ICronTabParser, NCronTabParser>();
builder.Services.AddUnique(factory => new LegacyPasswordSecurity());
builder.Services.AddUnique<UserEditorAuthorizationHelper>();
builder.Services.AddUnique<ContentPermissions>();
builder.Services.AddUnique<MediaPermissions>();
builder.Services.AddUnique<IImageDimensionExtractor, ImageDimensionExtractor>();
builder.Services.AddUnique<PackageDataInstallation>();
return builder;
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Data; using System.Data;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Persistence; using Umbraco.Core.Persistence;
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping
/// Provides scopes. /// Provides scopes.
/// </summary> /// </summary>
public interface IScopeProvider public interface IScopeProvider
{ {
/// <summary> /// <summary>
/// Creates an ambient scope. /// Creates an ambient scope.
/// </summary> /// </summary>

View File

@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection; using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing; using Umbraco.Core.Composing;
using Umbraco.Core.Models; using Umbraco.Core.Models;

View File

@@ -8,11 +8,11 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence; using Umbraco.Core.Persistence;
using Umbraco.Core.Services; using Umbraco.Core.Services;
using Umbraco.Core.Trees;
using Umbraco.Examine; using Umbraco.Examine;
using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping; using Umbraco.Web.Models.Mapping;
using Umbraco.Web.Routing; using Umbraco.Web.Routing;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Search namespace Umbraco.Web.Search
{ {

View File

@@ -14,6 +14,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Scoping; using Umbraco.Core.Scoping;
using Umbraco.Core.Mail;
namespace Umbraco.Core.Services.Implement namespace Umbraco.Core.Services.Implement
{ {

View File

@@ -1,8 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Umbraco.Core.Composing;
using Umbraco.Core.Events; using Umbraco.Core.Events;
using Umbraco.Core.Hosting; using Umbraco.Core.Hosting;
using Umbraco.Core.Models; using Umbraco.Core.Models;
@@ -26,11 +25,12 @@ namespace Umbraco.Core.Services.Implement
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ServerRegistrationService"/> class. /// Initializes a new instance of the <see cref="ServerRegistrationService"/> class.
/// </summary> /// </summary>
/// <param name="scopeProvider">A UnitOfWork provider.</param> public ServerRegistrationService(
/// <param name="loggerFactory">A logger factory</param> IScopeProvider scopeProvider,
/// <param name="eventMessagesFactory"></param> ILoggerFactory loggerFactory,
public ServerRegistrationService(IScopeProvider scopeProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IEventMessagesFactory eventMessagesFactory,
IServerRegistrationRepository serverRegistrationRepository, IHostingEnvironment hostingEnvironment) IServerRegistrationRepository serverRegistrationRepository,
IHostingEnvironment hostingEnvironment)
: base(scopeProvider, loggerFactory, eventMessagesFactory) : base(scopeProvider, loggerFactory, eventMessagesFactory)
{ {
_serverRegistrationRepository = serverRegistrationRepository; _serverRegistrationRepository = serverRegistrationRepository;
@@ -41,10 +41,10 @@ namespace Umbraco.Core.Services.Implement
/// Touches a server to mark it as active; deactivate stale servers. /// Touches a server to mark it as active; deactivate stale servers.
/// </summary> /// </summary>
/// <param name="serverAddress">The server URL.</param> /// <param name="serverAddress">The server URL.</param>
/// <param name="serverIdentity">The server unique identity.</param>
/// <param name="staleTimeout">The time after which a server is considered stale.</param> /// <param name="staleTimeout">The time after which a server is considered stale.</param>
public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout) public void TouchServer(string serverAddress, TimeSpan staleTimeout)
{ {
var serverIdentity = GetCurrentServerIdentity();
using (var scope = ScopeProvider.CreateScope()) using (var scope = ScopeProvider.CreateScope())
{ {
scope.WriteLock(Constants.Locks.Servers); scope.WriteLock(Constants.Locks.Servers);
@@ -144,19 +144,16 @@ namespace Umbraco.Core.Services.Implement
} }
} }
/// <summary>
/// Gets the local server identity.
/// </summary>
public string CurrentServerIdentity => NetworkHelper.MachineName // eg DOMAIN\SERVER
+ "/" + _hostingEnvironment.ApplicationId; // eg /LM/S3SVC/11/ROOT;
/// <summary> /// <summary>
/// Gets the role of the current server. /// Gets the role of the current server.
/// </summary> /// </summary>
/// <returns>The role of the current server.</returns> /// <returns>The role of the current server.</returns>
public ServerRole GetCurrentServerRole() public ServerRole GetCurrentServerRole() => _currentServerRole;
{
return _currentServerRole; /// <summary>
} /// Gets the local server identity.
/// </summary>
private string GetCurrentServerIdentity() => NetworkHelper.MachineName // eg DOMAIN\SERVER
+ "/" + _hostingEnvironment.ApplicationId; // eg /LM/S3SVC/11/ROOT;
} }
} }

View File

@@ -1,104 +1,83 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Cache; using Umbraco.Core.Cache;
using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting; using Umbraco.Core.Hosting;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Scoping; using Umbraco.Core.Scoping;
using Umbraco.Core.Sync; using Umbraco.Web;
using Umbraco.Web.Routing;
namespace Umbraco.Web namespace Umbraco.Core.Sync
{ {
/// <summary> /// <summary>
/// An <see cref="IServerMessenger"/> implementation that works by storing messages in the database. /// An <see cref="IServerMessenger"/> implementation that works by storing messages in the database.
/// </summary> /// </summary>
/// <remarks> public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
/// This binds to appropriate umbraco events in order to trigger the Boot(), Sync() & FlushBatch() calls
/// </remarks>
public class BatchedDatabaseServerMessenger : DatabaseServerMessenger, IBatchedDatabaseServerMessenger
{ {
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IRequestCache _requestCache; private readonly IRequestCache _requestCache;
private readonly IRequestAccessor _requestAccessor; private readonly IRequestAccessor _requestAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="BatchedDatabaseServerMessenger"/> class.
/// </summary>
public BatchedDatabaseServerMessenger( public BatchedDatabaseServerMessenger(
IMainDom mainDom, IMainDom mainDom,
IUmbracoDatabaseFactory databaseFactory,
IScopeProvider scopeProvider, IScopeProvider scopeProvider,
IProfilingLogger proflog, IProfilingLogger proflog,
ILogger<BatchedDatabaseServerMessenger> logger, ILogger<BatchedDatabaseServerMessenger> logger,
IServerRegistrar serverRegistrar, IServerRoleAccessor serverRegistrar,
DatabaseServerMessengerCallbacks callbacks, DatabaseServerMessengerCallbacks callbacks,
IHostingEnvironment hostingEnvironment, IHostingEnvironment hostingEnvironment,
CacheRefresherCollection cacheRefreshers, CacheRefresherCollection cacheRefreshers,
IRequestCache requestCache, IRequestCache requestCache,
IRequestAccessor requestAccessor, IRequestAccessor requestAccessor,
IOptions<GlobalSettings> globalSettings) IOptions<GlobalSettings> globalSettings)
: base(mainDom, scopeProvider, databaseFactory, proflog, logger, serverRegistrar, true, callbacks, hostingEnvironment, cacheRefreshers, globalSettings) : base(mainDom, scopeProvider, proflog, logger, serverRegistrar, true, callbacks, hostingEnvironment, cacheRefreshers, globalSettings)
{ {
_databaseFactory = databaseFactory;
_requestCache = requestCache; _requestCache = requestCache;
_requestAccessor = requestAccessor; _requestAccessor = requestAccessor;
} }
// invoked by DatabaseServerRegistrarAndMessengerComponent /// <inheritdoc/>
public void Startup()
{
_requestAccessor.EndRequest += UmbracoModule_EndRequest;
if (_databaseFactory.CanConnect == false)
{
Logger.LogWarning("Cannot connect to the database, distributed calls will not be enabled for this server.");
}
else
{
Boot();
}
}
private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e)
{
// will clear the batch - will remain in HttpContext though - that's ok
FlushBatch();
}
protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null) protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable<object> ids = null, string json = null)
{ {
var idsA = ids?.ToArray(); var idsA = ids?.ToArray();
Type arrayType; if (GetArrayType(idsA, out Type arrayType) == false)
if (GetArrayType(idsA, out arrayType) == false) {
throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids)); throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids));
}
BatchMessage(refresher, messageType, idsA, arrayType, json); BatchMessage(refresher, messageType, idsA, arrayType, json);
} }
public void FlushBatch() /// <inheritdoc/>
public override void SendMessages()
{ {
var batch = GetBatch(false); ICollection<RefreshInstructionEnvelope> batch = GetBatch(false);
if (batch == null) return; if (batch == null)
{
return;
}
var instructions = batch.SelectMany(x => x.Instructions).ToArray(); RefreshInstruction[] instructions = batch.SelectMany(x => x.Instructions).ToArray();
batch.Clear(); batch.Clear();
//Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount // Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount
using (var scope = ScopeProvider.CreateScope()) using (IScope scope = ScopeProvider.CreateScope())
{ {
foreach (var instructionsBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) foreach (IEnumerable<RefreshInstruction> instructionsBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount))
{ {
WriteInstructions(scope, instructionsBatch); WriteInstructions(scope, instructionsBatch);
} }
scope.Complete(); scope.Complete();
} }
} }
private void WriteInstructions(IScope scope, IEnumerable<RefreshInstruction> instructions) private void WriteInstructions(IScope scope, IEnumerable<RefreshInstruction> instructions)
@@ -113,11 +92,14 @@ namespace Umbraco.Web
scope.Database.Insert(dto); scope.Database.Insert(dto);
} }
protected ICollection<RefreshInstructionEnvelope> GetBatch(bool create) private ICollection<RefreshInstructionEnvelope> GetBatch(bool create)
{ {
var key = nameof(BatchedDatabaseServerMessenger); var key = nameof(BatchedDatabaseServerMessenger);
if (!_requestCache.IsAvailable) return null; if (!_requestCache.IsAvailable)
{
return null;
}
// no thread-safety here because it'll run in only 1 thread (request) at a time // no thread-safety here because it'll run in only 1 thread (request) at a time
var batch = (ICollection<RefreshInstructionEnvelope>)_requestCache.Get(key); var batch = (ICollection<RefreshInstructionEnvelope>)_requestCache.Get(key);
@@ -130,26 +112,27 @@ namespace Umbraco.Web
return batch; return batch;
} }
protected void BatchMessage( private void BatchMessage(
ICacheRefresher refresher, ICacheRefresher refresher,
MessageType messageType, MessageType messageType,
IEnumerable<object> ids = null, IEnumerable<object> ids = null,
Type idType = null, Type idType = null,
string json = null) string json = null)
{ {
var batch = GetBatch(true); ICollection<RefreshInstructionEnvelope> batch = GetBatch(true);
var instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json); IEnumerable<RefreshInstruction> instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json);
// batch if we can, else write to DB immediately // batch if we can, else write to DB immediately
if (batch == null) if (batch == null)
{ {
//only write the json blob with a maximum count of the MaxProcessingInstructionCount // only write the json blob with a maximum count of the MaxProcessingInstructionCount
using (var scope = ScopeProvider.CreateScope()) using (IScope scope = ScopeProvider.CreateScope())
{ {
foreach (var maxBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) foreach (IEnumerable<RefreshInstruction> maxBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount))
{ {
WriteInstructions(scope, maxBatch); WriteInstructions(scope, maxBatch);
} }
scope.Complete(); scope.Complete();
} }
} }

View File

@@ -23,19 +23,21 @@ namespace Umbraco.Core.Sync
/// <summary> /// <summary>
/// An <see cref="IServerMessenger"/> that works by storing messages in the database. /// An <see cref="IServerMessenger"/> that works by storing messages in the database.
/// </summary> /// </summary>
// public abstract class DatabaseServerMessenger : ServerMessengerBase
// this messenger writes ALL instructions to the database,
// but only processes instructions coming from remote servers,
// thus ensuring that instructions run only once
//
public class DatabaseServerMessenger : ServerMessengerBase, IDatabaseServerMessenger
{ {
// TODO: This class needs to be split into a service/repo for DB access
/*
* this messenger writes ALL instructions to the database,
* but only processes instructions coming from remote servers,
* thus ensuring that instructions run only once
*/
private readonly IMainDom _mainDom; private readonly IMainDom _mainDom;
private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory;
private readonly ManualResetEvent _syncIdle; private readonly ManualResetEvent _syncIdle;
private readonly object _locko = new object(); private readonly object _locko = new object();
private readonly IProfilingLogger _profilingLogger; private readonly IProfilingLogger _profilingLogger;
private readonly IServerRegistrar _serverRegistrar; private readonly IServerRoleAccessor _serverRegistrar;
private readonly IHostingEnvironment _hostingEnvironment; private readonly IHostingEnvironment _hostingEnvironment;
private readonly CacheRefresherCollection _cacheRefreshers; private readonly CacheRefresherCollection _cacheRefreshers;
@@ -43,22 +45,28 @@ namespace Umbraco.Core.Sync
private int _lastId = -1; private int _lastId = -1;
private DateTime _lastSync; private DateTime _lastSync;
private DateTime _lastPruned; private DateTime _lastPruned;
private bool _initialized; private readonly Lazy<bool> _initialized;
private bool _syncing; private bool _syncing;
private bool _released; private bool _released;
public DatabaseServerMessengerCallbacks Callbacks { get; } /// <summary>
/// Initializes a new instance of the <see cref="DatabaseServerMessenger"/> class.
public GlobalSettings GlobalSettings { get; } /// </summary>
protected DatabaseServerMessenger(
public DatabaseServerMessenger( IMainDom mainDom,
IMainDom mainDom, IScopeProvider scopeProvider, IUmbracoDatabaseFactory umbracoDatabaseFactory, IProfilingLogger proflog, ILogger<DatabaseServerMessenger> logger, IServerRegistrar serverRegistrar, IScopeProvider scopeProvider,
bool distributedEnabled, DatabaseServerMessengerCallbacks callbacks, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers, IOptions<GlobalSettings> globalSettings) IProfilingLogger proflog,
ILogger<DatabaseServerMessenger> logger,
IServerRoleAccessor serverRegistrar,
bool distributedEnabled,
DatabaseServerMessengerCallbacks callbacks,
IHostingEnvironment hostingEnvironment,
CacheRefresherCollection cacheRefreshers,
IOptions<GlobalSettings> globalSettings)
: base(distributedEnabled) : base(distributedEnabled)
{ {
ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
_mainDom = mainDom; _mainDom = mainDom;
_umbracoDatabaseFactory = umbracoDatabaseFactory;
_profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog));
_serverRegistrar = serverRegistrar; _serverRegistrar = serverRegistrar;
_hostingEnvironment = hostingEnvironment; _hostingEnvironment = hostingEnvironment;
@@ -76,24 +84,28 @@ namespace Umbraco.Core.Sync
+ " [P" + Process.GetCurrentProcess().Id // eg 1234 + " [P" + Process.GetCurrentProcess().Id // eg 1234
+ "/D" + AppDomain.CurrentDomain.Id // eg 22 + "/D" + AppDomain.CurrentDomain.Id // eg 22
+ "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique
_initialized = new Lazy<bool>(EnsureInitialized);
} }
public DatabaseServerMessengerCallbacks Callbacks { get; }
public GlobalSettings GlobalSettings { get; }
protected ILogger<DatabaseServerMessenger> Logger { get; } protected ILogger<DatabaseServerMessenger> Logger { get; }
protected IScopeProvider ScopeProvider { get; } protected IScopeProvider ScopeProvider { get; }
protected Sql<ISqlContext> Sql() => _umbracoDatabaseFactory.SqlContext.Sql(); protected Sql<ISqlContext> Sql() => ScopeProvider.SqlContext.Sql();
private string DistCacheFilePath => _distCacheFilePath.Value; private string DistCacheFilePath => _distCacheFilePath.Value;
#region Messenger #region Messenger
// we don't care if there's servers listed or not,
// if distributed call is enabled we will make the call
protected override bool RequiresDistributed(ICacheRefresher refresher, MessageType dispatchType) protected override bool RequiresDistributed(ICacheRefresher refresher, MessageType dispatchType)
{ => _initialized.Value && DistributedEnabled;
// we don't care if there's servers listed or not,
// if distributed call is enabled we will make the call
return _initialized && DistributedEnabled;
}
protected override void DeliverRemote( protected override void DeliverRemote(
ICacheRefresher refresher, ICacheRefresher refresher,
@@ -104,7 +116,9 @@ namespace Umbraco.Core.Sync
var idsA = ids?.ToArray(); var idsA = ids?.ToArray();
if (GetArrayType(idsA, out var idType) == false) if (GetArrayType(idsA, out var idType) == false)
{
throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids)); throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids));
}
var instructions = RefreshInstruction.GetInstructions(refresher, messageType, idsA, idType, json); var instructions = RefreshInstruction.GetInstructions(refresher, messageType, idsA, idType, json);
@@ -130,17 +144,12 @@ namespace Umbraco.Core.Sync
/// <summary> /// <summary>
/// Boots the messenger. /// Boots the messenger.
/// </summary> /// </summary>
/// <remarks> private bool EnsureInitialized()
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
/// Callers MUST ensure thread-safety.
/// </remarks>
protected void Boot()
{ {
// weight:10, must release *before* the published snapshot service, because once released // weight:10, must release *before* the published snapshot service, because once released
// the service will *not* be able to properly handle our notifications anymore // the service will *not* be able to properly handle our notifications anymore
const int weight = 10; const int weight = 10;
var registered = _mainDom.Register( var registered = _mainDom.Register(
() => () =>
{ {
@@ -154,7 +163,7 @@ namespace Umbraco.Core.Sync
// properly releasing MainDom - a timeout here means that one refresher // properly releasing MainDom - a timeout here means that one refresher
// is taking too much time processing, however when it's done we will // is taking too much time processing, however when it's done we will
// not update lastId and stop everything // not update lastId and stop everything
var idle =_syncIdle.WaitOne(5000); var idle = _syncIdle.WaitOne(5000);
if (idle == false) if (idle == false)
{ {
Logger.LogWarning("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed."); Logger.LogWarning("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed.");
@@ -163,15 +172,16 @@ namespace Umbraco.Core.Sync
weight); weight);
if (registered == false) if (registered == false)
return; {
return false;
}
ReadLastSynced(); // get _lastId ReadLastSynced(); // get _lastId
using (var scope = ScopeProvider.CreateScope())
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{ {
EnsureInstructions(scope.Database); // reset _lastId if instructions are missing EnsureInstructions(scope.Database); // reset _lastId if instructions are missing
Initialize(scope.Database); // boot return Initialize(scope.Database); // boot
scope.Complete();
} }
} }
@@ -182,14 +192,19 @@ namespace Umbraco.Core.Sync
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
/// Callers MUST ensure thread-safety. /// Callers MUST ensure thread-safety.
/// </remarks> /// </remarks>
private void Initialize(IUmbracoDatabase database) private bool Initialize(IUmbracoDatabase database)
{ {
lock (_locko) lock (_locko)
{ {
if (_released) return; if (_released)
{
return false;
}
var coldboot = false; var coldboot = false;
if (_lastId < 0) // never synced before
// never synced before
if (_lastId < 0)
{ {
// we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new
// server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file.
@@ -201,12 +216,12 @@ namespace Umbraco.Core.Sync
} }
else else
{ {
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each // check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
//row so we will sum these numbers to get the actual count. // row so we will sum these numbers to get the actual count.
var count = database.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); var count = database.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new { lastId = _lastId });
if (count > GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount) if (count > GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)
{ {
//too many instructions, proceed to cold boot // too many instructions, proceed to cold boot
Logger.LogWarning( Logger.LogWarning(
"The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})." "The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})."
+ " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id"
@@ -224,38 +239,55 @@ namespace Umbraco.Core.Sync
// when doing it before, some instructions might run twice - not an issue // when doing it before, some instructions might run twice - not an issue
var maxId = database.ExecuteScalar<int>("SELECT MAX(id) FROM umbracoCacheInstruction"); var maxId = database.ExecuteScalar<int>("SELECT MAX(id) FROM umbracoCacheInstruction");
//if there is a max currently, or if we've never synced // if there is a max currently, or if we've never synced
if (maxId > 0 || _lastId < 0) if (maxId > 0 || _lastId < 0)
{
SaveLastSynced(maxId); SaveLastSynced(maxId);
}
// execute initializing callbacks // execute initializing callbacks
if (Callbacks.InitializingCallbacks != null) if (Callbacks.InitializingCallbacks != null)
{
foreach (var callback in Callbacks.InitializingCallbacks) foreach (var callback in Callbacks.InitializingCallbacks)
{
callback(); callback();
}
}
} }
_initialized = true; return true;
} }
} }
/// <summary> /// <summary>
/// Synchronize the server (throttled). /// Synchronize the server (throttled).
/// </summary> /// </summary>
public void Sync() public override void Sync()
{ {
if (!_initialized.Value)
{
return;
}
lock (_locko) lock (_locko)
{ {
if (_syncing) if (_syncing)
{
return; return;
}
//Don't continue if we are released // Don't continue if we are released
if (_released) if (_released)
{
return; return;
}
if ((DateTime.UtcNow - _lastSync) <= GlobalSettings.DatabaseServerMessenger.TimeBetweenSyncOperations) if ((DateTime.UtcNow - _lastSync) <= GlobalSettings.DatabaseServerMessenger.TimeBetweenSyncOperations)
{
return; return;
}
//Set our flag and the lock to be in it's original state (i.e. it can be awaited) // Set our flag and the lock to be in it's original state (i.e. it can be awaited)
_syncing = true; _syncing = true;
_syncIdle.Reset(); _syncIdle.Reset();
_lastSync = DateTime.UtcNow; _lastSync = DateTime.UtcNow;
@@ -268,7 +300,7 @@ namespace Umbraco.Core.Sync
{ {
ProcessDatabaseInstructions(scope.Database); ProcessDatabaseInstructions(scope.Database);
//Check for pruning throttling // Check for pruning throttling
if (_released || (DateTime.UtcNow - _lastPruned) <= GlobalSettings.DatabaseServerMessenger.TimeBetweenPruneOperations) if (_released || (DateTime.UtcNow - _lastPruned) <= GlobalSettings.DatabaseServerMessenger.TimeBetweenPruneOperations)
{ {
scope.Complete(); scope.Complete();
@@ -277,7 +309,7 @@ namespace Umbraco.Core.Sync
_lastPruned = _lastSync; _lastPruned = _lastSync;
switch (_serverRegistrar.GetCurrentServerRole()) switch (_serverRegistrar.CurrentServerRole)
{ {
case ServerRole.Single: case ServerRole.Single:
case ServerRole.Master: case ServerRole.Master:
@@ -292,7 +324,7 @@ namespace Umbraco.Core.Sync
{ {
lock (_locko) lock (_locko)
{ {
//We must reset our flag and signal any waiting locks // We must reset our flag and signal any waiting locks
_syncing = false; _syncing = false;
} }
@@ -306,9 +338,6 @@ namespace Umbraco.Core.Sync
/// <remarks> /// <remarks>
/// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded.
/// </remarks> /// </remarks>
/// <returns>
/// Returns the number of processed instructions
/// </returns>
private void ProcessDatabaseInstructions(IUmbracoDatabase database) private void ProcessDatabaseInstructions(IUmbracoDatabase database)
{ {
// NOTE // NOTE
@@ -324,7 +353,7 @@ namespace Umbraco.Core.Sync
.Where<CacheInstructionDto>(dto => dto.Id > _lastId) .Where<CacheInstructionDto>(dto => dto.Id > _lastId)
.OrderBy<CacheInstructionDto>(dto => dto.Id); .OrderBy<CacheInstructionDto>(dto => dto.Id);
//only retrieve the top 100 (just in case there's tons) // only retrieve the top 100 (just in case there's tons)
// even though MaxProcessingInstructionCount is by default 1000 we still don't want to process that many // even though MaxProcessingInstructionCount is by default 1000 we still don't want to process that many
// rows in one request thread since each row can contain a ton of instructions (until 7.5.5 in which case // rows in one request thread since each row can contain a ton of instructions (until 7.5.5 in which case
// a row can only contain MaxProcessingInstructionCount) // a row can only contain MaxProcessingInstructionCount)
@@ -337,15 +366,15 @@ namespace Umbraco.Core.Sync
var lastId = 0; var lastId = 0;
//tracks which ones have already been processed to avoid duplicates // tracks which ones have already been processed to avoid duplicates
var processed = new HashSet<RefreshInstruction>(); var processed = new HashSet<RefreshInstruction>();
//It would have been nice to do this in a Query instead of Fetch using a data reader to save // It would have been nice to do this in a Query instead of Fetch using a data reader to save
// some memory however we cannot do that because inside of this loop the cache refreshers are also // some memory however we cannot do that because inside of this loop the cache refreshers are also
// performing some lookups which cannot be done with an active reader open // performing some lookups which cannot be done with an active reader open
foreach (var dto in database.Fetch<CacheInstructionDto>(topSql)) foreach (var dto in database.Fetch<CacheInstructionDto>(topSql))
{ {
//If this flag gets set it means we're shutting down! In this case, we need to exit asap and cannot // If this flag gets set it means we're shutting down! In this case, we need to exit asap and cannot
// continue processing anything otherwise we'll hold up the app domain shutdown // continue processing anything otherwise we'll hold up the app domain shutdown
if (_released) if (_released)
{ {
@@ -377,10 +406,10 @@ namespace Umbraco.Core.Sync
var instructionBatch = GetAllInstructions(jsonA); var instructionBatch = GetAllInstructions(jsonA);
//process as per-normal // process as per-normal
var success = ProcessDatabaseInstructions(instructionBatch, dto, processed, ref lastId); var success = ProcessDatabaseInstructions(instructionBatch, dto, processed, ref lastId);
//if they couldn't be all processed (i.e. we're shutting down) then exit // if they couldn't be all processed (i.e. we're shutting down) then exit
if (success == false) if (success == false)
{ {
Logger.LogInformation("The current batch of instructions was not processed, app is shutting down"); Logger.LogInformation("The current batch of instructions was not processed, app is shutting down");
@@ -425,11 +454,11 @@ namespace Umbraco.Core.Sync
//} //}
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError( Logger.LogError(
ex, ex,
"DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored", "DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored",
dto.Id, dto.Id,
dto.Instructions); dto.Instructions);
//we cannot throw here because this invalid instruction will just keep getting processed over and over and errors //we cannot throw here because this invalid instruction will just keep getting processed over and over and errors
// will be thrown over and over. The only thing we can do is ignore and move on. // will be thrown over and over. The only thing we can do is ignore and move on.
@@ -509,7 +538,8 @@ namespace Umbraco.Core.Sync
/// </remarks> /// </remarks>
private void ReadLastSynced() private void ReadLastSynced()
{ {
if (File.Exists(DistCacheFilePath) == false) return; if (File.Exists(DistCacheFilePath) == false)
return;
var content = File.ReadAllText(DistCacheFilePath); var content = File.ReadAllText(DistCacheFilePath);
if (int.TryParse(content, out var last)) if (int.TryParse(content, out var last))

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