diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index 66b4126574..1a43139632 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -7,6 +7,8 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
+using System.Web.Caching;
+using System.Web.Compilation;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
@@ -33,8 +35,29 @@ namespace Umbraco.Core
///
internal class PluginManager
{
+ private readonly ApplicationContext _appContext;
- internal PluginManager()
+ ///
+ /// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml
+ /// file is cached temporarily until app startup completes.
+ ///
+ ///
+ ///
+ internal PluginManager(ApplicationContext appContext, bool detectBinChanges = true)
+ : this(detectBinChanges)
+ {
+ if (appContext == null) throw new ArgumentNullException("appContext");
+ _appContext = appContext;
+ }
+
+ ///
+ /// Creates a new PluginManager
+ ///
+ ///
+ /// If true will detect changes in the /bin folder and therefor load plugins from the
+ /// cached plugins file if one is found. If false will never use the cache file for plugins
+ ///
+ internal PluginManager(bool detectBinChanges = true)
{
_tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
//create the folder if it doesn't exist
@@ -42,13 +65,24 @@ namespace Umbraco.Core
{
Directory.CreateDirectory(_tempFolder);
}
- //do the check if they've changed
- HaveAssembliesChanged = CachedAssembliesHash != CurrentAssembliesHash;
- //if they have changed, we need to write the new file
- if (HaveAssembliesChanged)
+
+ if (detectBinChanges)
{
- WriteCachePluginsHash();
+ //first check if the cached hash is 0, if it is then we ne
+ //do the check if they've changed
+ HaveAssembliesChanged = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == 0;
+ //if they have changed, we need to write the new file
+ if (HaveAssembliesChanged)
+ {
+ WriteCachePluginsHash();
+ }
}
+ else
+ {
+ //always set to true if we're not detecting (generally only for testing)
+ HaveAssembliesChanged = true;
+ }
+
}
static PluginManager _resolver;
@@ -72,7 +106,9 @@ namespace Umbraco.Core
if (_resolver == null)
{
l.UpgradeToWriteLock();
- _resolver = new PluginManager();
+ _resolver = ApplicationContext.Current == null
+ ? new PluginManager()
+ : new PluginManager(ApplicationContext.Current);
}
return _resolver;
}
@@ -180,37 +216,49 @@ namespace Umbraco.Core
///
///
///
- internal Attempt>> TryGetCachedPluginsFromFile()
+ internal Attempt> TryGetCachedPluginsFromFile()
{
var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
if (!File.Exists(filePath))
- return Attempt>>.False;
+ return Attempt>.False;
try
{
- var xml = XDocument.Load(filePath);
+ //we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes)
+ //while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests)
+ XDocument xml;
+ if (_appContext != null)
+ {
+ xml = _appContext.ApplicationCache.GetCacheItem("umbraco-plugins.list",
+ new TimeSpan(0, 0, 5, 0),
+ () => XDocument.Load(filePath));
+ }
+ else
+ {
+ xml = XDocument.Load(filePath);
+ }
+
+
if (xml.Root == null)
- return Attempt>>.False;
+ return Attempt>.False;
var typeElement = xml.Root.Elements()
.SingleOrDefault(x =>
x.Name.LocalName == "baseType"
&& ((string) x.Attribute("type")) == typeof (T).FullName);
if (typeElement == null)
- return Attempt>>.False;
+ return Attempt>.False;
//return success
- return new Attempt>>(
+ return new Attempt>(
true,
typeElement.Elements("add")
- .Select(x => new Tuple(
- (string) x.Attribute("type"),
- (string) x.Attribute("assembly"))));
+ .Select(x => (string) x.Attribute("type")));
}
catch (Exception)
{
//if the file is corrupted, etc... return false
- return Attempt>>.False;
+ return Attempt>.False;
}
}
@@ -268,10 +316,9 @@ namespace Umbraco.Core
//now we have the type element, we need to clear any previous types as children and add/update it with new ones
typeElement.ReplaceNodes(typesFound
- .Select(x =>
- new XElement("add",
- new XAttribute("type", x.FullName),
- new XAttribute("assembly", x.Assembly.FullName))));
+ .Select(x =>
+ new XElement("add",
+ new XAttribute("type", x.AssemblyQualifiedName))));
//save the xml file
xml.Save(filePath);
}
@@ -444,15 +491,17 @@ namespace Umbraco.Core
{
try
{
- var type = Assembly.Load(t.Item2).GetType(t.Item1);
- typeList.AddType(type);
+ //we use the build manager to ensure we get all types loaded, this is slightly slower than
+ //Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that.
+ var type = BuildManager.GetType(t, true);
+ typeList.AddType(type);
}
catch (Exception ex)
{
//if there are any exceptions loading types, we have to exist, this should never happen so
//we will need to revert to scanning for types.
successfullyLoadedFromCache = false;
- LogHelper.Error("Could not load a cached plugin type: " + t.Item1 + " in assembly: " + t.Item2 + " now reverting to re-scanning assemblies for the base type: " + typeof (T).FullName, ex);
+ LogHelper.Error("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof (T).FullName, ex);
break;
}
}
@@ -461,6 +510,10 @@ namespace Umbraco.Core
//we need to manually load by scanning if loading from the file was not successful.
LoadViaScanningAndUpdateCacheFile(typeList, finder);
}
+ else
+ {
+ LogHelper.Debug("Loaded plugin types " + typeof(T).FullName + " from persisted cache");
+ }
}
}
else
diff --git a/src/Umbraco.Tests/CacheRefresherFactoryTests.cs b/src/Umbraco.Tests/CacheRefresherFactoryTests.cs
index e9a2c075fb..8fdef3f09a 100644
--- a/src/Umbraco.Tests/CacheRefresherFactoryTests.cs
+++ b/src/Umbraco.Tests/CacheRefresherFactoryTests.cs
@@ -17,7 +17,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Tests/DataTypeFactoryTests.cs b/src/Umbraco.Tests/DataTypeFactoryTests.cs
index d836b8f836..e0de0a4af6 100644
--- a/src/Umbraco.Tests/DataTypeFactoryTests.cs
+++ b/src/Umbraco.Tests/DataTypeFactoryTests.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Tests/MacroEngineFactoryTests.cs b/src/Umbraco.Tests/MacroEngineFactoryTests.cs
index 4a058e272e..f38a825f6f 100644
--- a/src/Umbraco.Tests/MacroEngineFactoryTests.cs
+++ b/src/Umbraco.Tests/MacroEngineFactoryTests.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Tests/MediaFactoryTests.cs b/src/Umbraco.Tests/MediaFactoryTests.cs
index 58fd81d446..11bf2de2fe 100644
--- a/src/Umbraco.Tests/MediaFactoryTests.cs
+++ b/src/Umbraco.Tests/MediaFactoryTests.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs
index 3812835115..f7262c8814 100644
--- a/src/Umbraco.Tests/PluginManagerTests.cs
+++ b/src/Umbraco.Tests/PluginManagerTests.cs
@@ -1,8 +1,10 @@
using System;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Web.Compilation;
using NUnit.Framework;
using SqlCE4Umbraco;
using Umbraco.Core;
@@ -15,6 +17,7 @@ using umbraco.MacroEngines.Iron;
using umbraco.businesslogic;
using umbraco.cms.businesslogic;
using umbraco.editorControls;
+using umbraco.interfaces;
using umbraco.uicontrols;
using umbraco.cms;
@@ -31,7 +34,7 @@ namespace Umbraco.Tests
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
//TODO: Should probably update this so it only searches this assembly and add custom types to be found
@@ -69,19 +72,97 @@ namespace Umbraco.Tests
return dir;
}
+ //[Test]
+ //public void Scan_Vs_Load_Benchmark()
+ //{
+ // var pluginManager = new PluginManager(false);
+ // var watch = new Stopwatch();
+ // watch.Start();
+ // for (var i = 0; i < 1000; i++)
+ // {
+ // var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // }
+ // watch.Stop();
+ // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
+ // watch.Start();
+ // for (var i = 0; i < 1000; i++)
+ // {
+ // var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // }
+ // watch.Stop();
+ // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
+ // watch.Reset();
+ // watch.Start();
+ // for (var i = 0; i < 1000; i++)
+ // {
+ // var refreshers = pluginManager.ResolveTypes(false);
+ // }
+ // watch.Stop();
+ // Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
+ //}
+
+ ////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :)
+ //[Test]
+ //public void Load_Type_Benchmark()
+ //{
+ // var watch = new Stopwatch();
+ // watch.Start();
+ // for (var i = 0; i < 1000; i++)
+ // {
+ // var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
+ // }
+ // watch.Stop();
+ // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
+ // watch.Reset();
+ // watch.Start();
+ // for (var i = 0; i < 1000; i++)
+ // {
+ // var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
+ // .GetType("umbraco.macroCacheRefresh");
+ // var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
+ // .GetType("umbraco.templateCacheRefresh");
+ // var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
+ // .GetType("umbraco.presentation.cache.MediaLibraryRefreshers");
+ // var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
+ // .GetType("umbraco.presentation.cache.pageRefresher");
+ // }
+ // watch.Stop();
+ // Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
+ // watch.Reset();
+ // watch.Start();
+ // for (var i = 0; i < 1000; i++)
+ // {
+ // var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
+ // }
+ // watch.Stop();
+ // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
+ //}
+
[Test]
public void Create_Cached_Plugin_File()
{
var types = new[] {typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext)};
- var manager = new PluginManager();
+ var manager = new PluginManager(false);
//yes this is silly, none of these types inherit from string, but this is just to test the xml file format
manager.UpdateCachedPluginsFile(types);
var plugins = manager.TryGetCachedPluginsFromFile();
Assert.IsTrue(plugins.Success);
Assert.AreEqual(3, plugins.Result.Count());
- var shouldContain = types.Select(x => new System.Tuple(x.FullName, x.Assembly.FullName));
+ var shouldContain = types.Select(x => x.AssemblyQualifiedName);
//ensure they are all found
Assert.IsTrue(plugins.Result.ContainsAll(shouldContain));
}
diff --git a/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs b/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs
index d9bd05c7ce..34b834753b 100644
--- a/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs
+++ b/src/Umbraco.Tests/Resolvers/ActionsResolverTests.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs b/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs
index eff9f82b53..6f7ae14ce9 100644
--- a/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs
+++ b/src/Umbraco.Tests/Resolvers/MacroFieldEditorsResolverTests.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs b/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs
index b126fde180..a12890a36f 100644
--- a/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs
+++ b/src/Umbraco.Tests/Resolvers/PackageActionsResolverTests.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Resolvers
TestHelper.SetupLog4NetForTests();
//this ensures its reset
- PluginManager.Current = new PluginManager();
+ PluginManager.Current = new PluginManager(false);
//for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
PluginManager.Current.AssembliesToScan = new[]
diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs
index caa62575a9..20ed9fdd1e 100644
--- a/src/Umbraco.Web/UmbracoContext.cs
+++ b/src/Umbraco.Web/UmbracoContext.cs
@@ -47,6 +47,8 @@ namespace Umbraco.Web
if (httpContext == null) throw new ArgumentNullException("httpContext");
if (applicationContext == null) throw new ArgumentNullException("applicationContext");
+ ObjectCreated = DateTime.Now;
+
HttpContext = httpContext;
Application = applicationContext;
RoutesCache = routesCache;
@@ -99,6 +101,13 @@ namespace Umbraco.Web
}
}
+ ///
+ /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this
+ /// object is instantiated which in the web site is created during the BeginRequest phase.
+ /// We can then determine complete rendering time from that.
+ ///
+ internal DateTime ObjectCreated { get; private set; }
+
///
/// Gets the current ApplicationContext
///
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index 37125b8b4d..cad2639599 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -425,8 +425,12 @@ namespace Umbraco.Web
app.EndRequest += (sender, args) =>
{
var httpContext = ((HttpApplication)sender).Context;
- //write the trace output for diagnostics at the end of the request
- httpContext.Trace.Write("UmbracoModule", "Umbraco request completed");
+ if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest)
+ {
+ //write the trace output for diagnostics at the end of the request
+ httpContext.Trace.Write("UmbracoModule", "Umbraco request completed");
+ LogHelper.Debug("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds);
+ }
};
}