2012-07-20 01:04:35 +06:00
using System ;
2012-07-26 21:12:54 +06:00
using System.Configuration ;
2013-03-12 01:50:56 +04:00
using System.Threading ;
2015-07-03 15:32:37 +02:00
using System.Threading.Tasks ;
2015-08-25 15:48:12 +02:00
using System.Web ;
2012-08-01 22:06:15 +06:00
using Umbraco.Core.Configuration ;
using Umbraco.Core.Logging ;
2013-03-12 01:50:56 +04:00
using Umbraco.Core.ObjectResolution ;
2015-01-16 12:29:27 +11:00
using Umbraco.Core.Profiling ;
2012-11-12 07:40:11 -01:00
using Umbraco.Core.Services ;
2015-07-06 13:58:10 +02:00
using Umbraco.Core.Sync ;
2012-07-26 21:12:54 +06:00
2012-07-20 01:04:35 +06:00
namespace Umbraco.Core
2012-07-28 23:08:02 +06:00
{
2012-07-26 21:12:54 +06:00
/// <summary>
2012-07-20 01:04:35 +06:00
/// the Umbraco Application context
/// </summary>
/// <remarks>
/// one per AppDomain, represents the global Umbraco application
/// </remarks>
2013-03-12 01:50:56 +04:00
public class ApplicationContext : IDisposable
2012-07-20 01:04:35 +06:00
{
2013-03-23 04:01:52 +06:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="dbContext"></param>
/// <param name="serviceContext"></param>
2013-07-02 17:47:20 +10:00
/// <param name="cache"></param>
2015-01-09 10:51:15 +11:00
/// <param name="logger"></param>
2015-01-16 12:29:27 +11:00
public ApplicationContext ( DatabaseContext dbContext , ServiceContext serviceContext , CacheHelper cache , ProfilingLogger logger )
2015-01-09 10:51:15 +11:00
{
2013-03-23 04:01:52 +06:00
if ( dbContext = = null ) throw new ArgumentNullException ( "dbContext" ) ;
if ( serviceContext = = null ) throw new ArgumentNullException ( "serviceContext" ) ;
2013-07-02 17:47:20 +10:00
if ( cache = = null ) throw new ArgumentNullException ( "cache" ) ;
2015-07-15 16:28:50 +02:00
if ( logger = = null ) throw new ArgumentNullException ( "logger" ) ;
2013-03-23 04:01:52 +06:00
_databaseContext = dbContext ;
2013-07-02 17:47:20 +10:00
_services = serviceContext ;
ApplicationCache = cache ;
2015-01-16 12:29:27 +11:00
ProfilingLogger = logger ;
2015-06-22 16:22:47 +02:00
Init ( ) ;
2015-01-09 10:51:15 +11:00
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="dbContext"></param>
/// <param name="serviceContext"></param>
/// <param name="cache"></param>
2015-07-15 16:28:50 +02:00
[Obsolete("Use the other constructor specifying a ProfilingLogger instead")]
2015-01-09 10:51:15 +11:00
public ApplicationContext ( DatabaseContext dbContext , ServiceContext serviceContext , CacheHelper cache )
2015-01-16 12:29:27 +11:00
: this ( dbContext , serviceContext , cache ,
new ProfilingLogger ( LoggerResolver . Current . Logger , ProfilerResolver . Current . Profiler ) )
2015-01-09 10:51:15 +11:00
{
2013-03-23 04:01:52 +06:00
}
2013-08-09 11:42:20 +10:00
/// <summary>
/// Creates a basic app context
/// </summary>
/// <param name="cache"></param>
2015-07-15 16:28:50 +02:00
[Obsolete("Use the other constructor specifying a ProfilingLogger instead")]
2013-10-09 13:41:14 +11:00
public ApplicationContext ( CacheHelper cache )
2013-08-09 11:42:20 +10:00
{
2015-07-15 16:40:50 +02:00
if ( cache = = null ) throw new ArgumentNullException ( "cache" ) ;
2013-08-09 11:42:20 +10:00
ApplicationCache = cache ;
2015-07-15 16:28:50 +02:00
ProfilingLogger = new ProfilingLogger ( LoggerResolver . Current . Logger , ProfilerResolver . Current . Profiler ) ;
Init ( ) ;
}
2015-06-22 16:22:47 +02:00
2015-07-15 16:28:50 +02:00
/// <summary>
/// Creates a basic app context
/// </summary>
/// <param name="cache"></param>
/// <param name="logger"></param>
public ApplicationContext ( CacheHelper cache , ProfilingLogger logger )
{
if ( cache = = null ) throw new ArgumentNullException ( "cache" ) ;
if ( logger = = null ) throw new ArgumentNullException ( "logger" ) ;
ApplicationCache = cache ;
ProfilingLogger = logger ;
2015-06-22 16:22:47 +02:00
Init ( ) ;
2013-08-09 11:42:20 +10:00
}
2013-10-09 13:17:19 +11:00
/// <summary>
/// A method used to set and/or ensure that a global ApplicationContext singleton is created.
/// </summary>
/// <param name="appContext">
/// The instance to set on the global application singleton
/// </param>
/// <param name="replaceContext">If set to true and the singleton is already set, it will be replaced</param>
/// <returns></returns>
/// <remarks>
/// This is NOT thread safe
/// </remarks>
public static ApplicationContext EnsureContext ( ApplicationContext appContext , bool replaceContext )
{
2015-07-03 15:32:37 +02:00
if ( Current ! = null )
2013-10-09 13:17:19 +11:00
{
if ( ! replaceContext )
2015-07-03 15:32:37 +02:00
return Current ;
2013-10-09 13:17:19 +11:00
}
2015-07-03 15:32:37 +02:00
Current = appContext ;
return Current ;
2013-10-09 13:17:19 +11:00
}
/// <summary>
/// A method used to create and ensure that a global ApplicationContext singleton is created.
/// </summary>
2013-10-09 13:52:24 +11:00
/// <param name="cache"></param>
2013-10-09 13:17:19 +11:00
/// <param name="replaceContext">
/// If set to true will replace the current singleton instance - This should only be used for unit tests or on app
/// startup if for some reason the boot manager is not the umbraco boot manager.
/// </param>
/// <param name="dbContext"></param>
/// <param name="serviceContext"></param>
/// <returns></returns>
/// <remarks>
/// This is NOT thread safe
/// </remarks>
2015-01-16 12:29:27 +11:00
[Obsolete("Use the other method specifying an ProfilingLogger instead")]
2013-10-09 13:52:24 +11:00
public static ApplicationContext EnsureContext ( DatabaseContext dbContext , ServiceContext serviceContext , CacheHelper cache , bool replaceContext )
2013-10-09 13:17:19 +11:00
{
2015-07-03 15:32:37 +02:00
if ( Current ! = null )
2013-10-09 13:17:19 +11:00
{
if ( ! replaceContext )
2015-07-03 15:32:37 +02:00
return Current ;
2013-10-09 13:17:19 +11:00
}
2013-10-09 13:52:24 +11:00
var ctx = new ApplicationContext ( dbContext , serviceContext , cache ) ;
2015-07-03 15:32:37 +02:00
Current = ctx ;
return Current ;
2013-10-09 13:17:19 +11:00
}
2015-01-09 10:51:15 +11:00
/// <summary>
/// A method used to create and ensure that a global ApplicationContext singleton is created.
/// </summary>
/// <param name="cache"></param>
/// <param name="logger"></param>
/// <param name="replaceContext">
/// If set to true will replace the current singleton instance - This should only be used for unit tests or on app
/// startup if for some reason the boot manager is not the umbraco boot manager.
/// </param>
/// <param name="dbContext"></param>
/// <param name="serviceContext"></param>
/// <returns></returns>
/// <remarks>
/// This is NOT thread safe
/// </remarks>
2015-01-16 12:29:27 +11:00
public static ApplicationContext EnsureContext ( DatabaseContext dbContext , ServiceContext serviceContext , CacheHelper cache , ProfilingLogger logger , bool replaceContext )
2015-01-09 10:51:15 +11:00
{
2015-07-13 22:52:41 +02:00
if ( Current ! = null )
2015-01-09 10:51:15 +11:00
{
if ( ! replaceContext )
2015-07-13 22:52:41 +02:00
return Current ;
2015-01-09 10:51:15 +11:00
}
var ctx = new ApplicationContext ( dbContext , serviceContext , cache , logger ) ;
2015-07-13 22:52:41 +02:00
Current = ctx ;
return Current ;
2015-01-09 10:51:15 +11:00
}
2013-10-09 13:17:19 +11:00
/// <summary>
2012-07-20 22:02:28 +06:00
/// Singleton accessor
/// </summary>
2012-07-20 23:42:55 +06:00
public static ApplicationContext Current { get ; internal set ; }
2012-07-20 01:04:35 +06:00
2012-10-31 11:36:22 +06:00
/// <summary>
/// Returns the application wide cache accessor
/// </summary>
/// <remarks>
/// Any caching that is done in the application (app wide) should be done through this property
/// </remarks>
2012-11-19 15:31:58 -01:00
public CacheHelper ApplicationCache { get ; private set ; }
2012-10-31 11:36:22 +06:00
2015-01-09 10:51:15 +11:00
/// <summary>
2015-01-16 12:29:27 +11:00
/// Exposes the global ProfilingLogger - this should generally not be accessed via the UmbracoContext and should normally just be exposed
2015-01-09 10:51:15 +11:00
/// on most base classes or injected with IoC
/// </summary>
2015-01-16 12:29:27 +11:00
public ProfilingLogger ProfilingLogger { get ; private set ; }
2015-01-09 10:51:15 +11:00
// IsReady is set to true by the boot manager once it has successfully booted
2012-07-20 01:04:35 +06:00
// note - the original umbraco module checks on content.Instance in umbraco.dll
// now, the boot task that setup the content store ensures that it is ready
bool _isReady = false ;
2013-09-25 19:23:41 +10:00
readonly ManualResetEventSlim _isReadyEvent = new ManualResetEventSlim ( false ) ;
2012-12-14 08:06:32 +05:00
private DatabaseContext _databaseContext ;
private ServiceContext _services ;
public bool IsReady
2012-07-20 01:04:35 +06:00
{
get
{
return _isReady ;
}
internal set
{
AssertIsNotReady ( ) ;
_isReady = value ;
2012-09-28 11:11:47 -02:00
_isReadyEvent . Set ( ) ;
2012-07-20 01:04:35 +06:00
}
}
2012-09-28 11:11:47 -02:00
public bool WaitForReady ( int timeout )
{
return _isReadyEvent . WaitHandle . WaitOne ( timeout ) ;
}
2012-07-20 01:04:35 +06:00
// notes
// GlobalSettings.ConfigurationStatus returns the value that's in the web.config, so it's the "configured version"
// GlobalSettings.CurrentVersion returns the hard-coded "current version"
// the system is configured if they match
// if they don't, install runs, updates web.config (presumably) and updates GlobalSettings.ConfiguredStatus
2015-06-22 16:22:47 +02:00
2012-07-20 01:04:35 +06:00
public bool IsConfigured
{
2015-06-22 16:22:47 +02:00
get { return _configured . Value ; }
2012-07-20 01:04:35 +06:00
}
2015-04-01 16:12:32 +11:00
/// <summary>
2015-06-09 12:17:45 +02:00
/// If the db is configured, there is a database context and there is an umbraco schema, but we are not 'configured' , then it means we are upgrading
2015-04-01 16:12:32 +11:00
/// </summary>
public bool IsUpgrading
{
2015-06-09 12:17:45 +02:00
get
{
if ( IsConfigured = = false
& & DatabaseContext ! = null
& & DatabaseContext . IsDatabaseConfigured )
{
var schemaresult = DatabaseContext . ValidateDatabaseSchema ( ) ;
if ( schemaresult . ValidTables . Count > 0 ) return true ;
}
return false ;
}
2015-04-01 16:12:32 +11:00
}
2013-09-13 14:06:36 +10:00
/// <summary>
2015-07-06 13:58:10 +02:00
/// The application url.
2013-04-05 23:28:43 +06:00
/// </summary>
/// <remarks>
2015-07-06 13:58:10 +02:00
/// The application url is the url that should be used by services to talk to the application,
/// eg keep alive or scheduled publishing services. It must exist on a global context because
/// some of these services may not run within a web context.
/// The format of the application url is:
/// - has a scheme (http or https)
/// - 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:
2015-07-14 19:23:38 +02:00
/// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting)
2015-07-06 13:58:10 +02:00
/// - 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.
/// See also issues:
/// - http://issues.umbraco.org/issue/U4-2059
/// - http://issues.umbraco.org/issue/U4-6788
/// - http://issues.umbraco.org/issue/U4-5728
/// - http://issues.umbraco.org/issue/U4-5391
2013-04-05 23:28:43 +06:00
/// </remarks>
2015-07-08 17:32:36 +02:00
internal string UmbracoApplicationUrl
{
get
{
2015-08-25 15:48:12 +02:00
ApplicationUrlHelper . EnsureApplicationUrl ( this ) ;
2015-07-06 13:58:10 +02:00
return _umbracoApplicationUrl ;
}
}
2015-08-25 15:48:12 +02:00
// ReSharper disable once InconsistentNaming
internal string _umbracoApplicationUrl ;
2015-06-22 16:22:47 +02:00
2015-07-09 14:30:32 +02:00
private Lazy < bool > _configured ;
2015-07-03 15:32:37 +02:00
internal MainDom MainDom { get ; private set ; }
2015-06-22 16:22:47 +02:00
private void Init ( )
2015-07-13 22:45:37 +02:00
{
2015-07-13 22:52:41 +02:00
MainDom = new MainDom ( ProfilingLogger . Logger ) ;
2015-07-03 15:32:37 +02:00
MainDom . Acquire ( ) ;
2015-07-13 22:45:37 +02:00
2015-06-22 16:22:47 +02:00
//Create the lazy value to resolve whether or not the application is 'configured'
_configured = new Lazy < bool > ( ( ) = >
{
try
{
var configStatus = ConfigurationStatus ;
2015-06-24 14:17:24 +02:00
var currentVersion = UmbracoVersion . GetSemanticVersion ( ) ;
2015-11-26 13:07:22 +01:00
var ok =
//we are not configured if this is null
string . IsNullOrWhiteSpace ( configStatus ) = = false
//they must match
& & configStatus = = currentVersion ;
2015-06-22 16:22:47 +02:00
if ( ok )
{
//The versions are the same in config, but are they the same in the database. We can only check this
// if we have a db context available, if we don't then we are not installed anyways
if ( DatabaseContext . IsDatabaseConfigured & & DatabaseContext . CanConnect )
{
2015-06-24 14:17:24 +02:00
var found = Services . MigrationEntryService . FindEntry ( GlobalSettings . UmbracoMigrationName , UmbracoVersion . GetSemanticVersion ( ) ) ;
2015-06-22 16:22:47 +02:00
if ( found = = null )
{
//we haven't executed this migration in this environment, so even though the config versions match,
// this db has not been updated.
2015-07-07 10:12:34 +02:00
ProfilingLogger . Logger . Debug < ApplicationContext > ( string . Format ( "The migration for version: '{0} has not been executed, there is no record in the database" , currentVersion . ToSemanticString ( ) ) ) ;
2015-06-22 16:22:47 +02:00
ok = false ;
}
}
}
else
{
2015-07-07 10:12:34 +02:00
ProfilingLogger . Logger . Debug < ApplicationContext > ( string . Format ( "CurrentVersion different from configStatus: '{0}','{1}'" , currentVersion . ToSemanticString ( ) , configStatus ) ) ;
2015-06-22 16:22:47 +02:00
}
return ok ;
}
2015-11-26 13:07:22 +01:00
catch ( Exception ex )
2015-06-22 16:22:47 +02:00
{
2015-11-26 13:07:22 +01:00
LogHelper . Error < ApplicationContext > ( "Error determining if application is configured, returning false" , ex ) ;
2015-06-22 16:22:47 +02:00
return false ;
}
} ) ;
2015-07-13 22:45:37 +02:00
}
2012-11-19 15:31:58 -01:00
2012-07-26 21:12:54 +06:00
private string ConfigurationStatus
{
get
{
try
{
return ConfigurationManager . AppSettings [ "umbracoConfigurationStatus" ] ;
}
catch
{
return String . Empty ;
}
}
}
2012-07-20 01:04:35 +06:00
private void AssertIsNotReady ( )
{
if ( this . IsReady )
throw new Exception ( "ApplicationContext has already been initialized." ) ;
}
2012-11-06 10:47:14 -01:00
2012-12-14 08:06:32 +05:00
/// <summary>
/// Gets the current DatabaseContext
/// </summary>
/// <remarks>
/// Internal set is generally only used for unit tests
/// </remarks>
public DatabaseContext DatabaseContext
{
get
{
if ( _databaseContext = = null )
throw new InvalidOperationException ( "The DatabaseContext has not been set on the ApplicationContext" ) ;
return _databaseContext ;
}
internal set { _databaseContext = value ; }
}
/// <summary>
/// Gets the current ServiceContext
/// </summary>
/// <remarks>
/// Internal set is generally only used for unit tests
/// </remarks>
public ServiceContext Services
{
get
{
if ( _services = = null )
throw new InvalidOperationException ( "The ServiceContext has not been set on the ApplicationContext" ) ;
return _services ;
}
internal set { _services = value ; }
}
2013-03-12 01:50:56 +04:00
2015-08-25 15:48:12 +02:00
internal ServerRole GetCurrentServerRole ( )
{
var registrar = ServerRegistrarResolver . Current . Registrar as IServerRegistrar2 ;
return registrar = = null ? ServerRole . Unknown : registrar . GetCurrentServerRole ( ) ;
}
2013-03-12 01:50:56 +04:00
private volatile bool _disposed ;
private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim ( ) ;
/// <summary>
/// This will dispose and reset all resources used to run the application
/// </summary>
/// <remarks>
/// IMPORTANT: Never dispose this object if you require the Umbraco application to run, disposing this object
/// is generally used for unit testing and when your application is shutting down after you have booted Umbraco.
/// </remarks>
void IDisposable . Dispose ( )
{
// Only operate if we haven't already disposed
if ( _disposed ) return ;
using ( new WriteLock ( _disposalLocker ) )
{
// Check again now we're inside the lock
if ( _disposed ) return ;
2013-03-12 22:58:21 +04:00
//clear the cache
2013-03-14 01:07:54 +04:00
if ( ApplicationCache ! = null )
{
2016-01-06 18:08:14 +01:00
ApplicationCache . RuntimeCache . ClearAllCache ( ) ;
ApplicationCache . IsolatedRuntimeCache . ClearAllCaches ( ) ;
2013-03-14 01:07:54 +04:00
}
2013-03-12 22:58:21 +04:00
//reset all resolvers
2013-03-12 01:50:56 +04:00
ResolverCollection . ResetAll ( ) ;
2013-03-12 22:58:21 +04:00
//reset resolution itself (though this should be taken care of by resetting any of the resolvers above)
2013-03-12 01:50:56 +04:00
Resolution . Reset ( ) ;
2013-03-13 18:31:07 +04:00
2013-03-12 22:58:21 +04:00
//reset the instance objects
this . ApplicationCache = null ;
2013-03-14 01:07:54 +04:00
if ( _databaseContext ! = null ) //need to check the internal field here
2013-03-13 18:31:07 +04:00
{
2015-01-22 15:16:10 +11:00
if ( DatabaseContext . IsDatabaseConfigured & & DatabaseContext . Database ! = null )
2013-03-14 01:07:54 +04:00
{
DatabaseContext . Database . Dispose ( ) ;
}
2013-03-13 18:31:07 +04:00
}
2013-03-12 22:58:21 +04:00
this . DatabaseContext = null ;
this . Services = null ;
2013-03-13 18:31:07 +04:00
this . _isReady = false ; //set the internal field
2013-03-12 01:50:56 +04:00
// Indicate that the instance has been disposed.
_disposed = true ;
}
}
2012-07-20 01:04:35 +06:00
}
}