Changed PluginResolver to PluginTypeResolver, created new base method to do the resolution and caching,

Updated ApplicationStartupHandler to use it and to not register on the static method, the real method is now
used and is called on app startup (much cleaner). Added tests for PluginTypeResolver. Added events to
UmbracoApplication and virtual methods for developers to override.
This commit is contained in:
shannon@ShandemVaio
2012-07-27 00:40:00 +06:00
parent 1731ad45f5
commit a6b9aca45a
17 changed files with 344 additions and 123 deletions

View File

@@ -18,9 +18,9 @@ namespace Umbraco.Core
/// Constructor
/// </summary>
/// <param name="pluginResolver"></param>
public ApplicationContext(PluginResolver pluginResolver)
public ApplicationContext(PluginTypeResolver pluginResolver)
{
Plugins = pluginResolver;
PluginTypes = pluginResolver;
}
/// <summary>
@@ -48,7 +48,7 @@ namespace Umbraco.Core
/// <summary>
/// Gets the plugin resolver for the application
/// </summary>
public PluginResolver Plugins { get; private set; }
public PluginTypeResolver PluginTypes { get; private set; }
// notes
// GlobalSettings.ConfigurationStatus returns the value that's in the web.config, so it's the "configured version"

View File

@@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Core
{
/// <summary>
/// Used to resolve all plugin types
/// </summary>
/// <remarks>
///
/// This class should be used to resolve all plugin types, the TypeFinder should not be used directly!
///
/// This class can expose extension methods to resolve custom plugins
///
/// </remarks>
public class PluginResolver
{
}
}

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Umbraco.Core
{
/// <summary>
/// Used to resolve all plugin types
/// </summary>
/// <remarks>
///
/// This class should be used to resolve all plugin types, the TypeFinder should not be used directly!
///
/// This class can expose extension methods to resolve custom plugins
///
/// </remarks>
public class PluginTypeResolver
{
private PluginTypeResolver()
{
}
static PluginTypeResolver _resolver;
static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
/// <summary>
/// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place
/// </summary>
internal static PluginTypeResolver Current
{
get
{
if (_resolver == null)
{
using (new WriteLock(Lock))
{
_resolver = new PluginTypeResolver();
}
}
return _resolver;
}
}
internal readonly TypeFinder2 TypeFinder = new TypeFinder2();
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly HashSet<TypeList> _types = new HashSet<TypeList>();
/// <summary>
/// Generic method to find the specified type and cache the result
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal IEnumerable<Type> ResolveTypes<T>()
{
using (var readLock = new UpgradeableReadLock(_lock))
{
//check if the TypeList already exists, if so return it, if not we'll create it
var typeList = _types.SingleOrDefault(x => x.GetListType().IsType<T>());
if (typeList == null)
{
//upgrade to a write lock since we're adding to the collection
readLock.UpgradeToWriteLock();
typeList = new TypeList<T>();
foreach (var t in TypeFinder.FindClassesOfType<T>())
{
typeList.AddType(t);
}
//add the type list to the collection
_types.Add(typeList);
}
return typeList.GetTypes();
}
}
/// <summary>
/// Used for unit tests
/// </summary>
/// <returns></returns>
internal HashSet<TypeList> GetTypeLists()
{
return _types;
}
#region Private classes
internal abstract class TypeList
{
public abstract void AddType(Type t);
public abstract Type GetListType();
public abstract IEnumerable<Type> GetTypes();
}
internal class TypeList<T> : TypeList
{
private readonly List<Type> _types = new List<Type>();
public override void AddType(Type t)
{
if (t.IsType<T>())
{
_types.Add(t);
}
}
public override Type GetListType()
{
return typeof(T);
}
public override IEnumerable<Type> GetTypes()
{
return _types;
}
}
#endregion
}
}

View File

@@ -20,4 +20,5 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("umbraco")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("businesslogic")]

View File

@@ -64,7 +64,7 @@
<Compile Include="DelegateEqualityComparer.cs" />
<Compile Include="EnumerableExtensions.cs" />
<Compile Include="IfExtensions.cs" />
<Compile Include="PluginResolver.cs" />
<Compile Include="PluginTypeResolver.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringAliasCaseType.cs" />
<Compile Include="StringExtensions.cs" />

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
namespace Umbraco.Tests
{
/// <summary>
/// Used for PluginTypeResolverTests
/// </summary>
public static class PluginTypeResolverExtensions
{
public static IEnumerable<Type> ResolveFindMeTypes(this PluginTypeResolver resolver)
{
return resolver.ResolveTypes<PluginTypeResolverTests.IFindMe>();
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
namespace Umbraco.Tests
{
public class PluginTypeResolverTests
{
[Test]
public void Ensure_Only_One_Type_List_Created()
{
var foundTypes1 = PluginTypeResolver.Current.ResolveFindMeTypes();
var foundTypes2 = PluginTypeResolver.Current.ResolveFindMeTypes();
Assert.AreEqual(1, PluginTypeResolver.Current.GetTypeLists().Count);
}
[Test]
public void Resolves_Types()
{
var foundTypes1 = PluginTypeResolver.Current.ResolveFindMeTypes();
Assert.AreEqual(2, foundTypes1.Count());
}
public interface IFindMe
{
}
public class FindMe1 : IFindMe
{
}
public class FindMe2 : IFindMe
{
}
}
}

View File

@@ -20,7 +20,6 @@ using umbraco.uicontrols;
namespace Umbraco.Tests
{
/// <summary>
/// Full Trust benchmark tests for typefinder and the old typefinder
/// </summary>

View File

@@ -49,6 +49,8 @@
<Compile Include="BusinessLogic\ApplicationTest.cs" />
<Compile Include="BusinessLogic\ApplicationTreeTest.cs" />
<Compile Include="BusinessLogic\BaseTest.cs" />
<Compile Include="PluginTypeResolverExtensions.cs" />
<Compile Include="PluginTypeResolverTests.cs" />
<Compile Include="TestHelper.cs" />
<Compile Include="EnumerableExtensionsTests.cs" />
<Compile Include="PartialTrust\AbstractPartialTrustFixture.cs" />

View File

@@ -1825,7 +1825,7 @@ xcopy "$(ProjectDir)"..\..\lib\SQLCE4\x86\*.* "$(TargetDir)x86\" /Y /F /E /D</Po
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>61637</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:61637/</IISUrl>
<IISUrl>http://localhost:61638/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>

View File

@@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Web.Routing;
using umbraco.BusinessLogic;
using umbraco.BusinessLogic.Utils;
namespace Umbraco.Web
{
/// <summary>
/// Extension methods for the PluginResolver
/// </summary>
public static class PluginResolverExtensions
{
private static volatile IEnumerable<IDocumentLookup> _lookups;
private static readonly object Locker = new object();
/// <summary>
/// Returns all available ILookup objects
/// </summary>
/// <param name="plugins"></param>
/// <returns></returns>
internal static IEnumerable<IDocumentLookup> ResolveLookups(this PluginResolver plugins)
{
if (_lookups == null)
{
lock (Locker)
{
if (_lookups == null)
{
var lookupTypes = TypeFinder.FindClassesOfType<IDocumentLookup>();
var lookups = new List<IDocumentLookup>();
foreach (var l in lookupTypes)
{
try
{
var typeInstance = Activator.CreateInstance(l) as IDocumentLookup;
lookups.Add(typeInstance);
}
catch (Exception ex)
{
//TODO: Need to fix logging so this doesn't bork if no SQL connection
//Log.Add(LogTypes.Error, -1, "Error loading ILookup: " + ex.ToString());
}
}
//set the global
_lookups = lookups;
}
}
}
return _lookups;
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Umbraco.Core;
using Umbraco.Web.Routing;
namespace Umbraco.Web
{
/// <summary>
/// Extension methods for the PluginTypeResolver
/// </summary>
public static class PluginTypeResolverExtensions
{
private static IEnumerable<IDocumentLookup> _lookups;
private static readonly ReaderWriterLockSlim LookupsLocker = new ReaderWriterLockSlim();
/// <summary>
/// Returns all available ILookup objects
/// </summary>
/// <param name="resolver"></param>
/// <returns></returns>
internal static IEnumerable<IDocumentLookup> ResolveLookups(this PluginTypeResolver resolver)
{
if (_lookups == null)
{
using (new WriteLock(LookupsLocker))
{
var lookupTypes = resolver.TypeFinder.FindClassesOfType<IDocumentLookup>();
var lookups = new List<IDocumentLookup>();
foreach (var l in lookupTypes)
{
try
{
var typeInstance = Activator.CreateInstance(l) as IDocumentLookup;
lookups.Add(typeInstance);
}
catch (Exception ex)
{
//TODO: Need to fix logging so this doesn't bork if no SQL connection
//Log.Add(LogTypes.Error, -1, "Error loading ILookup: " + ex.ToString());
}
}
//set the global
_lookups = lookups;
}
}
return _lookups;
}
}
}

View File

@@ -243,7 +243,7 @@
<Compile Include="Routing\IRequestDocumentLastChanceResolver.cs" />
<Compile Include="Routing\IRequestDocumentResolver.cs" />
<Compile Include="Routing\NiceUrlProvider.cs" />
<Compile Include="PluginResolverExtensions.cs" />
<Compile Include="PluginTypeResolverExtensions.cs" />
<Compile Include="Routing\Domains.cs" />
<Compile Include="Routing\DocumentLookupsResolver.cs" />
<Compile Include="Routing\DocumentRequest.cs" />

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using Umbraco.Core;
using Umbraco.Web.Routing;
using umbraco.businesslogic;
namespace Umbraco.Web
{
@@ -13,6 +14,9 @@ namespace Umbraco.Web
/// </summary>
public class UmbracoApplication : System.Web.HttpApplication
{
public static event EventHandler ApplicationStarting;
public static event EventHandler ApplicationStarted;
private static readonly TraceSource Trace = new TraceSource("UmbracoApplication");
@@ -21,7 +25,7 @@ namespace Umbraco.Web
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void Application_Start(object sender, EventArgs e)
protected void Application_Start(object sender, EventArgs e)
{
Trace.TraceInformation("Initialize AppDomain");
@@ -30,20 +34,50 @@ namespace Umbraco.Web
ClientDependency.Core.CompositeFiles.Providers.BaseCompositeFileProcessingProvider.UrlTypeDefault = ClientDependency.Core.CompositeFiles.Providers.CompositeUrlType.Base64QueryStrings;
//create the ApplicationContext
ApplicationContext.Current = new ApplicationContext(new PluginResolver())
ApplicationContext.Current = new ApplicationContext(PluginTypeResolver.Current)
{
IsReady = true // fixme
};
//find and initialize the application startup handlers
ApplicationStartupHandler.RegisterHandlers();
// create the resolvers
DocumentLookupsResolver.Current = new DocumentLookupsResolver(ApplicationContext.Current.Plugins.ResolveLookups(), new ResolveLastChance());
DocumentLookupsResolver.Current = new DocumentLookupsResolver(ApplicationContext.Current.PluginTypes.ResolveLookups(), new ResolveLastChance());
RoutesCacheResolver.Current = new RoutesCacheResolver(new DefaultRoutesCache());
OnApplicationStarting(sender, e);
//all resolvers are now frozen and cannot be modified
Umbraco.Core.Resolving.Resolution.Freeze();
OnApplicationStarted(sender, e);
Trace.TraceInformation("AppDomain is initialized");
}
/// <summary>
/// Developers can override this method to modify objects on startup
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnApplicationStarting(object sender, EventArgs e)
{
if (ApplicationStarting != null)
ApplicationStarting(sender, e);
}
/// <summary>
/// Developers can override this method to do anything they need to do once the application startup routine is completed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnApplicationStarted(object sender, EventArgs e)
{
if (ApplicationStarted != null)
ApplicationStarted(sender, e);
}
protected virtual void Application_Error(object sender, EventArgs e)
{

View File

@@ -2,55 +2,57 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using Umbraco.Core;
using umbraco.BusinessLogic;
using umbraco.BusinessLogic.Utils;
using umbraco.interfaces;
namespace umbraco.businesslogic
{
/// <summary>
/// <summary>
/// ApplicationStartupHandler provides an easy to use base class to install event handlers in umbraco.
/// Class inhiriting from ApplicationStartupHandler are automaticly registered and instantiated by umbraco on application start.
/// To use, inhirite the ApplicationStartupHandler Class and add an empty constructor.
/// </summary>
public abstract class ApplicationStartupHandler : IApplicationStartupHandler
{
private static readonly IList<IApplicationStartupHandler> _handlers = new List<IApplicationStartupHandler>();
static ApplicationStartupHandler()
{
if (!GlobalSettings.Configured)
return;
var typeFinder = new Umbraco.Core.TypeFinder2();
var types = typeFinder.FindClassesOfType<IApplicationStartupHandler>();
foreach (var t in types)
{
try
{
var typeInstance = Activator.CreateInstance(t) as IApplicationStartupHandler;
if (typeInstance != null)
{
_handlers.Add(typeInstance);
if (HttpContext.Current != null)
HttpContext.Current.Trace.Write("registerApplicationStartupHandlers",
" + Adding application startup handler '" +
t.FullName);
}
}
catch (Exception ee)
{
Log.Add(LogTypes.Error, -1, "Error loading application startup handler: " + ee.ToString());
}
}
}
public static void RegisterHandlers()
{
// We don't actually do anything in this method, it's just a handle to force the static constructor to fire.
// this ensures that the registration code only occurs once.
{
if (ApplicationContext.Current == null || !ApplicationContext.Current.IsConfigured)
return;
var types = PluginTypeResolver.Current.ResolveApplicationStartupHandlers();
foreach (var t in types)
{
try
{
//this creates the type instance which will trigger the constructor of the object
//previously we stored these objects in a list but that just takes up memory for no reason.
var typeInstance = (IApplicationStartupHandler)Activator.CreateInstance(t);
if (HttpContext.Current != null)
HttpContext.Current.Trace.Write("registerApplicationStartupHandlers",
" + Adding application startup handler '" +
t.FullName);
}
catch (Exception ee)
{
//TODO: Fix logging because if this fails here, the app pool wont startup!
try
{
Log.Add(LogTypes.Error, -1, "Error loading application startup handler: " + ee.ToString());
}
catch
{
//swallowed... see above comment
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
using umbraco.interfaces;
namespace umbraco.businesslogic
{
/// <summary>
/// Extension methods for the PluginTypeResolver
/// </summary>
public static class PluginTypeResolverExtensions
{
/// <summary>
/// Returns all available IApplicationStartupHandler objects
/// </summary>
/// <param name="resolver"></param>
/// <returns></returns>
internal static IEnumerable<Type> ResolveApplicationStartupHandlers(this PluginTypeResolver resolver)
{
return resolver.ResolveTypes<IApplicationStartupHandler>();
}
}
}

View File

@@ -149,6 +149,7 @@
<Compile Include="ApplicationTree.cs" />
<Compile Include="ApplicationTreeRegistrar.cs" />
<Compile Include="DefaultApps.cs" />
<Compile Include="PluginTypeResolverExtensions.cs" />
<Compile Include="TreeAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>