diff --git a/build/Build.proj b/build/Build.proj
index 051e02de5c..e937df15bd 100644
--- a/build/Build.proj
+++ b/build/Build.proj
@@ -97,15 +97,8 @@
-
-
-
-
-
-
-
@@ -151,14 +144,14 @@
DestinationFiles="@(WebPiFiles->'$(WebPiFolder)%(RecursiveDir)%(Filename)%(Extension)')" />
-
+
-
+
@@ -225,6 +218,7 @@
+
@@ -233,6 +227,11 @@
OverwriteReadOnlyFiles="true"
SkipUnchangedFiles="false" />
+
+
en-US
umbraco
-
+
-
-
-
+
+
@@ -27,16 +26,18 @@
-
+
-
-
+
+
-
-
+
+
+
+
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 76d72935e9..61a8188804 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -26,7 +26,6 @@
-
@@ -41,6 +40,7 @@
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt
index 1f5bbb0164..e4d621f693 100644
--- a/build/NuSpecs/tools/Dashboard.config.install.xdt
+++ b/build/NuSpecs/tools/Dashboard.config.install.xdt
@@ -35,6 +35,11 @@
views/dashboard/developer/examinemanagement.html
+
+
+ views/dashboard/developer/xmldataintegrityreport.html
+
+
diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt
new file mode 100644
index 0000000000..c34963f2b0
--- /dev/null
+++ b/build/NuSpecs/tools/Views.Web.config.install.xdt
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index 75eb8dc3cf..fbe2cbe439 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -20,9 +20,11 @@
-
+
+
+
@@ -144,7 +146,12 @@
-
+
+
+
+
+
+
@@ -160,12 +167,32 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1
index b479ecf340..1fa9352be7 100644
--- a/build/NuSpecs/tools/install.core.ps1
+++ b/build/NuSpecs/tools/install.core.ps1
@@ -58,6 +58,7 @@ if ($project) {
if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\MySql.Data.dll) { Remove-Item $umbracoBinFolder\MySql.Data.dll -Force -Confirm:$false }
diff --git a/build/NuSpecs/tools/log4net.config.install.xdt b/build/NuSpecs/tools/log4net.config.install.xdt
new file mode 100644
index 0000000000..f4d3caf7fc
--- /dev/null
+++ b/build/NuSpecs/tools/log4net.config.install.xdt
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt
index 6acc793e05..45f43206fe 100644
--- a/build/NuSpecs/tools/trees.config.install.xdt
+++ b/build/NuSpecs/tools/trees.config.install.xdt
@@ -7,12 +7,12 @@
-
+
-
+
diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt
index f767faaa60..b31e79056f 100644
--- a/build/UmbracoVersion.txt
+++ b/build/UmbracoVersion.txt
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.3.0
-beta2
\ No newline at end of file
+beta3
\ No newline at end of file
diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings
new file mode 100644
index 0000000000..662f95686e
--- /dev/null
+++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ CSharp50
\ No newline at end of file
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index e4258e943d..dbc0f2d41d 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.3.0")]
-[assembly: AssemblyInformationalVersion("7.3.0-beta2")]
\ No newline at end of file
+[assembly: AssemblyInformationalVersion("7.3.0-beta3")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs
index b48d4eb5e9..7e306a6361 100644
--- a/src/Umbraco.Core/ApplicationContext.cs
+++ b/src/Umbraco.Core/ApplicationContext.cs
@@ -1,18 +1,14 @@
using System;
using System.Configuration;
using System.Threading;
-using System.Web;
-using System.Web.Caching;
-using Umbraco.Core.Cache;
+using System.Threading.Tasks;
using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Profiling;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
-
namespace Umbraco.Core
{
///
@@ -35,6 +31,7 @@ namespace Umbraco.Core
if (dbContext == null) throw new ArgumentNullException("dbContext");
if (serviceContext == null) throw new ArgumentNullException("serviceContext");
if (cache == null) throw new ArgumentNullException("cache");
+ if (logger == null) throw new ArgumentNullException("logger");
_databaseContext = dbContext;
_services = serviceContext;
ApplicationCache = cache;
@@ -49,7 +46,7 @@ namespace Umbraco.Core
///
///
///
- [Obsolete("Use the other constructor specifying an ILogger instead")]
+ [Obsolete("Use the other constructor specifying a ProfilingLogger instead")]
public ApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache)
: this(dbContext, serviceContext, cache,
new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler))
@@ -60,10 +57,26 @@ namespace Umbraco.Core
/// Creates a basic app context
///
///
+ [Obsolete("Use the other constructor specifying a ProfilingLogger instead")]
public ApplicationContext(CacheHelper cache)
{
+ if (cache == null) throw new ArgumentNullException("cache");
ApplicationCache = cache;
+ ProfilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler);
+ Init();
+ }
+ ///
+ /// Creates a basic app context
+ ///
+ ///
+ ///
+ public ApplicationContext(CacheHelper cache, ProfilingLogger logger)
+ {
+ if (cache == null) throw new ArgumentNullException("cache");
+ if (logger == null) throw new ArgumentNullException("logger");
+ ApplicationCache = cache;
+ ProfilingLogger = logger;
Init();
}
@@ -80,13 +93,13 @@ namespace Umbraco.Core
///
public static ApplicationContext EnsureContext(ApplicationContext appContext, bool replaceContext)
{
- if (ApplicationContext.Current != null)
+ if (Current != null)
{
if (!replaceContext)
- return ApplicationContext.Current;
+ return Current;
}
- ApplicationContext.Current = appContext;
- return ApplicationContext.Current;
+ Current = appContext;
+ return Current;
}
///
@@ -106,14 +119,14 @@ namespace Umbraco.Core
[Obsolete("Use the other method specifying an ProfilingLogger instead")]
public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, bool replaceContext)
{
- if (ApplicationContext.Current != null)
+ if (Current != null)
{
if (!replaceContext)
- return ApplicationContext.Current;
+ return Current;
}
var ctx = new ApplicationContext(dbContext, serviceContext, cache);
- ApplicationContext.Current = ctx;
- return ApplicationContext.Current;
+ Current = ctx;
+ return Current;
}
///
@@ -133,14 +146,14 @@ namespace Umbraco.Core
///
public static ApplicationContext EnsureContext(DatabaseContext dbContext, ServiceContext serviceContext, CacheHelper cache, ProfilingLogger logger, bool replaceContext)
{
- if (ApplicationContext.Current != null)
+ if (Current != null)
{
if (!replaceContext)
- return ApplicationContext.Current;
+ return Current;
}
var ctx = new ApplicationContext(dbContext, serviceContext, cache, logger);
- ApplicationContext.Current = ctx;
- return ApplicationContext.Current;
+ Current = ctx;
+ return Current;
}
///
@@ -232,7 +245,7 @@ namespace Umbraco.Core
/// - has the SystemDirectories.Umbraco path
/// - does not end with a slash
/// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl:
- /// - if umbracoSettings:settings/web.routing/@appUrl is set, use the value (new setting)
+ /// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting)
/// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility)
/// - otherwise, use the url of the (first) request.
/// Not locking, does not matter if several threads write to this.
@@ -242,9 +255,10 @@ namespace Umbraco.Core
/// - http://issues.umbraco.org/issue/U4-5728
/// - http://issues.umbraco.org/issue/U4-5391
///
- internal string UmbracoApplicationUrl {
- get
- {
+ internal string UmbracoApplicationUrl
+ {
+ get
+ {
// if initialized, return
if (_umbracoApplicationUrl != null) return _umbracoApplicationUrl;
@@ -261,11 +275,15 @@ namespace Umbraco.Core
}
internal string _umbracoApplicationUrl; // internal for tests
- private Lazy _configured;
+ private Lazy _configured;
+ internal MainDom MainDom { get; private set; }
+
private void Init()
- {
-
+ {
+ MainDom = new MainDom(ProfilingLogger.Logger);
+ MainDom.Acquire();
+
//Create the lazy value to resolve whether or not the application is 'configured'
_configured = new Lazy(() =>
{
@@ -304,7 +322,7 @@ namespace Umbraco.Core
}
});
- }
+ }
private string ConfigurationStatus
{
@@ -321,12 +339,6 @@ namespace Umbraco.Core
}
}
- private void AssertIsReady()
- {
- if (!this.IsReady)
- throw new Exception("ApplicationContext is not ready yet.");
- }
-
private void AssertIsNotReady()
{
if (this.IsReady)
diff --git a/src/Umbraco.Core/ApplicationEventHandler.cs b/src/Umbraco.Core/ApplicationEventHandler.cs
index a725a08a8e..8d97baac95 100644
--- a/src/Umbraco.Core/ApplicationEventHandler.cs
+++ b/src/Umbraco.Core/ApplicationEventHandler.cs
@@ -74,7 +74,7 @@ namespace Umbraco.Core
///
private bool ShouldExecute(ApplicationContext applicationContext)
{
- if (applicationContext.IsConfigured && applicationContext.DatabaseContext.CanConnect)
+ if (applicationContext.IsConfigured && applicationContext.DatabaseContext.IsDatabaseConfigured)
{
return true;
}
@@ -84,7 +84,7 @@ namespace Umbraco.Core
return true;
}
- if (!applicationContext.DatabaseContext.CanConnect && ExecuteWhenDatabaseNotConfigured)
+ if (!applicationContext.DatabaseContext.IsDatabaseConfigured && ExecuteWhenDatabaseNotConfigured)
{
return true;
}
diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs
index 608b19a700..b4e8e64312 100644
--- a/src/Umbraco.Core/AsyncLock.cs
+++ b/src/Umbraco.Core/AsyncLock.cs
@@ -70,7 +70,7 @@ namespace Umbraco.Core
{
var wait = _semaphore != null
? _semaphore.WaitAsync()
- : WaitOneAsync(_semaphore2);
+ : _semaphore2.WaitOneAsync();
return wait.IsCompleted
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
@@ -83,7 +83,7 @@ namespace Umbraco.Core
{
var wait = _semaphore != null
? _semaphore.WaitAsync(millisecondsTimeout)
- : WaitOneAsync(_semaphore2, millisecondsTimeout);
+ : _semaphore2.WaitOneAsync(millisecondsTimeout);
return wait.IsCompleted
? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named
@@ -181,40 +181,5 @@ namespace Umbraco.Core
Dispose(false);
}
}
-
- // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100
- // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html
- // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex...
- // version below should be OK
-
- private static Task WaitOneAsync(WaitHandle handle, int millisecondsTimeout = Timeout.Infinite)
- {
- var tcs = new TaskCompletionSource
/// The version comment.
- public static string CurrentComment { get { return "beta2"; } }
+ public static string CurrentComment { get { return "beta3"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index 998bda32f2..80f5a14662 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -2,10 +2,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Web;
using AutoMapper;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Events;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Mapping;
@@ -39,15 +42,15 @@ namespace Umbraco.Core
///
public class CoreBootManager : IBootManager
{
- private ProfilingLogger _profilingLogger;
+ protected ProfilingLogger ProfilingLogger { get; private set; }
private DisposableTimer _timer;
private bool _isInitialized = false;
private bool _isStarted = false;
private bool _isComplete = false;
private readonly IServiceProvider _serviceProvider = new ActivatorServiceProvider();
private readonly UmbracoApplicationBase _umbracoApplication;
- protected ApplicationContext ApplicationContext { get; set; }
- protected CacheHelper ApplicationCache { get; set; }
+ protected ApplicationContext ApplicationContext { get; private set; }
+ protected CacheHelper ApplicationCache { get; private set; }
protected PluginManager PluginManager { get; private set; }
protected UmbracoApplicationBase UmbracoApplication
@@ -66,6 +69,14 @@ namespace Umbraco.Core
_umbracoApplication = umbracoApplication;
}
+ internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger)
+ {
+ if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication");
+ if (logger == null) throw new ArgumentNullException("logger");
+ _umbracoApplication = umbracoApplication;
+ ProfilingLogger = logger;
+ }
+
public virtual IBootManager Initialize()
{
if (_isInitialized)
@@ -74,17 +85,17 @@ namespace Umbraco.Core
InitializeLoggerResolver();
InitializeProfilerResolver();
- _profilingLogger = new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler);
+ ProfilingLogger = ProfilingLogger?? new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler);
- _timer = _profilingLogger.TraceDuration(
- string.Format("Umbraco application ({0}) starting", UmbracoVersion.GetSemanticVersion().ToSemanticString()),
+ _timer = ProfilingLogger.TraceDuration(
+ string.Format("Umbraco {0} application starting on {1}", UmbracoVersion.GetSemanticVersion().ToSemanticString(), NetworkHelper.MachineName),
"Umbraco application startup complete");
- CreateApplicationCache();
+ ApplicationCache = CreateApplicationCache();
//create and set the plugin manager (I'd much prefer to not use this singleton anymore but many things are using it unfortunately and
// the way that it is setup, there must only ever be one per app so without IoC it would be hard to make this not a singleton)
- PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, _profilingLogger);
+ PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, ProfilingLogger);
PluginManager.Current = PluginManager;
//Create the legacy prop-eds mapping
@@ -92,38 +103,48 @@ namespace Umbraco.Core
LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors();
//create database and service contexts for the app context
- var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, LoggerResolver.Current.Logger);
+ var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, ProfilingLogger.Logger);
Database.Mapper = new PetaPocoMapper();
var dbContext = new DatabaseContext(
dbFactory,
- LoggerResolver.Current.Logger,
- SqlSyntaxProviders.CreateDefault(LoggerResolver.Current.Logger));
+ ProfilingLogger.Logger,
+ SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger));
//initialize the DatabaseContext
dbContext.Initialize();
-
- var serviceContext = new ServiceContext(
- new RepositoryFactory(ApplicationCache, LoggerResolver.Current.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
- new PetaPocoUnitOfWorkProvider(dbFactory),
- new FileUnitOfWorkProvider(),
- new PublishingStrategy(),
- ApplicationCache,
- LoggerResolver.Current.Logger);
- CreateApplicationContext(dbContext, serviceContext);
+ //get the service context
+ var serviceContext = CreateServiceContext(dbContext, dbFactory);
+
+ //set property and singleton from response
+ ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext);
InitializeApplicationEventsResolver();
InitializeResolvers();
-
-
InitializeModelMappers();
- //now we need to call the initialize methods
- ApplicationEventsResolver.Current.ApplicationEventHandlers
- .ForEach(x => x.OnApplicationInitialized(UmbracoApplication, ApplicationContext));
+ using (ProfilingLogger.DebugDuration(
+ string.Format("Executing {0} IApplicationEventHandler.OnApplicationInitialized", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()),
+ "Finished executing IApplicationEventHandler.OnApplicationInitialized"))
+ {
+ //now we need to call the initialize methods
+ ApplicationEventsResolver.Current.ApplicationEventHandlers
+ .ForEach(x =>
+ {
+ try
+ {
+ x.OnApplicationInitialized(UmbracoApplication, ApplicationContext);
+ }
+ catch (Exception ex)
+ {
+ ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex);
+ throw;
+ }
+ });
+ }
_isInitialized = true;
@@ -131,28 +152,48 @@ namespace Umbraco.Core
}
///
- /// Creates and assigns the application context singleton
+ /// Creates and returns the service context for the app
///
///
- ///
- protected virtual void CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext)
+ ///
+ ///
+ protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory)
{
- //create the ApplicationContext
- ApplicationContext = ApplicationContext.Current = new ApplicationContext(dbContext, serviceContext, ApplicationCache, _profilingLogger);
+ //default transient factory
+ var msgFactory = new TransientMessagesFactory();
+ return new ServiceContext(
+ new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
+ new PetaPocoUnitOfWorkProvider(dbFactory),
+ new FileUnitOfWorkProvider(),
+ new PublishingStrategy(msgFactory, ProfilingLogger.Logger),
+ ApplicationCache,
+ ProfilingLogger.Logger,
+ msgFactory);
}
///
- /// Creates and assigns the ApplicationCache based on a new instance of System.Web.Caching.Cache
+ /// Creates and returns the application context for the app
///
- protected virtual void CreateApplicationCache()
+ ///
+ ///
+ protected virtual ApplicationContext CreateApplicationContext(DatabaseContext dbContext, ServiceContext serviceContext)
+ {
+ //create the ApplicationContext
+ return new ApplicationContext(dbContext, serviceContext, ApplicationCache, ProfilingLogger);
+ }
+
+ ///
+ /// Creates and returns the CacheHelper for the app
+ ///
+ protected virtual CacheHelper CreateApplicationCache()
{
var cacheHelper = new CacheHelper(
- new ObjectCacheRuntimeCacheProvider(),
- new StaticCacheProvider(),
+ new ObjectCacheRuntimeCacheProvider(),
+ new StaticCacheProvider(),
//we have no request based cache when not running in web-based context
- new NullCacheProvider());
+ new NullCacheProvider());
- ApplicationCache = cacheHelper;
+ return cacheHelper;
}
///
@@ -178,7 +219,7 @@ namespace Umbraco.Core
///
protected virtual void InitializeLoggerResolver()
{
- LoggerResolver.Current = new LoggerResolver(Logger.CreateWithDefaultLog4NetConfiguration())
+ LoggerResolver.Current = new LoggerResolver(ProfilingLogger == null ? Logger.CreateWithDefaultLog4NetConfiguration() : ProfilingLogger.Logger)
{
//This is another special resolver that needs to be resolvable before resolution is frozen
//since it is used for profiling the application startup
@@ -192,7 +233,7 @@ namespace Umbraco.Core
protected virtual void InitializeProfilerResolver()
{
//By default we'll initialize the Log profiler (in the web project, we'll override with the web profiler)
- ProfilerResolver.Current = new ProfilerResolver(new LogProfiler(LoggerResolver.Current.Logger))
+ ProfilerResolver.Current = new ProfilerResolver(ProfilingLogger == null ? new LogProfiler(LoggerResolver.Current.Logger) : ProfilingLogger.Profiler)
{
//This is another special resolver that needs to be resolvable before resolution is frozen
//since it is used for profiling the application startup
@@ -213,7 +254,7 @@ namespace Umbraco.Core
//... and set the special flag to let us resolve before frozen resolution
ApplicationEventsResolver.Current = new ApplicationEventsResolver(
ServiceProvider,
- LoggerResolver.Current.Logger,
+ ProfilingLogger.Logger,
PluginManager.ResolveApplicationStartupHandlers())
{
CanResolveBeforeFrozen = true
@@ -228,7 +269,7 @@ namespace Umbraco.Core
/// Absolute
protected virtual void InitializeApplicationRootPath(string rootPath)
{
- Umbraco.Core.IO.IOHelper.SetRootDirectory(rootPath);
+ IOHelper.SetRootDirectory(rootPath);
}
///
@@ -242,11 +283,27 @@ namespace Umbraco.Core
if (_isStarted)
throw new InvalidOperationException("The boot manager has already been initialized");
- //call OnApplicationStarting of each application events handler
- ApplicationEventsResolver.Current.ApplicationEventHandlers
- .ForEach(x => x.OnApplicationStarting(UmbracoApplication, ApplicationContext));
+ using (ProfilingLogger.DebugDuration(
+ string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarting", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()),
+ "Finished executing IApplicationEventHandler.OnApplicationStarting"))
+ {
+ //call OnApplicationStarting of each application events handler
+ ApplicationEventsResolver.Current.ApplicationEventHandlers
+ .ForEach(x =>
+ {
+ try
+ {
+ x.OnApplicationStarting(UmbracoApplication, ApplicationContext);
+ }
+ catch (Exception ex)
+ {
+ ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex);
+ throw;
+ }
+ });
+ }
- if (afterStartup != null)
+ if (afterStartup != null)
{
afterStartup(ApplicationContext.Current);
}
@@ -265,12 +322,37 @@ namespace Umbraco.Core
{
if (_isComplete)
throw new InvalidOperationException("The boot manager has already been completed");
-
+
FreezeResolution();
- //call OnApplicationStarting of each application events handler
- ApplicationEventsResolver.Current.ApplicationEventHandlers
- .ForEach(x => x.OnApplicationStarted(UmbracoApplication, ApplicationContext));
+ //Here we need to make sure the db can be connected to
+ EnsureDatabaseConnection();
+
+
+ //This is a special case for the user service, we need to tell it if it's an upgrade, if so we need to ensure that
+ // exceptions are bubbled up if a user is attempted to be persisted during an upgrade (i.e. when they auth to login)
+ ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true;
+
+
+ using (ProfilingLogger.DebugDuration(
+ string.Format("Executing {0} IApplicationEventHandler.OnApplicationStarted", ApplicationEventsResolver.Current.ApplicationEventHandlers.Count()),
+ "Finished executing IApplicationEventHandler.OnApplicationStarted"))
+ {
+ //call OnApplicationStarting of each application events handler
+ ApplicationEventsResolver.Current.ApplicationEventHandlers
+ .ForEach(x =>
+ {
+ try
+ {
+ x.OnApplicationStarted(UmbracoApplication, ApplicationContext);
+ }
+ catch (Exception ex)
+ {
+ ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex);
+ throw;
+ }
+ });
+ }
//Now, startup all of our legacy startup handler
ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers();
@@ -288,6 +370,32 @@ namespace Umbraco.Core
//stop the timer and log the output
_timer.Dispose();
return this;
+ }
+
+ ///
+ /// We cannot continue if the db cannot be connected to
+ ///
+ private void EnsureDatabaseConnection()
+ {
+ if (ApplicationContext.IsConfigured == false) return;
+ if (ApplicationContext.DatabaseContext.IsDatabaseConfigured == false) return;
+
+ var currentTry = 0;
+ while (currentTry < 5)
+ {
+ if (ApplicationContext.DatabaseContext.CanConnect)
+ break;
+
+ //wait and retry
+ Thread.Sleep(1000);
+ currentTry++;
+ }
+
+ if (currentTry == 5)
+ {
+ throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database.");
+ }
+
}
///
@@ -307,12 +415,12 @@ namespace Umbraco.Core
ApplicationCache.RuntimeCache,
new ManifestParser(new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), ApplicationCache.RuntimeCache));
- PropertyEditorResolver.Current = new PropertyEditorResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolvePropertyEditors(), builder);
- ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, LoggerResolver.Current.Logger, () => PluginManager.ResolveParameterEditors(), builder);
+ PropertyEditorResolver.Current = new PropertyEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolvePropertyEditors(), builder);
+ ParameterEditorResolver.Current = new ParameterEditorResolver(ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveParameterEditors(), builder);
//setup the validators resolver with our predefined validators
ValidatorsResolver.Current = new ValidatorsResolver(
- ServiceProvider, LoggerResolver.Current.Logger, new[]
+ ServiceProvider, ProfilingLogger.Logger, new[]
{
new Lazy(() => typeof (RequiredManifestValueValidator)),
new Lazy(() => typeof (RegexValidator)),
@@ -321,67 +429,76 @@ namespace Umbraco.Core
new Lazy(() => typeof (IntegerValidator)),
});
- //by default we'll use the standard configuration based sync
- ServerRegistrarResolver.Current = new ServerRegistrarResolver(
- new ConfigServerRegistrar());
+ //by default we'll use the db server registrar unless the developer has the legacy
+ // dist calls enabled, in which case we'll use the config server registrar
+ if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled)
+ {
+ ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar());
+ }
+ else
+ {
+ ServerRegistrarResolver.Current = new ServerRegistrarResolver(
+ new DatabaseServerRegistrar(
+ new Lazy(() => ApplicationContext.Services.ServerRegistrationService),
+ new DatabaseServerRegistrarOptions()));
+ }
+
- //by default (outside of the web) we'll use the default server messenger without
- //supplying a username/password, this will automatically disable distributed calls
- // .. we'll override this in the WebBootManager
+ //by default we'll use the database server messenger with default options (no callbacks),
+ // this will be overridden in the web startup
ServerMessengerResolver.Current = new ServerMessengerResolver(
- new WebServiceServerMessenger());
+ new DatabaseServerMessenger(ApplicationContext, true, new DatabaseServerMessengerOptions()));
MappingResolver.Current = new MappingResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveAssignedMapperTypes());
-
//RepositoryResolver.Current = new RepositoryResolver(
// new RepositoryFactory(ApplicationCache));
CacheRefreshersResolver.Current = new CacheRefreshersResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveCacheRefreshers());
DataTypesResolver.Current = new DataTypesResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveDataTypes());
MacroFieldEditorsResolver.Current = new MacroFieldEditorsResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveMacroRenderings());
PackageActionsResolver.Current = new PackageActionsResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolvePackageActions());
ActionsResolver.Current = new ActionsResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveActions());
//the database migration objects
MigrationResolver.Current = new MigrationResolver(
- LoggerResolver.Current.Logger,
+ ProfilingLogger.Logger,
() => PluginManager.ResolveTypes());
// todo: remove once we drop IPropertyEditorValueConverter support.
PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
PluginManager.ResolvePropertyEditorValueConverters());
// need to filter out the ones we dont want!!
PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
PluginManager.ResolveTypes());
// use the new DefaultShortStringHelper
ShortStringHelperResolver.Current = new ShortStringHelperResolver(
//new LegacyShortStringHelper());
- new DefaultShortStringHelper().WithDefaultConfig());
+ new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig());
UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver(
- ServiceProvider, LoggerResolver.Current.Logger,
+ ServiceProvider, ProfilingLogger.Logger,
typeof(DefaultUrlSegmentProvider));
// by default, no factory is activated
diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs
index f3665ae0b8..7f8cbbf0f7 100644
--- a/src/Umbraco.Core/DatabaseContext.cs
+++ b/src/Umbraco.Core/DatabaseContext.cs
@@ -30,8 +30,6 @@ namespace Umbraco.Core
private readonly ILogger _logger;
private readonly SqlSyntaxProviders _syntaxProviders;
private bool _configured;
- private bool _canConnect;
- private volatile bool _connectCheck = false;
private readonly object _locker = new object();
private string _connectionString;
private string _providerName;
@@ -113,21 +111,9 @@ namespace Umbraco.Core
get
{
if (IsDatabaseConfigured == false) return false;
-
- //double check lock so that it is only checked once and is fast
- if (_connectCheck == false)
- {
- lock (_locker)
- {
- if (_canConnect == false)
- {
- _canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
- _connectCheck = true;
- }
- }
- }
-
- return _canConnect;
+ var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
+ LogHelper.Info("CanConnect = " + canConnect);
+ return canConnect;
}
}
diff --git a/src/Umbraco.Core/DisposableObject.cs b/src/Umbraco.Core/DisposableObject.cs
index f054b53c98..516a9712e5 100644
--- a/src/Umbraco.Core/DisposableObject.cs
+++ b/src/Umbraco.Core/DisposableObject.cs
@@ -4,69 +4,58 @@ using System.Threading;
namespace Umbraco.Core
{
///
- /// Abstract implementation of logic commonly required to safely handle disposable unmanaged resources.
+ /// Abstract implementation of IDisposable.
///
+ ///
+ /// Can also be used as a pattern for when inheriting is not possible.
+ ///
+ /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx
+ /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
+ ///
+ /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor
+ /// has allocated disposable objects, it should take care of disposing them.
+ ///
public abstract class DisposableObject : IDisposable
{
private bool _disposed;
- private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim();
+ private readonly object _locko = new object();
- ///
- /// Gets a value indicating whether this instance is disposed.
- ///
- ///
- /// true if this instance is disposed; otherwise, false.
- ///
- public bool IsDisposed
- {
- get { return _disposed; }
- }
+ // gets a value indicating whether this instance is disposed.
+ // for internal tests only (not thread safe)
+ //TODO make this internal + rename "Disposed" when we can break compatibility
+ public bool IsDisposed { get { return _disposed; } }
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- /// 2
+ // implements IDisposable
public void Dispose()
{
Dispose(true);
-
- // Use SupressFinalize in case a subclass of this type implements a finalizer.
GC.SuppressFinalize(this);
}
+ // finalizer
~DisposableObject()
{
- // Run dispose but let the class know it was due to the finalizer running.
Dispose(false);
}
- protected virtual void Dispose(bool disposing)
+ //TODO make this private, non-virtual when we can break compatibility
+ protected virtual void Dispose(bool disposing)
{
- // Only operate if we haven't already disposed
- if (IsDisposed || !disposing) return;
+ lock (_locko)
+ {
+ if (_disposed) return;
+ _disposed = true;
+ }
- using (new WriteLock(_disposalLocker))
- {
- // Check again now we're inside the lock
- if (IsDisposed) return;
+ DisposeUnmanagedResources();
- // Call to actually release resources. This method is only
- // kept separate so that the entire disposal logic can be used as a VS snippet
- DisposeResources();
-
- // Indicate that the instance has been disposed.
- _disposed = true;
- }
+ if (disposing)
+ DisposeResources();
}
- ///
- /// Handles the disposal of resources. Derived from abstract class which handles common required locking logic.
- ///
protected abstract void DisposeResources();
protected virtual void DisposeUnmanagedResources()
- {
-
- }
+ { }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs
index 79b48af3ab..58d4d453b7 100644
--- a/src/Umbraco.Core/EnumerableExtensions.cs
+++ b/src/Umbraco.Core/EnumerableExtensions.cs
@@ -101,6 +101,7 @@ namespace Umbraco.Core
/// The select child.
/// Item type
/// list of TItem
+ [Obsolete("Do not use, use SelectRecursive instead which has far less potential of re-iterating an iterator which may cause significantly more SQL queries")]
public static IEnumerable FlattenList(this IEnumerable e, Func> f)
{
return e.SelectMany(c => f(c).FlattenList(f)).Concat(e);
diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs
index 80e69ae353..506ba1c22e 100644
--- a/src/Umbraco.Core/Events/CancellableEventArgs.cs
+++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs
@@ -3,7 +3,7 @@ using System.Security.Permissions;
namespace Umbraco.Core.Events
{
- ///
+ ///
/// Event args for that can support cancellation
///
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
@@ -11,15 +11,30 @@ namespace Umbraco.Core.Events
{
private bool _cancel;
- public CancellableEventArgs(bool canCancel)
+ public CancellableEventArgs(bool canCancel, EventMessages eventMessages)
+ {
+ if (eventMessages == null) throw new ArgumentNullException("eventMessages");
+ CanCancel = canCancel;
+ Messages = eventMessages;
+ }
+
+ public CancellableEventArgs(bool canCancel)
{
CanCancel = canCancel;
- }
+ //create a standalone messages
+ Messages = new EventMessages();
+ }
- public CancellableEventArgs()
+ public CancellableEventArgs(EventMessages eventMessages)
+ : this(true, eventMessages)
+ {
+ }
+
+ public CancellableEventArgs()
: this(true)
{
}
+
///
/// Flag to determine if this instance will support being cancellable
///
@@ -32,7 +47,7 @@ namespace Umbraco.Core.Events
{
get
{
- if (!CanCancel)
+ if (CanCancel == false)
{
throw new InvalidOperationException("This event argument class does not support cancelling.");
}
@@ -40,12 +55,28 @@ namespace Umbraco.Core.Events
}
set
{
- if (!CanCancel)
+ if (CanCancel == false)
{
throw new InvalidOperationException("This event argument class does not support cancelling.");
}
_cancel = value;
}
}
- }
+
+ ///
+ /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message
+ ///
+ ///
+ public void CancelOperation(EventMessage cancelationMessage)
+ {
+ Cancel = true;
+ cancelationMessage.IsDefaultEventMessage = true;
+ Messages.Add(cancelationMessage);
+ }
+
+ ///
+ /// Returns the EventMessages object which is used to add messages to the message collection for this event
+ ///
+ public EventMessages Messages { get; private set; }
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
index a51da5652e..726d32d7b0 100644
--- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
+++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
@@ -12,7 +12,18 @@ namespace Umbraco.Core.Events
public class CancellableObjectEventArgs : CancellableEventArgs
{
- public CancellableObjectEventArgs(T eventObject, bool canCancel)
+ public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
+ : base(canCancel, eventMessages)
+ {
+ EventObject = eventObject;
+ }
+
+ public CancellableObjectEventArgs(T eventObject, EventMessages eventMessages)
+ : this(eventObject, true, eventMessages)
+ {
+ }
+
+ public CancellableObjectEventArgs(T eventObject, bool canCancel)
: base(canCancel)
{
EventObject = eventObject;
diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs
index 4edee846cf..1025066bcc 100644
--- a/src/Umbraco.Core/Events/DeleteEventArgs.cs
+++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs
@@ -4,12 +4,56 @@ namespace Umbraco.Core.Events
{
public class DeleteEventArgs : CancellableObjectEventArgs>
{
- ///
- /// Constructor accepting multiple entities that are used in the delete operation
- ///
- ///
- ///
- public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel)
+ ///
+ /// Constructor accepting multiple entities that are used in the delete operation
+ ///
+ ///
+ ///
+ ///
+ public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages)
+ {
+ MediaFilesToDelete = new List();
+ }
+
+ ///
+ /// Constructor accepting multiple entities that are used in the delete operation
+ ///
+ ///
+ ///
+ public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages)
+ {
+ MediaFilesToDelete = new List();
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages)
+ : base(new List { eventObject }, eventMessages)
+ {
+ MediaFilesToDelete = new List();
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ ///
+ public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages)
+ : base(new List { eventObject }, canCancel, eventMessages)
+ {
+ MediaFilesToDelete = new List();
+ }
+
+ ///
+ /// Constructor accepting multiple entities that are used in the delete operation
+ ///
+ ///
+ ///
+ public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel)
{
MediaFilesToDelete = new List();
}
@@ -60,7 +104,13 @@ namespace Umbraco.Core.Events
public class DeleteEventArgs : CancellableEventArgs
{
- public DeleteEventArgs(int id, bool canCancel)
+ public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages)
+ : base(canCancel, eventMessages)
+ {
+ Id = id;
+ }
+
+ public DeleteEventArgs(int id, bool canCancel)
: base(canCancel)
{
Id = id;
diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs
index 99cf145fe0..8c645aead6 100644
--- a/src/Umbraco.Core/Events/EventExtensions.cs
+++ b/src/Umbraco.Core/Events/EventExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
namespace Umbraco.Core.Events
{
@@ -7,16 +8,16 @@ namespace Umbraco.Core.Events
///
public static class EventExtensions
{
- ///
- /// Raises the event and returns a boolean value indicating if the event was cancelled
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static bool IsRaisedEventCancelled(
+ ///
+ /// Raises the event and returns a boolean value indicating if the event was cancelled
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsRaisedEventCancelled(
this TypedEventHandler eventHandler,
TArgs args,
TSender sender)
@@ -27,16 +28,16 @@ namespace Umbraco.Core.Events
return args.Cancel;
}
-
- ///
- /// Raises the event
- ///
- ///
- ///
- ///
- ///
- ///
- public static void RaiseEvent(
+
+ ///
+ /// Raises the event
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void RaiseEvent(
this TypedEventHandler eventHandler,
TArgs args,
TSender sender)
diff --git a/src/Umbraco.Core/Events/EventMessage.cs b/src/Umbraco.Core/Events/EventMessage.cs
new file mode 100644
index 0000000000..473f955d45
--- /dev/null
+++ b/src/Umbraco.Core/Events/EventMessage.cs
@@ -0,0 +1,27 @@
+namespace Umbraco.Core.Events
+{
+ ///
+ /// An event message
+ ///
+ public sealed class EventMessage
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EventMessage(string category, string message, EventMessageType messageType = EventMessageType.Default)
+ {
+ Category = category;
+ Message = message;
+ MessageType = messageType;
+ }
+
+ public string Category { get; private set; }
+ public string Message { get; private set; }
+ public EventMessageType MessageType { get; private set; }
+
+ ///
+ /// This is used to track if this message should be used as a default message so that Umbraco doesn't also append it's own default messages
+ ///
+ internal bool IsDefaultEventMessage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs
new file mode 100644
index 0000000000..2900f3d471
--- /dev/null
+++ b/src/Umbraco.Core/Events/EventMessages.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// Event messages collection
+ ///
+ public sealed class EventMessages : DisposableObject
+ {
+ private readonly List _msgs = new List();
+
+ public void Add(EventMessage msg)
+ {
+ _msgs.Add(msg);
+ }
+
+ public int Count
+ {
+ get { return _msgs.Count; }
+ }
+
+ public IEnumerable GetAll()
+ {
+ return _msgs;
+ }
+
+ protected override void DisposeResources()
+ {
+ _msgs.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs
new file mode 100644
index 0000000000..cb2391186d
--- /dev/null
+++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// Event messages factory
+ ///
+ public interface IEventMessagesFactory
+ {
+ EventMessages Get();
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/MessageType.cs b/src/Umbraco.Core/Events/MessageType.cs
new file mode 100644
index 0000000000..6299a0e7d7
--- /dev/null
+++ b/src/Umbraco.Core/Events/MessageType.cs
@@ -0,0 +1,14 @@
+namespace Umbraco.Core.Events
+{
+ ///
+ /// The type of event message
+ ///
+ public enum EventMessageType
+ {
+ Default = 0,
+ Info = 1,
+ Error = 2,
+ Success = 3,
+ Warning = 4
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/MigrationEventArgs.cs b/src/Umbraco.Core/Events/MigrationEventArgs.cs
index 61fa016332..7eed61484b 100644
--- a/src/Umbraco.Core/Events/MigrationEventArgs.cs
+++ b/src/Umbraco.Core/Events/MigrationEventArgs.cs
@@ -77,7 +77,7 @@ namespace Umbraco.Core.Events
get { return EventObject; }
}
- [Obsolete("Use ConfiguredUmbracoVersion instead")]
+ [Obsolete("Use ConfiguredSemVersion instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public Version ConfiguredVersion
{
diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs
index 107629ff19..0f0a5183a9 100644
--- a/src/Umbraco.Core/Events/MoveEventArgs.cs
+++ b/src/Umbraco.Core/Events/MoveEventArgs.cs
@@ -4,22 +4,51 @@ using System.Linq;
namespace Umbraco.Core.Events
{
- public class MoveEventInfo
- {
- public MoveEventInfo(TEntity entity, string originalPath, int newParentId)
- {
- Entity = entity;
- OriginalPath = originalPath;
- NewParentId = newParentId;
- }
-
- public TEntity Entity { get; set; }
- public string OriginalPath { get; set; }
- public int NewParentId { get; set; }
- }
-
public class MoveEventArgs : CancellableObjectEventArgs
{
+ ///
+ /// Constructor accepting a collection of MoveEventInfo objects
+ ///
+ ///
+ ///
+ ///
+ /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation
+ ///
+ public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo)
+ : base(default(TEntity), canCancel, eventMessages)
+ {
+ if (moveInfo.FirstOrDefault() == null)
+ {
+ throw new ArgumentException("moveInfo argument must contain at least one item");
+ }
+
+ MoveInfoCollection = moveInfo;
+ //assign the legacy props
+ EventObject = moveInfo.First().Entity;
+ ParentId = moveInfo.First().NewParentId;
+ }
+
+ ///
+ /// Constructor accepting a collection of MoveEventInfo objects
+ ///
+ ///
+ ///
+ /// A colleciton of MoveEventInfo objects that exposes all entities that have been moved during a single move operation
+ ///
+ public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo)
+ : base(default(TEntity), eventMessages)
+ {
+ if (moveInfo.FirstOrDefault() == null)
+ {
+ throw new ArgumentException("moveInfo argument must contain at least one item");
+ }
+
+ MoveInfoCollection = moveInfo;
+ //assign the legacy props
+ EventObject = moveInfo.First().Entity;
+ ParentId = moveInfo.First().NewParentId;
+ }
+
///
/// Constructor accepting a collection of MoveEventInfo objects
///
diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs
new file mode 100644
index 0000000000..a74db7f36e
--- /dev/null
+++ b/src/Umbraco.Core/Events/MoveEventInfo.cs
@@ -0,0 +1,16 @@
+namespace Umbraco.Core.Events
+{
+ public class MoveEventInfo
+ {
+ public MoveEventInfo(TEntity entity, string originalPath, int newParentId)
+ {
+ Entity = entity;
+ OriginalPath = originalPath;
+ NewParentId = newParentId;
+ }
+
+ public TEntity Entity { get; set; }
+ public string OriginalPath { get; set; }
+ public int NewParentId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs
index 8e6cd64db1..acfd64e60d 100644
--- a/src/Umbraco.Core/Events/NewEventArgs.cs
+++ b/src/Umbraco.Core/Events/NewEventArgs.cs
@@ -4,7 +4,39 @@ namespace Umbraco.Core.Events
{
public class NewEventArgs : CancellableObjectEventArgs
{
- public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel)
+
+
+ public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages)
+ : base(eventObject, canCancel, eventMessages)
+ {
+ Alias = alias;
+ ParentId = parentId;
+ }
+
+ public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity parent, EventMessages eventMessages)
+ : base(eventObject, canCancel, eventMessages)
+ {
+ Alias = alias;
+ Parent = parent;
+ }
+
+ public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages)
+ : base(eventObject, eventMessages)
+ {
+ Alias = alias;
+ ParentId = parentId;
+ }
+
+ public NewEventArgs(TEntity eventObject, string @alias, TEntity parent, EventMessages eventMessages)
+ : base(eventObject, eventMessages)
+ {
+ Alias = alias;
+ Parent = parent;
+ }
+
+
+
+ public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel)
{
Alias = alias;
ParentId = parentId;
diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs
index 2355e250ef..a791781617 100644
--- a/src/Umbraco.Core/Events/PublishEventArgs.cs
+++ b/src/Umbraco.Core/Events/PublishEventArgs.cs
@@ -4,6 +4,52 @@ namespace Umbraco.Core.Events
{
public class PublishEventArgs : CancellableObjectEventArgs>
{
+ ///
+ /// Constructor accepting multiple entities that are used in the publish operation
+ ///
+ ///
+ ///
+ ///
+ ///
+ public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages)
+ : base(eventObject, canCancel, eventMessages)
+ {
+ IsAllRepublished = isAllPublished;
+ }
+
+ ///
+ /// Constructor accepting multiple entities that are used in the publish operation
+ ///
+ ///
+ ///
+ public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages)
+ : base(eventObject, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ public PublishEventArgs(TEntity eventObject, EventMessages eventMessages)
+ : base(new List { eventObject }, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ ///
+ ///
+ public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished, EventMessages eventMessages)
+ : base(new List { eventObject }, canCancel, eventMessages)
+ {
+ IsAllRepublished = isAllPublished;
+ }
+
///
/// Constructor accepting multiple entities that are used in the publish operation
///
diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs
index d3ac8b22e5..b84e28285e 100644
--- a/src/Umbraco.Core/Events/SaveEventArgs.cs
+++ b/src/Umbraco.Core/Events/SaveEventArgs.cs
@@ -4,12 +4,55 @@ namespace Umbraco.Core.Events
{
public class SaveEventArgs : CancellableObjectEventArgs>
{
- ///
- /// Constructor accepting multiple entities that are used in the saving operation
- ///
- ///
- ///
- public SaveEventArgs(IEnumerable eventObject, bool canCancel)
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages)
+ : base(eventObject, canCancel, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages)
+ : base(eventObject, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ public SaveEventArgs(TEntity eventObject, EventMessages eventMessages)
+ : base(new List { eventObject }, eventMessages)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ ///
+ public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages)
+ : base(new List { eventObject }, canCancel, eventMessages)
+ {
+ }
+
+
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, bool canCancel)
: base(eventObject, canCancel)
{
}
diff --git a/src/Umbraco.Core/Events/TransientMessagesFactory.cs b/src/Umbraco.Core/Events/TransientMessagesFactory.cs
new file mode 100644
index 0000000000..5cd291a37f
--- /dev/null
+++ b/src/Umbraco.Core/Events/TransientMessagesFactory.cs
@@ -0,0 +1,13 @@
+namespace Umbraco.Core.Events
+{
+ ///
+ /// A simple/default transient messages factory
+ ///
+ internal class TransientMessagesFactory : IEventMessagesFactory
+ {
+ public EventMessages Get()
+ {
+ return new EventMessages();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs
new file mode 100644
index 0000000000..d27d38de9a
--- /dev/null
+++ b/src/Umbraco.Core/Exceptions/UmbracoStartupFailedException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Umbraco.Core.Exceptions
+{
+ ///
+ /// An exception that is thrown if the umbraco application cannnot boot
+ ///
+ public class UmbracoStartupFailedException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public UmbracoStartupFailedException(string message) : base(message)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/xslt/Web.config b/src/Umbraco.Core/FileResources/BlockingWeb.config
similarity index 96%
rename from src/Umbraco.Web.UI/xslt/Web.config
rename to src/Umbraco.Core/FileResources/BlockingWeb.config
index fd6e3a816a..80182af9a1 100644
--- a/src/Umbraco.Web.UI/xslt/Web.config
+++ b/src/Umbraco.Core/FileResources/BlockingWeb.config
@@ -1,18 +1,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Core/FileResources/Files.Designer.cs b/src/Umbraco.Core/FileResources/Files.Designer.cs
new file mode 100644
index 0000000000..456dae221f
--- /dev/null
+++ b/src/Umbraco.Core/FileResources/Files.Designer.cs
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.0
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Umbraco.Core.FileResources {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Files {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Files() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Umbraco.Core.FileResources.Files", typeof(Files).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to <?xml version="1.0"?>
+ ///
+ ///<!-- Blocks public downloading of anything in this folder and sub folders -->
+ ///
+ ///<configuration>
+ /// <system.web>
+ /// <httpHandlers>
+ /// <add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
+ /// </httpHandlers>
+ /// </system.web>
+ /// <system.webServer>
+ /// <validation validateIntegratedModeConfiguration="false" />
+ /// <handlers>
+ /// <remove name="BlockViewHandler"/>
+ /// <add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.H [rest of string was truncated]";.
+ ///
+ internal static string BlockingWebConfig {
+ get {
+ return ResourceManager.GetString("BlockingWebConfig", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/FileResources/Files.resx b/src/Umbraco.Core/FileResources/Files.resx
new file mode 100644
index 0000000000..823aacefb7
--- /dev/null
+++ b/src/Umbraco.Core/FileResources/Files.resx
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ blockingweb.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252
+
+
\ No newline at end of file
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index dc5e278970..2d87f634f4 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Configuration;
using System.Web;
using System.Text.RegularExpressions;
+using System.Web.Hosting;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
@@ -51,7 +52,23 @@ namespace Umbraco.Core.IO
return VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root);
}
- [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
+ public static Attempt TryResolveUrl(string virtualPath)
+ {
+ try
+ {
+ if (virtualPath.StartsWith("~"))
+ return Attempt.Succeed(virtualPath.Replace("~", SystemDirectories.Root).Replace("//", "/"));
+ if (Uri.IsWellFormedUriString(virtualPath, UriKind.Absolute))
+ return Attempt.Succeed(virtualPath);
+ return Attempt.Succeed(VirtualPathUtility.ToAbsolute(virtualPath, SystemDirectories.Root));
+ }
+ catch (Exception ex)
+ {
+ return Attempt.Fail(virtualPath, ex);
+ }
+ }
+
+ [Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
internal static string ResolveUrlsFromTextString(string text)
{
if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString)
@@ -67,7 +84,7 @@ namespace Umbraco.Core.IO
if (tag.Groups[1].Success)
url = tag.Groups[1].Value;
- if (string.IsNullOrEmpty(url) == false)
+ if (String.IsNullOrEmpty(url) == false)
{
string resolvedUrl = (url.Substring(0, 1) == "/") ? ResolveUrl(url.Substring(1)) : ResolveUrl(url);
text = text.Replace(url, resolvedUrl);
@@ -92,10 +109,10 @@ namespace Umbraco.Core.IO
if (useHttpContext && HttpContext.Current != null)
{
//string retval;
- if (string.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root)))
- return System.Web.Hosting.HostingEnvironment.MapPath(path);
+ if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root)))
+ return HostingEnvironment.MapPath(path);
else
- return System.Web.Hosting.HostingEnvironment.MapPath("~/" + path.TrimStart('/'));
+ return HostingEnvironment.MapPath("~/" + path.TrimStart('/'));
}
var root = GetRootDirectorySafe();
@@ -115,7 +132,7 @@ namespace Umbraco.Core.IO
{
string retval = ConfigurationManager.AppSettings[settingsKey];
- if (string.IsNullOrEmpty(retval))
+ if (String.IsNullOrEmpty(retval))
retval = standardPath;
return retval.TrimEnd('/');
@@ -232,7 +249,7 @@ namespace Umbraco.Core.IO
///
internal static string GetRootDirectorySafe()
{
- if (string.IsNullOrEmpty(_rootDir) == false)
+ if (String.IsNullOrEmpty(_rootDir) == false)
{
return _rootDir;
}
@@ -241,7 +258,7 @@ namespace Umbraco.Core.IO
var uri = new Uri(codeBase);
var path = uri.LocalPath;
var baseDirectory = Path.GetDirectoryName(path);
- if (string.IsNullOrEmpty(baseDirectory))
+ if (String.IsNullOrEmpty(baseDirectory))
throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured.");
_rootDir = baseDirectory.Contains("bin")
@@ -253,8 +270,8 @@ namespace Umbraco.Core.IO
internal static string GetRootDirectoryBinFolder()
{
- string binFolder = string.Empty;
- if (string.IsNullOrEmpty(_rootDir))
+ string binFolder = String.Empty;
+ if (String.IsNullOrEmpty(_rootDir))
{
binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory.FullName;
return binFolder;
@@ -298,5 +315,25 @@ namespace Umbraco.Core.IO
// use string extensions
return filePath.ToSafeFileName();
}
+
+ public static void EnsurePathExists(string path)
+ {
+ var absolutePath = IOHelper.MapPath(path);
+ if (Directory.Exists(absolutePath) == false)
+ Directory.CreateDirectory(absolutePath);
+ }
+
+ public static void EnsureFileExists(string path, string contents)
+ {
+ var absolutePath = IOHelper.MapPath(path);
+ if (File.Exists(absolutePath) == false)
+ {
+ using (var writer = File.CreateText(absolutePath))
+ {
+ writer.Write(contents);
+ }
+ }
+
+ }
}
}
diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs
index 7da2e1d3fc..850e3845af 100644
--- a/src/Umbraco.Core/Macros/MacroTagParser.cs
+++ b/src/Umbraco.Core/Macros/MacroTagParser.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Macros
internal class MacroTagParser
{
private static readonly Regex MacroRteContent = new Regex(@"()", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
- private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO(?:.+?)?macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
+ private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO (?:.+?)?macroAlias=[""']([^""\'\n\r]+?)[""'].+?)(?:/>|>.*?\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
///
/// This formats the persisted string to something useful for the rte so that the macro renders properly since we
diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs
new file mode 100644
index 0000000000..6f4a539194
--- /dev/null
+++ b/src/Umbraco.Core/MainDom.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO.MemoryMappedFiles;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Hosting;
+using Umbraco.Core.Logging;
+using Umbraco.Core.ObjectResolution;
+
+namespace Umbraco.Core
+{
+ // represents the main domain
+ class MainDom : IRegisteredObject
+ {
+ #region Vars
+
+ private readonly ILogger _logger;
+
+ // our own lock for local consistency
+ private readonly object _locko = new object();
+
+ // async lock representing the main domain lock
+ private readonly AsyncLock _asyncLock;
+ private IDisposable _asyncLocker;
+
+ // event wait handle used to notify current main domain that it should
+ // release the lock because a new domain wants to be the main domain
+ private readonly EventWaitHandle _signal;
+
+ // indicates whether...
+ private volatile bool _isMainDom; // we are the main domain
+ private volatile bool _signaled; // we have been signaled
+
+ // actions to run before releasing the main domain
+ private readonly SortedList _callbacks = new SortedList();
+
+ private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
+
+ #endregion
+
+ #region Ctor
+
+ // initializes a new instance of MainDom
+ public MainDom(ILogger logger)
+ {
+ _logger = logger;
+
+ var appId = string.Empty;
+ // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
+ if (HostingEnvironment.ApplicationID != null)
+ appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty);
+
+ var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK";
+ _asyncLock = new AsyncLock(lockName);
+
+ var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT";
+ _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
+ }
+
+ #endregion
+
+ // register a main domain consumer
+ public bool Register(Action release, int weight = 100)
+ {
+ return Register(null, release, weight);
+ }
+
+ // register a main domain consumer
+ public bool Register(Action install, Action release, int weight = 100)
+ {
+ lock (_locko)
+ {
+ if (_signaled) return false;
+ if (install != null)
+ install();
+ if (release != null)
+ _callbacks.Add(weight, release);
+ return true;
+ }
+ }
+
+ // handles the signal requesting that the main domain is released
+ private void OnSignal(string source)
+ {
+ // once signaled, we stop waiting, but then there is the hosting environment
+ // so we have to make sure that we only enter that method once
+
+ lock (_locko)
+ {
+ _logger.Debug("Signaled" + (_signaled ? " (again)" : "") + " (" + source + ").");
+ if (_signaled) return;
+ if (_isMainDom == false) return; // probably not needed
+ _signaled = true;
+ }
+
+ try
+ {
+ _logger.Debug("Stopping...");
+ foreach (var callback in _callbacks.Values)
+ {
+ try
+ {
+ callback(); // no timeout on callbacks
+ }
+ catch (Exception e)
+ {
+ _logger.Error("Error while running callback, remaining callbacks will not run.", e);
+ throw;
+ }
+
+ }
+ _logger.Debug("Stopped.");
+ }
+ finally
+ {
+ // in any case...
+ _isMainDom = false;
+ _asyncLocker.Dispose();
+ _logger.Debug("Released MainDom.");
+ }
+ }
+
+ // acquires the main domain
+ public bool Acquire()
+ {
+ lock (_locko) // we don't want the hosting environment to interfere by signaling
+ {
+ // if signaled, too late to acquire, give up
+ // the handler is not installed so that would be the hosting environment
+ if (_signaled)
+ {
+ _logger.Debug("Cannot acquire MainDom (signaled).");
+ return false;
+ }
+
+ _logger.Debug("Acquiring MainDom...");
+
+ // signal other instances that we want the lock, then wait one the lock,
+ // which may timeout, and this is accepted - see comments below
+
+ // signal, then wait for the lock, then make sure the event is
+ // resetted (maybe there was noone listening..)
+ _signal.Set();
+
+ // if more than 1 instance reach that point, one will get the lock
+ // and the other one will timeout, which is accepted
+
+ _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds);
+ _isMainDom = true;
+
+ // we need to reset the event, because otherwise we would end up
+ // signaling ourselves and commiting suicide immediately.
+ // only 1 instance can reach that point, but other instances may
+ // have started and be trying to get the lock - they will timeout,
+ // which is accepted
+
+ _signal.Reset();
+ _signal.WaitOneAsync()
+ .ContinueWith(_ => OnSignal("signal"));
+
+ HostingEnvironment.RegisterObject(this);
+
+ _logger.Debug("Acquired MainDom.");
+ return true;
+ }
+ }
+
+ // gets a value indicating whether we are the main domain
+ public bool IsMainDom
+ {
+ get { return _isMainDom; }
+ }
+
+ // IRegisteredObject
+ public void Stop(bool immediate)
+ {
+ try
+ {
+ OnSignal("environment"); // will run once
+ }
+ finally
+ {
+ HostingEnvironment.UnregisterObject(this);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs
index aeb4da888c..571c916123 100644
--- a/src/Umbraco.Core/Manifest/ManifestParser.cs
+++ b/src/Umbraco.Core/Manifest/ManifestParser.cs
@@ -96,11 +96,15 @@ namespace Umbraco.Core.Manifest
if (depth < 1)
{
- var dirs = currDir.GetDirectories();
var result = new List();
- foreach (var d in dirs)
+ if (currDir.Exists)
{
- result.AddRange(GetAllManifestFileContents(d));
+ var dirs = currDir.GetDirectories();
+
+ foreach (var d in dirs)
+ {
+ result.AddRange(GetAllManifestFileContents(d));
+ }
}
return result;
}
diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs
index df8c09b3d1..a47b430979 100644
--- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models
{
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
var descendants = contentTypeService.GetContentTypeChildren(contentType.Id)
- .FlattenList(type => contentTypeService.GetContentTypeChildren(type.Id));
+ .SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id));
return descendants;
}
diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs
index 660d6674ca..eeacb771a9 100644
--- a/src/Umbraco.Core/Models/EntityBase/Entity.cs
+++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs
@@ -101,6 +101,7 @@ namespace Umbraco.Core.Models.EntityBase
/// the new api, which also needs to take effect in the legacy api.
///
[IgnoreDataMember]
+ [Obsolete("Anytime there's a cancellable method it needs to return an Attempt so we know the outcome instead of this hack, not all services have been updated to use this though yet.")]
internal bool WasCancelled
{
get { return _wasCancelled; }
diff --git a/src/Umbraco.Core/Models/IDomain.cs b/src/Umbraco.Core/Models/IDomain.cs
index 9fb3e8019d..b9f6bc65ec 100644
--- a/src/Umbraco.Core/Models/IDomain.cs
+++ b/src/Umbraco.Core/Models/IDomain.cs
@@ -4,9 +4,14 @@ namespace Umbraco.Core.Models
{
public interface IDomain : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
- ILanguage Language { get; set; }
+ int? LanguageId { get; set; }
string DomainName { get; set; }
- IContent RootContent { get; set; }
+ int? RootContentId { get; set; }
bool IsWildcard { get; }
+
+ ///
+ /// Readonly value of the language ISO code for the domain
+ ///
+ string LanguageIsoCode { get; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs
index bd0bbc859c..e43c1bdeae 100644
--- a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs
+++ b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs
@@ -23,5 +23,11 @@ namespace Umbraco.Core.Models.Rdbms
[Column("domainName")]
public string DomainName { get; set; }
+
+ ///
+ /// Used for a result on the query to get the associated language for a domain if there is one
+ ///
+ [ResultColumn("languageISOCode")]
+ public string IsoCode { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs
index 7381da0930..586c5915b2 100644
--- a/src/Umbraco.Core/Models/Stylesheet.cs
+++ b/src/Umbraco.Core/Models/Stylesheet.cs
@@ -33,11 +33,11 @@ namespace Umbraco.Core.Models
//re-parse it so we can check what properties are different and adjust the event handlers
var parsed = StylesheetHelper.ParseRules(Content).ToArray();
var names = parsed.Select(x => x.Name).ToArray();
- var existing = _properties.Value.Where(x => names.Contains(x.Name)).ToArray();
+ var existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray();
//update existing
foreach (var stylesheetProperty in existing)
{
- var updateFrom = parsed.Single(x => x.Name == stylesheetProperty.Name);
+ var updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name));
//remove current event handler while we update, we'll reset it after
stylesheetProperty.PropertyChanged -= Property_PropertyChanged;
stylesheetProperty.Alias = updateFrom.Selector;
@@ -46,14 +46,14 @@ namespace Umbraco.Core.Models
stylesheetProperty.PropertyChanged += Property_PropertyChanged;
}
//remove no longer existing
- var nonExisting = _properties.Value.Where(x => names.Contains(x.Name) == false).ToArray();
+ var nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray();
foreach (var stylesheetProperty in nonExisting)
{
stylesheetProperty.PropertyChanged -= Property_PropertyChanged;
_properties.Value.Remove(stylesheetProperty);
}
//add new ones
- var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).Contains(x.Name) == false);
+ var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false);
foreach (var stylesheetRule in newItems)
{
var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles);
@@ -128,7 +128,7 @@ namespace Umbraco.Core.Models
///
public void AddProperty(StylesheetProperty property)
{
- if (Properties.Any(x => x.Name == property.Name))
+ if (Properties.Any(x => x.Name.InvariantEquals(property.Name)))
{
throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection");
}
@@ -151,7 +151,7 @@ namespace Umbraco.Core.Models
///
public void RemoveProperty(string name)
{
- if (Properties.Any(x => x.Name == name))
+ if (Properties.Any(x => x.Name.InvariantEquals(name)))
{
Content = StylesheetHelper.ReplaceRule(Content, name, null);
}
diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs
index 963535729c..943f96c9f9 100644
--- a/src/Umbraco.Core/Models/UmbracoDomain.cs
+++ b/src/Umbraco.Core/Models/UmbracoDomain.cs
@@ -5,8 +5,6 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
- //TODO: Need to custom serialize this
-
[Serializable]
[DataContract(IsReference = true)]
public class UmbracoDomain : Entity, IDomain
@@ -16,25 +14,32 @@ namespace Umbraco.Core.Models
_domainName = domainName;
}
- private IContent _content;
- private ILanguage _language;
+ public UmbracoDomain(string domainName, string languageIsoCode)
+ : this(domainName)
+ {
+ LanguageIsoCode = languageIsoCode;
+ }
+
+ private int? _contentId;
+ private int? _languageId;
private string _domainName;
- private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language);
+ private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContentId);
+ private static readonly PropertyInfo DefaultLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.LanguageId);
private static readonly PropertyInfo DomainNameSelector = ExpressionHelper.GetPropertyInfo(x => x.DomainName);
- private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo(x => x.RootContent);
+
[DataMember]
- public ILanguage Language
+ public int? LanguageId
{
- get { return _language; }
+ get { return _languageId; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
- _language = value;
- return _language;
- }, _language, DefaultLanguageSelector);
+ _languageId = value;
+ return _languageId;
+ }, _languageId, DefaultLanguageSelector);
}
}
@@ -53,16 +58,16 @@ namespace Umbraco.Core.Models
}
[DataMember]
- public IContent RootContent
+ public int? RootContentId
{
- get { return _content; }
+ get { return _contentId; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
- _content = value;
- return _content;
- }, _content, ContentSelector);
+ _contentId = value;
+ return _contentId;
+ }, _contentId, ContentSelector);
}
}
@@ -70,5 +75,10 @@ namespace Umbraco.Core.Models
{
get { return string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); }
}
+
+ ///
+ /// Readonly value of the language ISO code for the domain
+ ///
+ public string LanguageIsoCode { get; internal set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs
index 1b55a89a9c..78c8f483f2 100644
--- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs
+++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs
@@ -29,6 +29,7 @@ namespace Umbraco.Core.ObjectResolution
{
//create the legacy resolver and only include the legacy types
_legacyResolver = new LegacyStartupHandlerResolver(
+ serviceProvider, logger,
applicationEventHandlers.Where(x => !TypeHelper.IsTypeAssignableFrom(x)));
}
@@ -70,8 +71,8 @@ namespace Umbraco.Core.ObjectResolution
private class LegacyStartupHandlerResolver : ManyObjectsResolverBase, IDisposable
{
- internal LegacyStartupHandlerResolver(IEnumerable legacyStartupHandlers)
- : base(legacyStartupHandlers)
+ internal LegacyStartupHandlerResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable legacyStartupHandlers)
+ : base(serviceProvider, logger, legacyStartupHandlers)
{
}
diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs
index c4ca875eb2..5145ec95c5 100644
--- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs
+++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs
@@ -7,6 +7,7 @@ using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Umbraco.Core.Logging;
namespace Umbraco.Core.Persistence
{
@@ -75,8 +76,10 @@ namespace Umbraco.Core.Persistence
connection.Open();
connection.Close();
}
- catch (DbException)
+ catch (DbException exc)
{
+ // Don't swallow this error, the exception is super handy for knowing "why" its not available
+ LogHelper.WarnWithException("Configured database is reporting as not being available!", exc);
return false;
}
diff --git a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs
index 63ae627c62..bc38f72dab 100644
--- a/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs
+++ b/src/Umbraco.Core/Persistence/Mappers/DomainMapper.cs
@@ -5,7 +5,8 @@ using Umbraco.Core.Persistence.Repositories;
namespace Umbraco.Core.Persistence.Mappers
{
- [MapperFor(typeof(DomainRepository.CacheableDomain))]
+ [MapperFor(typeof(IDomain))]
+ [MapperFor(typeof(UmbracoDomain))]
public sealed class DomainMapper : BaseMapper
{
private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary();
@@ -22,10 +23,10 @@ namespace Umbraco.Core.Persistence.Mappers
internal override void BuildMap()
{
- CacheMap(src => src.Id, dto => dto.Id);
- CacheMap(src => src.RootContentId, dto => dto.RootStructureId);
- CacheMap(src => src.DefaultLanguageId, dto => dto.DefaultLanguage);
- CacheMap(src => src.DomainName, dto => dto.DomainName);
+ CacheMap(src => src.Id, dto => dto.Id);
+ CacheMap(src => src.RootContentId, dto => dto.RootStructureId);
+ CacheMap(src => src.LanguageId, dto => dto.DefaultLanguage);
+ CacheMap(src => src.DomainName, dto => dto.DomainName);
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs
index 44757aed9a..00f968941d 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/CreateCacheInstructionTable.cs
@@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
Create.Table("umbracoCacheInstruction")
.WithColumn("id").AsInt32().Identity().NotNullable()
.WithColumn("utcStamp").AsDateTime().NotNullable()
- .WithColumn("jsonInstruction").AsCustom(textType).NotNullable();
+ .WithColumn("jsonInstruction").AsCustom(textType).NotNullable()
+ .WithColumn("originated").AsString(500).NotNullable();
Create.PrimaryKey("PK_umbracoCacheInstruction")
.OnTable("umbracoCacheInstruction")
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index 3395b38b3d..bb474a3440 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -820,12 +820,13 @@ namespace Umbraco.Core.Persistence.Repositories
var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray())
.ToArray();
+
+ var ids = dtos
+ .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
+ .Select(x => x.TemplateId.Value).ToArray();
+
//NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types
- var templates = _templateRepository.GetAll(
- dtos
- .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
- .Select(x => x.TemplateId.Value).ToArray())
- .ToArray();
+ var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray();
var dtosWithContentTypes = dtos
//This select into and null check are required because we don't have a foreign damn key on the contentType column
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
index fd4577faa5..9ac905cf03 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs
@@ -773,7 +773,7 @@ AND umbracoNode.id <> @id",
var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc,
cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
- ParentTypes.parentContentTypeId as chtParentId,
+ ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey,
umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed,
umbracoNode.uniqueID as nUniqueId
@@ -787,7 +787,12 @@ AND umbracoNode.id <> @id",
ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId
) AllowedTypes
ON AllowedTypes.Id = cmsContentType.nodeId
- LEFT JOIN cmsContentType2ContentType as ParentTypes
+ LEFT JOIN (
+ SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId
+ FROM cmsContentType2ContentType
+ INNER JOIN umbracoNode
+ ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @"
+ ) ParentTypes
ON ParentTypes.childContentTypeId = cmsContentType.nodeId
WHERE (umbracoNode.nodeObjectType = @nodeObjectType)";
@@ -902,7 +907,7 @@ AND umbracoNode.id <> @id",
cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc,
cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
- ParentTypes.parentContentTypeId as chtParentId,
+ ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey,
umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed,
umbracoNode.uniqueID as nUniqueId,
@@ -925,7 +930,12 @@ AND umbracoNode.id <> @id",
ON cmsTemplate.nodeId = umbracoNode.id
) as Template
ON Template.nodeId = cmsDocumentType.templateNodeId
- LEFT JOIN cmsContentType2ContentType as ParentTypes
+ LEFT JOIN (
+ SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId
+ FROM cmsContentType2ContentType
+ INNER JOIN umbracoNode
+ ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @"
+ ) ParentTypes
ON ParentTypes.childContentTypeId = cmsContentType.nodeId
WHERE (umbracoNode.nodeObjectType = @nodeObjectType)";
@@ -1103,7 +1113,14 @@ AND umbracoNode.id <> @id",
? x.ctId == currentCtId
: x.nUniqueId == currentCtId;
})
- .Select(x => (TId?)x.chtParentId)
+ .Select(x =>
+ {
+ //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is
+ // how it is for now.
+ return (typeof(TId) == typeof(int))
+ ? (TId?)x.chtParentId
+ : (TId?)x.chtParentKey;
+ })
.Where(x => x.HasValue)
.Distinct()
.Select(x => x.Value).ToList());
diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
index 1e84814d1b..1985875717 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
@@ -33,13 +33,16 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new {Id = id})
- .OrderBy(x => x.UniqueId);
+ .OrderBy(x => x.UniqueId, SqlSyntax);
var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault();
if (dto == null)
return null;
- var entity = ConvertFromDto(dto);
+ //This will be cached
+ var allLanguages = _languageRepository.GetAll().ToArray();
+
+ var entity = ConvertFromDto(dto, allLanguages);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -56,8 +59,11 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids });
}
+ //This will be cached
+ var allLanguages = _languageRepository.GetAll().ToArray();
+
return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql)
- .Select(ConvertFromDto);
+ .Select(dto => ConvertFromDto(dto, allLanguages));
}
protected override IEnumerable PerformGetByQuery(IQuery query)
@@ -65,10 +71,13 @@ namespace Umbraco.Core.Persistence.Repositories
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate();
- sql.OrderBy(x => x.UniqueId);
+ sql.OrderBy(x => x.UniqueId, SqlSyntax);
+
+ //This will be cached
+ var allLanguages = _languageRepository.GetAll().ToArray();
return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql)
- .Select(ConvertFromDto);
+ .Select(x => ConvertFromDto(x, allLanguages));
}
#endregion
@@ -81,14 +90,14 @@ namespace Umbraco.Core.Persistence.Repositories
if(isCount)
{
sql.Select("COUNT(*)")
- .From();
+ .From(SqlSyntax);
}
else
{
sql.Select("*")
- .From()
- .LeftJoin()
- .On(left => left.UniqueId, right => right.UniqueId);
+ .From(SqlSyntax)
+ .LeftJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.UniqueId, right => right.UniqueId);
}
return sql;
}
@@ -200,7 +209,7 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
- protected IDictionaryItem ConvertFromDto(DictionaryDto dto)
+ protected IDictionaryItem ConvertFromDto(DictionaryDto dto, ILanguage[] allLanguages)
{
var factory = new DictionaryItemFactory();
var entity = factory.BuildEntity(dto);
@@ -208,9 +217,11 @@ namespace Umbraco.Core.Persistence.Repositories
var list = new List();
foreach (var textDto in dto.LanguageTextDtos)
{
- var language = _languageRepository.Get(textDto.LanguageId);
+ //Assuming this is cached!
+ var language = allLanguages.FirstOrDefault(x => x.Id == textDto.LanguageId);
if (language == null)
continue;
+
var translationFactory = new DictionaryTranslationFactory(dto.UniqueId, language);
list.Add(translationFactory.BuildEntity(textDto));
}
@@ -235,6 +246,47 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
+ private IEnumerable GetRootDictionaryItems()
+ {
+ var query = Query.Builder.Where(x => x.ParentId == null);
+ return GetByQuery(query);
+ }
+
+ public IEnumerable GetDictionaryItemDescendants(Guid? parentId)
+ {
+ //This will be cached
+ var allLanguages = _languageRepository.GetAll().ToArray();
+
+ //This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive
+ // lookup to get descendants. Currently this is the most efficient way to do it
+
+ Func>> getItemsFromParents = guids =>
+ {
+ //needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used.
+ return guids.InGroupsOf(2000)
+ .Select(@group =>
+ {
+ var sqlClause = GetBaseQuery(false)
+ .Where(x => x.Parent != null)
+ .Where(string.Format("{0} IN (@parentIds)", SqlSyntax.GetQuotedColumnName("parent")), new { parentIds = @group });
+
+ var translator = new SqlTranslator(sqlClause, Query.Builder);
+ var sql = translator.Translate();
+ sql.OrderBy(x => x.UniqueId, SqlSyntax);
+
+ return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql)
+ .Select(x => ConvertFromDto(x, allLanguages));
+ });
+ };
+
+ var childItems = parentId.HasValue == false
+ ? new[] { GetRootDictionaryItems() }
+ : getItemsFromParents(new[] { parentId.Value });
+
+ return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items);
+
+ }
+
private class DictionaryByUniqueIdRepository : SimpleGetRepository
{
private readonly DictionaryRepository _dictionaryRepository;
@@ -262,7 +314,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto)
{
- return _dictionaryRepository.ConvertFromDto(dto);
+ //This will be cached
+ var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray();
+ return _dictionaryRepository.ConvertFromDto(dto, allLanguages);
}
protected override object GetBaseWhereClauseArguments(Guid id)
@@ -303,7 +357,9 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto)
{
- return _dictionaryRepository.ConvertFromDto(dto);
+ //This will be cached
+ var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray();
+ return _dictionaryRepository.ConvertFromDto(dto, allLanguages);
}
protected override object GetBaseWhereClauseArguments(string id)
diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
index 0dec93e25e..21ba8b4baf 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
@@ -7,53 +7,53 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
+using Umbraco.Core.Persistence.FaultHandling;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
+ //TODO: We need to get a readonly ISO code for the domain assigned
+
internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository
{
- private readonly IContentRepository _contentRepository;
- private readonly ILanguageRepository _languageRepository;
+ private readonly RepositoryCacheOptions _cacheOptions;
- public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentRepository contentRepository, ILanguageRepository languageRepository)
+ public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
- _contentRepository = contentRepository;
- _languageRepository = languageRepository;
+ //Custom cache options for better performance
+ _cacheOptions = new RepositoryCacheOptions
+ {
+ GetAllCacheAllowZeroCount = true,
+ GetAllCacheValidateCount = false
+ };
}
///
- /// Override the cache, this repo will not perform any cache, the caching is taken care of in the inner repository
+ /// Returns the repository cache options
///
- ///
- /// This is required because IDomain is a deep object and we dont' want to cache it since it contains an ILanguage and an IContent, when these
- /// are deep cloned the object graph that will be cached will be huge. Instead we'll have an internal repository that caches the simple
- /// Domain structure and we'll use the other repositories to resolve the entities to attach
- ///
- protected override IRuntimeCacheProvider RuntimeCache
+ protected override RepositoryCacheOptions RepositoryCacheOptions
{
- get { return new NullCacheProvider(); }
+ get { return _cacheOptions; }
}
protected override IDomain PerformGet(int id)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
- {
- var factory = new DomainModelFactory();
- return factory.BuildDomainEntity(repo.Get(id), _contentRepository, _languageRepository);
- }
+ //use the underlying GetAll which will force cache all domains
+ return GetAll().FirstOrDefault(x => x.Id == id);
}
protected override IEnumerable PerformGetAll(params int[] ids)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
+ var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0");
+ if (ids.Any())
{
- var factory = new DomainModelFactory();
- return factory.BuildDomainEntities(repo.GetAll(ids).ToArray(), _contentRepository, _languageRepository);
+ sql.Where("umbracoDomains.id in (@ids)", new { ids = ids });
}
+
+ return Database.Fetch(sql).Select(ConvertFromDto);
}
protected override IEnumerable PerformGetByQuery(IQuery query)
@@ -64,7 +64,18 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql();
- sql.Select(isCount ? "COUNT(*)" : "*").From(SqlSyntax);
+ if (isCount)
+ {
+ sql.Select("COUNT(*)").From(SqlSyntax);
+ }
+ else
+ {
+ sql.Select("umbracoDomains.*, umbracoLanguage.languageISOCode")
+ .From(SqlSyntax)
+ .LeftJoin(SqlSyntax)
+ .On(SqlSyntax, dto => dto.DefaultLanguage, dto => dto.Id);
+ }
+
return sql;
}
@@ -73,18 +84,13 @@ namespace Umbraco.Core.Persistence.Repositories
return "umbracoDomains.id = @Id";
}
- protected override void PersistDeletedItem(IDomain entity)
- {
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
- {
- var factory = new DomainModelFactory();
- repo.PersistDeletedItem(factory.BuildEntity(entity));
- }
- }
-
protected override IEnumerable GetDeleteClauses()
{
- throw new NotImplementedException();
+ var list = new List
+ {
+ "DELETE FROM umbracoDomains WHERE id = @Id"
+ };
+ return list;
}
protected override Guid NodeObjectTypeId
@@ -94,245 +100,112 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PersistNewItem(IDomain entity)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
+ var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName });
+ if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName));
+
+ if (entity.RootContentId.HasValue)
{
- var factory = new DomainModelFactory();
- var cacheableEntity = factory.BuildEntity(entity);
- repo.PersistNewItem(cacheableEntity);
- //re-map the id
- entity.Id = cacheableEntity.Id;
- entity.ResetDirtyProperties();
+ var contentExists = Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value });
+ if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value);
}
+
+ if (entity.LanguageId.HasValue)
+ {
+ var languageExists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value });
+ if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
+ }
+
+ ((UmbracoDomain)entity).AddingEntity();
+
+ var factory = new DomainModelFactory();
+ var dto = factory.BuildDto(entity);
+
+ var id = Convert.ToInt32(Database.Insert(dto));
+ entity.Id = id;
+
+ //if the language changed, we need to resolve the ISO code!
+ if (entity.LanguageId.HasValue)
+ {
+ ((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar("SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new { langId = entity.LanguageId });
+ }
+
+ entity.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IDomain entity)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
+ ((UmbracoDomain)entity).UpdatingEntity();
+
+ var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName AND umbracoDomains.id <> @id",
+ new { domainName = entity.DomainName, id = entity.Id });
+ //ensure there is no other domain with the same name on another entity
+ if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName));
+
+ if (entity.RootContentId.HasValue)
{
- var factory = new DomainModelFactory();
- repo.PersistUpdatedItem(factory.BuildEntity(entity));
- entity.ResetDirtyProperties();
+ var contentExists = Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContent WHERE nodeId = @id", new { id = entity.RootContentId.Value });
+ if (contentExists == 0) throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value);
}
+
+ if (entity.LanguageId.HasValue)
+ {
+ var languageExists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id", new { id = entity.LanguageId.Value });
+ if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
+ }
+
+ var factory = new DomainModelFactory();
+ var dto = factory.BuildDto(entity);
+
+ Database.Update(dto);
+
+ //if the language changed, we need to resolve the ISO code!
+ if (entity.WasPropertyDirty("LanguageId"))
+ {
+ ((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar("SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new {langId = entity.LanguageId});
+ }
+
+ entity.ResetDirtyProperties();
}
public IDomain GetByName(string domainName)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
- {
- var factory = new DomainModelFactory();
- return factory.BuildDomainEntity(
- repo.GetByQuery(new Query().Where(x => x.DomainName.InvariantEquals(domainName))).FirstOrDefault(),
- _contentRepository, _languageRepository);
- }
+ return GetAll().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName));
}
public bool Exists(string domainName)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
- {
- var query = new Query().Where(x => x.DomainName.InvariantEquals(domainName));
- return repo.GetByQuery(query).Any();
- }
+ return GetAll().Any(x => x.DomainName.InvariantEquals(domainName));
}
public IEnumerable GetAll(bool includeWildcards)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
- {
- var factory = new DomainModelFactory();
- return factory.BuildDomainEntities(repo.GetAll().ToArray(), _contentRepository, _languageRepository)
- .Where(x => includeWildcards || x.IsWildcard == false);
- }
+ return GetAll().Where(x => includeWildcards || x.IsWildcard == false);
}
public IEnumerable GetAssignedDomains(int contentId, bool includeWildcards)
{
- using (var repo = new CachedDomainRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
- {
- var factory = new DomainModelFactory();
-
- var query = new Query().Where(x => x.RootContentId == contentId);
-
- return factory.BuildDomainEntities(repo.GetByQuery(query).ToArray(), _contentRepository, _languageRepository)
- .Where(x => includeWildcards || x.IsWildcard == false);
- }
+ return GetAll()
+ .Where(x => x.RootContentId == contentId)
+ .Where(x => includeWildcards || x.IsWildcard == false);
}
- ///
- /// Dispose disposable properties
- ///
- ///
- /// Ensure the unit of work is disposed
- ///
- protected override void DisposeResources()
+ private IDomain ConvertFromDto(DomainDto dto)
{
- _contentRepository.Dispose();
- _languageRepository.Dispose();
- }
-
- ///
- /// A simple domain model that is cacheable without a large object graph
- ///
- internal class CacheableDomain : Entity, IAggregateRoot
- {
- public int? DefaultLanguageId { get; set; }
- public string DomainName { get; set; }
- public int? RootContentId { get; set; }
- }
-
- ///
- /// Inner repository responsible for CRUD for domains that allows caching simple data
- ///
- private class CachedDomainRepository : PetaPocoRepositoryBase
- {
- private readonly DomainRepository _domainRepo;
-
- public CachedDomainRepository(DomainRepository domainRepo, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
- : base(work, cache, logger, sqlSyntax)
- {
- _domainRepo = domainRepo;
- }
-
- protected override CacheableDomain PerformGet(int id)
- {
- var sql = GetBaseQuery(false);
- sql.Where(GetBaseWhereClause(), new { Id = id });
-
- var dto = Database.FirstOrDefault(sql);
- if (dto == null)
- return null;
-
- var entity = ConvertFromDto(dto);
-
- //on initial construction we don't want to have dirty properties tracked
- // http://issues.umbraco.org/issue/U4-1946
- ((Entity)entity).ResetDirtyProperties(false);
-
- return entity;
- }
-
- protected override IEnumerable PerformGetAll(params int[] ids)
- {
- var sql = GetBaseQuery(false).Where("umbracoDomains.id > 0");
- if (ids.Any())
- {
- sql.Where("umbracoDomains.id in (@ids)", new { ids = ids });
- }
-
- return Database.Fetch(sql).Select(ConvertFromDto);
- }
-
- protected override IEnumerable PerformGetByQuery(IQuery query)
- {
- var sqlClause = GetBaseQuery(false);
- var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate();
- return Database.Fetch(sql).Select(ConvertFromDto);
- }
-
- protected override Sql GetBaseQuery(bool isCount)
- {
- return _domainRepo.GetBaseQuery(isCount);
- }
-
- protected override string GetBaseWhereClause()
- {
- return _domainRepo.GetBaseWhereClause();
- }
-
- protected override IEnumerable GetDeleteClauses()
- {
- var list = new List
- {
- "DELETE FROM umbracoDomains WHERE id = @Id"
- };
- return list;
- }
-
- protected override Guid NodeObjectTypeId
- {
- get { throw new NotImplementedException(); }
- }
-
- protected override void PersistNewItem(CacheableDomain entity)
- {
- var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomains WHERE domainName = @domainName", new { domainName = entity.DomainName });
- if (exists > 0) throw new DuplicateNameException(string.Format("The domain name {0} is already assigned", entity.DomainName));
-
- entity.AddingEntity();
-
- var factory = new DomainModelFactory();
- var dto = factory.BuildDto(entity);
-
- var id = Convert.ToInt32(Database.Insert(dto));
- entity.Id = id;
-
- entity.ResetDirtyProperties();
- }
-
- protected override void PersistUpdatedItem(CacheableDomain entity)
- {
- entity.UpdatingEntity();
-
- var factory = new DomainModelFactory();
- var dto = factory.BuildDto(entity);
-
- Database.Update(dto);
-
- entity.ResetDirtyProperties();
- }
-
- private CacheableDomain ConvertFromDto(DomainDto dto)
- {
- var factory = new DomainModelFactory();
- var entity = factory.BuildEntity(dto);
- return entity;
- }
- }
+ var factory = new DomainModelFactory();
+ var entity = factory.BuildEntity(dto);
+ return entity;
+ }
internal class DomainModelFactory
{
- public IEnumerable BuildDomainEntities(CacheableDomain[] cacheableDomains, IContentRepository contentRepository, ILanguageRepository languageRepository)
+
+ public IDomain BuildEntity(DomainDto dto)
{
- var contentIds = cacheableDomains.Select(x => x.RootContentId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray();
- var langIds = cacheableDomains.Select(x => x.DefaultLanguageId).Where(x => x.HasValue).Select(x => x.Value).Distinct().ToArray();
- var contentItems = contentRepository.GetAll(contentIds);
- var langItems = languageRepository.GetAll(langIds);
-
- return cacheableDomains
- .WhereNotNull()
- .Select(cacheableDomain => new UmbracoDomain(cacheableDomain.DomainName)
- {
- Id = cacheableDomain.Id,
- //lookup from repo - this will be cached
- Language = cacheableDomain.DefaultLanguageId.HasValue ? langItems.FirstOrDefault(l => l.Id == cacheableDomain.DefaultLanguageId.Value) : null,
- //lookup from repo - this will be cached
- RootContent = cacheableDomain.RootContentId.HasValue ? contentItems.FirstOrDefault(l => l.Id == cacheableDomain.RootContentId.Value) : null,
- });
- }
-
- public IDomain BuildDomainEntity(CacheableDomain cacheableDomain, IContentRepository contentRepository, ILanguageRepository languageRepository)
- {
- if (cacheableDomain == null) return null;
-
- return new UmbracoDomain(cacheableDomain.DomainName)
+ var domain = new UmbracoDomain(dto.DomainName, dto.IsoCode)
{
- Id = cacheableDomain.Id,
- //lookup from repo - this will be cached
- Language = cacheableDomain.DefaultLanguageId.HasValue ? languageRepository.Get(cacheableDomain.DefaultLanguageId.Value) : null,
- //lookup from repo - this will be cached
- RootContent = cacheableDomain.RootContentId.HasValue ? contentRepository.Get(cacheableDomain.RootContentId.Value) : null
- };
- }
-
- public CacheableDomain BuildEntity(IDomain entity)
- {
- var domain = new CacheableDomain
- {
- Id = entity.Id,
- DefaultLanguageId = entity.Language == null ? null : (int?)entity.Language.Id,
- DomainName = entity.DomainName,
- RootContentId = entity.RootContent == null ? null : (int?)entity.RootContent.Id
+ Id = dto.Id,
+ LanguageId = dto.DefaultLanguage,
+ RootContentId = dto.RootStructureId
};
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -340,18 +213,9 @@ namespace Umbraco.Core.Persistence.Repositories
return domain;
}
- public CacheableDomain BuildEntity(DomainDto dto)
+ public DomainDto BuildDto(IDomain entity)
{
- var domain = new CacheableDomain { Id = dto.Id, DefaultLanguageId = dto.DefaultLanguage, DomainName = dto.DomainName, RootContentId = dto.RootStructureId };
- //on initial construction we don't want to have dirty properties tracked
- // http://issues.umbraco.org/issue/U4-1946
- domain.ResetDirtyProperties(false);
- return domain;
- }
-
- public DomainDto BuildDto(CacheableDomain entity)
- {
- var dto = new DomainDto { DefaultLanguage = entity.DefaultLanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId };
+ var dto = new DomainDto { DefaultLanguage = entity.LanguageId, DomainName = entity.DomainName, Id = entity.Id, RootStructureId = entity.RootContentId };
return dto;
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs
index 3a0328294a..d030bcda2a 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDictionaryRepository.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
@@ -7,5 +8,6 @@ namespace Umbraco.Core.Persistence.Repositories
{
IDictionaryItem Get(Guid uniqueId);
IDictionaryItem Get(string key);
+ IEnumerable GetDictionaryItemDescendants(Guid? parentId);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs
index 993f350ce1..afd03f4273 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITagRepository.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
@@ -5,6 +6,10 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface ITagRepository : IRepositoryQueryable
{
+
+ TaggedEntity GetTaggedEntityByKey(Guid key);
+ TaggedEntity GetTaggedEntityById(int id);
+
IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup);
IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null);
@@ -25,6 +30,14 @@ namespace Umbraco.Core.Persistence.Repositories
///
IEnumerable GetTagsForEntity(int contentId, string group = null);
+ ///
+ /// Returns all tags that exist on the content item - Content/Media/Member
+ ///
+ /// The content item id to get tags for
+ /// Optional group
+ ///
+ IEnumerable GetTagsForEntity(Guid contentId, string group = null);
+
///
/// Returns all tags that exist on the content item for the property specified - Content/Media/Member
///
@@ -34,6 +47,15 @@ namespace Umbraco.Core.Persistence.Repositories
///
IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string group = null);
+ ///
+ /// Returns all tags that exist on the content item for the property specified - Content/Media/Member
+ ///
+ /// The content item id to get tags for
+ /// The property alias to get tags for
+ /// Optional group
+ ///
+ IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string group = null);
+
///
/// Assigns the given tags to a content item's property
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
index 9a789ac3f6..2f379b4587 100644
--- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
@@ -21,26 +21,30 @@ namespace Umbraco.Core.Persistence.Repositories
public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
+ //Custom cache options for better performance
+ _cacheOptions = new RepositoryCacheOptions
+ {
+ GetAllCacheAllowZeroCount = true,
+ GetAllCacheValidateCount = false
+ };
+ }
+
+ private readonly RepositoryCacheOptions _cacheOptions;
+
+ ///
+ /// Returns the repository cache options
+ ///
+ protected override RepositoryCacheOptions RepositoryCacheOptions
+ {
+ get { return _cacheOptions; }
}
#region Overrides of RepositoryBase
protected override ILanguage PerformGet(int id)
{
- var sql = GetBaseQuery(false);
- sql.Where(GetBaseWhereClause(), new { Id = id });
-
- var languageDto = Database.FirstOrDefault(sql);
- if (languageDto == null)
- return null;
-
- var entity = ConvertFromDto(languageDto);
-
- //on initial construction we don't want to have dirty properties tracked
- // http://issues.umbraco.org/issue/U4-1946
- ((Entity)entity).ResetDirtyProperties(false);
-
- return entity;
+ //use the underlying GetAll which will force cache all domains
+ return GetAll().FirstOrDefault(x => x.Id == id);
}
protected override IEnumerable PerformGetAll(params int[] ids)
@@ -155,94 +159,16 @@ namespace Umbraco.Core.Persistence.Repositories
public ILanguage GetByCultureName(string cultureName)
{
- var cultureNameRepo = new LanguageByCultureNameRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax);
- return cultureNameRepo.Get(cultureName);
+ //use the underlying GetAll which will force cache all domains
+ return GetAll().FirstOrDefault(x => x.CultureName.InvariantEquals(cultureName));
}
public ILanguage GetByIsoCode(string isoCode)
{
- var isoRepo = new LanguageByIsoCodeRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax);
- return isoRepo.Get(isoCode);
+ //use the underlying GetAll which will force cache all domains
+ return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode));
}
- ///
- /// Inner repository for looking up languages by ISO code, this deals with caching by a string key
- ///
- private class LanguageByIsoCodeRepository : SimpleGetRepository
- {
- private readonly LanguageRepository _languageRepository;
-
- public LanguageByIsoCodeRepository(LanguageRepository languageRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
- : base(work, cache, logger, sqlSyntax)
- {
- _languageRepository = languageRepository;
- }
-
- protected override Sql GetBaseQuery(bool isCount)
- {
- return _languageRepository.GetBaseQuery(isCount);
- }
-
- protected override string GetBaseWhereClause()
- {
- return "umbracoLanguage.languageISOCode = @Id";
- }
-
- protected override ILanguage ConvertToEntity(LanguageDto dto)
- {
- var factory = new LanguageFactory();
- return factory.BuildEntity(dto);
- }
-
- protected override object GetBaseWhereClauseArguments(string id)
- {
- return new {Id = id};
- }
-
- protected override string GetWhereInClauseForGetAll()
- {
- return "umbracoLanguage.languageISOCode in (@ids)";
- }
- }
-
- ///
- /// Inner repository for looking up languages by culture name, this deals with caching by a string key
- ///
- private class LanguageByCultureNameRepository : SimpleGetRepository
- {
- private readonly LanguageRepository _languageRepository;
-
- public LanguageByCultureNameRepository(LanguageRepository languageRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
- : base(work, cache, logger, sqlSyntax)
- {
- _languageRepository = languageRepository;
- }
-
- protected override Sql GetBaseQuery(bool isCount)
- {
- return _languageRepository.GetBaseQuery(isCount);
- }
-
- protected override string GetBaseWhereClause()
- {
- return "umbracoLanguage.languageCultureName = @Id";
- }
-
- protected override ILanguage ConvertToEntity(LanguageDto dto)
- {
- var factory = new LanguageFactory();
- return factory.BuildEntity(dto);
- }
-
- protected override object GetBaseWhereClauseArguments(string id)
- {
- return new {Id = id};
- }
-
- protected override string GetWhereInClauseForGetAll()
- {
- return "umbracoLanguage.languageCultureName in (@ids)";
- }
- }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
index 4a9c110359..162ad88ca0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
@@ -468,10 +468,11 @@ namespace Umbraco.Core.Persistence.Repositories
{
//NOTE: This doesn't allow properties to be part of the query
var dtos = Database.Fetch(sql);
+
+ var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray();
//content types
- var contentTypes = _mediaTypeRepository.GetAll(dtos.Select(x => x.ContentDto.ContentTypeId).ToArray())
- .ToArray();
+ var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray();
var dtosWithContentTypes = dtos
//This select into and null check are required because we don't have a foreign damn key on the contentType column
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
index 02f5fb10c1..ac5529b523 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
@@ -674,8 +674,10 @@ namespace Umbraco.Core.Persistence.Repositories
//NOTE: This doesn't allow properties to be part of the query
var dtos = Database.Fetch(sql);
+ var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray();
+
//content types
- var contentTypes = _memberTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()).ToArray();
+ var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray();
var dtosWithContentTypes = dtos
//This select into and null check are required because we don't have a foreign damn key on the contentType column
diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs
index 63527c7fb9..a88672ea6f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs
@@ -56,7 +56,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable PerformGetAll(params TId[] ids)
{
- var sql = new Sql().From();
+ var sql = new Sql().From(SqlSyntax);
if (ids.Any())
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs
index 14e32d6f9c..a701219ef4 100644
--- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs
@@ -96,7 +96,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = new Sql();
if (isCount)
{
- sql.Select("COUNT(*)").From();
+ sql.Select("COUNT(*)").From(SqlSyntax);
}
else
{
@@ -105,10 +105,10 @@ namespace Umbraco.Core.Persistence.Repositories
return sql;
}
- private static Sql GetBaseQuery()
+ private Sql GetBaseQuery()
{
var sql = new Sql();
- sql.Select("*").From();
+ sql.Select("*").From(SqlSyntax);
return sql;
}
@@ -162,19 +162,55 @@ namespace Umbraco.Core.Persistence.Repositories
//TODO: We need to add lookups for parentId or path! (i.e. get content in tag group that are descendants of x)
+ public TaggedEntity GetTaggedEntityByKey(Guid key)
+ {
+ var sql = new Sql()
+ .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.TagId, right => right.Id)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .Where(dto => dto.UniqueId == key);
+
+ return CreateTaggedEntityCollection(Database.Fetch(sql)).FirstOrDefault();
+ }
+
+ public TaggedEntity GetTaggedEntityById(int id)
+ {
+ var sql = new Sql()
+ .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.TagId, right => right.Id)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .Where(dto => dto.NodeId == id);
+
+ return CreateTaggedEntityCollection(Database.Fetch(sql)).FirstOrDefault();
+ }
+
public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup)
{
var sql = new Sql()
.Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
- .From()
- .InnerJoin()
- .On(left => left.TagId, right => right.Id)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.Id, right => right.PropertyTypeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.TagId, right => right.Id)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
.Where(dto => dto.Group == tagGroup);
if (objectType != TaggableObjectTypes.All)
@@ -185,22 +221,22 @@ namespace Umbraco.Core.Persistence.Repositories
}
return CreateTaggedEntityCollection(
- ApplicationContext.Current.DatabaseContext.Database.Fetch(sql));
+ Database.Fetch(sql));
}
public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null)
{
var sql = new Sql()
.Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group"))
- .From()
- .InnerJoin()
- .On(left => left.TagId, right => right.Id)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.Id, right => right.PropertyTypeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
+ .From(SqlSyntax)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.TagId, right => right.Id)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.NodeId, right => right.NodeId)
.Where(dto => dto.Tag == tag);
if (objectType != TaggableObjectTypes.All)
@@ -216,12 +252,11 @@ namespace Umbraco.Core.Persistence.Repositories
}
return CreateTaggedEntityCollection(
- ApplicationContext.Current.DatabaseContext.Database.Fetch(sql));
+ Database.Fetch(sql));
}
private IEnumerable CreateTaggedEntityCollection(IEnumerable dbResult)
{
- var list = new List();
foreach (var node in dbResult.GroupBy(x => (int)x.nodeId))
{
var properties = new List();
@@ -230,9 +265,8 @@ namespace Umbraco.Core.Persistence.Repositories
var tags = propertyType.Select(x => new Tag((int)x.tagId, (string)x.tag, (string)x.group));
properties.Add(new TaggedProperty(propertyType.Key.id, propertyType.Key.alias, tags));
}
- list.Add(new TaggedEntity(node.Key, properties));
+ yield return new TaggedEntity(node.Key, properties);
}
- return list;
}
public IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string group = null)
@@ -240,13 +274,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetTagsQuerySelect(true);
sql = ApplyRelationshipJoinToTagsQuery(sql);
-
- sql = sql
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId);
-
+
if (objectType != TaggableObjectTypes.All)
{
var nodeObjectType = GetNodeObjectType(objectType);
@@ -268,7 +296,21 @@ namespace Umbraco.Core.Persistence.Repositories
sql = ApplyRelationshipJoinToTagsQuery(sql);
sql = sql
- .Where(dto => dto.NodeId == contentId);
+ .Where(dto => dto.NodeId == contentId);
+
+ sql = ApplyGroupFilterToTagsQuery(sql, group);
+
+ return ExecuteTagsQuery(sql);
+ }
+
+ public IEnumerable GetTagsForEntity(Guid contentId, string group = null)
+ {
+ var sql = GetTagsQuerySelect();
+
+ sql = ApplyRelationshipJoinToTagsQuery(sql);
+
+ sql = sql
+ .Where(dto => dto.UniqueId == contentId);
sql = ApplyGroupFilterToTagsQuery(sql, group);
@@ -282,9 +324,26 @@ namespace Umbraco.Core.Persistence.Repositories
sql = ApplyRelationshipJoinToTagsQuery(sql);
sql = sql
- .InnerJoin()
- .On(left => left.Id, right => right.PropertyTypeId)
- .Where(dto => dto.NodeId == contentId)
+ .InnerJoin(SqlSyntax)
+ .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId)
+ .Where