diff --git a/NuGet.Config b/NuGet.Config
index d6c63173f8..92eaf83792 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -1,13 +1,15 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Core/Cache/DistributedCache.cs b/src/Umbraco.Core/Cache/DistributedCache.cs
index 698e97c610..7ad9f9569f 100644
--- a/src/Umbraco.Core/Cache/DistributedCache.cs
+++ b/src/Umbraco.Core/Cache/DistributedCache.cs
@@ -46,7 +46,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return;
- _serverMessenger.PerformRefresh(
+ _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid),
getNumericId,
instances);
@@ -61,7 +61,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty || id == default(int)) return;
- _serverMessenger.PerformRefresh(
+ _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid),
id);
}
@@ -75,7 +75,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty || id == Guid.Empty) return;
- _serverMessenger.PerformRefresh(
+ _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid),
id);
}
@@ -86,7 +86,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty || payload == null) return;
- _serverMessenger.PerformRefresh(
+ _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid),
payload);
}
@@ -97,7 +97,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty || payloads == null) return;
- _serverMessenger.PerformRefresh(
+ _serverMessenger.QueueRefresh(
GetRefresherById(refresherGuid),
payloads.ToArray());
}
@@ -125,7 +125,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty) return;
- _serverMessenger.PerformRefreshAll(
+ _serverMessenger.QueueRefreshAll(
GetRefresherById(refresherGuid));
}
@@ -138,7 +138,7 @@ namespace Umbraco.Web.Cache
{
if (refresherGuid == Guid.Empty || id == default(int)) return;
- _serverMessenger.PerformRemove(
+ _serverMessenger.QueueRemove(
GetRefresherById(refresherGuid),
id);
}
@@ -155,7 +155,7 @@ namespace Umbraco.Web.Cache
///
public void Remove(Guid refresherGuid, Func getNumericId, params T[] instances)
{
- _serverMessenger.PerformRemove(
+ _serverMessenger.QueueRemove(
GetRefresherById(refresherGuid),
getNumericId,
instances);
@@ -164,9 +164,15 @@ namespace Umbraco.Web.Cache
#endregion
// 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;
}
}
}
diff --git a/src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs b/src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs
deleted file mode 100644
index 31e876892e..0000000000
--- a/src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs
+++ /dev/null
@@ -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()
- { }
- }
-}
diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
index 1c4de6cd8e..14089ba924 100644
--- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
@@ -1,7 +1,8 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Composing
{
diff --git a/src/Umbraco.Core/Composing/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs
index 47f272cbf4..91c8244324 100644
--- a/src/Umbraco.Core/Composing/Composers.cs
+++ b/src/Umbraco.Core/Composing/Composers.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -70,7 +70,6 @@ namespace Umbraco.Core.Composing
foreach (var composer in composers)
{
- var componentType = composer.GetType();
composer.Compose(_builder);
}
}
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs
index e4e02443eb..d7b143df38 100644
--- a/src/Umbraco.Core/Composing/CompositionExtensions.cs
+++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs
@@ -1,5 +1,4 @@
-using System;
-using Umbraco.Core;
+using System;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
using Umbraco.Web.PublishedCache;
diff --git a/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs b/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs
deleted file mode 100644
index d88eb44ea3..0000000000
--- a/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Umbraco.Core.Composing
-{
- public interface IPublishedCacheComposer : ICoreComposer
- { }
-}
diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs
index edbf554a2c..9229a95cc3 100644
--- a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.Composing
{
diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs
deleted file mode 100644
index 58aac7b811..0000000000
--- a/src/Umbraco.Core/CompositionExtensions.cs
+++ /dev/null
@@ -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
-
- ///
- /// Gets the actions collection builder.
- ///
- /// The builder.
- ///
- public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the content apps collection builder.
- ///
- /// The builder.
- ///
- public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the content finders collection builder.
- ///
- /// The builder.
- ///
- public static ContentFinderCollectionBuilder ContentFinders(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the editor validators collection builder.
- ///
- /// The builder.
- ///
- public static EditorValidatorCollectionBuilder EditorValidators(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the health checks collection builder.
- ///
- /// The builder.
- public static HealthCheckCollectionBuilder HealthChecks(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the TourFilters collection builder.
- ///
- public static TourFilterCollectionBuilder TourFilters(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the URL providers collection builder.
- ///
- /// The builder.
- public static UrlProviderCollectionBuilder UrlProviders(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the media url providers collection builder.
- ///
- /// The builder.
- public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the backoffice sections/applications collection builder.
- ///
- /// The builder.
- public static SectionCollectionBuilder Sections(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the components collection builder.
- ///
- public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
-
- ///
- /// Gets the backoffice dashboards collection builder.
- ///
- /// The builder.
- public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add()
- .Add();
-
-
- ///
- /// Gets the content finders collection builder.
- ///
- /// The builder.
- ///
- public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- #endregion
- }
-}
diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs
index 9f06046452..d94fdd8496 100644
--- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs
@@ -10,6 +10,17 @@ namespace Umbraco.Core.Configuration.Models
///
public class WebRoutingSettings
{
+ ///
+ /// Gets or sets a value indicating whether to check if any routed endpoints match a front-end request before
+ /// the Umbraco dynamic router tries to map the request to an Umbraco content item.
+ ///
+ ///
+ /// This should not be necessary if the Umbraco catch-all/dynamic route is registered last like it's supposed to be. In that case
+ /// ASP.NET Core will automatically handle this in all cases. This is more of a backward compatible option since this is what v7/v8 used
+ /// to do.
+ ///
+ public bool TryMatchingEndpointsForAllPages { get; set; } = false;
+
///
/// Gets or sets a value indicating whether IIS custom errors should be skipped.
///
diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs
index 5d059d8a23..8199d9fbd0 100644
--- a/src/Umbraco.Core/Constants-Web.cs
+++ b/src/Umbraco.Core/Constants-Web.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Core
+namespace Umbraco.Core
{
public static partial class Constants
{
@@ -7,10 +7,6 @@
///
public static class Web
{
- public const string UmbracoContextDataToken = "umbraco-context";
- public const string UmbracoDataToken = "umbraco";
- public const string PublishedDocumentRequestDataToken = "umbraco-doc-request";
- public const string CustomRouteDataToken = "umbraco-custom-route";
public const string UmbracoRouteDefinitionDataToken = "umbraco-route-def";
///
diff --git a/src/Umbraco.Core/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs
similarity index 89%
rename from src/Umbraco.Core/ServiceCollectionExtensions.cs
rename to src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs
index d1c89ea17e..97e70da9be 100644
--- a/src/Umbraco.Core/ServiceCollectionExtensions.cs
+++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs
@@ -1,9 +1,10 @@
-using System;
+using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Umbraco.Core;
using Umbraco.Core.Composing;
-namespace Umbraco.Core
+namespace Umbraco.Core.DependencyInjection
{
public static class ServiceCollectionExtensions
{
@@ -21,7 +22,7 @@ namespace Umbraco.Core
where TImplementing : class, TService1, TService2
{
services.AddUnique();
- services.AddUnique(factory => (TImplementing) factory.GetRequiredService());
+ services.AddUnique(factory => (TImplementing)factory.GetRequiredService());
}
public static void AddUnique(this IServiceCollection services)
@@ -48,9 +49,9 @@ namespace Umbraco.Core
///
public static void AddUnique(this IServiceCollection services, TService instance)
where TService : class
- => services.Replace(ServiceDescriptor.Singleton(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<>)));
return services;
diff --git a/src/Umbraco.Core/ServiceProviderExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs
similarity index 96%
rename from src/Umbraco.Core/ServiceProviderExtensions.cs
rename to src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs
index e0d3da2c03..a1cc779500 100644
--- a/src/Umbraco.Core/ServiceProviderExtensions.cs
+++ b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs
@@ -1,11 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
+using System;
using Microsoft.Extensions.DependencyInjection;
-using Umbraco.Core.Composing;
-namespace Umbraco.Core
+namespace Umbraco.Core.DependencyInjection
{
///
/// Provides extension methods to the class.
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
new file mode 100644
index 0000000000..f6dc6fd6ff
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -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
+{
+ ///
+ /// Extension methods for
+ ///
+ public static partial class UmbracoBuilderExtensions
+ {
+ ///
+ /// Adds all core collection builders
+ ///
+ 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());
+ // register known content apps
+ builder.ContentApps()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append();
+ // all built-in finders in the correct order,
+ // devs can then modify this list on application startup
+ builder.ContentFinders()
+ .Append()
+ .Append()
+ .Append()
+ /*.Append() // disabled, this is an odd finder */
+ .Append()
+ .Append();
+ builder.EditorValidators().Add(() => builder.TypeLoader.GetTypes());
+ builder.HealthChecks().Add(() => builder.TypeLoader.GetTypes());
+ builder.HealthCheckNotificationMethods().Add(() => builder.TypeLoader.GetTypes());
+ builder.TourFilters();
+ builder.UrlProviders()
+ .Append()
+ .Append();
+ builder.MediaUrlProviders()
+ .Append();
+ // register back office sections in the order we want them rendered
+ builder.Sections()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append();
+ builder.Components();
+ // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards
+ builder.Dashboards()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add(builder.TypeLoader.GetTypes());
+ builder.PackageActions().Add(() => builder.TypeLoader.GetPackageActions());
+ builder.DataValueReferenceFactories();
+ builder.PropertyValueConverters().Append(builder.TypeLoader.GetTypes());
+ builder.UrlSegmentProviders().Append();
+ builder.ManifestValueValidators()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add();
+ builder.ManifestFilters();
+ builder.MediaUrlGenerators();
+ // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable
+ builder.OEmbedProviders()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append()
+ .Append();
+ builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes());
+ }
+
+ ///
+ /// Gets the actions collection builder.
+ ///
+ /// The builder.
+ public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the content apps collection builder.
+ ///
+ /// The builder.
+ public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the content finders collection builder.
+ ///
+ /// The builder.
+ public static ContentFinderCollectionBuilder ContentFinders(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the editor validators collection builder.
+ ///
+ /// The builder.
+ public static EditorValidatorCollectionBuilder EditorValidators(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the health checks collection builder.
+ ///
+ /// The builder.
+ public static HealthCheckCollectionBuilder HealthChecks(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ public static HealthCheckNotificationMethodCollectionBuilder HealthCheckNotificationMethods(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the TourFilters collection builder.
+ ///
+ public static TourFilterCollectionBuilder TourFilters(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the URL providers collection builder.
+ ///
+ /// The builder.
+ public static UrlProviderCollectionBuilder UrlProviders(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the media url providers collection builder.
+ ///
+ /// The builder.
+ public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the backoffice sections/applications collection builder.
+ ///
+ /// The builder.
+ public static SectionCollectionBuilder Sections(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the components collection builder.
+ ///
+ public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the backoffice dashboards collection builder.
+ ///
+ /// The builder.
+ public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the cache refreshers collection builder.
+ ///
+ /// The builder.
+ public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the package actions collection builder.
+ ///
+ /// The builder.
+ internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the data editor collection builder.
+ ///
+ /// The builder.
+ public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the data value reference factory collection builder.
+ ///
+ /// The builder.
+ public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the property value converters collection builder.
+ ///
+ /// The builder.
+ public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the url segment providers collection builder.
+ ///
+ /// The builder.
+ public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the validators collection builder.
+ ///
+ /// The builder.
+ internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the manifest filter collection builder.
+ ///
+ /// The builder.
+ public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the content finders collection builder.
+ ///
+ /// The builder.
+ public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the backoffice OEmbed Providers collection builder.
+ ///
+ /// The builder.
+ public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+
+ ///
+ /// Gets the back office searchable tree collection builder
+ ///
+ public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+ }
+}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs
new file mode 100644
index 0000000000..5bd2fe9e8c
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.DependencyInjection
+{
+ ///
+ /// Extension methods for
+ ///
+ public static partial class UmbracoBuilderExtensions
+ {
+ ///
+ /// Adds Umbraco composers for plugins
+ ///
+ public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder)
+ {
+ // TODO: Should have a better name
+
+ IEnumerable composerTypes = builder.TypeLoader.GetTypes();
+ IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute));
+ new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose();
+
+ return builder;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
new file mode 100644
index 0000000000..a31a44beeb
--- /dev/null
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
@@ -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
+{
+ ///
+ /// Extension methods for
+ ///
+ public static partial class UmbracoBuilderExtensions
+ {
+ ///
+ /// Add Umbraco configuration services and options
+ ///
+ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
+ {
+ // Register configuration validators.
+ builder.Services.AddSingleton, ContentSettingsValidator>();
+ builder.Services.AddSingleton, GlobalSettingsValidator>();
+ builder.Services.AddSingleton, HealthChecksSettingsValidator>();
+ builder.Services.AddSingleton, RequestHandlerSettingsValidator>();
+
+ // Register configuration sections.
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigActiveDirectory));
+ builder.Services.Configure(builder.Config.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true);
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigContent));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigCoreDebug));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExceptionFilter));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigGlobal));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHealthChecks));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHosting));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigImaging));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExamine));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigKeepAlive));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigLogging));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigMemberPassword));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true);
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigNuCache));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRequestHandler));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntime));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigSecurity));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTours));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigUserPassword));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigWebRouting));
+ builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigPlugins));
+
+ return builder;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs
index a4ae2874e3..c24936b4fb 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs
@@ -1,8 +1,8 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using System;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Core.Events;
namespace Umbraco.Core.DependencyInjection
@@ -24,24 +24,15 @@ namespace Umbraco.Core.DependencyInjection
where TNotification : INotification
{
// Register the handler as transient. This ensures that anything can be injected into it.
- builder.Services.AddTransient(typeof(INotificationHandler), typeof(TNotificationHandler));
- return builder;
- }
+ var descriptor = new ServiceDescriptor(typeof(INotificationHandler), 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);
+ }
- ///
- /// Registers a notification handler against the Umbraco service collection.
- ///
- /// The type of notification.
- /// The type of notificiation handler.
- /// The Umbraco builder.
- /// Factory method
- /// The .
- public static IUmbracoBuilder AddNotificationHandler(this IUmbracoBuilder builder, Func factory)
- where TNotificationHandler : class, INotificationHandler
- where TNotification : INotification
- {
- // Register the handler as transient. This ensures that anything can be injected into it.
- builder.Services.AddTransient(typeof(INotificationHandler), factory);
return builder;
}
}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index d56712cdcf..54e5982a58 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -3,28 +3,66 @@
using System;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Umbraco.Core.Cache;
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.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.Routing;
+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
{
public class UmbracoBuilder : IUmbracoBuilder
{
- public IServiceCollection Services { get; }
- public IConfiguration Config { get; }
- public TypeLoader TypeLoader { get; }
- public ILoggerFactory BuilderLoggerFactory { get; }
-
private readonly Dictionary _builders = new Dictionary();
+ public IServiceCollection Services { get; }
+
+ public IConfiguration Config { get; }
+
+ public TypeLoader TypeLoader { get; }
+
+ public ILoggerFactory BuilderLoggerFactory { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader)
: this(services, config, typeLoader, NullLoggerFactory.Instance)
{ }
+ ///
+ /// Initializes a new instance of the class.
+ ///
public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader, ILoggerFactory loggerFactory)
{
Services = services;
@@ -43,10 +81,12 @@ namespace Umbraco.Core.DependencyInjection
public TBuilder WithCollectionBuilder()
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;
+ }
var builder = new TBuilder();
_builders[typeOfBuilder] = builder;
@@ -55,8 +95,10 @@ namespace Umbraco.Core.DependencyInjection
public void Build()
{
- foreach (var builder in _builders.Values)
+ foreach (ICollectionBuilder builder in _builders.Values)
+ {
builder.RegisterWith(Services);
+ }
_builders.Clear();
}
@@ -66,6 +108,114 @@ namespace Umbraco.Core.DependencyInjection
// Register as singleton to allow injection everywhere.
Services.AddSingleton(p => p.GetService);
Services.AddSingleton();
+
+ 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();
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddUnique();
+
+ Services.AddUnique(factory =>
+ {
+ IHostingEnvironment hostingEnvironment = factory.GetRequiredService();
+
+ 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().RuntimeCache);
+ Services.AddUnique(factory => factory.GetRequiredService().RequestCache);
+ Services.AddUnique();
+ Services.AddUnique();
+
+ this.AddAllCoreCollectionBuilders();
+ this.AddNotificationHandler();
+
+ Services.AddSingleton();
+ Services.AddSingleton();
+ this.AddNotificationHandler();
+
+ Services.AddUnique();
+
+ // by default, register a noop factory
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddSingleton(f => f.GetRequiredService().CreateDictionary());
+
+ Services.AddUnique();
+
+ Services.AddUnique();
+
+ // will be injected in controllers when needed to invoke rest endpoints on Our
+ Services.AddUnique();
+ Services.AddUnique();
+
+ // Grid config is not a real config file as we know them
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddUnique();
+ Services.AddUnique();
+
+ // register properties fallback
+ Services.AddUnique();
+
+ Services.AddUnique();
+
+ // register published router
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddUnique();
+ Services.AddUnique();
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddUnique();
+
+ // register distributed cache
+ Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), f.GetRequiredService()));
+
+ // 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();
+
+ Services.AddUnique();
+ Services.AddUnique();
+ Services.AddUnique();
+ Services.AddUnique();
+
+ Services.AddUnique();
+ Services.AddUnique();
+
+ // register a server registrar, by default it's the db registrar
+ Services.AddUnique(f =>
+ {
+ GlobalSettings globalSettings = f.GetRequiredService>().Value;
+ var singleServer = globalSettings.DisableElectionForSingleServer;
+ return singleServer
+ ? (IServerRoleAccessor)new SingleServerRoleAccessor()
+ : new ElectedServerRoleAccessor(f.GetRequiredService());
+ });
}
}
}
diff --git a/src/Umbraco.Core/Diagnostics/NoopMarchal.cs b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs
new file mode 100644
index 0000000000..09629f9595
--- /dev/null
+++ b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Umbraco.Core.Diagnostics
+{
+ internal class NoopMarchal : IMarchal
+ {
+ public IntPtr GetExceptionPointers() => IntPtr.Zero;
+ }
+}
diff --git a/src/Umbraco.Core/DisposableObjectSlim.cs b/src/Umbraco.Core/DisposableObjectSlim.cs
index 4992f8bc0f..6874ad8001 100644
--- a/src/Umbraco.Core/DisposableObjectSlim.cs
+++ b/src/Umbraco.Core/DisposableObjectSlim.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Umbraco.Core
{
@@ -6,8 +6,7 @@ namespace Umbraco.Core
/// Abstract implementation of managed IDisposable.
///
///
- /// This is for objects that do NOT have unmanaged resources. Use
- /// for objects that DO have unmanaged resources and need to deal with them when disposing.
+ /// This is for objects that do NOT have unmanaged resources.
///
/// Can also be used as a pattern for when inheriting is not possible.
///
@@ -19,35 +18,39 @@ namespace Umbraco.Core
///
public abstract class DisposableObjectSlim : IDisposable
{
- private readonly object _locko = new object();
-
- // gets a value indicating whether this instance is disposed.
- // for internal tests only (not thread safe)
+ ///
+ /// Gets a value indicating whether this instance is disposed.
+ ///
+ ///
+ /// for internal tests only (not thread safe)
+ ///
public bool Disposed { get; private set; }
- // implements IDisposable
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ ///
+ /// Disposes managed resources
+ ///
+ protected abstract void DisposeResources();
- private void Dispose(bool disposing)
+ ///
+ /// Disposes managed resources
+ ///
+ /// True if disposing via Dispose method and not a finalizer. Always true for this class.
+ protected virtual void Dispose(bool disposing)
{
- // can happen if the object construction failed
- if (_locko == null)
- return;
-
- lock (_locko)
+ if (!Disposed)
{
- if (Disposed) return;
+ if (disposing)
+ {
+ DisposeResources();
+ }
+
Disposed = true;
}
-
- if (disposing)
- DisposeResources();
}
- protected virtual void DisposeResources() { }
+ ///
+#pragma warning disable CA1063 // Implement IDisposable Correctly
+ public void Dispose() => Dispose(disposing: true); // We do not use GC.SuppressFinalize because this has no finalizer
+#pragma warning restore CA1063 // Implement IDisposable Correctly
}
}
diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs
index bd01ad0b57..d78aed36b7 100644
--- a/src/Umbraco.Core/Events/IEventAggregator.cs
+++ b/src/Umbraco.Core/Events/IEventAggregator.cs
@@ -22,27 +22,4 @@ namespace Umbraco.Core.Events
Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default)
where TNotification : INotification;
}
-
- ///
- /// A marker interface to represent a notification.
- ///
- public interface INotification
- {
- }
-
- ///
- /// Defines a handler for a notification.
- ///
- /// The type of notification being handled.
- public interface INotificationHandler
- where TNotification : INotification
- {
- ///
- /// Handles a notification
- ///
- /// The notification
- /// The cancellation token.
- /// A representing the asynchronous operation.
- Task HandleAsync(TNotification notification, CancellationToken cancellationToken);
- }
}
diff --git a/src/Umbraco.Core/Events/INotification.cs b/src/Umbraco.Core/Events/INotification.cs
new file mode 100644
index 0000000000..3030b0836f
--- /dev/null
+++ b/src/Umbraco.Core/Events/INotification.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// A marker interface to represent a notification.
+ ///
+ public interface INotification
+ {
+ }
+}
diff --git a/src/Umbraco.Core/Events/INotificationHandler.cs b/src/Umbraco.Core/Events/INotificationHandler.cs
new file mode 100644
index 0000000000..dc5d83e0f7
--- /dev/null
+++ b/src/Umbraco.Core/Events/INotificationHandler.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// Defines a handler for a notification.
+ ///
+ /// The type of notification being handled.
+ public interface INotificationHandler
+ where TNotification : INotification
+ {
+ ///
+ /// Handles a notification
+ ///
+ /// The notification
+ /// The cancellation token.
+ /// A representing the asynchronous operation.
+ Task HandleAsync(TNotification notification, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
index a22748094a..28e1de3996 100644
--- a/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
+++ b/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
@@ -32,22 +32,16 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
///
/// Get the status for this health check
///
- ///
- public override IEnumerable GetStatus()
- {
- //return the statuses
- return new[] { CheckFolderPermissions(), CheckFilePermissions() };
- }
+ // TODO: This should really just run the IFilePermissionHelper.RunFilePermissionTestSuite and then we'd have a
+ // IFilePermissions interface resolved as a collection within the IFilePermissionHelper that runs checks against all
+ // IFilePermissions registered. Then there's no hard coding things done here and the checks here will be consistent
+ // with the checks run in IFilePermissionHelper.RunFilePermissionTestSuite which occurs on install too.
+ public override IEnumerable GetStatus() => new[] { CheckFolderPermissions(), CheckFilePermissions() };
///
/// Executes the action and returns it's status
///
- ///
- ///
- public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
- {
- throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions");
- }
+ public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions");
private HealthCheckStatus CheckFolderPermissions()
{
@@ -67,8 +61,8 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
{ Constants.SystemDirectories.MvcViews, PermissionCheckRequirement.Optional }
};
- //These are special paths to check that will restart an app domain if a file is written to them,
- //so these need to be tested differently
+ // These are special paths to check that will restart an app domain if a file is written to them,
+ // so these need to be tested differently
var pathsToCheckWithRestarts = new Dictionary
{
{ Constants.SystemDirectories.Bin, PermissionCheckRequirement.Optional }
@@ -80,7 +74,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
var optionalPathCheckResult = _filePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out var optionalFailedPaths);
- //now check the special folders
+ // now check the special folders
var requiredPathCheckResult2 = _filePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart: true);
var optionalPathCheckResult2 = _filePermissionHelper.EnsureDirectories(
@@ -89,7 +83,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
requiredPathCheckResult = requiredPathCheckResult && requiredPathCheckResult2;
optionalPathCheckResult = optionalPathCheckResult && optionalPathCheckResult2;
- //combine the paths
+ // combine the paths
requiredFailedPaths = requiredFailedPaths.Concat(requiredFailedPaths2).ToList();
optionalFailedPaths = requiredFailedPaths.Concat(optionalFailedPaths2).ToList();
@@ -106,23 +100,19 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
};
// Run checks for required and optional paths for modify permission
- IEnumerable requiredFailedPaths;
- IEnumerable optionalFailedPaths;
- var requiredPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths);
- var optionalPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths);
+ var requiredPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out IEnumerable requiredFailedPaths);
+ var optionalPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out IEnumerable optionalFailedPaths);
return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.File);
}
- private string[] GetPathsToCheck(Dictionary pathsToCheck,
- PermissionCheckRequirement requirement)
- {
- return pathsToCheck
+ private string[] GetPathsToCheck(
+ Dictionary pathsToCheck,
+ PermissionCheckRequirement requirement) => pathsToCheck
.Where(x => x.Value == requirement)
.Select(x => _hostingEnvironment.MapPathContentRoot(x.Key))
.OrderBy(x => x)
.ToArray();
- }
private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable requiredFailedPaths, bool optionalPathCheckResult, IEnumerable optionalFailedPaths, PermissionCheckFor checkingFor)
{
diff --git a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
index d29f3ccb0b..ad92886ecd 100644
--- a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
+++ b/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs
@@ -5,6 +5,7 @@ using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.HealthCheck;
+using Umbraco.Core.Mail;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Infrastructure.HealthCheck;
diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs
similarity index 64%
rename from src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
rename to src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs
index a032720d46..50b7727ecf 100644
--- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs
+++ b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs
@@ -1,25 +1,15 @@
-using System;
-
-namespace Umbraco.Net
+namespace Umbraco.Core.Hosting
{
- // TODO: This shouldn't be in this namespace?
public interface IUmbracoApplicationLifetime
{
///
/// A value indicating whether the application is restarting after the current request.
///
bool IsRestarting { get; }
+
///
/// Terminates the current application. The application restarts the next time a request is received for it.
///
void Restart();
-
- event EventHandler ApplicationInit;
- }
-
-
- public interface IUmbracoApplicationLifetimeManager
- {
- void InvokeApplicationInit();
}
}
diff --git a/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs
new file mode 100644
index 0000000000..3ffef04410
--- /dev/null
+++ b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Core.Hosting
+{
+ internal class NoopApplicationShutdownRegistry : IApplicationShutdownRegistry
+ {
+ public void RegisterObject(IRegisteredObject registeredObject) { }
+ public void UnregisterObject(IRegisteredObject registeredObject) { }
+ }
+}
diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs
index b078172213..62f46edce4 100644
--- a/src/Umbraco.Core/IO/FileSystems.cs
+++ b/src/Umbraco.Core/IO/FileSystems.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
using Umbraco.Core.Configuration.Models;
using Microsoft.Extensions.Options;
+using Umbraco.Core.DependencyInjection;
namespace Umbraco.Core.IO
{
diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
index 551be602dd..c8d49e0c19 100644
--- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs
+++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs
@@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using Microsoft.Extensions.Logging;
-using Umbraco.Core.Exceptions;
-using Umbraco.Core.Composing;
using System.Threading;
+using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
namespace Umbraco.Core.IO
@@ -177,7 +175,7 @@ namespace Umbraco.Core.IO
/// The filesystem-relative path of the directory.
/// A filter.
/// The filesystem-relative path to the matching files in the directory.
- /// Filesystem-relative paths use forward-slashes as directory separators.
+ /// Filesystem-relative paths use forward-slashes as directory separators. //TODO check is this is true on linux and windows..
public IEnumerable GetFiles(string path, string filter)
{
var fullPath = GetFullPath(path);
diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs
index a395938050..1683fb5b4e 100644
--- a/src/Umbraco.Core/IO/ShadowWrapper.cs
+++ b/src/Umbraco.Core/IO/ShadowWrapper.cs
@@ -89,9 +89,9 @@ namespace Umbraco.Core.IO
Directory.Delete(dir, true);
// shadowPath make be path/to/dir, remove each
- dir = dir.Replace("/", "\\");
+ dir = dir.Replace('/', Path.DirectorySeparatorChar);
var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length;
- var pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase);
+ var pos = dir.LastIndexOf(Path.DirectorySeparatorChar);
while (pos > min)
{
dir = dir.Substring(0, pos);
@@ -99,7 +99,7 @@ namespace Umbraco.Core.IO
Directory.Delete(dir, true);
else
break;
- pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase);
+ pos = dir.LastIndexOf(Path.DirectorySeparatorChar);
}
}
catch
diff --git a/src/Umbraco.Core/Install/IFilePermissionHelper.cs b/src/Umbraco.Core/Install/IFilePermissionHelper.cs
index b60839cb00..ab521d214e 100644
--- a/src/Umbraco.Core/Install/IFilePermissionHelper.cs
+++ b/src/Umbraco.Core/Install/IFilePermissionHelper.cs
@@ -1,11 +1,26 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace Umbraco.Core.Install
{
public interface IFilePermissionHelper
{
bool RunFilePermissionTestSuite(out Dictionary> report);
+
+ ///
+ /// This will test the directories for write access
+ ///
+ /// The directories to check
+ /// The resulting errors, if any
+ ///
+ /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause
+ /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as
+ /// reliable but we cannot write a file since it will cause an app domain restart.
+ ///
+ /// Returns true if test succeeds
+ // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus
bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false);
+
+ // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus
bool EnsureFiles(string[] files, out IEnumerable errors);
}
}
diff --git a/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs b/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs
index e3cd56c5c1..4866c472e6 100644
--- a/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs
+++ b/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs
@@ -2,9 +2,9 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using Umbraco.Core.Hosting;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
-using Umbraco.Net;
using Umbraco.Web.Install.Models;
namespace Umbraco.Web.Install.InstallSteps
diff --git a/src/Umbraco.Core/Logging/VoidProfiler.cs b/src/Umbraco.Core/Logging/NoopProfiler.cs
similarity index 89%
rename from src/Umbraco.Core/Logging/VoidProfiler.cs
rename to src/Umbraco.Core/Logging/NoopProfiler.cs
index d771fd7630..e7b43e5e2d 100644
--- a/src/Umbraco.Core/Logging/VoidProfiler.cs
+++ b/src/Umbraco.Core/Logging/NoopProfiler.cs
@@ -1,8 +1,8 @@
-using System;
+using System;
namespace Umbraco.Core.Logging
{
- public class VoidProfiler : IProfiler
+ public class NoopProfiler : IProfiler
{
private readonly VoidDisposable _disposable = new VoidDisposable();
diff --git a/src/Umbraco.Core/IEmailSender.cs b/src/Umbraco.Core/Mail/IEmailSender.cs
similarity index 78%
rename from src/Umbraco.Core/IEmailSender.cs
rename to src/Umbraco.Core/Mail/IEmailSender.cs
index aab944e04d..3862d0e717 100644
--- a/src/Umbraco.Core/IEmailSender.cs
+++ b/src/Umbraco.Core/Mail/IEmailSender.cs
@@ -1,7 +1,7 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Umbraco.Core.Models;
-namespace Umbraco.Core
+namespace Umbraco.Core.Mail
{
///
/// Simple abstraction to send an email message
diff --git a/src/Umbraco.Core/ISmsSender.cs b/src/Umbraco.Core/Mail/ISmsSender.cs
similarity index 84%
rename from src/Umbraco.Core/ISmsSender.cs
rename to src/Umbraco.Core/Mail/ISmsSender.cs
index f296a2ea9b..a2ff054c48 100644
--- a/src/Umbraco.Core/ISmsSender.cs
+++ b/src/Umbraco.Core/Mail/ISmsSender.cs
@@ -1,6 +1,6 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
-namespace Umbraco.Core
+namespace Umbraco.Core.Mail
{
///
/// Service to send an SMS
diff --git a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs
new file mode 100644
index 0000000000..bb8d787cbf
--- /dev/null
+++ b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs
@@ -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");
+ }
+}
diff --git a/src/Umbraco.Infrastructure/NotImplementedSmsSender.cs b/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs
similarity index 90%
rename from src/Umbraco.Infrastructure/NotImplementedSmsSender.cs
rename to src/Umbraco.Core/Mail/NotImplementedSmsSender.cs
index ffc33373d0..16c3d04711 100644
--- a/src/Umbraco.Infrastructure/NotImplementedSmsSender.cs
+++ b/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs
@@ -1,7 +1,7 @@
-using System;
+using System;
using System.Threading.Tasks;
-namespace Umbraco.Core
+namespace Umbraco.Core.Mail
{
///
/// An that throws
diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs
index 6bd893d298..b6cd82b31f 100644
--- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs
+++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs
@@ -1,14 +1,13 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Hosting;
-using Umbraco.Net;
namespace Umbraco.Core.Manifest
{
- public class ManifestWatcher : DisposableObjectSlim
+ public class ManifestWatcher : IDisposable
{
private static readonly object Locker = new object();
private static volatile bool _isRestarting;
@@ -16,6 +15,7 @@ namespace Umbraco.Core.Manifest
private readonly ILogger _logger;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly List _fws = new List();
+ private bool _disposed;
public ManifestWatcher(ILogger logger, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
@@ -48,7 +48,10 @@ namespace Umbraco.Core.Manifest
private void FswChanged(object sender, FileSystemEventArgs e)
{
- if (e.Name.InvariantContains("package.manifest") == false) return;
+ if (!e.Name.InvariantContains("package.manifest"))
+ {
+ return;
+ }
// ensure the app is not restarted multiple times for multiple
// savings during the same app domain execution - restart once
@@ -59,14 +62,25 @@ namespace Umbraco.Core.Manifest
_isRestarting = true;
_logger.LogInformation("Manifest has changed, app pool is restarting ({Path})", e.FullPath);
_umbracoApplicationLifetime.Restart();
- Dispose(); // uh? if the app restarts then this should be disposed anyways?
}
}
- protected override void DisposeResources()
+ private void Dispose(bool disposing)
{
- foreach (var fw in _fws)
- fw.Dispose();
+ // ReSharper disable InvertIf
+ if (disposing && !_disposed)
+ {
+ foreach (FileSystemWatcher fw in _fws)
+ {
+ fw.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ // ReSharper restore InvertIf
}
+
+ public void Dispose() => Dispose(true);
}
}
diff --git a/src/Umbraco.Infrastructure/Media/IImageDimensionExtractor.cs b/src/Umbraco.Core/Media/IImageDimensionExtractor.cs
similarity index 100%
rename from src/Umbraco.Infrastructure/Media/IImageDimensionExtractor.cs
rename to src/Umbraco.Core/Media/IImageDimensionExtractor.cs
diff --git a/src/Umbraco.Infrastructure/Media/ImageSize.cs b/src/Umbraco.Core/Media/ImageSize.cs
similarity index 100%
rename from src/Umbraco.Infrastructure/Media/ImageSize.cs
rename to src/Umbraco.Core/Media/ImageSize.cs
diff --git a/src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs
similarity index 100%
rename from src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs
rename to src/Umbraco.Core/Media/UploadAutoFillProperties.cs
diff --git a/src/Umbraco.Core/Models/IContentModel.cs b/src/Umbraco.Core/Models/IContentModel.cs
index d0d4f175d7..692547aa3e 100644
--- a/src/Umbraco.Core/Models/IContentModel.cs
+++ b/src/Umbraco.Core/Models/IContentModel.cs
@@ -1,10 +1,30 @@
-using Umbraco.Core.Models;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models
{
+ ///
+ /// The basic view model returned for front-end Umbraco controllers
+ ///
+ ///
+ ///
+ /// exists in order to unify all view models in Umbraco, whether it's a normal template view or a partial view macro, or
+ /// a user's custom model that they have created when doing route hijacking or custom routes.
+ ///
+ ///
+ /// By default all front-end template views inherit from UmbracoViewPage which has a model of but the model returned
+ /// from the controllers is which in normal circumstances would not work. This works with UmbracoViewPage because it
+ /// performs model binding between IContentModel and IPublishedContent. This offers a lot of flexibility when rendering views. In some cases if you
+ /// are route hijacking and returning a custom implementation of and your view is strongly typed to this model, you can still
+ /// render partial views created in the back office that have the default model of IPublishedContent without having to worry about explicitly passing
+ /// that model to the view.
+ ///
+ ///
public interface IContentModel
{
+ ///
+ /// Gets the
+ ///
IPublishedContent Content { get; }
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs
index 09604281dc..9410a4f611 100644
--- a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs
@@ -1,4 +1,4 @@
-using Umbraco.Core.Cache;
+using Umbraco.Core.Cache;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models.PublishedContent
@@ -14,10 +14,7 @@ namespace Umbraco.Web.Models.PublishedContent
///
/// Initializes a new instance of the class.
///
- public HttpContextVariationContextAccessor(IRequestCache requestCache)
- {
- _requestCache = requestCache;
- }
+ public HttpContextVariationContextAccessor(IRequestCache requestCache) => _requestCache = requestCache;
///
public VariationContext VariationContext
diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs
index cfc789324a..f9330176aa 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.PublishedContent
@@ -8,21 +8,13 @@ namespace Umbraco.Core.Models.PublishedContent
///
/// Instances implementing the interface should be
/// immutable, ie if the content type changes, then a new instance needs to be created.
- public interface IPublishedContentType2 : IPublishedContentType
+ public interface IPublishedContentType
{
///
/// Gets the unique key for the content type.
///
Guid Key { get; }
- }
- ///
- /// Represents an type.
- ///
- /// Instances implementing the interface should be
- /// immutable, ie if the content type changes, then a new instance needs to be created.
- public interface IPublishedContentType
- {
///
/// Gets the content type identifier.
///
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index 14c26442eb..daf75f5c50 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.PublishedContent
///
/// Instances of the class are immutable, ie
/// if the content type changes, then a new class needs to be created.
- public class PublishedContentType : IPublishedContentType2
+ public class PublishedContentType : IPublishedContentType
{
private readonly IPublishedPropertyType[] _propertyTypes;
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs
deleted file mode 100644
index feab33c1d6..0000000000
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Models.PublishedContent
-{
- public static class PublishedContentTypeExtensions
- {
- ///
- /// Get the GUID key from an
- ///
- ///
- ///
- ///
- public static bool TryGetKey(this IPublishedContentType publishedContentType, out Guid key)
- {
- if (publishedContentType is IPublishedContentType2 contentTypeWithKey)
- {
- key = contentTypeWithKey.Key;
- return true;
- }
- key = Guid.Empty;
- return false;
- }
- }
-}
diff --git a/src/Umbraco.Core/Models/SimpleValidationModel.cs b/src/Umbraco.Core/Models/SimpleValidationModel.cs
new file mode 100644
index 0000000000..cca44613fb
--- /dev/null
+++ b/src/Umbraco.Core/Models/SimpleValidationModel.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Models
+{
+ public class SimpleValidationModel
+ {
+ public SimpleValidationModel(IDictionary modelState, string message = "The request is invalid.")
+ {
+ Message = message;
+ ModelState = modelState;
+ }
+
+ public string Message { get; }
+ public IDictionary ModelState { get; }
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs
index 2149ece02a..a3c02aeb0d 100644
--- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs
+++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs
@@ -1,11 +1,9 @@
-using System.Linq;
+using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Manifest;
namespace Umbraco.Core.PropertyEditors
{
-
-
public class PropertyEditorCollection : BuilderCollectionBase
{
public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser)
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs
similarity index 100%
rename from src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/TextStringValueConverter.cs
rename to src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs
similarity index 95%
rename from src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs
rename to src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs
index 51471f6da7..64ecba5c7c 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Umbraco.Core.Models.PublishedContent;
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.
///
[DefaultPropertyValueConverter]
- public class TinyMceValueConverter : PropertyValueConverterBase
+ public class SimpleTinyMceValueConverter : PropertyValueConverterBase
{
public override bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce;
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs
deleted file mode 100644
index 7caa9a90cc..0000000000
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
index 68b2367ce0..7307ba97f9 100644
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
+++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using Umbraco.Core.Models.Membership;
+using System.Threading.Tasks;
using Umbraco.Web.Cache;
namespace Umbraco.Web.PublishedCache
@@ -11,8 +11,6 @@ namespace Umbraco.Web.PublishedCache
///
public interface IPublishedSnapshotService : IDisposable
{
- #region PublishedSnapshot
-
/* Various places (such as Node) want to access the XML content, today as an XmlDocument
* but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need
* to find out how to get that navigator.
@@ -36,85 +34,23 @@ namespace Umbraco.Web.PublishedCache
IPublishedSnapshot CreatePublishedSnapshot(string previewToken);
///
- /// Gets the published snapshot accessor.
- ///
- IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
-
- ///
- /// Ensures that the published snapshot has the proper environment to run.
- ///
- /// The errors, if any.
- /// A value indicating whether the published snapshot has the proper environment to run.
- bool EnsureEnvironment(out IEnumerable errors);
-
- #endregion
-
- #region Rebuild
-
- ///
- /// Rebuilds internal caches (but does not reload).
+ /// Rebuilds internal database caches (but does not reload).
///
+ /// The operation batch size to process the items
+ /// If not null will process content for the matching content types, if empty will process all content
+ /// If not null will process content for the matching media types, if empty will process all media
+ /// If not null will process content for the matching members types, if empty will process all members
///
- /// Forces the snapshot service to rebuild its internal caches. For instance, some caches
+ /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
/// may rely on a database table to store pre-serialized version of documents.
/// This does *not* reload the caches. Caches need to be reloaded, for instance via
/// RefreshAllPublishedSnapshot method.
///
- void Rebuild();
-
- #endregion
-
- #region Preview
-
- /* Later on we can imagine that EnterPreview would handle a "level" that would be either
- * the content only, or the content's branch, or the whole tree + it could be possible
- * to register filters against the factory to filter out which nodes should be preview
- * vs non preview.
- *
- * EnterPreview() returns the previewToken. It is up to callers to store that token
- * wherever they want, most probably in a cookie.
- *
- */
-
- ///
- /// Enters preview for specified user and content.
- ///
- /// The user.
- /// The content identifier.
- /// A preview token.
- ///
- /// Tells the caches that they should prepare any data that they would be keeping
- /// in order to provide preview to a give user. In the Xml cache this means creating the Xml
- /// file, though other caches may do things differently.
- /// Does not handle the preview token storage (cookie, etc) that must be handled separately.
- ///
- string EnterPreview(IUser user, int contentId);
-
- ///
- /// Refreshes preview for a specified content.
- ///
- /// The preview token.
- /// The content identifier.
- /// Tells the caches that they should update any data that they would be keeping
- /// in order to provide preview to a given user. In the Xml cache this means updating the Xml
- /// file, though other caches may do things differently.
- void RefreshPreview(string previewToken, int contentId);
-
- ///
- /// Exits preview for a specified preview token.
- ///
- /// The preview token.
- ///
- /// Tells the caches that they can dispose of any data that they would be keeping
- /// in order to provide preview to a given user. In the Xml cache this means deleting the Xml file,
- /// though other caches may do things differently.
- /// Does not handle the preview token storage (cookie, etc) that must be handled separately.
- ///
- void ExitPreview(string previewToken);
-
- #endregion
-
- #region Changes
+ void Rebuild(
+ int groupSize = 5000,
+ IReadOnlyCollection contentTypeIds = null,
+ IReadOnlyCollection mediaTypeIds = null,
+ IReadOnlyCollection memberTypeIds = null);
/* An IPublishedCachesService implementation can rely on transaction-level events to update
* its internal, database-level data, as these events are purely internal. However, it cannot
@@ -160,16 +96,9 @@ namespace Umbraco.Web.PublishedCache
/// The changes.
void Notify(DomainCacheRefresher.JsonPayload[] payloads);
- #endregion
-
- #region Status
-
- string GetStatus();
-
- string StatusUrl { get; }
-
- #endregion
-
- void Collect();
+ ///
+ /// Cleans up unused snapshots
+ ///
+ Task CollectAsync();
}
}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs
new file mode 100644
index 0000000000..0f88bd4085
--- /dev/null
+++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Web.PublishedCache
+{
+ ///
+ /// Returns the currents status for nucache
+ ///
+ public interface IPublishedSnapshotStatus
+ {
+ ///
+ /// Gets the status report as a string
+ ///
+ string GetStatus();
+
+ ///
+ /// Gets the URL used to retreive the status
+ ///
+ string StatusUrl { get; }
+ }
+}
diff --git a/src/Umbraco.Core/ITagQuery.cs b/src/Umbraco.Core/PublishedCache/ITagQuery.cs
similarity index 100%
rename from src/Umbraco.Core/ITagQuery.cs
rename to src/Umbraco.Core/PublishedCache/ITagQuery.cs
diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
deleted file mode 100644
index 9c71bdc04b..0000000000
--- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Web.Cache;
-
-namespace Umbraco.Web.PublishedCache
-{
- public abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService
- {
- protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor)
- {
- PublishedSnapshotAccessor = publishedSnapshotAccessor;
- VariationContextAccessor = variationContextAccessor;
- }
-
- public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
- public IVariationContextAccessor VariationContextAccessor { get; }
-
- // note: NOT setting _publishedSnapshotAccessor.PublishedSnapshot here because it is the
- // responsibility of the caller to manage what the 'current' facade is
- public abstract IPublishedSnapshot CreatePublishedSnapshot(string previewToken);
-
- protected IPublishedSnapshot CurrentPublishedSnapshot => PublishedSnapshotAccessor.PublishedSnapshot;
-
- public abstract bool EnsureEnvironment(out IEnumerable errors);
-
- public abstract string EnterPreview(IUser user, int contentId);
- public abstract void RefreshPreview(string previewToken, int contentId);
- public abstract void ExitPreview(string previewToken);
- public abstract void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged);
- public abstract void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged);
- public abstract void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads);
- public abstract void Notify(DataTypeCacheRefresher.JsonPayload[] payloads);
- public abstract void Notify(DomainCacheRefresher.JsonPayload[] payloads);
-
- public virtual void Rebuild()
- { }
-
- public virtual void Dispose()
- { }
-
- public abstract string GetStatus();
-
- public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html";
-
- public virtual void Collect()
- {
- }
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
index 7e0ebc6f13..874da1f3aa 100644
--- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
+++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
@@ -1,6 +1,11 @@
-using System;
+using System;
namespace Umbraco.Web.PublishedCache
{
+ // TODO: This is a mess. This is a circular reference:
+ // IPublishedSnapshotAccessor -> PublishedSnapshotService -> UmbracoContext -> PublishedSnapshotService -> IPublishedSnapshotAccessor
+ // Injecting IPublishedSnapshotAccessor into PublishedSnapshotService seems pretty strange
+ // The underlying reason for this mess is because IPublishedContent is both a service and a model.
+ // Until that is fixed, IPublishedContent will need to have a IPublishedSnapshotAccessor
public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs
index 1e6056942f..65e094690e 100644
--- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
@@ -112,10 +112,10 @@ namespace Umbraco.Web.Routing
// if the property varies, get the variant value, URL is "/"
// but! only if the culture is published, else ignore
- if (varies && !node.HasCulture(domainUri.Culture.Name)) continue;
+ if (varies && !node.HasCulture(domainUri.Culture)) continue;
var umbracoUrlName = varies
- ? node.Value(_publishedValueFallback,Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture.Name)
+ ? node.Value(_publishedValueFallback,Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture)
: node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias);
var aliases = umbracoUrlName?.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries);
@@ -127,7 +127,7 @@ namespace Umbraco.Web.Routing
{
var path = "/" + alias;
var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path));
- yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture.Name);
+ yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture);
}
}
}
diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs
index 856b91e36f..500bd65f82 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs
@@ -1,11 +1,9 @@
-using Umbraco.Core;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
-using Umbraco.Core.Models.PublishedContent;
using System.Globalization;
-using Umbraco.Core.Configuration.Models;
-using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Umbraco.Core;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Routing
{
@@ -19,13 +17,22 @@ namespace Umbraco.Web.Routing
{
private readonly ILogger _logger;
private readonly IRequestAccessor _requestAccessor;
+ private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly WebRoutingSettings _webRoutingSettings;
- public ContentFinderByIdPath(IOptions webRoutingSettings, ILogger logger, IRequestAccessor requestAccessor)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentFinderByIdPath(
+ IOptions webRoutingSettings,
+ ILogger logger,
+ IRequestAccessor requestAccessor,
+ IUmbracoContextAccessor umbracoContextAccessor)
{
_webRoutingSettings = webRoutingSettings.Value ?? throw new System.ArgumentNullException(nameof(webRoutingSettings));
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
- _requestAccessor = requestAccessor;
+ _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor));
+ _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
}
///
@@ -33,43 +40,48 @@ namespace Umbraco.Web.Routing
///
/// The PublishedRequest.
/// A value indicating whether an Umbraco document was found and assigned.
- public bool TryFindContent(IPublishedRequest frequest)
+ public bool TryFindContent(IPublishedRequestBuilder frequest)
{
-
- if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false
- && _webRoutingSettings.DisableFindContentByIdPath)
+ IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext;
+ if (umbCtx == null || (umbCtx != null && umbCtx.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath))
+ {
return false;
+ }
IPublishedContent node = null;
- var path = frequest.Uri.GetAbsolutePathDecoded();
+ var path = frequest.AbsolutePathDecoded;
var nodeId = -1;
- if (path != "/") // no id if "/"
+
+ // no id if "/"
+ if (path != "/")
{
var noSlashPath = path.Substring(1);
if (int.TryParse(noSlashPath, out nodeId) == false)
+ {
nodeId = -1;
+ }
if (nodeId > 0)
{
_logger.LogDebug("Id={NodeId}", nodeId);
- node = frequest.UmbracoContext.Content.GetById(nodeId);
+ node = umbCtx.Content.GetById(nodeId);
if (node != null)
{
var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture");
- //if we have a node, check if we have a culture in the query string
+ // if we have a node, check if we have a culture in the query string
if (!string.IsNullOrEmpty(cultureFromQuerystring))
{
- //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though
- frequest.Culture = CultureInfo.GetCultureInfo(cultureFromQuerystring);
+ // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though
+ frequest.SetCulture(cultureFromQuerystring);
}
- frequest.PublishedContent = node;
- _logger.LogDebug("Found node with id={PublishedContentId}", frequest.PublishedContent.Id);
+ frequest.SetPublishedContent(node);
+ _logger.LogDebug("Found node with id={PublishedContentId}", node.Id);
}
else
{
@@ -79,7 +91,9 @@ namespace Umbraco.Web.Routing
}
if (nodeId == -1)
+ {
_logger.LogDebug("Not a node id");
+ }
return node != null;
}
diff --git a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs
index 16d7a0c4cf..15698f9134 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs
@@ -1,4 +1,6 @@
-
+
+using Umbraco.Core.Models.PublishedContent;
+
namespace Umbraco.Web.Routing
{
///
@@ -11,25 +13,37 @@ namespace Umbraco.Web.Routing
public class ContentFinderByPageIdQuery : IContentFinder
{
private readonly IRequestAccessor _requestAccessor;
+ private readonly IUmbracoContextAccessor _umbracoContextAccessor;
- public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor, IUmbracoContextAccessor umbracoContextAccessor)
{
- _requestAccessor = requestAccessor;
+ _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor));
+ _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
}
- public bool TryFindContent(IPublishedRequest frequest)
+ ///
+ public bool TryFindContent(IPublishedRequestBuilder frequest)
{
- int pageId;
- if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out pageId))
+ IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext;
+ if (umbCtx == null)
{
- var doc = frequest.UmbracoContext.Content.GetById(pageId);
+ return false;
+ }
+
+ if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out int pageId))
+ {
+ IPublishedContent doc = umbCtx.Content.GetById(pageId);
if (doc != null)
{
- frequest.PublishedContent = doc;
+ frequest.SetPublishedContent(doc);
return true;
}
}
+
return false;
}
}
diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs
index 895917c69d..e3c5b28a2a 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
namespace Umbraco.Web.Routing
@@ -17,12 +19,21 @@ namespace Umbraco.Web.Routing
private readonly IRedirectUrlService _redirectUrlService;
private readonly ILogger _logger;
private readonly IPublishedUrlProvider _publishedUrlProvider;
+ private readonly IUmbracoContextAccessor _umbracoContextAccessor;
- public ContentFinderByRedirectUrl(IRedirectUrlService redirectUrlService, ILogger logger, IPublishedUrlProvider publishedUrlProvider)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentFinderByRedirectUrl(
+ IRedirectUrlService redirectUrlService,
+ ILogger logger,
+ IPublishedUrlProvider publishedUrlProvider,
+ IUmbracoContextAccessor umbracoContextAccessor)
{
_redirectUrlService = redirectUrlService;
_logger = logger;
_publishedUrlProvider = publishedUrlProvider;
+ _umbracoContextAccessor = umbracoContextAccessor;
}
///
@@ -31,15 +42,19 @@ namespace Umbraco.Web.Routing
/// The PublishedRequest.
/// A value indicating whether an Umbraco document was found and assigned.
/// Optionally, can also assign the template or anything else on the document request, although that is not required.
- public bool TryFindContent(IPublishedRequest frequest)
+ public bool TryFindContent(IPublishedRequestBuilder frequest)
{
- var route = frequest.HasDomain
- ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded())
- : frequest.Uri.GetAbsolutePathDecoded();
+ IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext;
+ if (umbCtx == null)
+ {
+ return false;
+ }
+ var route = frequest.Domain != null
+ ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded)
+ : frequest.AbsolutePathDecoded;
-
- var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name);
+ IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture);
if (redirectUrl == null)
{
@@ -47,7 +62,7 @@ namespace Umbraco.Web.Routing
return false;
}
- var content = frequest.UmbracoContext.Content.GetById(redirectUrl.ContentId);
+ IPublishedContent content = umbCtx.Content.GetById(redirectUrl.ContentId);
var url = content == null ? "#" : content.Url(_publishedUrlProvider, redirectUrl.Culture);
if (url.StartsWith("#"))
{
@@ -59,17 +74,17 @@ namespace Umbraco.Web.Routing
url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query;
_logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, content.Id, url);
- frequest.SetRedirectPermanent(url);
+ frequest
+ .SetRedirectPermanent(url)
- // From: http://stackoverflow.com/a/22468386/5018
- // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532
- // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads
- // to problems if you rename a page back to it's original name or create a new page with the original name
- //frequest.Cacheability = HttpCacheability.NoCache;
- frequest.CacheabilityNoCache = true;
- frequest.CacheExtensions = new List { "no-store, must-revalidate" };
- frequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } };
+ // From: http://stackoverflow.com/a/22468386/5018
+ // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532
+ // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads
+ // to problems if you rename a page back to it's original name or create a new page with the original name
+ .SetNoCacheHeader(true)
+ .SetCacheExtensions(new List { "no-store, must-revalidate" })
+ .SetHeaders(new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } });
return true;
}
diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs
index 653e808dfe..c20cf9fd85 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs
@@ -14,44 +14,70 @@ namespace Umbraco.Web.Routing
{
private readonly ILogger _logger;
- public ContentFinderByUrl(ILogger logger)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentFinderByUrl(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor)
{
- _logger = logger;
+ _logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
+ UmbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
}
+ ///
+ /// Gets the
+ ///
+ protected IUmbracoContextAccessor UmbracoContextAccessor { get; }
+
///
/// Tries to find and assign an Umbraco document to a PublishedRequest.
///
/// The PublishedRequest.
/// A value indicating whether an Umbraco document was found and assigned.
- public virtual bool TryFindContent(IPublishedRequest frequest)
+ public virtual bool TryFindContent(IPublishedRequestBuilder frequest)
{
- string route;
- if (frequest.HasDomain)
- route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded());
- else
- route = frequest.Uri.GetAbsolutePathDecoded();
+ IUmbracoContext umbCtx = UmbracoContextAccessor.UmbracoContext;
+ if (umbCtx == null)
+ {
+ return false;
+ }
- var node = FindContent(frequest, route);
+ string route;
+ if (frequest.Domain != null)
+ {
+ route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded);
+ }
+ else
+ {
+ route = frequest.AbsolutePathDecoded;
+ }
+
+ IPublishedContent node = FindContent(frequest, route);
return node != null;
}
///
/// Tries to find an Umbraco document for a PublishedRequest and a route.
///
- /// The document request.
- /// The route.
/// The document node, or null.
- protected IPublishedContent FindContent(IPublishedRequest docreq, string route)
+ protected IPublishedContent FindContent(IPublishedRequestBuilder docreq, string route)
{
- if (docreq == null) throw new System.ArgumentNullException(nameof(docreq));
+ IUmbracoContext umbCtx = UmbracoContextAccessor.UmbracoContext;
+ if (umbCtx == null)
+ {
+ return null;
+ }
+
+ if (docreq == null)
+ {
+ throw new System.ArgumentNullException(nameof(docreq));
+ }
_logger.LogDebug("Test route {Route}", route);
- var node = docreq.UmbracoContext.Content.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name);
+ IPublishedContent node = umbCtx.Content.GetByRoute(umbCtx.InPreviewMode, route, culture: docreq.Culture);
if (node != null)
{
- docreq.PublishedContent = node;
+ docreq.SetPublishedContent(node);
_logger.LogDebug("Got content, id={NodeId}", node.Id);
}
else
diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs
index 24bfad914d..4745ea8cd3 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs
@@ -20,12 +20,21 @@ namespace Umbraco.Web.Routing
{
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly IVariationContextAccessor _variationContextAccessor;
+ private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ILogger _logger;
- public ContentFinderByUrlAlias(ILogger logger, IPublishedValueFallback publishedValueFallback, IVariationContextAccessor variationContextAccessor)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentFinderByUrlAlias(
+ ILogger logger,
+ IPublishedValueFallback publishedValueFallback,
+ IVariationContextAccessor variationContextAccessor,
+ IUmbracoContextAccessor umbracoContextAccessor)
{
_publishedValueFallback = publishedValueFallback;
_variationContextAccessor = variationContextAccessor;
+ _umbracoContextAccessor = umbracoContextAccessor;
_logger = logger;
}
@@ -34,21 +43,29 @@ namespace Umbraco.Web.Routing
///
/// The PublishedRequest.
/// A value indicating whether an Umbraco document was found and assigned.
- public bool TryFindContent(IPublishedRequest frequest)
+ public bool TryFindContent(IPublishedRequestBuilder frequest)
{
+ IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext;
+ if (umbCtx == null)
+ {
+ return false;
+ }
+
IPublishedContent node = null;
- if (frequest.Uri.AbsolutePath != "/") // no alias if "/"
+ // no alias if "/"
+ if (frequest.Uri.AbsolutePath != "/")
{
- node = FindContentByAlias(frequest.UmbracoContext.Content,
- frequest.HasDomain ? frequest.Domain.ContentId : 0,
- frequest.Culture.Name,
- frequest.Uri.GetAbsolutePathDecoded());
+ node = FindContentByAlias(
+ umbCtx.Content,
+ frequest.Domain != null ? frequest.Domain.ContentId : 0,
+ frequest.Culture,
+ frequest.AbsolutePathDecoded);
if (node != null)
{
- frequest.PublishedContent = node;
- _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, frequest.PublishedContent.Id);
+ frequest.SetPublishedContent(node);
+ _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, node.Id);
}
}
@@ -57,7 +74,10 @@ namespace Umbraco.Web.Routing
private IPublishedContent FindContentByAlias(IPublishedContentCache cache, int rootNodeId, string culture, string alias)
{
- if (alias == null) throw new ArgumentNullException(nameof(alias));
+ if (alias == null)
+ {
+ throw new ArgumentNullException(nameof(alias));
+ }
// the alias may be "foo/bar" or "/foo/bar"
// there may be spaces as in "/foo/bar, /foo/nil"
@@ -65,7 +85,6 @@ namespace Umbraco.Web.Routing
// TODO: can we normalize the values so that they contain no whitespaces, and no leading slashes?
// and then the comparisons in IsMatch can be way faster - and allocate way less strings
-
const string propertyAlias = Constants.Conventions.Content.UrlAlias;
var test1 = alias.TrimStart('/') + ",";
@@ -80,38 +99,52 @@ namespace Umbraco.Web.Routing
// "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" +
// " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" +
// ")]"
+ if (!c.HasProperty(propertyAlias))
+ {
+ return false;
+ }
- if (!c.HasProperty(propertyAlias)) return false;
- var p = c.GetProperty(propertyAlias);
+ IPublishedProperty p = c.GetProperty(propertyAlias);
var varies = p.PropertyType.VariesByCulture();
string v;
if (varies)
{
- if (!c.HasCulture(culture)) return false;
+ if (!c.HasCulture(culture))
+ {
+ return false;
+ }
+
v = c.Value(_publishedValueFallback, propertyAlias, culture);
}
else
{
v = c.Value(_publishedValueFallback, propertyAlias);
}
- if (string.IsNullOrWhiteSpace(v)) return false;
- v = "," + v.Replace(" ", "") + ",";
+
+ if (string.IsNullOrWhiteSpace(v))
+ {
+ return false;
+ }
+
+ v = "," + v.Replace(" ", string.Empty) + ",";
return v.InvariantContains(a1) || v.InvariantContains(a2);
}
// TODO: even with Linq, what happens below has to be horribly slow
// but the only solution is to entirely refactor URL providers to stop being dynamic
-
if (rootNodeId > 0)
{
- var rootNode = cache.GetById(rootNodeId);
+ IPublishedContent rootNode = cache.GetById(rootNodeId);
return rootNode?.Descendants(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2));
}
- foreach (var rootContent in cache.GetAtRoot())
+ foreach (IPublishedContent rootContent in cache.GetAtRoot())
{
- var c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2));
- if (c != null) return c;
+ IPublishedContent c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2));
+ if (c != null)
+ {
+ return c;
+ }
}
return null;
diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs
index 8ae4e2aead..2e69446d68 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs
@@ -1,10 +1,10 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Umbraco.Core;
-using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
-using Umbraco.Core.Configuration.Models;
-using Microsoft.Extensions.Options;
-using Microsoft.Extensions.Logging;
namespace Umbraco.Web.Routing
{
@@ -24,8 +24,16 @@ namespace Umbraco.Web.Routing
private readonly IContentTypeService _contentTypeService;
private readonly WebRoutingSettings _webRoutingSettings;
- public ContentFinderByUrlAndTemplate(ILogger logger, IFileService fileService, IContentTypeService contentTypeService, IOptions webRoutingSettings)
- : base(logger)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentFinderByUrlAndTemplate(
+ ILogger logger,
+ IFileService fileService,
+ IContentTypeService contentTypeService,
+ IUmbracoContextAccessor umbracoContextAccessor,
+ IOptions webRoutingSettings)
+ : base(logger, umbracoContextAccessor)
{
_logger = logger;
_fileService = fileService;
@@ -39,13 +47,14 @@ namespace Umbraco.Web.Routing
/// The PublishedRequest.
/// A value indicating whether an Umbraco document was found and assigned.
/// If successful, also assigns the template.
- public override bool TryFindContent(IPublishedRequest frequest)
+ public override bool TryFindContent(IPublishedRequestBuilder frequest)
{
- IPublishedContent node = null;
- var path = frequest.Uri.GetAbsolutePathDecoded();
+ var path = frequest.AbsolutePathDecoded;
- if (frequest.HasDomain)
+ if (frequest.Domain != null)
+ {
path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path);
+ }
// no template if "/"
if (path == "/")
@@ -59,7 +68,7 @@ namespace Umbraco.Web.Routing
var templateAlias = path.Substring(pos + 1);
path = pos == 0 ? "/" : path.Substring(0, pos);
- var template = _fileService.GetTemplate(templateAlias);
+ ITemplate template = _fileService.GetTemplate(templateAlias);
if (template == null)
{
@@ -70,8 +79,8 @@ namespace Umbraco.Web.Routing
_logger.LogDebug("Valid template: '{TemplateAlias}'", templateAlias);
// look for node corresponding to the rest of the route
- var route = frequest.HasDomain ? (frequest.Domain.ContentId + path) : path;
- node = FindContent(frequest, route); // also assigns to published request
+ var route = frequest.Domain != null ? (frequest.Domain.ContentId + path) : path;
+ IPublishedContent node = FindContent(frequest, route);
if (node == null)
{
@@ -83,12 +92,12 @@ namespace Umbraco.Web.Routing
if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id))
{
_logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id);
- frequest.PublishedContent = null; // clear
+ frequest.SetPublishedContent(null); // clear
return false;
}
// got it
- frequest.TemplateModel = template;
+ frequest.SetTemplate(template);
return true;
}
}
diff --git a/src/Umbraco.Core/Routing/CreatingRequestNotification.cs b/src/Umbraco.Core/Routing/CreatingRequestNotification.cs
new file mode 100644
index 0000000000..859ccd24b0
--- /dev/null
+++ b/src/Umbraco.Core/Routing/CreatingRequestNotification.cs
@@ -0,0 +1,21 @@
+using System;
+using Umbraco.Core.Events;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Used for notifying when an Umbraco request is being created
+ ///
+ public class CreatingRequestNotification : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CreatingRequestNotification(Uri url) => Url = url;
+
+ ///
+ /// Gets or sets the URL for the request
+ ///
+ public Uri Url { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
index 51c212aa3c..d739f851ad 100644
--- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
@@ -83,7 +83,9 @@ namespace Umbraco.Web.Routing
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
var node = umbracoContext.Content.GetById(id);
if (node == null)
+ {
yield break;
+ }
// look for domains, walking up the tree
var n = node;
@@ -96,17 +98,19 @@ namespace Umbraco.Web.Routing
// no domains = exit
if (domainUris ==null)
+ {
yield break;
+ }
foreach (var d in domainUris)
{
- var culture = d?.Culture?.Name;
+ var culture = d?.Culture;
- //although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok
+ // although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok
var route = umbracoContext.Content.GetRouteById(id, culture);
if (route == null) continue;
- //need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
+ // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
var pos = route.IndexOf('/');
var path = pos == 0 ? route : route.Substring(pos);
diff --git a/src/Umbraco.Core/Routing/Domain.cs b/src/Umbraco.Core/Routing/Domain.cs
index b9116c6b51..7d1808ef97 100644
--- a/src/Umbraco.Core/Routing/Domain.cs
+++ b/src/Umbraco.Core/Routing/Domain.cs
@@ -1,4 +1,4 @@
-using System.Globalization;
+using System.Globalization;
namespace Umbraco.Web.Routing
{
@@ -15,7 +15,7 @@ namespace Umbraco.Web.Routing
/// The identifier of the content which supports the domain.
/// The culture of the domain.
/// A value indicating whether the domain is a wildcard domain.
- public Domain(int id, string name, int contentId, CultureInfo culture, bool isWildcard)
+ public Domain(int id, string name, int contentId, string culture, bool isWildcard)
{
Id = id;
Name = name;
@@ -55,7 +55,7 @@ namespace Umbraco.Web.Routing
///
/// Gets the culture of the domain.
///
- public CultureInfo Culture { get; }
+ public string Culture { get; }
///
/// Gets a value indicating whether the domain is a wildcard domain.
diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs
index 85347abb42..0d14b26396 100644
--- a/src/Umbraco.Core/Routing/DomainUtilities.cs
+++ b/src/Umbraco.Core/Routing/DomainUtilities.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
@@ -51,8 +51,8 @@ namespace Umbraco.Web.Routing
var rootContentId = domain?.ContentId ?? -1;
var wcDomain = FindWildcardDomainInPath(umbracoContext.Domains.GetAll(true), contentPath, rootContentId);
- if (wcDomain != null) return wcDomain.Culture.Name;
- if (domain != null) return domain.Culture.Name;
+ if (wcDomain != null) return wcDomain.Culture;
+ if (domain != null) return domain.Culture;
return umbracoContext.Domains.DefaultCulture;
}
@@ -233,13 +233,13 @@ namespace Umbraco.Web.Routing
if (culture != null) // try the supplied culture
{
- var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(culture)).ToList();
+ var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList();
if (cultureDomains.Count > 0) return cultureDomains;
}
if (defaultCulture != null) // try the defaultCulture culture
{
- var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(defaultCulture)).ToList();
+ var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList();
if (cultureDomains.Count > 0) return cultureDomains;
}
@@ -254,13 +254,13 @@ namespace Umbraco.Web.Routing
if (culture != null) // try the supplied culture
{
- domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture));
+ domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture));
if (domainAndUri != null) return domainAndUri;
}
if (defaultCulture != null) // try the defaultCulture culture
{
- domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(defaultCulture));
+ domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture));
if (domainAndUri != null) return domainAndUri;
}
@@ -364,9 +364,7 @@ namespace Umbraco.Web.Routing
/// The path part relative to the uri of the domain.
/// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil.
public static string PathRelativeToDomain(Uri domainUri, string path)
- {
- return path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/');
- }
+ => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/');
#endregion
}
diff --git a/src/Umbraco.Core/Routing/IContentFinder.cs b/src/Umbraco.Core/Routing/IContentFinder.cs
index 39d31741a6..57575b3cf0 100644
--- a/src/Umbraco.Core/Routing/IContentFinder.cs
+++ b/src/Umbraco.Core/Routing/IContentFinder.cs
@@ -11,6 +11,6 @@ namespace Umbraco.Web.Routing
/// The PublishedRequest.
/// A value indicating whether an Umbraco document was found and assigned.
/// Optionally, can also assign the template or anything else on the document request, although that is not required.
- bool TryFindContent(IPublishedRequest request);
+ bool TryFindContent(IPublishedRequestBuilder request);
}
}
diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs
index f357108a4e..58523d12e4 100644
--- a/src/Umbraco.Core/Routing/IPublishedRequest.cs
+++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs
@@ -6,219 +6,95 @@ using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Routing
{
+ ///
+ /// The result of Umbraco routing built with the
+ ///
public interface IPublishedRequest
{
///
- /// Gets the UmbracoContext.
- ///
- IUmbracoContext UmbracoContext { get; }
-
- ///
- /// Gets or sets the cleaned up Uri used for routing.
+ /// Gets the cleaned up inbound Uri used for routing.
///
/// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc.
- Uri Uri { get; set; }
+ Uri Uri { get; }
///
- /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
+ /// Gets the URI decoded absolute path of the
///
- bool IgnorePublishedContentCollisions { get; set; }
+ string AbsolutePathDecoded { get; }
///
- /// Gets or sets the requested content.
+ /// Gets a value indicating the requested content.
///
- /// Setting the requested content clears Template.
- IPublishedContent PublishedContent { get; set; }
+ IPublishedContent PublishedContent { get; }
///
- /// Gets the initial requested content.
- ///
- /// The initial requested content is the content that was found by the finders,
- /// before anything such as 404, redirect... took place.
- IPublishedContent InitialPublishedContent { get; }
-
- ///
- /// Gets value indicating whether the current published content is the initial one.
- ///
- bool IsInitialPublishedContent { get; }
-
- ///
- /// Gets or sets a value indicating whether the current published content has been obtained
+ /// Gets a value indicating whether the current published content has been obtained
/// from the initial published content following internal redirections exclusively.
///
/// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to
/// apply the internal redirect or not, when content is not the initial content.
- bool IsInternalRedirectPublishedContent { get; }
+ bool IsInternalRedirect { get; }
///
- /// Gets a value indicating whether the content request has a content.
+ /// Gets the template assigned to the request (if any)
///
- bool HasPublishedContent { get; }
-
- ITemplate TemplateModel { get; set; }
+ ITemplate Template { get; }
///
- /// Gets the alias of the template to use to display the requested content.
- ///
- string TemplateAlias { get; }
-
- ///
- /// Gets a value indicating whether the content request has a template.
- ///
- bool HasTemplate { get; }
-
- void UpdateToNotFound();
-
- ///
- /// Gets or sets the content request's domain.
+ /// Gets the content request's domain.
///
/// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example,
/// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/".
- DomainAndUri Domain { get; set; }
+ DomainAndUri Domain { get; }
///
- /// Gets a value indicating whether the content request has a domain.
+ /// Gets the content request's culture.
///
- bool HasDomain { get; }
+ ///
+ /// This will get mapped to a CultureInfo eventually but CultureInfo are expensive to create so we want to leave that up to the
+ /// localization middleware to do. See https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165.
+ ///
+ string Culture { get; }
///
- /// Gets or sets the content request's culture.
- ///
- CultureInfo Culture { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the requested content could not be found.
- ///
- /// This is set in the PublishedContentRequestBuilder and can also be used in
- /// custom content finders or Prepared event handlers, where we want to allow developers
- /// to indicate a request is 404 but not to cancel it.
- bool Is404 { get; set; }
-
- ///
- /// Gets a value indicating whether the content request triggers a redirect (permanent or not).
- ///
- bool IsRedirect { get; }
-
- ///
- /// Gets or sets a value indicating whether the redirect is permanent.
- ///
- bool IsRedirectPermanent { get; }
-
- ///
- /// Gets or sets the url to redirect to, when the content request triggers a redirect.
+ /// Gets the url to redirect to, when the content request triggers a redirect.
///
string RedirectUrl { get; }
///
- /// Gets or sets the content request http response status code.
+ /// Gets the content request http response status code.
///
/// Does not actually set the http response status code, only registers that the response
/// should use the specified code. The code will or will not be used, in due time.
- int ResponseStatusCode { get; }
+ int? ResponseStatusCode { get; }
///
- /// Gets or sets the content request http response status description.
+ /// Gets a list of Extensions to append to the Response.Cache object.
///
- /// Does not actually set the http response status description, only registers that the response
- /// should use the specified description. The description will or will not be used, in due time.
- string ResponseStatusDescription { get; }
+ IReadOnlyList CacheExtensions { get; }
///
- /// Gets or sets a list of Extensions to append to the Response.Cache object.
+ /// Gets a dictionary of Headers to append to the Response object.
///
- List CacheExtensions { get; set; }
+ IReadOnlyDictionary Headers { get; }
///
- /// Gets or sets a dictionary of Headers to append to the Response object.
+ /// Gets a value indicating whether the no-cache value should be added to the Cache-Control header
///
- Dictionary Headers { get; set; }
-
- bool CacheabilityNoCache { get; set; }
+ bool SetNoCacheHeader { get; }
///
- /// Prepares the request.
+ /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
///
- void Prepare();
-
- ///
- /// Triggers the Preparing event.
- ///
- void OnPreparing();
-
- ///
- /// Triggers the Prepared event.
- ///
- void OnPrepared();
-
- ///
- /// Sets the requested content, following an internal redirect.
- ///
- /// The requested content.
- /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will
- /// preserve or reset the template, if any.
- void SetInternalRedirectPublishedContent(IPublishedContent content);
-
- ///
- /// Indicates that the current PublishedContent is the initial one.
- ///
- void SetIsInitialPublishedContent();
-
- ///
- /// Tries to set the template to use to display the requested content.
- ///
- /// The alias of the template.
- /// A value indicating whether a valid template with the specified alias was found.
///
- /// Successfully setting the template does refresh RenderingEngine.
- /// If setting the template fails, then the previous template (if any) remains in place.
+ /// This is an uncommon API used for edge cases with complex routing and would be used
+ /// by developers to configure the request to disable collision checks in .
+ /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since
+ /// collission checking only occurs in the back office which is launched by
+ /// for which events do not execute.
+ /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345
+ /// but it's still unclear how this was used.
///
- bool TrySetTemplate(string alias);
-
- ///
- /// Sets the template to use to display the requested content.
- ///
- /// The template.
- /// Setting the template does refresh RenderingEngine.
- void SetTemplate(ITemplate template);
-
- ///
- /// Resets the template.
- ///
- void ResetTemplate();
-
- ///
- /// Indicates that the content request should trigger a redirect (302).
- ///
- /// The url to redirect to.
- /// Does not actually perform a redirect, only registers that the response should
- /// redirect. Redirect will or will not take place in due time.
- void SetRedirect(string url);
-
- ///
- /// Indicates that the content request should trigger a permanent redirect (301).
- ///
- /// The url to redirect to.
- /// Does not actually perform a redirect, only registers that the response should
- /// redirect. Redirect will or will not take place in due time.
- void SetRedirectPermanent(string url);
-
- ///
- /// Indicates that the content request should trigger a redirect, with a specified status code.
- ///
- /// The url to redirect to.
- /// The status code (300-308).
- /// Does not actually perform a redirect, only registers that the response should
- /// redirect. Redirect will or will not take place in due time.
- void SetRedirect(string url, int status);
-
- ///
- /// Sets the http response status code, along with an optional associated description.
- ///
- /// The http status code.
- /// The description.
- /// Does not actually set the http response status code and description, only registers that
- /// the response should use the specified code and description. The code and description will or will
- /// not be used, in due time.
- void SetResponseStatus(int code, string description = null);
+ bool IgnorePublishedContentCollisions { get; }
}
}
diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs
new file mode 100644
index 0000000000..bd5b5625a3
--- /dev/null
+++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Used by to route inbound requests to Umbraco content
+ ///
+ public interface IPublishedRequestBuilder
+ {
+ ///
+ /// Gets the cleaned up inbound Uri used for routing.
+ ///
+ /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc.
+ Uri Uri { get; }
+
+ ///
+ /// Gets the URI decoded absolute path of the
+ ///
+ string AbsolutePathDecoded { get; }
+
+ ///
+ /// Gets the assigned (if any)
+ ///
+ DomainAndUri Domain { get; }
+
+ ///
+ /// Gets the assigned (if any)
+ ///
+ string Culture { get; }
+
+ ///
+ /// Gets a value indicating whether the current published content has been obtained
+ /// from the initial published content following internal redirections exclusively.
+ ///
+ /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to
+ /// apply the internal redirect or not, when content is not the initial content.
+ bool IsInternalRedirect { get; }
+
+ ///
+ /// Gets the content request http response status code.
+ ///
+ int? ResponseStatusCode { get; }
+
+ ///
+ /// Gets the current assigned (if any)
+ ///
+ IPublishedContent PublishedContent { get; }
+
+ ///
+ /// Gets the template assigned to the request (if any)
+ ///
+ ITemplate Template { get; }
+
+ ///
+ /// Builds the
+ ///
+ IPublishedRequest Build();
+
+ ///
+ /// Sets the domain for the request which also sets the culture
+ ///
+ IPublishedRequestBuilder SetDomain(DomainAndUri domain);
+
+ ///
+ /// Sets the culture for the request
+ ///
+ IPublishedRequestBuilder SetCulture(string culture);
+
+ ///
+ /// Sets the found for the request
+ ///
+ /// Setting the content clears the template and redirect
+ IPublishedRequestBuilder SetPublishedContent(IPublishedContent content);
+
+ ///
+ /// Sets the requested content, following an internal redirect.
+ ///
+ /// The requested content.
+ /// Since this sets the content, it will clear the template
+ IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content);
+
+ ///
+ /// Tries to set the template to use to display the requested content.
+ ///
+ /// The alias of the template.
+ /// A value indicating whether a valid template with the specified alias was found.
+ ///
+ /// Successfully setting the template does refresh RenderingEngine.
+ /// If setting the template fails, then the previous template (if any) remains in place.
+ ///
+ bool TrySetTemplate(string alias);
+
+ ///
+ /// Sets the template to use to display the requested content.
+ ///
+ /// The template.
+ /// Setting the template does refresh RenderingEngine.
+ IPublishedRequestBuilder SetTemplate(ITemplate template);
+
+ ///
+ /// Indicates that the content request should trigger a permanent redirect (301).
+ ///
+ /// The url to redirect to.
+ /// Does not actually perform a redirect, only registers that the response should
+ /// redirect. Redirect will or will not take place in due time.
+ IPublishedRequestBuilder SetRedirectPermanent(string url);
+
+ ///
+ /// Indicates that the content request should trigger a redirect, with a specified status code.
+ ///
+ /// The url to redirect to.
+ /// The status code (300-308).
+ /// Does not actually perform a redirect, only registers that the response should
+ /// redirect. Redirect will or will not take place in due time.
+ IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect);
+
+ ///
+ /// Sets the http response status code, along with an optional associated description.
+ ///
+ /// The http status code.
+ /// Does not actually set the http response status code and description, only registers that
+ /// the response should use the specified code and description. The code and description will or will
+ /// not be used, in due time.
+ IPublishedRequestBuilder SetResponseStatus(int code);
+
+ ///
+ /// Sets the no-cache value to the Cache-Control header
+ ///
+ /// True to set the header, false to not set it
+ IPublishedRequestBuilder SetNoCacheHeader(bool setHeader);
+
+ ///
+ /// Sets a list of Extensions to append to the Response.Cache object.
+ ///
+ IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions);
+
+ ///
+ /// Sets a dictionary of Headers to append to the Response object.
+ ///
+ IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers);
+
+ ///
+ /// Can be called to configure the result to ignore URL collisions
+ ///
+ ///
+ /// This is an uncommon API used for edge cases with complex routing and would be used
+ /// by developers to configure the request to disable collision checks in .
+ /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since
+ /// collission checking only occurs in the back office which is launched by
+ /// for which events do not execute.
+ /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345
+ /// but it's still unclear how this was used.
+ ///
+ void IgnorePublishedContentCollisions();
+ }
+}
diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs
index db9d69df20..b4c35c0e4d 100644
--- a/src/Umbraco.Core/Routing/IPublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs
@@ -1,6 +1,5 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Core.Models;
+using System;
+using System.Threading.Tasks;
namespace Umbraco.Web.Routing
{
@@ -9,46 +8,34 @@ namespace Umbraco.Web.Routing
///
public interface IPublishedRouter
{
- // TODO: consider this and RenderRouteHandler - move some code around?
-
///
/// Creates a published request.
///
- /// The current Umbraco context.
- /// The (optional) request Uri.
- /// A published request.
- IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null);
+ /// The current request Uri.
+ /// A published request builder.
+ Task CreateRequestAsync(Uri uri);
///
- /// Prepares a request for rendering.
+ /// Sends a through the routing pipeline and builds a result.
///
/// The request.
- /// A value indicating whether the request was successfully prepared and can be rendered.
- bool PrepareRequest(IPublishedRequest request);
-
- ///
- /// Tries to route a request.
- ///
- /// The request.
- /// A value indicating whether the request can be routed to a document.
- bool TryRouteRequest(IPublishedRequest request);
-
- ///
- /// Gets a template.
- ///
- /// The template alias
- /// The template.
- ITemplate GetTemplate(string alias);
+ /// The options.
+ /// The built instance.
+ Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options);
///
/// Updates the request to "not found".
///
/// The request.
///
+ /// A new based on values from the original
/// This method is invoked when the pipeline decides it cannot render
/// the request, for whatever reason, and wants to force it to be re-routed
/// and rendered as if no document were found (404).
+ /// This occurs if there is no template found and route hijacking was not matched.
+ /// In that case it's the same as if there was no content which means even if there was
+ /// content matched we want to run the request through the last chance finders.
///
- void UpdateRequestToNotFound(IPublishedRequest request);
+ IPublishedRequestBuilder UpdateRequestToNotFound(IPublishedRequest request);
}
}
diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs
index bab60e49f6..7a3d44149d 100644
--- a/src/Umbraco.Core/Routing/PublishedRequest.cs
+++ b/src/Umbraco.Core/Routing/PublishedRequest.cs
@@ -1,481 +1,70 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.Threading;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Core.Configuration.UmbracoSettings;
-using Umbraco.Core.Configuration.Models;
-using Microsoft.Extensions.Options;
namespace Umbraco.Web.Routing
{
- ///
- /// Represents a request for one specified Umbraco IPublishedContent to be rendered
- /// by one specified template, using one specified Culture and RenderingEngine.
- ///
public class PublishedRequest : IPublishedRequest
{
- private readonly IPublishedRouter _publishedRouter;
- private readonly WebRoutingSettings _webRoutingSettings;
-
- private bool _readonly; // after prepared
- private bool _readonlyUri; // after preparing
- private Uri _uri; // clean uri, no virtual dir, no trailing slash nor .aspx, nothing
- private bool _is404;
- private DomainAndUri _domain;
- private CultureInfo _culture;
- private IPublishedContent _publishedContent;
- private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc
-
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The published router.
- /// The Umbraco context.
- /// The request Uri.
- public PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null)
+ public PublishedRequest(Uri uri, string absolutePathDecoded, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions)
{
- UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
- _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter));
- _webRoutingSettings = webRoutingSettings.Value;
- Uri = uri ?? umbracoContext.CleanedUmbracoUrl;
+ Uri = uri ?? throw new ArgumentNullException(nameof(uri));
+ AbsolutePathDecoded = absolutePathDecoded ?? throw new ArgumentNullException(nameof(absolutePathDecoded));
+ PublishedContent = publishedContent;
+ IsInternalRedirect = isInternalRedirect;
+ Template = template;
+ Domain = domain;
+ Culture = culture;
+ RedirectUrl = redirectUrl;
+ ResponseStatusCode = responseStatusCode;
+ CacheExtensions = cacheExtensions;
+ Headers = headers;
+ SetNoCacheHeader = setNoCacheHeader;
+ IgnorePublishedContentCollisions = ignorePublishedContentCollisions;
}
- ///
- /// Gets the UmbracoContext.
- ///
- public IUmbracoContext UmbracoContext { get; }
+ ///
+ public Uri Uri { get; }
- ///
- /// Gets or sets the cleaned up Uri used for routing.
- ///
- /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc.
- public Uri Uri
- {
- get => _uri;
- set
- {
- if (_readonlyUri)
- throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered.");
- _uri = value;
- }
- }
+ ///
+ public string AbsolutePathDecoded { get; }
- // utility for ensuring it is ok to set some properties
- public void EnsureWriteable()
- {
- if (_readonly)
- throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only.");
- }
+ ///
+ public bool IgnorePublishedContentCollisions { get; }
- public bool CacheabilityNoCache { get; set; }
+ ///
+ public IPublishedContent PublishedContent { get; }
- ///
- /// Prepares the request.
- ///
- public void Prepare()
- {
- _publishedRouter.PrepareRequest(this);
- }
+ ///
+ public bool IsInternalRedirect { get; }
- ///
- /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
- ///
- public bool IgnorePublishedContentCollisions { get; set; }
+ ///
+ public ITemplate Template { get; }
- #region Events
+ ///
+ public DomainAndUri Domain { get; }
- ///
- /// Triggers before the published content request is prepared.
- ///
- /// When the event triggers, no preparation has been done. It is still possible to
- /// modify the request's Uri property, for example to restore its original, public-facing value
- /// that might have been modified by an in-between equipment such as a load-balancer.
- public static event EventHandler Preparing;
+ ///
+ public string Culture { get; }
- ///
- /// Triggers once the published content request has been prepared, but before it is processed.
- ///
- /// When the event triggers, preparation is done ie domain, culture, document, template,
- /// rendering engine, etc. have been setup. It is then possible to change anything, before
- /// the request is actually processed and rendered by Umbraco.
- public static event EventHandler Prepared;
+ ///
+ public string RedirectUrl { get; }
- ///
- /// Triggers the Preparing event.
- ///
- public void OnPreparing()
- {
- Preparing?.Invoke(this, EventArgs.Empty);
- _readonlyUri = true;
- }
+ ///
+ public int? ResponseStatusCode { get; }
- ///
- /// Triggers the Prepared event.
- ///
- public void OnPrepared()
- {
- Prepared?.Invoke(this, EventArgs.Empty);
+ ///
+ public IReadOnlyList CacheExtensions { get; }
- if (HasPublishedContent == false)
- Is404 = true; // safety
+ ///
+ public IReadOnlyDictionary Headers { get; }
- _readonly = true;
- }
-
- #endregion
-
- #region PublishedContent
-
- ///
- /// Gets or sets the requested content.
- ///
- /// Setting the requested content clears Template.
- public IPublishedContent PublishedContent
- {
- get { return _publishedContent; }
- set
- {
- EnsureWriteable();
- _publishedContent = value;
- IsInternalRedirectPublishedContent = false;
- TemplateModel = null;
- }
- }
-
- ///
- /// Sets the requested content, following an internal redirect.
- ///
- /// The requested content.
- /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will
- /// preserve or reset the template, if any.
- public void SetInternalRedirectPublishedContent(IPublishedContent content)
- {
- if (content == null) throw new ArgumentNullException(nameof(content));
- EnsureWriteable();
-
- // unless a template has been set already by the finder,
- // template should be null at that point.
-
- // IsInternalRedirect if IsInitial, or already IsInternalRedirect
- var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent;
-
- // redirecting to self
- if (content.Id == PublishedContent.Id) // neither can be null
- {
- // no need to set PublishedContent, we're done
- IsInternalRedirectPublishedContent = isInternalRedirect;
- return;
- }
-
- // else
-
- // save
- var template = TemplateModel;
-
- // set published content - this resets the template, and sets IsInternalRedirect to false
- PublishedContent = content;
- IsInternalRedirectPublishedContent = isInternalRedirect;
-
- // must restore the template if it's an internal redirect & the config option is set
- if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate)
- {
- // restore
- TemplateModel = template;
- }
- }
-
- ///
- /// Gets the initial requested content.
- ///
- /// The initial requested content is the content that was found by the finders,
- /// before anything such as 404, redirect... took place.
- public IPublishedContent InitialPublishedContent => _initialPublishedContent;
-
- ///
- /// Gets value indicating whether the current published content is the initial one.
- ///
- public bool IsInitialPublishedContent => _initialPublishedContent != null && _initialPublishedContent == _publishedContent;
-
- ///
- /// Indicates that the current PublishedContent is the initial one.
- ///
- public void SetIsInitialPublishedContent()
- {
- EnsureWriteable();
-
- // note: it can very well be null if the initial content was not found
- _initialPublishedContent = _publishedContent;
- IsInternalRedirectPublishedContent = false;
- }
-
- ///
- /// Gets or sets a value indicating whether the current published content has been obtained
- /// from the initial published content following internal redirections exclusively.
- ///
- /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to
- /// apply the internal redirect or not, when content is not the initial content.
- public bool IsInternalRedirectPublishedContent { get; private set; }
-
- ///
- /// Gets a value indicating whether the content request has a content.
- ///
- public bool HasPublishedContent => PublishedContent != null;
-
- #endregion
-
- #region Template
-
- ///
- /// Gets or sets the template model to use to display the requested content.
- ///
- public ITemplate TemplateModel { get; set; }
-
- ///
- /// Gets the alias of the template to use to display the requested content.
- ///
- public string TemplateAlias => TemplateModel?.Alias;
-
- ///
- /// Tries to set the template to use to display the requested content.
- ///
- /// The alias of the template.
- /// A value indicating whether a valid template with the specified alias was found.
- ///
- /// Successfully setting the template does refresh RenderingEngine.
- /// If setting the template fails, then the previous template (if any) remains in place.
- ///
- public bool TrySetTemplate(string alias)
- {
- EnsureWriteable();
-
- if (string.IsNullOrWhiteSpace(alias))
- {
- TemplateModel = null;
- return true;
- }
-
- // NOTE - can we still get it with whitespaces in it due to old legacy bugs?
- alias = alias.Replace(" ", "");
-
- var model = _publishedRouter.GetTemplate(alias);
- if (model == null)
- return false;
-
- TemplateModel = model;
- return true;
- }
-
- ///
- /// Sets the template to use to display the requested content.
- ///
- /// The template.
- /// Setting the template does refresh RenderingEngine.
- public void SetTemplate(ITemplate template)
- {
- EnsureWriteable();
- TemplateModel = template;
- }
-
- ///
- /// Resets the template.
- ///
- public void ResetTemplate()
- {
- EnsureWriteable();
- TemplateModel = null;
- }
-
- ///
- /// Gets a value indicating whether the content request has a template.
- ///
- public bool HasTemplate => TemplateModel != null;
-
- public void UpdateToNotFound()
- {
- var __readonly = _readonly;
- _readonly = false;
- _publishedRouter.UpdateRequestToNotFound(this);
- _readonly = __readonly;
- }
-
- #endregion
-
- #region Domain and Culture
-
- ///
- /// Gets or sets the content request's domain.
- ///
- /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example,
- /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/".
- public DomainAndUri Domain
- {
- get { return _domain; }
- set
- {
- EnsureWriteable();
- _domain = value;
- }
- }
-
- ///
- /// Gets a value indicating whether the content request has a domain.
- ///
- public bool HasDomain => Domain != null;
-
- ///
- /// Gets or sets the content request's culture.
- ///
- public CultureInfo Culture
- {
- get { return _culture ?? Thread.CurrentThread.CurrentCulture; }
- set
- {
- EnsureWriteable();
- _culture = value;
- }
- }
-
- // note: do we want to have an ordered list of alternate cultures,
- // to allow for fallbacks when doing dictionary lookup and such?
-
- #endregion
-
- #region Status
-
- ///
- /// Gets or sets a value indicating whether the requested content could not be found.
- ///
- /// This is set in the PublishedContentRequestBuilder and can also be used in
- /// custom content finders or Prepared event handlers, where we want to allow developers
- /// to indicate a request is 404 but not to cancel it.
- public bool Is404
- {
- get { return _is404; }
- set
- {
- EnsureWriteable();
- _is404 = value;
- }
- }
-
- ///
- /// Gets a value indicating whether the content request triggers a redirect (permanent or not).
- ///
- public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false;
-
- ///
- /// Gets or sets a value indicating whether the redirect is permanent.
- ///
- public bool IsRedirectPermanent { get; private set; }
-
- ///
- /// Gets or sets the URL to redirect to, when the content request triggers a redirect.
- ///
- public string RedirectUrl { get; private set; }
-
- ///
- /// Indicates that the content request should trigger a redirect (302).
- ///
- /// The URL to redirect to.
- /// Does not actually perform a redirect, only registers that the response should
- /// redirect. Redirect will or will not take place in due time.
- public void SetRedirect(string url)
- {
- EnsureWriteable();
- RedirectUrl = url;
- IsRedirectPermanent = false;
- }
-
- ///
- /// Indicates that the content request should trigger a permanent redirect (301).
- ///
- /// The URL to redirect to.
- /// Does not actually perform a redirect, only registers that the response should
- /// redirect. Redirect will or will not take place in due time.
- public void SetRedirectPermanent(string url)
- {
- EnsureWriteable();
- RedirectUrl = url;
- IsRedirectPermanent = true;
- }
-
- ///
- /// Indicates that the content request should trigger a redirect, with a specified status code.
- ///
- /// The URL to redirect to.
- /// The status code (300-308).
- /// Does not actually perform a redirect, only registers that the response should
- /// redirect. Redirect will or will not take place in due time.
- public void SetRedirect(string url, int status)
- {
- EnsureWriteable();
-
- if (status < 300 || status > 308)
- throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308.");
-
- RedirectUrl = url;
- IsRedirectPermanent = (status == 301 || status == 308);
- if (status != 301 && status != 302) // default redirect statuses
- ResponseStatusCode = status;
- }
-
- ///
- /// Gets or sets the content request http response status code.
- ///
- /// Does not actually set the http response status code, only registers that the response
- /// should use the specified code. The code will or will not be used, in due time.
- public int ResponseStatusCode { get; private set; }
-
- ///
- /// Gets or sets the content request http response status description.
- ///
- /// Does not actually set the http response status description, only registers that the response
- /// should use the specified description. The description will or will not be used, in due time.
- public string ResponseStatusDescription { get; private set; }
-
- ///
- /// Sets the http response status code, along with an optional associated description.
- ///
- /// The http status code.
- /// The description.
- /// Does not actually set the http response status code and description, only registers that
- /// the response should use the specified code and description. The code and description will or will
- /// not be used, in due time.
- public void SetResponseStatus(int code, string description = null)
- {
- EnsureWriteable();
-
- // .Status is deprecated
- // .SubStatusCode is IIS 7+ internal, ignore
- ResponseStatusCode = code;
- ResponseStatusDescription = description;
- }
-
- #endregion
-
- #region Response Cache
-
- ///
- /// Gets or sets the System.Web.HttpCacheability
- ///
- // Note: we used to set a default value here but that would then be the default
- // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example
- // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752
- //public HttpCacheability Cacheability { get; set; }
-
- ///
- /// Gets or sets a list of Extensions to append to the Response.Cache object.
- ///
- public List CacheExtensions { get; set; } = new List();
-
- ///
- /// Gets or sets a dictionary of Headers to append to the Response object.
- ///
- public Dictionary Headers { get; set; } = new Dictionary();
-
- #endregion
+ ///
+ public bool SetNoCacheHeader { get; }
}
}
diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs
new file mode 100644
index 0000000000..606031564b
--- /dev/null
+++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Web.Routing
+{
+ public class PublishedRequestBuilder : IPublishedRequestBuilder
+ {
+ private readonly IFileService _fileService;
+ private IReadOnlyDictionary _headers;
+ private bool _cacheability;
+ private IReadOnlyList _cacheExtensions;
+ private string _redirectUrl;
+ private HttpStatusCode? _responseStatus;
+ private IPublishedContent _publishedContent;
+ private bool _ignorePublishedContentCollisions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PublishedRequestBuilder(Uri uri, IFileService fileService)
+ {
+ Uri = uri;
+ AbsolutePathDecoded = uri.GetAbsolutePathDecoded();
+ _fileService = fileService;
+ }
+
+ ///
+ public Uri Uri { get; }
+
+ ///
+ public string AbsolutePathDecoded { get; }
+
+ ///
+ public DomainAndUri Domain { get; private set; }
+
+ ///
+ public string Culture { get; private set; }
+
+ ///
+ public ITemplate Template { get; private set; }
+
+ ///
+ public bool IsInternalRedirect { get; private set; }
+
+ ///
+ public int? ResponseStatusCode => _responseStatus.HasValue ? (int?)_responseStatus : null;
+
+ ///
+ public IPublishedContent PublishedContent
+ {
+ get => _publishedContent;
+ private set
+ {
+ _publishedContent = value;
+ IsInternalRedirect = false;
+ Template = null;
+ }
+ }
+
+ ///
+ public IPublishedRequest Build() => new PublishedRequest(
+ Uri,
+ AbsolutePathDecoded,
+ PublishedContent,
+ IsInternalRedirect,
+ Template,
+ Domain,
+ Culture,
+ _redirectUrl,
+ _responseStatus.HasValue ? (int?)_responseStatus : null,
+ _cacheExtensions,
+ _headers,
+ _cacheability,
+ _ignorePublishedContentCollisions);
+
+ ///
+ public IPublishedRequestBuilder SetNoCacheHeader(bool cacheability)
+ {
+ _cacheability = cacheability;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions)
+ {
+ _cacheExtensions = cacheExtensions.ToList();
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetCulture(string culture)
+ {
+ Culture = culture;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetDomain(DomainAndUri domain)
+ {
+ Domain = domain;
+ SetCulture(domain.Culture);
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers)
+ {
+ _headers = headers;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content)
+ {
+ // unless a template has been set already by the finder,
+ // template should be null at that point.
+
+ // redirecting to self
+ if (PublishedContent != null && content.Id == PublishedContent.Id)
+ {
+ // no need to set PublishedContent, we're done
+ IsInternalRedirect = true;
+ return this;
+ }
+
+ // else
+
+ // set published content - this resets the template, and sets IsInternalRedirect to false
+ PublishedContent = content;
+ IsInternalRedirect = true;
+
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content)
+ {
+ PublishedContent = content;
+ IsInternalRedirect = false;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect)
+ {
+ _redirectUrl = url;
+ _responseStatus = (HttpStatusCode)status;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetRedirectPermanent(string url)
+ {
+ _redirectUrl = url;
+ _responseStatus = HttpStatusCode.Moved;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetResponseStatus(int code)
+ {
+ _responseStatus = (HttpStatusCode)code;
+ return this;
+ }
+
+ ///
+ public IPublishedRequestBuilder SetTemplate(ITemplate template)
+ {
+ Template = template;
+ return this;
+ }
+
+ ///
+ public bool TrySetTemplate(string alias)
+ {
+ if (string.IsNullOrWhiteSpace(alias))
+ {
+ Template = null;
+ return true;
+ }
+
+ // NOTE - can we still get it with whitespaces in it due to old legacy bugs?
+ alias = alias.Replace(" ", string.Empty);
+
+ ITemplate model = _fileService.GetTemplate(alias);
+ if (model == null)
+ {
+ return false;
+ }
+
+ Template = model;
+ return true;
+ }
+
+ ///
+ public void IgnorePublishedContentCollisions() => _ignorePublishedContentCollisions = true;
+ }
+}
diff --git a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs
new file mode 100644
index 0000000000..2a4e4323f0
--- /dev/null
+++ b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs
@@ -0,0 +1,97 @@
+using System.Net;
+
+namespace Umbraco.Web.Routing
+{
+
+ public static class PublishedRequestExtensions
+ {
+ ///
+ /// Gets the
+ ///
+ public static UmbracoRouteResult GetRouteResult(this IPublishedRequest publishedRequest)
+ {
+ if (publishedRequest.IsRedirect())
+ {
+ return UmbracoRouteResult.Redirect;
+ }
+
+ if (!publishedRequest.HasPublishedContent())
+ {
+ return UmbracoRouteResult.NotFound;
+ }
+
+ return UmbracoRouteResult.Success;
+ }
+
+ ///
+ /// Gets a value indicating whether the request was successfully routed
+ ///
+ public static bool Success(this IPublishedRequest publishedRequest)
+ => !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent();
+
+ ///
+ /// Sets the response status to be 404 not found
+ ///
+ public static IPublishedRequestBuilder SetIs404(this IPublishedRequestBuilder publishedRequest)
+ {
+ publishedRequest.SetResponseStatus((int)HttpStatusCode.NotFound);
+ return publishedRequest;
+ }
+
+ ///
+ /// Gets a value indicating whether the content request has a content.
+ ///
+ public static bool HasPublishedContent(this IPublishedRequestBuilder publishedRequest) => publishedRequest.PublishedContent != null;
+
+ ///
+ /// Gets a value indicating whether the content request has a content.
+ ///
+ public static bool HasPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.PublishedContent != null;
+
+ ///
+ /// Gets a value indicating whether the content request has a template.
+ ///
+ public static bool HasTemplate(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Template != null;
+
+ ///
+ /// Gets a value indicating whether the content request has a template.
+ ///
+ public static bool HasTemplate(this IPublishedRequest publishedRequest) => publishedRequest.Template != null;
+
+ ///
+ /// Gets the alias of the template to use to display the requested content.
+ ///
+ public static string GetTemplateAlias(this IPublishedRequest publishedRequest) => publishedRequest.Template?.Alias;
+
+ ///
+ /// Gets a value indicating whether the requested content could not be found.
+ ///
+ public static bool Is404(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.NotFound;
+
+ ///
+ /// Gets a value indicating whether the content request triggers a redirect (permanent or not).
+ ///
+ public static bool IsRedirect(this IPublishedRequestBuilder publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved;
+
+ ///
+ /// Gets indicating whether the content request triggers a redirect (permanent or not).
+ ///
+ public static bool IsRedirect(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved;
+
+ ///
+ /// Gets a value indicating whether the redirect is permanent.
+ ///
+ public static bool IsRedirectPermanent(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved;
+
+ ///
+ /// Gets a value indicating whether the content request has a domain.
+ ///
+ public static bool HasDomain(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Domain != null;
+
+ ///
+ /// Gets a value indicating whether the content request has a domain.
+ ///
+ public static bool HasDomain(this IPublishedRequest publishedRequest) => publishedRequest.Domain != null;
+
+ }
+}
diff --git a/src/Umbraco.Core/Routing/PublishedRequestOld.cs b/src/Umbraco.Core/Routing/PublishedRequestOld.cs
new file mode 100644
index 0000000000..c851d4ebb6
--- /dev/null
+++ b/src/Umbraco.Core/Routing/PublishedRequestOld.cs
@@ -0,0 +1,391 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading;
+using Microsoft.Extensions.Options;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.PublishedContent;
+
+namespace Umbraco.Web.Routing
+{
+ // TODO: Kill this, but we need to port all of it's functionality
+ public class PublishedRequestOld // : IPublishedRequest
+ {
+ private readonly IPublishedRouter _publishedRouter;
+ private readonly WebRoutingSettings _webRoutingSettings;
+
+ private bool _readonly; // after prepared
+ private bool _is404;
+ private DomainAndUri _domain;
+ private CultureInfo _culture;
+ private IPublishedContent _publishedContent;
+ private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null)
+ {
+ UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext));
+ _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter));
+ _webRoutingSettings = webRoutingSettings.Value;
+ Uri = uri ?? umbracoContext.CleanedUmbracoUrl;
+ }
+
+ ///
+ /// Gets the UmbracoContext.
+ ///
+ public IUmbracoContext UmbracoContext { get; }
+
+ ///
+ /// Gets or sets the cleaned up Uri used for routing.
+ ///
+ /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc.
+ public Uri Uri { get; }
+
+ // utility for ensuring it is ok to set some properties
+ public void EnsureWriteable()
+ {
+ if (_readonly)
+ {
+ throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only.");
+ }
+ }
+
+ public bool CacheabilityNoCache { get; set; }
+
+ /////
+ ///// Prepares the request.
+ /////
+ //public void Prepare()
+ //{
+ // _publishedRouter.PrepareRequest(this);
+ //}
+
+ ///
+ /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request.
+ ///
+ public bool IgnorePublishedContentCollisions { get; set; }
+
+ //#region Events
+
+ /////
+ ///// Triggers before the published content request is prepared.
+ /////
+ ///// When the event triggers, no preparation has been done. It is still possible to
+ ///// modify the request's Uri property, for example to restore its original, public-facing value
+ ///// that might have been modified by an in-between equipment such as a load-balancer.
+ //public static event EventHandler Preparing;
+
+ /////
+ ///// Triggers once the published content request has been prepared, but before it is processed.
+ /////
+ ///// When the event triggers, preparation is done ie domain, culture, document, template,
+ ///// rendering engine, etc. have been setup. It is then possible to change anything, before
+ ///// the request is actually processed and rendered by Umbraco.
+ //public static event EventHandler Prepared;
+
+ /////
+ ///// Triggers the Preparing event.
+ /////
+ //public void OnPreparing()
+ //{
+ // Preparing?.Invoke(this, EventArgs.Empty);
+ //}
+
+ /////
+ ///// Triggers the Prepared event.
+ /////
+ //public void OnPrepared()
+ //{
+ // Prepared?.Invoke(this, EventArgs.Empty);
+
+ // if (HasPublishedContent == false)
+ // Is404 = true; // safety
+
+ // _readonly = true;
+ //}
+
+ //#endregion
+
+ #region PublishedContent
+
+ /////
+ ///// Gets or sets the requested content.
+ /////
+ ///// Setting the requested content clears Template.
+ //public IPublishedContent PublishedContent
+ //{
+ // get { return _publishedContent; }
+ // set
+ // {
+ // EnsureWriteable();
+ // _publishedContent = value;
+ // IsInternalRedirectPublishedContent = false;
+ // TemplateModel = null;
+ // }
+ //}
+
+ ///
+ /// Sets the requested content, following an internal redirect.
+ ///
+ /// The requested content.
+ /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will
+ /// preserve or reset the template, if any.
+ public void SetInternalRedirectPublishedContent(IPublishedContent content)
+ {
+ //if (content == null)
+ // throw new ArgumentNullException(nameof(content));
+ //EnsureWriteable();
+
+ //// unless a template has been set already by the finder,
+ //// template should be null at that point.
+
+ //// IsInternalRedirect if IsInitial, or already IsInternalRedirect
+ //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent;
+
+ //// redirecting to self
+ //if (content.Id == PublishedContent.Id) // neither can be null
+ //{
+ // // no need to set PublishedContent, we're done
+ // IsInternalRedirectPublishedContent = isInternalRedirect;
+ // return;
+ //}
+
+ //// else
+
+ //// save
+ //var template = Template;
+
+ //// set published content - this resets the template, and sets IsInternalRedirect to false
+ //PublishedContent = content;
+ //IsInternalRedirectPublishedContent = isInternalRedirect;
+
+ //// must restore the template if it's an internal redirect & the config option is set
+ //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate)
+ //{
+ // // restore
+ // TemplateModel = template;
+ //}
+ }
+
+ ///
+ /// Gets the initial requested content.
+ ///
+ /// The initial requested content is the content that was found by the finders,
+ /// before anything such as 404, redirect... took place.
+ public IPublishedContent InitialPublishedContent => _initialPublishedContent;
+
+ ///
+ /// Gets value indicating whether the current published content is the initial one.
+ ///
+ public bool IsInitialPublishedContent => _initialPublishedContent != null && _initialPublishedContent == _publishedContent;
+
+ ///
+ /// Indicates that the current PublishedContent is the initial one.
+ ///
+ public void SetIsInitialPublishedContent()
+ {
+ EnsureWriteable();
+
+ // note: it can very well be null if the initial content was not found
+ _initialPublishedContent = _publishedContent;
+ IsInternalRedirectPublishedContent = false;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the current published content has been obtained
+ /// from the initial published content following internal redirections exclusively.
+ ///
+ /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to
+ /// apply the internal redirect or not, when content is not the initial content.
+ public bool IsInternalRedirectPublishedContent { get; private set; }
+
+
+ #endregion
+
+ ///
+ /// Gets or sets the template model to use to display the requested content.
+ ///
+ public ITemplate Template { get; }
+
+ ///
+ /// Gets the alias of the template to use to display the requested content.
+ ///
+ public string TemplateAlias => Template?.Alias;
+
+
+ ///
+ /// Gets or sets the content request's domain.
+ ///
+ /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example,
+ /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/".
+ public DomainAndUri Domain
+ {
+ get { return _domain; }
+ set
+ {
+ EnsureWriteable();
+ _domain = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the content request has a domain.
+ ///
+ public bool HasDomain => Domain != null;
+
+ ///
+ /// Gets or sets the content request's culture.
+ ///
+ public CultureInfo Culture
+ {
+ get { return _culture ?? Thread.CurrentThread.CurrentCulture; }
+ set
+ {
+ EnsureWriteable();
+ _culture = value;
+ }
+ }
+
+ // note: do we want to have an ordered list of alternate cultures,
+ // to allow for fallbacks when doing dictionary lookup and such?
+
+
+ #region Status
+
+ ///
+ /// Gets or sets a value indicating whether the requested content could not be found.
+ ///
+ /// This is set in the PublishedContentRequestBuilder and can also be used in
+ /// custom content finders or Prepared event handlers, where we want to allow developers
+ /// to indicate a request is 404 but not to cancel it.
+ public bool Is404
+ {
+ get { return _is404; }
+ set
+ {
+ EnsureWriteable();
+ _is404 = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the content request triggers a redirect (permanent or not).
+ ///
+ public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false;
+
+ ///
+ /// Gets or sets a value indicating whether the redirect is permanent.
+ ///
+ public bool IsRedirectPermanent { get; private set; }
+
+ ///
+ /// Gets or sets the URL to redirect to, when the content request triggers a redirect.
+ ///
+ public string RedirectUrl { get; private set; }
+
+ ///
+ /// Indicates that the content request should trigger a redirect (302).
+ ///
+ /// The URL to redirect to.
+ /// Does not actually perform a redirect, only registers that the response should
+ /// redirect. Redirect will or will not take place in due time.
+ public void SetRedirect(string url)
+ {
+ EnsureWriteable();
+ RedirectUrl = url;
+ IsRedirectPermanent = false;
+ }
+
+ ///
+ /// Indicates that the content request should trigger a permanent redirect (301).
+ ///
+ /// The URL to redirect to.
+ /// Does not actually perform a redirect, only registers that the response should
+ /// redirect. Redirect will or will not take place in due time.
+ public void SetRedirectPermanent(string url)
+ {
+ EnsureWriteable();
+ RedirectUrl = url;
+ IsRedirectPermanent = true;
+ }
+
+ ///
+ /// Indicates that the content request should trigger a redirect, with a specified status code.
+ ///
+ /// The URL to redirect to.
+ /// The status code (300-308).
+ /// Does not actually perform a redirect, only registers that the response should
+ /// redirect. Redirect will or will not take place in due time.
+ public void SetRedirect(string url, int status)
+ {
+ EnsureWriteable();
+
+ if (status < 300 || status > 308)
+ throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308.");
+
+ RedirectUrl = url;
+ IsRedirectPermanent = (status == 301 || status == 308);
+ if (status != 301 && status != 302) // default redirect statuses
+ ResponseStatusCode = status;
+ }
+
+ ///
+ /// Gets or sets the content request http response status code.
+ ///
+ /// Does not actually set the http response status code, only registers that the response
+ /// should use the specified code. The code will or will not be used, in due time.
+ public int ResponseStatusCode { get; private set; }
+
+ ///
+ /// Gets or sets the content request http response status description.
+ ///
+ /// Does not actually set the http response status description, only registers that the response
+ /// should use the specified description. The description will or will not be used, in due time.
+ public string ResponseStatusDescription { get; private set; }
+
+ ///
+ /// Sets the http response status code, along with an optional associated description.
+ ///
+ /// The http status code.
+ /// The description.
+ /// Does not actually set the http response status code and description, only registers that
+ /// the response should use the specified code and description. The code and description will or will
+ /// not be used, in due time.
+ public void SetResponseStatus(int code, string description = null)
+ {
+ EnsureWriteable();
+
+ // .Status is deprecated
+ // .SubStatusCode is IIS 7+ internal, ignore
+ ResponseStatusCode = code;
+ ResponseStatusDescription = description;
+ }
+
+ #endregion
+
+ #region Response Cache
+
+ ///
+ /// Gets or sets the System.Web.HttpCacheability
+ ///
+ // Note: we used to set a default value here but that would then be the default
+ // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example
+ // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752
+ //public HttpCacheability Cacheability { get; set; }
+
+ ///
+ /// Gets or sets a list of Extensions to append to the Response.Cache object.
+ ///
+ public List CacheExtensions { get; set; } = new List();
+
+ ///
+ /// Gets or sets a dictionary of Headers to append to the Response object.
+ ///
+ public Dictionary Headers { get; set; } = new Dictionary();
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs
index 9b7354de74..4e0cda4041 100644
--- a/src/Umbraco.Core/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/PublishedRouter.cs
@@ -1,21 +1,24 @@
using System;
-using System.Linq;
-using System.Threading;
using System.Globalization;
using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Umbraco.Core;
-using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
+using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security;
-using Umbraco.Core.Configuration.Models;
-using Microsoft.Extensions.Options;
namespace Umbraco.Web.Routing
{
+
///
/// Provides the default implementation.
///
@@ -34,6 +37,8 @@ namespace Umbraco.Web.Routing
private readonly IFileService _fileService;
private readonly IContentTypeService _contentTypeService;
private readonly IPublicAccessService _publicAccessService;
+ private readonly IUmbracoContextAccessor _umbracoContextAccessor;
+ private readonly IEventAggregator _eventAggregator;
///
/// Initializes a new instance of the class.
@@ -51,7 +56,9 @@ namespace Umbraco.Web.Routing
IPublicAccessChecker publicAccessChecker,
IFileService fileService,
IContentTypeService contentTypeService,
- IPublicAccessService publicAccessService)
+ IPublicAccessService publicAccessService,
+ IUmbracoContextAccessor umbracoContextAccessor,
+ IEventAggregator eventAggregator)
{
_webRoutingSettings = webRoutingSettings.Value ?? throw new ArgumentNullException(nameof(webRoutingSettings));
_contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders));
@@ -66,82 +73,93 @@ namespace Umbraco.Web.Routing
_fileService = fileService;
_contentTypeService = contentTypeService;
_publicAccessService = publicAccessService;
+ _umbracoContextAccessor = umbracoContextAccessor;
+ _eventAggregator = eventAggregator;
}
///
- public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null)
+ public async Task CreateRequestAsync(Uri uri)
{
- return new PublishedRequest(this, umbracoContext, Options.Create(_webRoutingSettings), uri ?? umbracoContext.CleanedUmbracoUrl);
+ // trigger the Creating event - at that point the URL can be changed
+ // this is based on this old task here: https://issues.umbraco.org/issue/U4-7914 which was fulfiled by
+ // this PR https://github.com/umbraco/Umbraco-CMS/pull/1137
+ // It's to do with proxies, quote:
+
+ /*
+ "Thinking about another solution.
+ We already have an event, PublishedContentRequest.Prepared, which triggers once the request has been prepared and domain, content, template have been figured out -- but before it renders -- so ppl can change things before rendering.
+ Wondering whether we could have a event, PublishedContentRequest.Preparing, which would trigger before the request is prepared, and would let ppl change the value of the request's URI (which by default derives from the HttpContext request).
+ That way, if an in-between equipement changes the URI, you could replace it with the original, public-facing URI before we process the request, meaning you could register your HTTPS domain and it would work. And you would have to supply code for each equipment. Less magic in Core."
+ */
+
+ // but now we'll just have one event for creating so if people wish to change the URL here they can but nothing else
+ var creatingRequest = new CreatingRequestNotification(uri);
+ await _eventAggregator.PublishAsync(creatingRequest);
+
+ var publishedRequestBuilder = new PublishedRequestBuilder(creatingRequest.Url, _fileService);
+ return publishedRequestBuilder;
}
- #region Request
-
- ///
- public bool TryRouteRequest(IPublishedRequest request)
+ private IPublishedRequest TryRouteRequest(IPublishedRequestBuilder request)
{
- // disabled - is it going to change the routing?
- //_pcr.OnPreparing();
-
FindDomain(request);
- if (request.IsRedirect) return false;
- if (request.HasPublishedContent) return true;
+
+ // TODO: This was ported from v8 but how could it possibly have a redirect here?
+ if (request.IsRedirect())
+ {
+ return request.Build();
+ }
+
+ // TODO: This was ported from v8 but how could it possibly have content here?
+ if (request.HasPublishedContent())
+ {
+ return request.Build();
+ }
+
FindPublishedContent(request);
- if (request.IsRedirect) return false;
- // don't handle anything - we just want to ensure that we find the content
- //HandlePublishedContent();
- //FindTemplate();
- //FollowExternalRedirect();
- //HandleWildcardDomains();
-
- // disabled - we just want to ensure that we find the content
- //_pcr.OnPrepared();
-
- return request.HasPublishedContent;
+ return request.Build();
}
private void SetVariationContext(string culture)
{
- var variationContext = _variationContextAccessor.VariationContext;
- if (variationContext != null && variationContext.Culture == culture) return;
+ VariationContext variationContext = _variationContextAccessor.VariationContext;
+ if (variationContext != null && variationContext.Culture == culture)
+ {
+ return;
+ }
+
_variationContextAccessor.VariationContext = new VariationContext(culture);
}
///
- public bool PrepareRequest(IPublishedRequest request)
+ public async Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options)
{
- // note - at that point the original legacy module did something do handle IIS custom 404 errors
- // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
- // "directory URLs" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
- // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
- //
- // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
- // so that they point to a non-existing page eg /redirect-404.aspx
- // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means.
-
- // trigger the Preparing event - at that point anything can still be changed
- // the idea is that it is possible to change the uri
- //
- request.OnPreparing();
-
- //find domain
- FindDomain(request);
-
- // if request has been flagged to redirect then return
- // whoever called us is in charge of actually redirecting
- if (request.IsRedirect)
+ // outbound routing performs different/simpler logic
+ if (options.RouteDirection == RouteDirection.Outbound)
{
- return false;
+ return TryRouteRequest(request);
}
- // set the culture on the thread - once, so it's set when running document lookups
- Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
- SetVariationContext(request.Culture.Name);
+ // find domain
+ FindDomain(request);
- //find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
+ // TODO: This was ported from v8 but how could it possibly have a redirect here?
+ // if request has been flagged to redirect then return
+ // whoever called us is in charge of actually redirecting
+ if (request.IsRedirect())
+ {
+ return request.Build();
+ }
+
+ // set the culture
+ SetVariationContext(request.Culture);
+
+ // find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
- if (request.PublishedContent == null)
+ // TODO: This might very well change when we look into porting custom routes in netcore like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler.
+ if (!request.HasPublishedContent())
{
// find the document & template
FindPublishedContentAndTemplate(request);
@@ -150,27 +168,23 @@ namespace Umbraco.Web.Routing
// handle wildcard domains
HandleWildcardDomains(request);
- // set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain
- Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
- SetVariationContext(request.Culture.Name);
+ // set the culture -- again, 'cos it might have changed due to a finder or wildcard domain
+ SetVariationContext(request.Culture);
- // trigger the Prepared event - at that point it is still possible to change about anything
+ // trigger the routing request (used to be called Prepared) event - at that point it is still possible to change about anything
// even though the request might be flagged for redirection - we'll redirect _after_ the event
- //
- // also, OnPrepared() will make the PublishedRequest readonly, so nothing can change
- //
- request.OnPrepared();
+ var routingRequest = new RoutingRequestNotification(request);
+ await _eventAggregator.PublishAsync(routingRequest);
// we don't take care of anything so if the content has changed, it's up to the user
// to find out the appropriate template
- //complete the PCR and assign the remaining values
- return ConfigureRequest(request);
+ // complete the PCR and assign the remaining values
+ return BuildRequest(request);
}
///
- /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
- /// finalizes the PCR with the values assigned.
+ /// This method finalizes/builds the PCR with the values assigned.
///
///
/// Returns false if the request was not successfully configured
@@ -179,81 +193,61 @@ namespace Umbraco.Web.Routing
/// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values
/// but need to finalize it themselves.
///
- public bool ConfigureRequest(IPublishedRequest frequest)
+ internal IPublishedRequest BuildRequest(IPublishedRequestBuilder frequest)
{
- if (frequest.HasPublishedContent == false)
+ IPublishedRequest result = frequest.Build();
+
+ if (!frequest.HasPublishedContent())
{
- return false;
+ return result;
}
- // set the culture on the thread -- again, 'cos it might have changed in the event handler
- Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture;
- SetVariationContext(frequest.Culture.Name);
+ // set the culture -- again, 'cos it might have changed in the event handler
+ SetVariationContext(result.Culture);
- // if request has been flagged to redirect, or has no content to display,
- // then return - whoever called us is in charge of actually redirecting
- if (frequest.IsRedirect || frequest.HasPublishedContent == false)
- {
- return false;
- }
-
- // we may be 404 _and_ have a content
-
- // can't go beyond that point without a PublishedContent to render
- // it's ok not to have a template, in order to give MVC a chance to hijack routes
-
- return true;
+ return result;
}
///
- public void UpdateRequestToNotFound(IPublishedRequest request)
+ public IPublishedRequestBuilder UpdateRequestToNotFound(IPublishedRequest request)
{
- // clear content
- var content = request.PublishedContent;
- request.PublishedContent = null;
+ var builder = new PublishedRequestBuilder(request.Uri, _fileService);
- HandlePublishedContent(request); // will go 404
- FindTemplate(request);
+ // clear content
+ IPublishedContent content = request.PublishedContent;
+ builder.SetPublishedContent(null);
+
+ HandlePublishedContent(builder); // will go 404
+ FindTemplate(builder, false);
// if request has been flagged to redirect then return
- // whoever called us is in charge of redirecting
- if (request.IsRedirect)
- return;
+ if (request.IsRedirect())
+ {
+ return builder;
+ }
- if (request.HasPublishedContent == false)
+ if (!builder.HasPublishedContent())
{
// means the engine could not find a proper document to handle 404
// restore the saved content so we know it exists
- request.PublishedContent = content;
- return;
+ builder.SetPublishedContent(content);
}
- if (request.HasTemplate == false)
- {
- // means we may have a document, but we have no template
- // at that point there isn't much we can do and there is no point returning
- // to Mvc since Mvc can't do much either
- return;
- }
+ return builder;
}
- #endregion
-
- #region Domain
-
///
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
///
/// A value indicating whether a domain was found.
- internal bool FindDomain(IPublishedRequest request)
+ internal bool FindDomain(IPublishedRequestBuilder request)
{
const string tracePrefix = "FindDomain: ";
// note - we are not handling schemes nor ports here.
-
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
- var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains;
+ IDomainCache domainsCache = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains;
var domains = domainsCache.GetAll(includeWildcards: false).ToList();
// determines whether a domain corresponds to a published document, since some
@@ -263,18 +257,22 @@ namespace Umbraco.Web.Routing
bool IsPublishedContentDomain(Domain domain)
{
// just get it from content cache - optimize there, not here
- var domainDocument = request.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId);
+ IPublishedContent domainDocument = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId);
// not published - at all
if (domainDocument == null)
+ {
return false;
+ }
// invariant - always published
if (!domainDocument.ContentType.VariesByCulture())
+ {
return true;
+ }
// variant, ensure that the culture corresponding to the domain's language is published
- return domainDocument.Cultures.ContainsKey(domain.Culture.Name);
+ return domainDocument.Cultures.ContainsKey(domain.Culture);
}
domains = domains.Where(IsPublishedContentDomain).ToList();
@@ -282,7 +280,7 @@ namespace Umbraco.Web.Routing
var defaultCulture = domainsCache.DefaultCulture;
// try to find a domain matching the current request
- var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
+ DomainAndUri domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
// handle domain - always has a contentId and a culture
if (domainAndUri != null)
@@ -290,8 +288,7 @@ namespace Umbraco.Web.Routing
// matching an existing domain
_logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture);
- request.Domain = domainAndUri;
- request.Culture = domainAndUri.Culture;
+ request.SetDomain(domainAndUri);
// canonical? not implemented at the moment
// if (...)
@@ -305,10 +302,10 @@ namespace Umbraco.Web.Routing
// not matching any existing domain
_logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix);
- request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture);
+ request.SetCulture(defaultCulture ?? CultureInfo.CurrentUICulture.Name);
}
- _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name);
+ _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture);
return request.Domain != null;
}
@@ -316,23 +313,25 @@ namespace Umbraco.Web.Routing
///
/// Looks for wildcard domains in the path and updates Culture accordingly.
///
- internal void HandleWildcardDomains(IPublishedRequest request)
+ internal void HandleWildcardDomains(IPublishedRequestBuilder request)
{
const string tracePrefix = "HandleWildcardDomains: ";
- if (request.HasPublishedContent == false)
+ if (request.PublishedContent == null)
+ {
return;
+ }
var nodePath = request.PublishedContent.Path;
_logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath);
- var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null;
- var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId);
+ var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null;
+ Domain domain = DomainUtilities.FindWildcardDomainInPath(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId);
// always has a contentId and a culture
if (domain != null)
{
- request.Culture = domain.Culture;
- _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name);
+ request.SetCulture(domain.Culture);
+ _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture);
}
else
{
@@ -340,20 +339,18 @@ namespace Umbraco.Web.Routing
}
}
- #endregion
-
- #region Rendering engine
-
internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions)
{
if (directory == null || directory.Exists == false)
+ {
return false;
+ }
var pos = alias.IndexOf('/');
if (pos > 0)
{
// recurse
- var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
+ DirectoryInfo subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
alias = alias.Substring(pos + 1);
return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
}
@@ -362,21 +359,10 @@ namespace Umbraco.Web.Routing
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
}
- #endregion
-
- #region Document and template
-
- ///
- public ITemplate GetTemplate(string alias)
- {
- return _fileService.GetTemplate(alias);
- }
-
///
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly.
///
- /// A value indicating whether a document and template were found.
- private void FindPublishedContentAndTemplate(IPublishedRequest request)
+ private void FindPublishedContentAndTemplate(IPublishedRequestBuilder request)
{
_logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath);
@@ -386,8 +372,12 @@ namespace Umbraco.Web.Routing
// if request has been flagged to redirect then return
// whoever called us is in charge of actually redirecting
// -- do not process anything any further --
- if (request.IsRedirect)
+ if (request.IsRedirect())
+ {
return;
+ }
+
+ var foundContentByFinders = request.HasPublishedContent();
// not handling umbracoRedirect here but after LookupDocument2
// so internal redirect, 404, etc has precedence over redirect
@@ -396,7 +386,7 @@ namespace Umbraco.Web.Routing
HandlePublishedContent(request);
// find a template
- FindTemplate(request);
+ FindTemplate(request, foundContentByFinders);
// handle umbracoRedirect
FollowExternalRedirect(request);
@@ -406,39 +396,35 @@ namespace Umbraco.Web.Routing
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
///
/// There is no finder collection.
- internal void FindPublishedContent(IPublishedRequest request)
+ internal void FindPublishedContent(IPublishedRequestBuilder request)
{
const string tracePrefix = "FindPublishedContent: ";
// look for the document
// the first successful finder, if any, will set this.PublishedContent, and may also set this.Template
// some finders may implement caching
-
using (_profilingLogger.DebugDuration(
$"{tracePrefix}Begin finders",
- $"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}"))
+ $"{tracePrefix}End finders"))
{
- //iterate but return on first one that finds it
+ // iterate but return on first one that finds it
var found = _contentFinders.Any(finder =>
{
_logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName);
return finder.TryFindContent(request);
});
}
-
- // indicate that the published content (if any) we have at the moment is the
- // one that was found by the standard finders before anything else took place.
- request.SetIsInitialPublishedContent();
}
///
/// Handles the published content (if any).
///
+ /// The request builder.
///
/// Handles "not found", internal redirects, access validation...
/// things that must be handled in one place because they can create loops
///
- private void HandlePublishedContent(IPublishedRequest request)
+ private void HandlePublishedContent(IPublishedRequestBuilder request)
{
// because these might loop, we have to have some sort of infinite loop detection
int i = 0, j = 0;
@@ -448,9 +434,9 @@ namespace Umbraco.Web.Routing
_logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i);
// handle not found
- if (request.HasPublishedContent == false)
+ if (request.PublishedContent == null)
{
- request.Is404 = true;
+ request.SetIs404();
_logger.LogDebug("HandlePublishedContent: No document, try last chance lookup");
// if it fails then give up, there isn't much more that we can do
@@ -467,23 +453,28 @@ namespace Umbraco.Web.Routing
j = 0;
while (FollowInternalRedirects(request) && j++ < maxLoop)
{ }
- if (j == maxLoop) // we're running out of control
+
+ // we're running out of control
+ if (j == maxLoop)
+ {
break;
+ }
// ensure access
- if (request.HasPublishedContent)
+ if (request.PublishedContent != null)
+ {
EnsurePublishedContentAccess(request);
+ }
// loop while we don't have page, ie the redirect or access
// got us to nowhere and now we need to run the notFoundLookup again
// as long as it's not running out of control ie infinite loop of some sort
-
- } while (request.HasPublishedContent == false && i++ < maxLoop);
+ } while (request.PublishedContent == null && i++ < maxLoop);
if (i == maxLoop || j == maxLoop)
{
_logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort");
- request.PublishedContent = null;
+ request.SetPublishedContent(null);
}
_logger.LogDebug("HandlePublishedContent: End");
@@ -492,19 +483,24 @@ namespace Umbraco.Web.Routing
///
/// Follows internal redirections through the umbracoInternalRedirectId document property.
///
+ /// The request builder.
/// A value indicating whether redirection took place and led to a new published document.
///
/// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.
/// As per legacy, if the redirect does not work, we just ignore it.
///
- private bool FollowInternalRedirects(IPublishedRequest request)
+ private bool FollowInternalRedirects(IPublishedRequestBuilder request)
{
if (request.PublishedContent == null)
+ {
throw new InvalidOperationException("There is no PublishedContent.");
+ }
// don't try to find a redirect if the property doesn't exist
if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false)
+ {
return false;
+ }
var redirect = false;
var valid = false;
@@ -515,29 +511,31 @@ namespace Umbraco.Web.Routing
{
// try and get the redirect node from a legacy integer ID
valid = true;
- internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId);
+ internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(internalRedirectId);
}
else
{
- var udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId);
+ GuidUdi udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId);
if (udiInternalRedirectId != null)
{
// try and get the redirect node from a UDI Guid
valid = true;
- internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid);
+ internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid);
}
}
if (valid == false)
{
// bad redirect - log and display the current page (legacy behavior)
- _logger.LogDebug("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.",
+ _logger.LogDebug(
+ "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.",
request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
}
if (internalRedirectNode == null)
{
- _logger.LogDebug("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.",
+ _logger.LogDebug(
+ "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.",
request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
}
else if (internalRedirectId == request.PublishedContent.Id)
@@ -547,7 +545,18 @@ namespace Umbraco.Web.Routing
}
else
{
- request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here
+ // save since it will be cleared
+ ITemplate template = request.Template;
+
+ request.SetInternalRedirect(internalRedirectNode); // don't use .PublishedContent here
+
+ // must restore the template if it's an internal redirect & the config option is set
+ if (request.IsInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate)
+ {
+ // restore
+ request.SetTemplate(template);
+ }
+
redirect = true;
_logger.LogDebug("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId);
}
@@ -559,20 +568,22 @@ namespace Umbraco.Web.Routing
/// Ensures that access to current node is permitted.
///
/// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.
- private void EnsurePublishedContentAccess(IPublishedRequest request)
+ private void EnsurePublishedContentAccess(IPublishedRequestBuilder request)
{
if (request.PublishedContent == null)
+ {
throw new InvalidOperationException("There is no PublishedContent.");
+ }
var path = request.PublishedContent.Path;
- var publicAccessAttempt = _publicAccessService.IsProtected(path);
+ Attempt publicAccessAttempt = _publicAccessService.IsProtected(path);
if (publicAccessAttempt)
{
_logger.LogDebug("EnsurePublishedContentAccess: Page is protected, check for access");
- var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id);
+ PublicAccessStatus status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id);
switch (status)
{
case PublicAccessStatus.NotLoggedIn:
@@ -602,24 +613,28 @@ namespace Umbraco.Web.Routing
}
}
- private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId)
+ private void SetPublishedContentAsOtherPage(IPublishedRequestBuilder request, int errorPageId)
{
if (errorPageId != request.PublishedContent.Id)
- request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId);
+ {
+ request.SetPublishedContent(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId));
+ }
}
///
/// Finds a template for the current node, if any.
///
- private void FindTemplate(IPublishedRequest request)
+ /// The request builder.
+ /// If the content was found by the finders, before anything such as 404, redirect... took place.
+ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByFinders)
{
+ // TODO: We've removed the event, might need to re-add?
// NOTE: at the moment there is only 1 way to find a template, and then ppl must
// use the Prepared event to change the template if they wish. Should we also
// implement an ITemplateFinder logic?
-
if (request.PublishedContent == null)
{
- request.TemplateModel = null;
+ request.SetTemplate(null);
return;
}
@@ -627,8 +642,9 @@ namespace Umbraco.Web.Routing
// only if the published content is the initial once, else the alternate template
// does not apply
// + optionally, apply the alternate template on internal redirects
- var useAltTemplate = request.IsInitialPublishedContent
- || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent);
+ var useAltTemplate = contentFoundByFinders
+ || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirect);
+
var altTemplate = useAltTemplate
? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate)
: null;
@@ -638,21 +654,25 @@ namespace Umbraco.Web.Routing
// we don't have an alternate template specified. use the current one if there's one already,
// which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...),
// else lookup the template id on the document then lookup the template with that id.
-
- if (request.HasTemplate)
+ if (request.HasTemplate())
{
_logger.LogDebug("FindTemplate: Has a template already, and no alternate template.");
return;
}
- // TODO: When we remove the need for a database for templates, then this id should be irrelevant,
- // not sure how were going to do this nicely.
-
// TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type!
// if the template isn't assigned to the document type we should log a warning and return 404
-
var templateId = request.PublishedContent.TemplateId;
- request.TemplateModel = GetTemplateModel(templateId);
+ ITemplate template = GetTemplate(templateId);
+ request.SetTemplate(template);
+ if (template != null)
+ {
+ _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
+ }
+ else
+ {
+ _logger.LogWarning("FindTemplate: Could not find template with id {TemplateId}", templateId);
+ }
}
else
{
@@ -661,9 +681,11 @@ namespace Umbraco.Web.Routing
// so /path/to/page/template1?altTemplate=template2 will use template2
// ignore if the alias does not match - just trace
-
- if (request.HasTemplate)
+ if (request.HasTemplate())
+ {
_logger.LogDebug("FindTemplate: Has a template already, but also an alternative template.");
+ }
+
_logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate);
// IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings
@@ -675,11 +697,11 @@ namespace Umbraco.Web.Routing
altTemplate))
{
// allowed, use
- var template = _fileService.GetTemplate(altTemplate);
+ ITemplate template = _fileService.GetTemplate(altTemplate);
if (template != null)
{
- request.TemplateModel = template;
+ request.SetTemplate(template);
_logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
}
else
@@ -693,11 +715,13 @@ namespace Umbraco.Web.Routing
// no allowed, back to default
var templateId = request.PublishedContent.TemplateId;
- request.TemplateModel = GetTemplateModel(templateId);
+ ITemplate template = GetTemplate(templateId);
+ request.SetTemplate(template);
+ _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
}
}
- if (request.HasTemplate == false)
+ if (!request.HasTemplate())
{
_logger.LogDebug("FindTemplate: No template was found.");
@@ -710,13 +734,9 @@ namespace Umbraco.Web.Routing
//
// so, don't set _pcr.Document to null here
}
- else
- {
- _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias);
- }
}
- private ITemplate GetTemplateModel(int? templateId)
+ private ITemplate GetTemplate(int? templateId)
{
if (templateId.HasValue == false || templateId.Value == default)
{
@@ -727,11 +747,16 @@ namespace Umbraco.Web.Routing
_logger.LogDebug("GetTemplateModel: Get template id={TemplateId}", templateId);
if (templateId == null)
+ {
throw new InvalidOperationException("The template is not set, the page cannot render.");
+ }
- var template = _fileService.GetTemplate(templateId.Value);
+ ITemplate template = _fileService.GetTemplate(templateId.Value);
if (template == null)
+ {
throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render.");
+ }
+
_logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
return template;
}
@@ -740,13 +765,18 @@ namespace Umbraco.Web.Routing
/// Follows external redirection through umbracoRedirect document property.
///
/// As per legacy, if the redirect does not work, we just ignore it.
- private void FollowExternalRedirect(IPublishedRequest request)
+ private void FollowExternalRedirect(IPublishedRequestBuilder request)
{
- if (request.HasPublishedContent == false) return;
+ if (request.PublishedContent == null)
+ {
+ return;
+ }
// don't try to find a redirect if the property doesn't exist
if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false)
+ {
return;
+ }
var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1);
var redirectUrl = "#";
@@ -757,14 +787,17 @@ namespace Umbraco.Web.Routing
else
{
// might be a UDI instead of an int Id
- var redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect);
+ GuidUdi redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect);
if (redirectUdi != null)
+ {
redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid);
+ }
}
- if (redirectUrl != "#")
- request.SetRedirect(redirectUrl);
- }
- #endregion
+ if (redirectUrl != "#")
+ {
+ request.SetRedirect(redirectUrl);
+ }
+ }
}
}
diff --git a/src/Umbraco.Core/Routing/RouteDirection.cs b/src/Umbraco.Core/Routing/RouteDirection.cs
new file mode 100644
index 0000000000..1d811b06e3
--- /dev/null
+++ b/src/Umbraco.Core/Routing/RouteDirection.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// The direction of a route
+ ///
+ public enum RouteDirection
+ {
+ ///
+ /// An inbound route used to map a URL to a content item
+ ///
+ Inbound = 1,
+
+ ///
+ /// An outbound route used to generate a URL for a content item
+ ///
+ Outbound = 2
+ }
+}
diff --git a/src/Umbraco.Core/Routing/RouteRequestOptions.cs b/src/Umbraco.Core/Routing/RouteRequestOptions.cs
new file mode 100644
index 0000000000..91a343e2f0
--- /dev/null
+++ b/src/Umbraco.Core/Routing/RouteRequestOptions.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Options for routing an Umbraco request
+ ///
+ public struct RouteRequestOptions : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public RouteRequestOptions(RouteDirection direction) => RouteDirection = direction;
+
+ ///
+ /// Gets the
+ ///
+ public RouteDirection RouteDirection { get; }
+
+ ///
+ public override bool Equals(object obj) => obj is RouteRequestOptions options && Equals(options);
+
+ ///
+ public bool Equals(RouteRequestOptions other) => RouteDirection == other.RouteDirection;
+
+ ///
+ public override int GetHashCode() => 15391035 + RouteDirection.GetHashCode();
+ }
+}
diff --git a/src/Umbraco.Core/Routing/RoutingRequestNotification.cs b/src/Umbraco.Core/Routing/RoutingRequestNotification.cs
new file mode 100644
index 0000000000..dbf1cbc15b
--- /dev/null
+++ b/src/Umbraco.Core/Routing/RoutingRequestNotification.cs
@@ -0,0 +1,20 @@
+using Umbraco.Core.Events;
+
+namespace Umbraco.Web.Routing
+{
+ ///
+ /// Used for notifying when an Umbraco request is being built
+ ///
+ public class RoutingRequestNotification : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RoutingRequestNotification(IPublishedRequestBuilder requestBuilder) => RequestBuilder = requestBuilder;
+
+ ///
+ /// Gets the
+ ///
+ public IPublishedRequestBuilder RequestBuilder { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Routing/SiteDomainHelper.cs b/src/Umbraco.Core/Routing/SiteDomainHelper.cs
index 35475ae292..bf43514f4a 100644
--- a/src/Umbraco.Core/Routing/SiteDomainHelper.cs
+++ b/src/Umbraco.Core/Routing/SiteDomainHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -284,9 +284,11 @@ namespace Umbraco.Web.Routing
// we do our best, but can't do the impossible
// get the "default" domain ie the first one for the culture, else the first one (exists, length > 0)
if (qualifiedSites == null)
- return domainAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture)) ??
- domainAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(defaultCulture)) ??
- domainAndUris.First();
+ {
+ return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture))
+ ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture))
+ ?? domainAndUris.First();
+ }
// find a site that contains the current authority
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
@@ -308,7 +310,7 @@ namespace Umbraco.Web.Routing
.FirstOrDefault(domainAndUri => domainAndUri != null);
// random, really
- ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture)) ?? domainAndUris.First();
+ ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First();
return ret;
}
diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs
new file mode 100644
index 0000000000..8e8541cb2c
--- /dev/null
+++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Extensions.Options;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Hosting;
+
+namespace Umbraco.Core.Routing
+{
+ ///
+ /// Utility for checking paths
+ ///
+ public class UmbracoRequestPaths
+ {
+ private readonly string _backOfficePath;
+ private readonly string _mvcArea;
+ private readonly string _backOfficeMvcPath;
+ private readonly string _previewMvcPath;
+ private readonly string _surfaceMvcPath;
+ private readonly string _apiMvcPath;
+ private readonly string _installPath;
+ private readonly string _appPath;
+ private readonly List _defaultUmbPaths;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UmbracoRequestPaths(IOptions globalSettings, IHostingEnvironment hostingEnvironment)
+ {
+ var applicationPath = hostingEnvironment.ApplicationVirtualPath;
+ _appPath = applicationPath.TrimStart('/');
+
+ _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment)
+ .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/');
+
+ _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
+ _defaultUmbPaths = new List { "/" + _mvcArea, "/" + _mvcArea + "/" };
+ _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/";
+ _previewMvcPath = "/" + _mvcArea + "/Preview/";
+ _surfaceMvcPath = "/" + _mvcArea + "/Surface/";
+ _apiMvcPath = "/" + _mvcArea + "/Api/";
+ _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install);
+ }
+
+ ///
+ /// Checks if the current uri is a back office request
+ ///
+ ///
+ ///
+ /// There are some special routes we need to check to properly determine this:
+ ///
+ ///
+ /// These are def back office:
+ /// /Umbraco/BackOffice = back office
+ /// /Umbraco/Preview = back office
+ ///
+ ///
+ /// If it's not any of the above then we cannot determine if it's back office or front-end
+ /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice
+ /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute.
+ ///
+ ///
+ /// These are def front-end:
+ /// /Umbraco/Surface = front-end
+ /// /Umbraco/Api = front-end
+ /// But if we've got this far we'll just have to assume it's front-end anyways.
+ ///
+ ///
+ public bool IsBackOfficeRequest(string absPath)
+ {
+ var fullUrlPath = absPath.TrimStart('/');
+ var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/');
+
+ // check if this is in the umbraco back office
+ var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath);
+
+ // if not, then def not back office
+ if (isUmbracoPath == false)
+ {
+ return false;
+ }
+
+ // if its the normal /umbraco path
+ if (_defaultUmbPaths.Any(x => urlPath.InvariantEquals(x)))
+ {
+ return true;
+ }
+
+ // check for special back office paths
+ if (urlPath.InvariantStartsWith(_backOfficeMvcPath)
+ || urlPath.InvariantStartsWith(_previewMvcPath))
+ {
+ return true;
+ }
+
+ // check for special front-end paths
+ if (urlPath.InvariantStartsWith(_surfaceMvcPath)
+ || urlPath.InvariantStartsWith(_apiMvcPath))
+ {
+ return false;
+ }
+
+ // if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by
+ // checking how many parts the route has, for example, all PluginController routes will be routed like
+ // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
+ // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
+ // plugin controller for the front-end.
+ if (urlPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
+ {
+ return false;
+ }
+
+ // if its anything else we can assume it's back office
+ return true;
+ }
+
+ ///
+ /// Checks if the current uri is an install request
+ ///
+ public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath);
+
+ ///
+ /// Rudimentary check to see if it's not a server side request
+ ///
+ public bool IsClientSideRequest(string absPath)
+ {
+ var ext = Path.GetExtension(absPath);
+ return !ext.IsNullOrWhiteSpace();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Routing/UmbracoRouteResult.cs b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs
new file mode 100644
index 0000000000..9f42f372ac
--- /dev/null
+++ b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs
@@ -0,0 +1,20 @@
+namespace Umbraco.Web.Routing
+{
+ public enum UmbracoRouteResult
+ {
+ ///
+ /// Routing was successful and a content item was matched
+ ///
+ Success,
+
+ ///
+ /// A redirection took place
+ ///
+ Redirect,
+
+ ///
+ /// Nothing matched
+ ///
+ NotFound
+ }
+}
diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs
index 8de78dfbf5..43a36db101 100644
--- a/src/Umbraco.Core/Routing/UriUtility.cs
+++ b/src/Umbraco.Core/Routing/UriUtility.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Text;
using Umbraco.Core;
using Umbraco.Core.Configuration;
@@ -55,9 +55,15 @@ namespace Umbraco.Web
{
if (virtualPath.InvariantStartsWith(_appPathPrefix)
&& (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/'))
+ {
virtualPath = virtualPath.Substring(_appPathPrefix.Length);
+ }
+
if (virtualPath.Length == 0)
+ {
virtualPath = "/";
+ }
+
return virtualPath;
}
@@ -88,6 +94,9 @@ namespace Umbraco.Web
// ie no virtual directory, no .aspx, lowercase...
public Uri UriToUmbraco(Uri uri)
{
+ // TODO: This is critical code that executes on every request, we should
+ // look into if all of this is necessary? not really sure we need ToLower?
+
// note: no need to decode uri here because we're returning a uri
// so it will be re-encoded anyway
var path = uri.GetSafeAbsolutePath();
@@ -95,23 +104,11 @@ namespace Umbraco.Web
path = path.ToLower();
path = ToAppRelative(path); // strip vdir if any
- //we need to check if the path is /default.aspx because this will occur when using a
- //web server pre IIS 7 when requesting the root document
- //if this is the case we need to change it to '/'
- if (path.StartsWith("/default.aspx", StringComparison.InvariantCultureIgnoreCase))
- {
- string rempath = path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length);
- path = rempath.StartsWith("/") ? rempath : "/" + rempath;
- }
if (path != "/")
{
path = path.TrimEnd('/');
}
- //if any part of the path contains .aspx, replace it with nothing.
- //sometimes .aspx is not at the end since we might have /home/sub1.aspx/customtemplate
- path = path.Replace(".aspx", "");
-
return uri.Rewrite(path);
}
diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
index 5c00b14af3..698b9ab526 100644
--- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
+++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs
@@ -1,11 +1,12 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
-using Umbraco.Core.Models;
-using Umbraco.Core.Services;
using Umbraco.Core;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.Services;
namespace Umbraco.Web.Routing
{
@@ -18,7 +19,8 @@ namespace Umbraco.Web.Routing
/// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list.
/// Contains all the URLs that we can figure out (based upon domains, etc).
///
- public static IEnumerable GetContentUrls(this IContent content,
+ public static async Task> GetContentUrlsAsync(
+ this IContent content,
IPublishedRouter publishedRouter,
IUmbracoContext umbracoContext,
ILocalizationService localizationService,
@@ -40,10 +42,12 @@ namespace Umbraco.Web.Routing
if (uriUtility == null) throw new ArgumentNullException(nameof(uriUtility));
if (variationContextAccessor == null) throw new ArgumentNullException(nameof(variationContextAccessor));
+ var result = new List();
+
if (content.Published == false)
{
- yield return UrlInfo.Message(textService.Localize("content/itemNotPublished"));
- yield break;
+ result.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished")));
+ return result;
}
// build a list of URLs, for the back-office
@@ -57,47 +61,47 @@ namespace Umbraco.Web.Routing
// for URLs for all cultures.
// and, not only for those assigned to domains in the branch, because we want
// to show what GetUrl() would return, for every culture.
-
var urls = new HashSet();
var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList();
- //get all URLs for all cultures
- //in a HashSet, so de-duplicates too
- foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider))
+ // get all URLs for all cultures
+ // in a HashSet, so de-duplicates too
+ foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider))
{
urls.Add(cultureUrl);
}
- //return the real URLs first, then the messages
- foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key))
+ // return the real URLs first, then the messages
+ foreach (IGrouping urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key))
{
- //in some cases there will be the same URL for multiple cultures:
+ // in some cases there will be the same URL for multiple cultures:
// * The entire branch is invariant
// * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed
-
- foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture))
- yield return dUrl;
+ foreach (UrlInfo dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture))
+ {
+ result.Add(dUrl);
+ }
}
// get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless.
// for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them.
foreach (var otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture))
- if (urls.Add(otherUrl)) //avoid duplicates
- yield return otherUrl;
+ {
+ // avoid duplicates
+ if (urls.Add(otherUrl))
+ {
+ result.Add(otherUrl);
+ }
+ }
+
+ return result;
}
///
/// Tries to return a for each culture for the content while detecting collisions/errors
///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- private static IEnumerable GetContentUrlsByCulture(IContent content,
+ private static async Task> GetContentUrlsByCultureAsync(
+ IContent content,
IEnumerable cultures,
IPublishedRouter publishedRouter,
IUmbracoContext umbracoContext,
@@ -108,14 +112,17 @@ namespace Umbraco.Web.Routing
UriUtility uriUtility,
IPublishedUrlProvider publishedUrlProvider)
{
+ var result = new List();
+
foreach (var culture in cultures)
{
// if content is variant, and culture is not published, skip
if (content.ContentType.VariesByCulture() && !content.IsCulturePublished(culture))
+ {
continue;
+ }
// if it's variant and culture is published, or if it's invariant, proceed
-
string url;
try
{
@@ -131,84 +138,105 @@ namespace Umbraco.Web.Routing
{
// deal with 'could not get the URL'
case "#":
- yield return HandleCouldNotGetUrl(content, culture, contentService, textService);
+ result.Add(HandleCouldNotGetUrl(content, culture, contentService, textService));
break;
// deal with exceptions
case "#ex":
- yield return UrlInfo.Message(textService.Localize("content/getUrlException"), culture);
+ result.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture));
break;
// got a URL, deal with collisions, add URL
default:
- if (DetectCollision(content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility, out var urlInfo)) // detect collisions, etc
- yield return urlInfo;
+ // detect collisions, etc
+ Attempt hasCollision = await DetectCollisionAsync(content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
+ if (hasCollision)
+ {
+ result.Add(hasCollision.Result);
+ }
else
- yield return UrlInfo.Url(url, culture);
+ {
+ result.Add(UrlInfo.Url(url, culture));
+ }
+
break;
}
}
+
+ return result;
}
private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService)
{
// document has a published version yet its URL is "#" => a parent must be
// unpublished, walk up the tree until we find it, and report.
- var parent = content;
+ IContent parent = content;
do
{
parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null;
}
while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
- if (parent == null) // oops, internal error
+ if (parent == null)
+ {
+ // oops, internal error
return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture);
-
- else if (!parent.Published) // totally not published
+ }
+ else if (!parent.Published)
+ {
+ // totally not published
return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture);
-
- else // culture not published
+ }
+ else
+ {
+ // culture not published
return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture);
+ }
}
- private static bool DetectCollision(IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility, out UrlInfo urlInfo)
+ private static async Task> DetectCollisionAsync(IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility)
{
// test for collisions on the 'main' URL
var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute);
- if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl);
- uri = uriUtility.UriToUmbraco(uri);
- var pcr = publishedRouter.CreateRequest(umbracoContext, uri);
- publishedRouter.TryRouteRequest(pcr);
-
- urlInfo = null;
-
- if (pcr.HasPublishedContent == false)
+ if (uri.IsAbsoluteUri == false)
{
- urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture);
- return true;
+ uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl);
+ }
+
+ uri = uriUtility.UriToUmbraco(uri);
+ IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri);
+ IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound));
+
+ if (!pcr.HasPublishedContent())
+ {
+ var urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture);
+ return Attempt.Succeed(urlInfo);
}
if (pcr.IgnorePublishedContentCollisions)
- return false;
+ {
+ return Attempt.Fail();
+ }
if (pcr.PublishedContent.Id != content.Id)
{
- var o = pcr.PublishedContent;
+ IPublishedContent o = pcr.PublishedContent;
var l = new List();
while (o != null)
{
l.Add(o.Name(variationContextAccessor));
o = o.Parent;
}
+
l.Reverse();
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
- urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture);
- return true;
+ var urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture);
+ return Attempt.Succeed(urlInfo);
}
// no collision
- return false;
+ return Attempt.Fail();
}
}
}
diff --git a/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
new file mode 100644
index 0000000000..5f219ac51f
--- /dev/null
+++ b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Umbraco.Core.Events;
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Manifest;
+
+namespace Umbraco.Core.Runtime
+{
+ ///
+ /// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes.
+ ///
+ public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationHandler
+ {
+ private readonly ManifestWatcher _manifestWatcher;
+ private readonly IHostingEnvironment _hostingEnvironment;
+
+ public AppPluginsManifestWatcherNotificationHandler(ManifestWatcher manifestWatcher, IHostingEnvironment hostingEnvironment)
+ {
+ _manifestWatcher = manifestWatcher ?? throw new ArgumentNullException(nameof(manifestWatcher));
+ _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
+ }
+
+ public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
+ {
+ if (!_hostingEnvironment.IsDebugMode)
+ {
+ return Task.CompletedTask;
+ }
+
+ var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
+
+ if (!Directory.Exists(appPlugins))
+ {
+ return Task.CompletedTask;
+ }
+
+ _manifestWatcher.Start(Directory.GetDirectories(appPlugins));
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
similarity index 96%
rename from src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
rename to src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
index 5543662464..92ad5808b3 100644
--- a/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
+++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs
@@ -1,13 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
-using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
-namespace Umbraco.Infrastructure.Runtime
+namespace Umbraco.Core.Runtime
{
public class EssentialDirectoryCreator : INotificationHandler
{
diff --git a/src/Umbraco.Infrastructure/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs
similarity index 92%
rename from src/Umbraco.Infrastructure/Runtime/MainDomSemaphoreLock.cs
rename to src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs
index eed13f5060..74dea6742c 100644
--- a/src/Umbraco.Infrastructure/Runtime/MainDomSemaphoreLock.cs
+++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Runtime
_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 AcquireLockAsync(int millisecondsTimeout)
@@ -44,7 +44,7 @@ namespace Umbraco.Core.Runtime
// if more than 1 instance reach that point, one will get the lock
// 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
{
_lockRelease = _systemLock.Lock(millisecondsTimeout);
diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
index 607c4748cc..13eab4ff80 100644
--- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs
+++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs
@@ -1,9 +1,5 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
using System.Globalization;
using System.Security.Principal;
-using System.Text;
using System.Threading;
namespace Umbraco.Core.Security
@@ -13,7 +9,6 @@ namespace Umbraco.Core.Security
///
/// Ensures that the thread culture is set based on the back office user's culture
///
- ///
public static void EnsureCulture(this IIdentity identity)
{
var culture = GetCulture(identity);
@@ -27,16 +22,10 @@ namespace Umbraco.Core.Security
{
if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated)
{
- return UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
+ return CultureInfo.GetCultureInfo(umbIdentity.Culture);
}
return null;
}
-
-
- ///
- /// Used so that we aren't creating a new CultureInfo object for every single request
- ///
- private static readonly ConcurrentDictionary UserCultures = new ConcurrentDictionary();
}
}
diff --git a/src/Umbraco.Core/Services/Implement/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs
similarity index 100%
rename from src/Umbraco.Core/Services/Implement/DashboardService.cs
rename to src/Umbraco.Core/Services/DashboardService.cs
diff --git a/src/Umbraco.Core/Services/IServerRegistrationService.cs b/src/Umbraco.Core/Services/IServerRegistrationService.cs
index 62bb68eb14..f0246dd287 100644
--- a/src/Umbraco.Core/Services/IServerRegistrationService.cs
+++ b/src/Umbraco.Core/Services/IServerRegistrationService.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Sync;
@@ -11,9 +11,8 @@ namespace Umbraco.Core.Services
/// Touches a server to mark it as active; deactivate stale servers.
///
/// The server URL.
- /// The server unique identity.
/// The time after which a server is considered stale.
- void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout);
+ void TouchServer(string serverAddress, TimeSpan staleTimeout);
///
/// Deactivates a server.
@@ -38,11 +37,6 @@ namespace Umbraco.Core.Services
/// from the database.
IEnumerable GetActiveServers(bool refresh = false);
- ///
- /// Gets the current server identity.
- ///
- string CurrentServerIdentity { get; }
-
///
/// Gets the role of the current server.
///
diff --git a/src/Umbraco.Infrastructure/Services/Implement/Implement/InstallationService.cs b/src/Umbraco.Core/Services/InstallationService.cs
similarity index 87%
rename from src/Umbraco.Infrastructure/Services/Implement/Implement/InstallationService.cs
rename to src/Umbraco.Core/Services/InstallationService.cs
index a1f74e0862..6e283a61d7 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/Implement/InstallationService.cs
+++ b/src/Umbraco.Core/Services/InstallationService.cs
@@ -1,8 +1,8 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
-namespace Umbraco.Core.Services.Implement
+namespace Umbraco.Core.Services
{
public class InstallationService : IInstallationService
{
diff --git a/src/Umbraco.Core/Services/UpgradeService.cs b/src/Umbraco.Core/Services/UpgradeService.cs
index ead5270b41..b36eac7789 100644
--- a/src/Umbraco.Core/Services/UpgradeService.cs
+++ b/src/Umbraco.Core/Services/UpgradeService.cs
@@ -1,10 +1,9 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Semver;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
-using Umbraco.Core.Services;
-namespace Umbraco.Core
+namespace Umbraco.Core.Services
{
public class UpgradeService : IUpgradeService
{
diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs
index 7438762295..f7dafac7a6 100644
--- a/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs
+++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs
@@ -1,10 +1,10 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Umbraco.Core.Sync
{
///
- /// Holds a list of callbacks associated with implementations of .
+ /// Holds a list of callbacks associated with implementations of .
///
public class DatabaseServerMessengerCallbacks
{
diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs
deleted file mode 100644
index f361eb7a67..0000000000
--- a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Core.Services;
-
-namespace Umbraco.Core.Sync
-{
- ///
- /// A registrar that stores registered server nodes in the database.
- ///
- ///
- /// 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.
- ///
- public sealed class DatabaseServerRegistrar : IServerRegistrar
- {
- private readonly Lazy _registrationService;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The registration service.
- /// Some options.
- public DatabaseServerRegistrar(Lazy registrationService)
- {
- _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService));
- }
-
- ///
- /// Gets the registered servers.
- ///
- public IEnumerable Registrations => _registrationService.Value.GetActiveServers();
-
- ///
- /// Gets the role of the current server in the application environment.
- ///
- public ServerRole GetCurrentServerRole()
- {
- var service = _registrationService.Value;
- return service.GetCurrentServerRole();
- }
-
- }
-}
diff --git a/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs
new file mode 100644
index 0000000000..e4accd046b
--- /dev/null
+++ b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs
@@ -0,0 +1,29 @@
+using System;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.Sync
+{
+ ///
+ /// Gets the current server's based on active servers registered with
+ ///
+ ///
+ /// 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.
+ ///
+ public sealed class ElectedServerRoleAccessor : IServerRoleAccessor
+ {
+ private readonly IServerRegistrationService _registrationService;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The registration service.
+ /// Some options.
+ public ElectedServerRoleAccessor(IServerRegistrationService registrationService) => _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService));
+
+ ///
+ /// Gets the role of the current server in the application environment.
+ ///
+ public ServerRole CurrentServerRole => _registrationService.GetCurrentServerRole();
+ }
+}
diff --git a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs
deleted file mode 100644
index 560b7fb235..0000000000
--- a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Umbraco.Core.Sync
-{
- ///
- /// An implementation that works by storing messages in the database.
- ///
- public interface IBatchedDatabaseServerMessenger : IDatabaseServerMessenger
- {
- void FlushBatch();
- DatabaseServerMessengerCallbacks Callbacks { get; }
- void Startup();
- }
-}
diff --git a/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs
deleted file mode 100644
index a49cfdd023..0000000000
--- a/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Umbraco.Core.Sync
-{
- public interface IDatabaseServerMessenger: IServerMessenger
- {
- void Sync();
- }
-}
diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs
index b8300b2d6d..df2d665057 100644
--- a/src/Umbraco.Core/Sync/IServerMessenger.cs
+++ b/src/Umbraco.Core/Sync/IServerMessenger.cs
@@ -1,21 +1,30 @@
-using System;
-using System.Collections.Generic;
+using System;
using Umbraco.Core.Cache;
namespace Umbraco.Core.Sync
{
///
- /// Broadcasts distributed cache notifications to all servers of a load balanced environment.
+ /// Transmits distributed cache notifications for all servers of a load balanced environment.
///
/// Also ensures that the notification is processed on the local environment.
public interface IServerMessenger
{
+ ///
+ /// Called to synchronize a server with queued notifications
+ ///
+ void Sync();
+
+ ///
+ /// Called to send/commit the queued messages created with the Perform methods
+ ///
+ void SendMessages();
+
///
/// Notifies the distributed cache, for a specified .
///
/// The ICacheRefresher.
/// The notification content.
- void PerformRefresh(ICacheRefresher refresher, TPayload[] payload);
+ void QueueRefresh(ICacheRefresher refresher, TPayload[] payload);
///
/// Notifies the distributed cache of specified item invalidation, for a specified .
@@ -24,7 +33,7 @@ namespace Umbraco.Core.Sync
/// The ICacheRefresher.
/// A function returning the unique identifier of items.
/// The invalidated items.
- void PerformRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances);
+ void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances);
///
/// Notifies the distributed cache of specified item invalidation, for a specified .
@@ -33,7 +42,7 @@ namespace Umbraco.Core.Sync
/// The ICacheRefresher.
/// A function returning the unique identifier of items.
/// The invalidated items.
- void PerformRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances);
+ void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances);
///
/// Notifies all servers of specified items removal, for a specified .
@@ -42,33 +51,33 @@ namespace Umbraco.Core.Sync
/// The ICacheRefresher.
/// A function returning the unique identifier of items.
/// The removed items.
- void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances);
+ void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances);
///
/// Notifies all servers of specified items removal, for a specified .
///
/// The ICacheRefresher.
/// The unique identifiers of the removed items.
- void PerformRemove(ICacheRefresher refresher, params int[] numericIds);
+ void QueueRemove(ICacheRefresher refresher, params int[] numericIds);
///
/// Notifies all servers of specified items invalidation, for a specified .
///
/// The ICacheRefresher.
/// The unique identifiers of the invalidated items.
- void PerformRefresh(ICacheRefresher refresher, params int[] numericIds);
+ void QueueRefresh(ICacheRefresher refresher, params int[] numericIds);
///
/// Notifies all servers of specified items invalidation, for a specified .
///
/// The ICacheRefresher.
/// The unique identifiers of the invalidated items.
- void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds);
+ void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds);
///
/// Notifies all servers of a global invalidation for a specified .
///
/// The ICacheRefresher.
- void PerformRefreshAll(ICacheRefresher refresher);
+ void QueueRefreshAll(ICacheRefresher refresher);
}
}
diff --git a/src/Umbraco.Core/Sync/IServerRegistrar.cs b/src/Umbraco.Core/Sync/IServerRegistrar.cs
deleted file mode 100644
index 780f865ad7..0000000000
--- a/src/Umbraco.Core/Sync/IServerRegistrar.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-
-namespace Umbraco.Core.Sync
-{
- ///
- /// Provides server registrations to the distributed cache.
- ///
- public interface IServerRegistrar
- {
- ///
- /// Gets the server registrations.
- ///
- IEnumerable Registrations { get; }
-
- ///
- /// Gets the role of the current server in the application environment.
- ///
- ServerRole GetCurrentServerRole();
-
- }
-}
diff --git a/src/Umbraco.Core/Sync/IServerRoleAccessor.cs b/src/Umbraco.Core/Sync/IServerRoleAccessor.cs
new file mode 100644
index 0000000000..b23acbac7c
--- /dev/null
+++ b/src/Umbraco.Core/Sync/IServerRoleAccessor.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Sync
+{
+ ///
+ /// Gets the current server's
+ ///
+ public interface IServerRoleAccessor
+ {
+ ///
+ /// Gets the role of the current server in the application environment.
+ ///
+ ServerRole CurrentServerRole { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Sync/SingleServerRegistrar.cs b/src/Umbraco.Core/Sync/SingleServerRegistrar.cs
deleted file mode 100644
index fe03e195b2..0000000000
--- a/src/Umbraco.Core/Sync/SingleServerRegistrar.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Web;
-
-namespace Umbraco.Core.Sync
-{
- ///
- /// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance
- ///
- ///
- /// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the
- /// 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.
- ///
- public class SingleServerRegistrar : IServerRegistrar
- {
- private readonly IRequestAccessor _requestAccessor;
- private readonly Lazy _registrations;
-
- public IEnumerable Registrations => _registrations.Value;
-
- public SingleServerRegistrar(IRequestAccessor requestAccessor)
- {
- _requestAccessor = requestAccessor;
- _registrations = new Lazy(() => 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; }
- }
- }
-}
diff --git a/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs b/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs
new file mode 100644
index 0000000000..65b9559522
--- /dev/null
+++ b/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Web;
+
+namespace Umbraco.Core.Sync
+{
+ ///
+ /// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance
+ ///
+ ///
+ /// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the
+ /// 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.
+ ///
+ public class SingleServerRoleAccessor : IServerRoleAccessor
+ {
+ public ServerRole CurrentServerRole => ServerRole.Single;
+ }
+}
diff --git a/src/Umbraco.Core/Templates/ITemplateRenderer.cs b/src/Umbraco.Core/Templates/ITemplateRenderer.cs
index f01edc58ed..7a6248e034 100644
--- a/src/Umbraco.Core/Templates/ITemplateRenderer.cs
+++ b/src/Umbraco.Core/Templates/ITemplateRenderer.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System.IO;
+using System.Threading.Tasks;
namespace Umbraco.Web.Templates
{
@@ -7,6 +8,6 @@ namespace Umbraco.Web.Templates
///
public interface ITemplateRenderer
{
- void Render(int pageId, int? altTemplateId, StringWriter writer);
+ Task RenderAsync(int pageId, int? altTemplateId, StringWriter writer);
}
}
diff --git a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs
index 5444ef3f96..fcfdd815f6 100644
--- a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs
+++ b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Strings;
@@ -12,35 +13,31 @@ namespace Umbraco.Core.Templates
///
/// Renders the template for the specified pageId and an optional altTemplateId
///
- ///
+ /// The content id
/// If not specified, will use the template assigned to the node
- ///
- IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null);
+ Task RenderTemplateAsync(int contentId, int? altTemplateId = null);
///
/// Renders the macro with the specified alias.
///
- ///
+ /// The content id
/// The alias.
- ///
IHtmlEncodedString RenderMacro(int contentId, string alias);
///
/// Renders the macro with the specified alias, passing in the specified parameters.
///
- ///
+ /// The content id
/// The alias.
/// The parameters.
- ///
IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters);
///
/// Renders the macro with the specified alias, passing in the specified parameters.
///
- ///
+ /// The content id
/// The alias.
/// The parameters.
- ///
IHtmlEncodedString RenderMacro(int contentId, string alias, IDictionary parameters);
///
diff --git a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs
index 4618eb2822..d0b6972bc3 100644
--- a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs
+++ b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.IO;
using Umbraco.Web.Templates;
@@ -8,6 +8,7 @@ using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Strings;
using Umbraco.Web;
using Umbraco.Web.Macros;
+using System.Threading.Tasks;
namespace Umbraco.Core.Templates
{
@@ -24,6 +25,9 @@ namespace Umbraco.Core.Templates
private readonly IMacroRenderer _macroRenderer;
private readonly ITemplateRenderer _templateRenderer;
+ ///
+ /// Initializes a new instance of the class.
+ ///
public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer)
{
_umbracoContextAccessor = umbracoContextAccessor;
@@ -31,76 +35,55 @@ namespace Umbraco.Core.Templates
_templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer));
}
- ///
- /// Renders the template for the specified pageId and an optional altTemplateId
- ///
- ///
- /// If not specified, will use the template assigned to the node
- ///
- public IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null)
+ ///
+ public async Task RenderTemplateAsync(int contentId, int? altTemplateId = null)
{
using (var sw = new StringWriter())
{
try
{
- _templateRenderer.Render(contentId, altTemplateId, sw);
+ await _templateRenderer.RenderAsync(contentId, altTemplateId, sw);
}
catch (Exception ex)
{
sw.Write("", contentId, ex);
}
+
return new HtmlEncodedString(sw.ToString());
}
}
- ///
- /// Renders the macro with the specified alias.
- ///
- ///
- /// The alias.
- ///
- public IHtmlEncodedString RenderMacro(int contentId, string alias)
- {
- return RenderMacro(contentId, alias, new { });
- }
+ ///
+ public IHtmlEncodedString RenderMacro(int contentId, string alias) => RenderMacro(contentId, alias, new { });
- ///
- /// Renders the macro with the specified alias, passing in the specified parameters.
- ///
- ///
- /// The alias.
- /// The parameters.
- ///
- public IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters)
- {
- return RenderMacro(contentId, alias, parameters?.ToDictionary
+ ///
DateTime ObjectCreated { get; }
///
@@ -47,19 +50,16 @@ namespace Umbraco.Web
IDomainCache Domains { get; }
///
- /// Boolean value indicating whether the current request is a front-end umbraco request
- ///
- bool IsFrontEndUmbracoRequest { get; }
-
- ///
- /// Gets/sets the PublishedRequest object
+ /// Gets or sets the PublishedRequest object
///
+ //// TODO: Can we refactor this so it's not a settable thing required for routing?
+ //// The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx/factory.
IPublishedRequest PublishedRequest { get; set; }
///
/// Gets the variation context accessor.
///
- IVariationContextAccessor VariationContextAccessor { get; }
+ IVariationContextAccessor VariationContextAccessor { get; } // TODO: This shouldn't expose the accessor should it? IUmbracoContext is basically the accessor to the VariationContext since IUmbracoContextFactory currently creates it?
///
/// Gets a value indicating whether the request has debugging enabled
@@ -68,11 +68,14 @@ namespace Umbraco.Web
bool IsDebug { get; }
///
- /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI)
+ /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin UI)
///
bool InPreviewMode { get; }
+ ///
+ /// Forces the context into preview
+ ///
+ /// A instance to be disposed to exit the preview context
IDisposable ForcedPreview(bool preview);
- void Dispose();
}
}
diff --git a/src/Umbraco.Core/IUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs
similarity index 100%
rename from src/Umbraco.Core/IUmbracoContextAccessor.cs
rename to src/Umbraco.Core/Web/IUmbracoContextAccessor.cs
diff --git a/src/Umbraco.Core/IUmbracoContextFactory.cs b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs
similarity index 100%
rename from src/Umbraco.Core/IUmbracoContextFactory.cs
rename to src/Umbraco.Core/Web/IUmbracoContextFactory.cs
diff --git a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs
index e2f2460d58..05315b05fe 100644
--- a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs
+++ b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs
@@ -1,5 +1,4 @@
using System.Runtime.InteropServices;
-using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs
new file mode 100644
index 0000000000..5b4dd4f0ed
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs
@@ -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
+{
+ ///
+ /// Ensures that distributed cache events are setup and the is initialized
+ ///
+ public sealed class DatabaseServerMessengerNotificationHandler : INotificationHandler
+ {
+ private readonly IServerMessenger _messenger;
+ private readonly IRequestAccessor _requestAccessor;
+ private readonly IUmbracoDatabaseFactory _databaseFactory;
+ private readonly IDistributedCacheBinder _distributedCacheBinder;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DatabaseServerMessengerNotificationHandler(
+ IServerMessenger serverMessenger,
+ IRequestAccessor requestAccessor,
+ IUmbracoDatabaseFactory databaseFactory,
+ IDistributedCacheBinder distributedCacheBinder,
+ ILogger logger)
+ {
+ _requestAccessor = requestAccessor;
+ _databaseFactory = databaseFactory;
+ _distributedCacheBinder = distributedCacheBinder;
+ _logger = logger;
+ _messenger = serverMessenger;
+ }
+
+ ///
+ 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();
+
+ ///
+ /// Clear the batch on end request
+ ///
+ private void EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.SendMessages();
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs
index f345f40bd7..67987915ac 100644
--- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs
+++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs
deleted file mode 100644
index 7279eaf10c..0000000000
--- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Umbraco.Core;
-using Umbraco.Core.DependencyInjection;
-using Umbraco.Core.Composing;
-
-namespace Umbraco.Web.Cache
-{
- ///
- /// Installs listeners on service events in order to refresh our caches.
- ///
- [ComposeBefore(typeof(ICoreComposer))] // runs before every other IUmbracoCoreComponent!
- public sealed class DistributedCacheBinderComposer : ComponentComposer, ICoreComposer
- {
- public override void Compose(IUmbracoBuilder builder)
- {
- base.Compose(builder);
-
- builder.Services.AddUnique();
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs
deleted file mode 100644
index 8d2a2e19cc..0000000000
--- a/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Ensures that servers are automatically registered in the database, when using the database server registrar.
- ///
- ///
- /// 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.
- /// 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.
- ///
- // 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, 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();
-
- // 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();
- indexRebuilder.RebuildIndexes(false, 5000);
- }
- }
- };
- }
-
- public override void Compose(IUmbracoBuilder builder)
- {
- base.Compose(builder);
-
- builder.SetDatabaseServerMessengerCallbacks(GetCallbacks);
- builder.SetServerMessenger();
- }
- }
-
- 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();
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs
index 0fee815560..79066dedd7 100644
--- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs
+++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs
@@ -1,5 +1,4 @@
-using Umbraco.Core;
-using Umbraco.Core.DependencyInjection;
+using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Compose
diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs
deleted file mode 100644
index 31b3133e4d..0000000000
--- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs
+++ /dev/null
@@ -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();
- builder.Services.AddScoped();
- builder.Services.AddScoped();
- builder.Services.AddScoped();
- builder.Services.AddScoped();
- builder.Services.AddScoped();
-
- // TODO: Add these back once we have a compatible Starter kit
- // composition.Services.AddScoped();
- // composition.Services.AddScoped();
- // composition.Services.AddScoped();
-
- builder.Services.AddScoped();
-
- builder.Services.AddTransient();
- builder.Services.AddUnique();
-
- return builder;
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/CompositionExtensions.cs b/src/Umbraco.Infrastructure/CompositionExtensions.cs
deleted file mode 100644
index 703c35e06b..0000000000
--- a/src/Umbraco.Infrastructure/CompositionExtensions.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Provides extension methods to the class.
- ///
- public static partial class CompositionExtensions
- {
- #region Collection Builders
-
- ///
- /// Gets the cache refreshers collection builder.
- ///
- /// The builder.
- public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the mappers collection builder.
- ///
- /// The builder.
- public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the package actions collection builder.
- ///
- /// The builder.
- internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the data editor collection builder.
- ///
- /// The builder.
- public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the data value reference factory collection builder.
- ///
- /// The builder.
- public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the property value converters collection builder.
- ///
- /// The builder.
- public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the url segment providers collection builder.
- ///
- /// The builder.
- public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the validators collection builder.
- ///
- /// The builder.
- internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the manifest filter collection builder.
- ///
- /// The builder.
- public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the backoffice OEmbed Providers collection builder.
- ///
- /// The builder.
- public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- ///
- /// Gets the back office searchable tree collection builder
- ///
- ///
- ///
- public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder)
- => builder.WithCollectionBuilder();
-
- #endregion
-
- #region Uniques
-
- ///
- /// Sets the culture dictionary factory.
- ///
- /// The type of the factory.
- /// The builder.
- public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder)
- where T : class, ICultureDictionaryFactory
- {
- builder.Services.AddUnique();
- }
-
- ///
- /// Sets the culture dictionary factory.
- ///
- /// The builder.
- /// A function creating a culture dictionary factory.
- public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the culture dictionary factory.
- ///
- /// The builder.
- /// A factory.
- public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the published content model factory.
- ///
- /// The type of the factory.
- /// The builder.
- public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder)
- where T : class, IPublishedModelFactory
- {
- builder.Services.AddUnique();
- }
-
- ///
- /// Sets the published content model factory.
- ///
- /// The builder.
- /// A function creating a published content model factory.
- public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the published content model factory.
- ///
- /// The builder.
- /// A published content model factory.
- public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the server registrar.
- ///
- /// The type of the server registrar.
- /// The builder.
- public static void SetServerRegistrar(this IUmbracoBuilder builder)
- where T : class, IServerRegistrar
- {
- builder.Services.AddUnique();
- }
-
- ///
- /// Sets the server registrar.
- ///
- /// The builder.
- /// A function creating a server registrar.
- public static void SetServerRegistrar(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the server registrar.
- ///
- /// The builder.
- /// A server registrar.
- public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRegistrar registrar)
- {
- builder.Services.AddUnique(registrar);
- }
-
- ///
- /// Sets the server messenger.
- ///
- /// The type of the server registrar.
- /// The builder.
- public static void SetServerMessenger(this IUmbracoBuilder builder)
- where T : class, IServerMessenger
- {
- builder.Services.AddUnique();
- }
-
- ///
- /// Sets the server messenger.
- ///
- /// The builder.
- /// A function creating a server messenger.
- public static void SetServerMessenger(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the server messenger.
- ///
- /// The builder.
- /// A server messenger.
- public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
- {
- builder.Services.AddUnique(registrar);
- }
-
- ///
- /// Sets the database server messenger options.
- ///
- /// The builder.
- /// A function creating the options.
- /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.
- public static void SetDatabaseServerMessengerCallbacks(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the database server messenger options.
- ///
- /// The builder.
- /// Options.
- /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.
- public static void SetDatabaseServerMessengerOptions(this IUmbracoBuilder builder, DatabaseServerMessengerCallbacks options)
- {
- builder.Services.AddUnique(options);
- }
-
- ///
- /// Sets the short string helper.
- ///
- /// The type of the short string helper.
- /// The builder.
- public static void SetShortStringHelper(this IUmbracoBuilder builder)
- where T : class, IShortStringHelper
- {
- builder.Services.AddUnique();
- }
-
- ///
- /// Sets the short string helper.
- ///
- /// The builder.
- /// A function creating a short string helper.
- public static void SetShortStringHelper(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the short string helper.
- ///
- /// A builder.
- /// A short string helper.
- public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
- {
- builder.Services.AddUnique(helper);
- }
-
- ///
- /// Sets the underlying media filesystem.
- ///
- /// A builder.
- /// A filesystem factory.
- ///
- /// Using this helper will ensure that your IFileSystem implementation is wrapped by the ShadowWrapper
- ///
- public static void SetMediaFileSystem(this IUmbracoBuilder builder, Func filesystemFactory)
- => builder.Services.AddUnique(factory =>
- {
- var fileSystems = factory.GetRequiredService();
- return fileSystems.GetFileSystem(filesystemFactory(factory));
- });
-
- ///
- /// Sets the log viewer.
- ///
- /// The type of the log viewer.
- /// The builder.
- public static void SetLogViewer(this IUmbracoBuilder builder)
- where T : class, ILogViewer
- {
- builder.Services.AddUnique();
- }
-
- ///
- /// Sets the log viewer.
- ///
- /// The builder.
- /// A function creating a log viewer.
- public static void SetLogViewer(this IUmbracoBuilder builder, Func factory)
- {
- builder.Services.AddUnique(factory);
- }
-
- ///
- /// Sets the log viewer.
- ///
- /// A builder.
- /// A log viewer.
- public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
- {
- builder.Services.AddUnique(viewer);
- }
-
- #endregion
- }
-}
diff --git a/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs b/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs
index 5642c882e6..ca25563730 100644
--- a/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs
+++ b/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs
@@ -1,9 +1,8 @@
-using System;
+using System;
using NCrontab;
namespace Umbraco.Core.Configuration
{
-
public class NCronTabParser : ICronTabParser
{
public bool IsValidCronTab(string cronTab)
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
new file mode 100644
index 0000000000..4da9c93fb3
--- /dev/null
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -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
+{
+ ///
+ /// Provides extension methods to the class.
+ ///
+ public static partial class UmbracoBuilderExtensions
+ {
+ ///
+ /// Gets the mappers collection builder.
+ ///
+ /// The builder.
+ public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
+ }
+}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
new file mode 100644
index 0000000000..94c1e3dcfa
--- /dev/null
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -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
+ {
+ ///
+ /// Adds all core Umbraco services required to run which may be replaced later in the pipeline
+ ///
+ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder)
+ {
+ builder
+ .AddMainDom()
+ .AddLogging();
+
+ builder.Services.AddUnique();
+ builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase());
+ builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext);
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+
+ // 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(); // implements both IScopeProvider and IScopeAccessor
+ builder.Services.AddUnique(f => f.GetRequiredService());
+ builder.Services.AddUnique(f => f.GetRequiredService());
+
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+
+ // register database builder
+ // *not* a singleton, don't want to keep it around
+ builder.Services.AddTransient();
+
+ // register manifest parser, will be injected in collection builders where needed
+ builder.Services.AddUnique();
+
+ // register the manifest filter collection builder (collection is empty by default)
+ builder.ManifestFilters();
+
+ builder.MediaUrlGenerators()
+ .Add()
+ .Add();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique(factory
+ => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetRequiredService>().Value)));
+
+ builder.Services.AddUnique(factory => new MigrationBuilder(factory));
+
+ builder.Services.AddUnique();
+
+ // register the published snapshot accessor - the "current" published snapshot is in the umbraco context
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ // Config manipulator
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+
+ // 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();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ // register *all* checks, except those marked [HideFromTypeFinder] of course
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddScoped();
+
+ // replace
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddScoped();
+
+ builder.Services.AddUnique();
+ builder.Services.AddScoped(factory =>
+ {
+ var umbCtx = factory.GetRequiredService();
+ return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetRequiredService(), factory.GetRequiredService());
+ });
+
+ // register accessors for cultures
+ builder.Services.AddUnique();
+
+ builder.Services.AddSingleton();
+
+ builder.Services.AddUnique();
+
+ // Register noop versions for examine to be overridden by examine
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ builder.Services.AddUnique();
+
+ builder.AddInstaller();
+
+ return builder;
+ }
+
+ ///
+ /// Adds logging requirements for Umbraco
+ ///
+ private static IUmbracoBuilder AddLogging(this IUmbracoBuilder builder)
+ {
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ builder.Services.AddUnique();
+ return builder;
+ }
+
+ private static IUmbracoBuilder AddMainDom(this IUmbracoBuilder builder)
+ {
+ builder.Services.AddUnique(factory =>
+ {
+ var globalSettings = factory.GetRequiredService>().Value;
+ var connectionStrings = factory.GetRequiredService>().Value;
+ var hostingEnvironment = factory.GetRequiredService();
+
+ var dbCreator = factory.GetRequiredService();
+ var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ var loggerFactory = factory.GetRequiredService();
+
+ return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
+ ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment)
+ : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment);
+ });
+
+ return builder;
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs
new file mode 100644
index 0000000000..db1d22e86c
--- /dev/null
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs
@@ -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
+{
+ ///
+ /// Provides extension methods to the class.
+ ///
+ public static partial class UmbracoBuilderExtensions
+ {
+ ///
+ /// Adds distributed cache support
+ ///
+ public static IUmbracoBuilder AddDistributedCache(this IUmbracoBuilder builder)
+ {
+ builder.SetDatabaseServerMessengerCallbacks(GetCallbacks);
+ builder.SetServerMessenger();
+ builder.AddNotificationHandler();
+
+ builder.Services.AddUnique();
+ return builder;
+ }
+
+ ///
+ /// Sets the server registrar.
+ ///
+ /// The type of the server registrar.
+ /// The builder.
+ public static void SetServerRegistrar(this IUmbracoBuilder builder)
+ where T : class, IServerRoleAccessor
+ => builder.Services.AddUnique();
+
+ ///
+ /// Sets the server registrar.
+ ///
+ /// The builder.
+ /// A function creating a server registrar.
+ public static void SetServerRegistrar(this IUmbracoBuilder builder, Func factory)
+ => builder.Services.AddUnique(factory);
+
+ ///
+ /// Sets the server registrar.
+ ///
+ /// The builder.
+ /// A server registrar.
+ public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar)
+ => builder.Services.AddUnique(registrar);
+
+ ///
+ /// Sets the database server messenger options.
+ ///
+ /// The builder.
+ /// A function creating the options.
+ /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.
+ public static void SetDatabaseServerMessengerCallbacks(this IUmbracoBuilder builder, Func factory)
+ => builder.Services.AddUnique(factory);
+
+ ///
+ /// Sets the database server messenger options.
+ ///
+ /// The builder.
+ /// Options.
+ /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default.
+ public static void SetDatabaseServerMessengerOptions(this IUmbracoBuilder builder, DatabaseServerMessengerCallbacks options)
+ => builder.Services.AddUnique(options);
+
+ ///
+ /// Sets the server messenger.
+ ///
+ /// The type of the server registrar.
+ /// The builder.
+ public static void SetServerMessenger(this IUmbracoBuilder builder)
+ where T : class, IServerMessenger
+ => builder.Services.AddUnique();
+
+ ///
+ /// Sets the server messenger.
+ ///
+ /// The builder.
+ /// A function creating a server messenger.
+ public static void SetServerMessenger(this IUmbracoBuilder builder, Func factory)
+ => builder.Services.AddUnique(factory);
+
+ ///
+ /// Sets the server messenger.
+ ///
+ /// The builder.
+ /// A server messenger.
+ 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();
+
+ // 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();
+ indexRebuilder.RebuildIndexes(false, 5000);
+ }
+ }
+ };
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
similarity index 88%
rename from src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs
rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
index f098cfaf57..5c61fd2c60 100644
--- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs
@@ -1,16 +1,15 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
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
@@ -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
// 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
// interface is really for end-users to get access to filesystems.
- builder.Services.AddUnique(factory => factory.CreateInstance(factory));
+ builder.Services.AddUnique(factory => factory.CreateInstance(factory));
// register IFileSystems, which gives access too all filesystems
- builder.Services.AddUnique(factory => factory.GetRequiredService());
+ builder.Services.AddUnique(factory => factory.GetRequiredService());
// register the scheme for media paths
builder.Services.AddUnique();
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
new file mode 100644
index 0000000000..e5bdd844d8
--- /dev/null
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs
@@ -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
+ {
+ ///
+ /// Adds the services for the Umbraco installer
+ ///
+ internal static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder)
+ {
+ // register the installer steps
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+
+ // TODO: Add these back once we have a compatible Starter kit
+ // composition.Services.AddScoped();
+ // composition.Services.AddScoped();
+ // composition.Services.AddScoped();
+ builder.Services.AddScoped();
+
+ builder.Services.AddTransient