diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs
new file mode 100644
index 0000000000..78f5eb6839
--- /dev/null
+++ b/src/Umbraco.Core/HashCodeCombiner.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Used to create a hash code from multiple objects.
+ ///
+ ///
+ /// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things
+ /// and is probably more stable in general, however we just need a quick easy class for this in order to create a unique
+ /// hash of plugins to see if they've changed.
+ ///
+ internal class HashCodeCombiner
+ {
+ private int _combinedHash;
+
+ internal void AddInt(int i)
+ {
+ _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i;
+ }
+
+ internal void AddObject(object o)
+ {
+ AddInt(o.GetHashCode());
+ }
+
+ internal void AddDateTime(DateTime d)
+ {
+ AddInt(d.GetHashCode());
+ }
+
+ internal void AddCaseInsensitiveString(string s)
+ {
+ if (s != null)
+ AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s));
+ }
+
+ internal void AddFile(FileInfo f)
+ {
+ AddCaseInsensitiveString(f.FullName);
+ AddDateTime(f.CreationTimeUtc);
+ AddDateTime(f.LastWriteTimeUtc);
+ AddInt(f.Length.GetHashCode());
+ }
+
+ internal void AddFolder(DirectoryInfo d)
+ {
+ AddCaseInsensitiveString(d.FullName);
+ AddDateTime(d.CreationTimeUtc);
+ AddDateTime(d.LastWriteTimeUtc);
+ foreach (var f in d.GetFiles())
+ {
+ AddFile(f);
+ }
+ foreach (var s in d.GetDirectories())
+ {
+ AddFolder(s);
+ }
+ }
+
+ ///
+ /// Returns the hex code of the combined hash code
+ ///
+ ///
+ internal string GetCombinedHashCode()
+ {
+ return _combinedHash.ToString("x", CultureInfo.InvariantCulture);
+ }
+
+ }
+}
diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs
index e7f6b29c60..6828b0e353 100644
--- a/src/Umbraco.Core/IO/SystemDirectories.cs
+++ b/src/Umbraco.Core/IO/SystemDirectories.cs
@@ -12,6 +12,7 @@ namespace Umbraco.Core.IO
//all paths has a starting but no trailing /
public class SystemDirectories
{
+ //TODO: Why on earth is this even configurable? You cannot change the /Bin folder in ASP.Net
public static string Bin
{
get
diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index b01e7c0683..66b4126574 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -1,10 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
+using System.Xml;
+using System.Xml.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors;
using umbraco.interfaces;
@@ -21,16 +27,35 @@ namespace Umbraco.Core
///
/// This class can expose extension methods to resolve custom plugins
///
+ /// Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't
+ /// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads
+ /// to much faster startup times.
///
internal class PluginManager
{
internal PluginManager()
{
+ _tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
+ //create the folder if it doesn't exist
+ if (!Directory.Exists(_tempFolder))
+ {
+ 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)
+ {
+ WriteCachePluginsHash();
+ }
}
static PluginManager _resolver;
static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
+ private readonly string _tempFolder;
+ private long _cachedAssembliesHash = -1;
+ private long _currentAssembliesHash = -1;
///
/// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place
@@ -55,6 +80,204 @@ namespace Umbraco.Core
set { _resolver = value; }
}
+ #region Hash checking methods
+
+ ///
+ /// Returns a bool if the assemblies in the /bin have changed since they were last hashed.
+ ///
+ internal bool HaveAssembliesChanged { get; private set; }
+
+ ///
+ /// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0
+ /// if no cache is found.
+ ///
+ ///
+ internal long CachedAssembliesHash
+ {
+ get
+ {
+ if (_cachedAssembliesHash != -1)
+ return _cachedAssembliesHash;
+
+ var filePath = Path.Combine(_tempFolder, "umbraco-plugins.hash");
+ if (!File.Exists(filePath))
+ return 0;
+ var hash = File.ReadAllText(filePath, Encoding.UTF8);
+ Int64 val;
+ if (Int64.TryParse(hash, out val))
+ {
+ _cachedAssembliesHash = val;
+ return _cachedAssembliesHash;
+ }
+ //it could not parse for some reason so we'll return 0.
+ return 0;
+ }
+ }
+
+ ///
+ /// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin
+ ///
+ ///
+ internal long CurrentAssembliesHash
+ {
+ get
+ {
+ if (_currentAssembliesHash != -1)
+ return _currentAssembliesHash;
+
+ _currentAssembliesHash = GetAssembliesHash(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)).GetFiles("*.dll"));
+ return _currentAssembliesHash;
+ }
+ }
+
+ ///
+ /// Writes the assembly hash file
+ ///
+ private void WriteCachePluginsHash()
+ {
+ var filePath = Path.Combine(_tempFolder, "umbraco-plugins.hash");
+ File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8);
+ }
+
+ ///
+ /// Returns a unique hash for the combination of FileInfo objects passed in
+ ///
+ ///
+ ///
+ internal static long GetAssembliesHash(IEnumerable plugins)
+ {
+ using (DisposableTimer.TraceDuration("Determining hash of plugins on disk", "Hash determined"))
+ {
+ var hashCombiner = new HashCodeCombiner();
+ //add each unique folder to the hash
+ foreach (var i in plugins.Select(x => x.Directory).DistinctBy(x => x.FullName))
+ {
+ hashCombiner.AddFolder(i);
+ }
+ return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode());
+ }
+ }
+
+ ///
+ /// Converts the hash value of current plugins to long from string
+ ///
+ ///
+ ///
+ internal static long ConvertPluginsHashFromHex(string val)
+ {
+ long outVal;
+ if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal))
+ {
+ return outVal;
+ }
+ return 0;
+ }
+
+ ///
+ /// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in.
+ /// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then
+ /// a false attempt is returned.
+ ///
+ ///
+ ///
+ internal Attempt>> TryGetCachedPluginsFromFile()
+ {
+ var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
+ if (!File.Exists(filePath))
+ return Attempt>>.False;
+
+ try
+ {
+ var xml = XDocument.Load(filePath);
+ if (xml.Root == null)
+ 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 success
+ return new Attempt>>(
+ true,
+ typeElement.Elements("add")
+ .Select(x => new Tuple(
+ (string) x.Attribute("type"),
+ (string) x.Attribute("assembly"))));
+ }
+ catch (Exception)
+ {
+ //if the file is corrupted, etc... return false
+ return Attempt>>.False;
+ }
+ }
+
+ ///
+ /// Adds/Updates the type list for the base type 'T' in the cached file
+ ///
+ ///
+ ///
+ ///
+ /// THIS METHOD IS NOT THREAD SAFE
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ internal void UpdateCachedPluginsFile(IEnumerable typesFound)
+ {
+ var filePath = Path.Combine(_tempFolder, "umbraco-plugins.list");
+ XDocument xml;
+ try
+ {
+ xml = XDocument.Load(filePath);
+ }
+ catch
+ {
+ //if there's an exception loading then this is somehow corrupt, we'll just replace it.
+ File.Delete(filePath);
+ //create the document and the root
+ xml = new XDocument(new XElement("plugins"));
+ }
+ if (xml.Root == null)
+ {
+ //if for some reason there is no root, create it
+ xml.Add(new XElement("plugins"));
+ }
+ //find the type 'T' element to add or update
+ var typeElement = xml.Root.Elements()
+ .SingleOrDefault(x =>
+ x.Name.LocalName == "baseType"
+ && ((string)x.Attribute("type")) == typeof(T).FullName);
+ if (typeElement == null)
+ {
+ //create the type element
+ typeElement = new XElement("baseType", new XAttribute("type", typeof(T).FullName));
+ //then add it to the root
+ xml.Root.Add(typeElement);
+ }
+
+
+ //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))));
+ //save the xml file
+ xml.Save(filePath);
+ }
+
+ #endregion
+
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly HashSet _types = new HashSet();
private IEnumerable _assemblies;
@@ -198,7 +421,7 @@ namespace Umbraco.Core
using (DisposableTimer.TraceDuration(
String.Format("Starting resolution types of {0}", typeof(T).FullName),
String.Format("Completed resolution of types of {0}", typeof(T).FullName)))
- {
+ {
//check if the TypeList already exists, if so return it, if not we'll create it
var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType));
//if we're not caching the result then proceed, or if the type list doesn't exist then proceed
@@ -209,9 +432,41 @@ namespace Umbraco.Core
typeList = new TypeList(resolutionType);
- foreach (var t in finder())
+ //we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory).
+ if (!HaveAssembliesChanged)
{
- typeList.AddType(t);
+ var fileCacheResult = TryGetCachedPluginsFromFile();
+ if (fileCacheResult.Success)
+ {
+ var successfullyLoadedFromCache = true;
+ //we have a previous cache for this so we don't need to scan we just load what has been found in the file
+ foreach(var t in fileCacheResult.Result)
+ {
+ try
+ {
+ var type = Assembly.Load(t.Item2).GetType(t.Item1);
+ 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);
+ break;
+ }
+ }
+ if (!successfullyLoadedFromCache )
+ {
+ //we need to manually load by scanning if loading from the file was not successful.
+ LoadViaScanningAndUpdateCacheFile(typeList, finder);
+ }
+ }
+ }
+ else
+ {
+ //we don't have a cache for this so proceed to look them up by scanning
+ LoadViaScanningAndUpdateCacheFile(typeList, finder);
}
//only add the cache if we are to cache the results
@@ -226,6 +481,25 @@ namespace Umbraco.Core
}
}
+ ///
+ /// This method invokes the finder which scans the assemblies for the types and then loads the result into the type finder.
+ /// Once the results are loaded, we update the cached type xml file
+ ///
+ ///
+ ///
+ ///
+ /// THIS METHODS IS NOT THREAD SAFE
+ ///
+ private void LoadViaScanningAndUpdateCacheFile(TypeList typeList, Func> finder)
+ {
+ //we don't have a cache for this so proceed to look them up by scanning
+ foreach (var t in finder())
+ {
+ typeList.AddType(t);
+ }
+ UpdateCachedPluginsFile(typeList.GetTypes());
+ }
+
///
/// Generic method to find the specified type and cache the result
///
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index d44959f632..00140be3ad 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -66,6 +66,7 @@
+
diff --git a/src/Umbraco.Tests/HashCodeCombinerTests.cs b/src/Umbraco.Tests/HashCodeCombinerTests.cs
new file mode 100644
index 0000000000..b1c808e7f8
--- /dev/null
+++ b/src/Umbraco.Tests/HashCodeCombinerTests.cs
@@ -0,0 +1,154 @@
+using System;
+using System.IO;
+using System.Reflection;
+using NUnit.Framework;
+using Umbraco.Core;
+
+namespace Umbraco.Tests
+{
+ [TestFixture]
+ public class HashCodeCombinerTests : PartialTrust.AbstractPartialTrustFixture
+ {
+
+ private DirectoryInfo PrepareFolder()
+ {
+ var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
+ var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N")));
+ foreach (var f in dir.GetFiles())
+ {
+ f.Delete();
+ }
+ return dir;
+ }
+
+ [Test]
+ public void HashCombiner_Test_String()
+ {
+ var combiner1 = new HashCodeCombiner();
+ combiner1.AddCaseInsensitiveString("Hello");
+
+ var combiner2 = new HashCodeCombiner();
+ combiner2.AddCaseInsensitiveString("hello");
+
+ Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+
+ combiner2.AddCaseInsensitiveString("world");
+
+ Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+ }
+
+ [Test]
+ public void HashCombiner_Test_Int()
+ {
+ var combiner1 = new HashCodeCombiner();
+ combiner1.AddInt(1234);
+
+ var combiner2 = new HashCodeCombiner();
+ combiner2.AddInt(1234);
+
+ Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+
+ combiner2.AddInt(1);
+
+ Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+ }
+
+ [Test]
+ public void HashCombiner_Test_DateTime()
+ {
+ var dt = DateTime.Now;
+ var combiner1 = new HashCodeCombiner();
+ combiner1.AddDateTime(dt);
+
+ var combiner2 = new HashCodeCombiner();
+ combiner2.AddDateTime(dt);
+
+ Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+
+ combiner2.AddDateTime(DateTime.Now);
+
+ Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+ }
+
+ [Test]
+ public void HashCombiner_Test_File()
+ {
+ var dir = PrepareFolder();
+ var file1Path = Path.Combine(dir.FullName, "hastest1.txt");
+ File.Delete(file1Path);
+ using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt")))
+ {
+ file1.WriteLine("hello");
+ }
+ var file2Path = Path.Combine(dir.FullName, "hastest2.txt");
+ File.Delete(file2Path);
+ using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt")))
+ {
+ //even though files are the same, the dates are different
+ file2.WriteLine("hello");
+ }
+
+ var combiner1 = new HashCodeCombiner();
+ combiner1.AddFile(new FileInfo(file1Path));
+
+ var combiner2 = new HashCodeCombiner();
+ combiner2.AddFile(new FileInfo(file1Path));
+
+ var combiner3 = new HashCodeCombiner();
+ combiner3.AddFile(new FileInfo(file2Path));
+
+ Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+ Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner3.GetCombinedHashCode());
+
+ combiner2.AddFile(new FileInfo(file2Path));
+
+ Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+ }
+
+ [Test]
+ public void HashCombiner_Test_Folder()
+ {
+ var dir = PrepareFolder();
+ var file1Path = Path.Combine(dir.FullName, "hastest1.txt");
+ File.Delete(file1Path);
+ using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt")))
+ {
+ file1.WriteLine("hello");
+ }
+
+ //first test the whole folder
+ var combiner1 = new HashCodeCombiner();
+ combiner1.AddFolder(dir);
+
+ var combiner2 = new HashCodeCombiner();
+ combiner2.AddFolder(dir);
+
+ Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode());
+
+ //now add a file to the folder
+
+ var file2Path = Path.Combine(dir.FullName, "hastest2.txt");
+ File.Delete(file2Path);
+ using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt")))
+ {
+ //even though files are the same, the dates are different
+ file2.WriteLine("hello");
+ }
+
+ var combiner3 = new HashCodeCombiner();
+ combiner3.AddFolder(dir);
+
+ Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner3.GetCombinedHashCode());
+ }
+
+ public override void TestSetup()
+ {
+
+ }
+
+ public override void TestTearDown()
+ {
+
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs
index 69fb1afa9a..3812835115 100644
--- a/src/Umbraco.Tests/PluginManagerTests.cs
+++ b/src/Umbraco.Tests/PluginManagerTests.cs
@@ -1,4 +1,8 @@
+using System;
+using System.Globalization;
+using System.IO;
using System.Linq;
+using System.Reflection;
using NUnit.Framework;
using SqlCE4Umbraco;
using Umbraco.Core;
@@ -54,6 +58,82 @@ namespace Umbraco.Tests
};
}
+ private DirectoryInfo PrepareFolder()
+ {
+ var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
+ var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "PluginManager", Guid.NewGuid().ToString("N")));
+ foreach (var f in dir.GetFiles())
+ {
+ f.Delete();
+ }
+ return dir;
+ }
+
+ [Test]
+ public void Create_Cached_Plugin_File()
+ {
+ var types = new[] {typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext)};
+
+ var manager = new PluginManager();
+ //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));
+ //ensure they are all found
+ Assert.IsTrue(plugins.Result.ContainsAll(shouldContain));
+ }
+
+ [Test]
+ public void PluginHash_From_String()
+ {
+ var s = "hello my name is someone".GetHashCode().ToString("x", CultureInfo.InvariantCulture);
+ var output = PluginManager.ConvertPluginsHashFromHex(s);
+ Assert.AreNotEqual(0, output);
+ }
+
+ [Test]
+ public void Get_Plugins_Hash()
+ {
+ //Arrange
+ var dir = PrepareFolder();
+ var d1 = dir.CreateSubdirectory("1");
+ var d2 = dir.CreateSubdirectory("2");
+ var d3 = dir.CreateSubdirectory("3");
+ var d4 = dir.CreateSubdirectory("4");
+ var f1 = new FileInfo(Path.Combine(d1.FullName, "test1.dll"));
+ var f2 = new FileInfo(Path.Combine(d1.FullName, "test2.dll"));
+ var f3 = new FileInfo(Path.Combine(d2.FullName, "test1.dll"));
+ var f4 = new FileInfo(Path.Combine(d2.FullName, "test2.dll"));
+ var f5 = new FileInfo(Path.Combine(d3.FullName, "test1.dll"));
+ var f6 = new FileInfo(Path.Combine(d3.FullName, "test2.dll"));
+ var f7 = new FileInfo(Path.Combine(d4.FullName, "test1.dll"));
+ f1.CreateText().Close();
+ f2.CreateText().Close();
+ f3.CreateText().Close();
+ f4.CreateText().Close();
+ f5.CreateText().Close();
+ f6.CreateText().Close();
+ f7.CreateText().Close();
+ var list1 = new[] { f1, f2, f3, f4, f5, f6 };
+ var list2 = new[] { f1, f3, f5 };
+ var list3 = new[] { f1, f3, f5, f7 };
+
+ //Act
+ var hash1 = PluginManager.GetAssembliesHash(list1);
+ var hash2 = PluginManager.GetAssembliesHash(list2);
+ var hash3 = PluginManager.GetAssembliesHash(list3);
+
+ //Assert
+
+ //both should be the same since we only create the hash based on the unique folder of the list passed in, yet
+ //all files will exist in those folders still
+ Assert.AreEqual(hash1, hash2);
+ Assert.AreNotEqual(hash1, hash3);
+ }
+
[Test]
public void Ensure_Only_One_Type_List_Created()
{
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index df5601255e..c9111cd6f9 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -59,6 +59,7 @@
+
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index 24292936cf..37125b8b4d 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -37,7 +37,10 @@ namespace Umbraco.Web
{
// do not process if client-side request
if (IsClientSideRequest(httpContext.Request.Url))
- return;
+ return;
+
+ //write the trace output for diagnostics at the end of the request
+ httpContext.Trace.Write("UmbracoModule", "Umbraco request begins");
// ok, process
@@ -94,6 +97,8 @@ namespace Umbraco.Web
if (!EnsureUmbracoRoutablePage(umbracoContext, httpContext))
return;
+ httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed");
+
// ok, process
var uri = umbracoContext.OriginalRequestUrl;
@@ -400,7 +405,7 @@ namespace Umbraco.Web
{
app.BeginRequest += (sender, e) =>
{
- var httpContext = ((HttpApplication)sender).Context;
+ var httpContext = ((HttpApplication)sender).Context;
BeginRequest(new HttpContextWrapper(httpContext));
};
@@ -417,7 +422,12 @@ namespace Umbraco.Web
PersistXmlCache(new HttpContextWrapper(httpContext));
};
- // todo: initialize request errors handler
+ 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");
+ };
}
public void Dispose()