resolution and created a ResolverCollection to track all resolvers created so it's super easy to reset all of them at once (which is what happens on ApplicationContext.Dispose. ApplicationContext.Dispose is also implicitly implemented as to not show in intellisense that the Dispose method exists... must be cast to IDisposable to work.
575 lines
21 KiB
C#
575 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Web;
|
|
|
|
namespace Umbraco.Core.ObjectResolution
|
|
{
|
|
/// <summary>
|
|
/// The base class for all many-objects resolvers.
|
|
/// </summary>
|
|
/// <typeparam name="TResolver">The type of the concrete resolver class.</typeparam>
|
|
/// <typeparam name="TResolved">The type of the resolved objects.</typeparam>
|
|
public abstract class ManyObjectsResolverBase<TResolver, TResolved> : ResolverBase<TResolver>
|
|
where TResolved : class
|
|
where TResolver : ResolverBase
|
|
{
|
|
private IEnumerable<TResolved> _applicationInstances = null;
|
|
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
|
private readonly string _httpContextKey;
|
|
private readonly List<Type> _instanceTypes = new List<Type>();
|
|
private IEnumerable<TResolved> _sortedValues = null;
|
|
|
|
private int _defaultPluginWeight = 10;
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an empty list of objects,
|
|
/// and an optional lifetime scope.
|
|
/// </summary>
|
|
/// <param name="scope">The lifetime scope of instantiated objects, default is per Application.</param>
|
|
/// <remarks>If <paramref name="scope"/> is per HttpRequest then there must be a current HttpContext.</remarks>
|
|
/// <exception cref="InvalidOperationException"><paramref name="scope"/> is per HttpRequest but the current HttpContext is null.</exception>
|
|
protected ManyObjectsResolverBase(ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
|
|
{
|
|
CanResolveBeforeFrozen = false;
|
|
if (scope == ObjectLifetimeScope.HttpRequest)
|
|
{
|
|
if (HttpContext.Current == null)
|
|
throw new InvalidOperationException("Use alternative constructor accepting a HttpContextBase object in order to set the lifetime scope to HttpRequest when HttpContext.Current is null");
|
|
|
|
CurrentHttpContext = new HttpContextWrapper(HttpContext.Current);
|
|
}
|
|
|
|
LifetimeScope = scope;
|
|
if (scope == ObjectLifetimeScope.HttpRequest)
|
|
_httpContextKey = this.GetType().FullName;
|
|
_instanceTypes = new List<Type>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an empty list of objects,
|
|
/// with creation of objects based on an HttpRequest lifetime scope.
|
|
/// </summary>
|
|
/// <param name="httpContext">The HttpContextBase corresponding to the HttpRequest.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="httpContext"/> is <c>null</c>.</exception>
|
|
protected ManyObjectsResolverBase(HttpContextBase httpContext)
|
|
{
|
|
CanResolveBeforeFrozen = false;
|
|
if (httpContext == null)
|
|
throw new ArgumentNullException("httpContext");
|
|
LifetimeScope = ObjectLifetimeScope.HttpRequest;
|
|
_httpContextKey = this.GetType().FullName;
|
|
CurrentHttpContext = httpContext;
|
|
_instanceTypes = new List<Type>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an initial list of object types,
|
|
/// and an optional lifetime scope.
|
|
/// </summary>
|
|
/// <param name="value">The list of object types.</param>
|
|
/// <param name="scope">The lifetime scope of instantiated objects, default is per Application.</param>
|
|
/// <remarks>If <paramref name="scope"/> is per HttpRequest then there must be a current HttpContext.</remarks>
|
|
/// <exception cref="InvalidOperationException"><paramref name="scope"/> is per HttpRequest but the current HttpContext is null.</exception>
|
|
protected ManyObjectsResolverBase(IEnumerable<Type> value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
|
|
: this(scope)
|
|
{
|
|
_instanceTypes = value.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ManyObjectsResolverBase{TResolver, TResolved}"/> class with an initial list of objects,
|
|
/// with creation of objects based on an HttpRequest lifetime scope.
|
|
/// </summary>
|
|
/// <param name="httpContext">The HttpContextBase corresponding to the HttpRequest.</param>
|
|
/// <param name="value">The list of object types.</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="httpContext"/> is <c>null</c>.</exception>
|
|
protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable<Type> value)
|
|
: this(httpContext)
|
|
{
|
|
_instanceTypes = value.ToList();
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen.
|
|
/// </summary>
|
|
/// <remarks>This is false by default and is used for some special internal resolvers.</remarks>
|
|
internal bool CanResolveBeforeFrozen { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets the list of types to create instances from.
|
|
/// </summary>
|
|
protected virtual IEnumerable<Type> InstanceTypes
|
|
{
|
|
get { return _instanceTypes; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="HttpContextBase"/> used to initialize this object, if any.
|
|
/// </summary>
|
|
/// <remarks>If not null, then <c>LifetimeScope</c> will be <c>ObjectLifetimeScope.HttpRequest</c>.</remarks>
|
|
protected HttpContextBase CurrentHttpContext { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the lifetime scope of resolved objects.
|
|
/// </summary>
|
|
protected ObjectLifetimeScope LifetimeScope { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the resolved object instances, sorted by weight.
|
|
/// </summary>
|
|
/// <returns>The sorted resolved object instances.</returns>
|
|
/// <remarks>
|
|
/// <para>The order is based upon the <c>WeightedPluginAttribute</c> and <c>DefaultPluginWeight</c>.</para>
|
|
/// <para>Weights are sorted ascendingly (lowest weights come first).</para>
|
|
/// </remarks>
|
|
protected IEnumerable<TResolved> GetSortedValues()
|
|
{
|
|
if (_sortedValues == null)
|
|
{
|
|
var values = Values.ToList();
|
|
values.Sort((f1, f2) => GetObjectWeight(f1).CompareTo(GetObjectWeight(f2)));
|
|
_sortedValues = values;
|
|
}
|
|
return _sortedValues;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the default type weight.
|
|
/// </summary>
|
|
/// <remarks>Determines the weight of types that do not have a <c>WeightedPluginAttribute</c> set on
|
|
/// them, when calling <c>GetSortedValues</c>.</remarks>
|
|
protected virtual int DefaultPluginWeight
|
|
{
|
|
get { return _defaultPluginWeight; }
|
|
set { _defaultPluginWeight = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the weight of an object for user with GetSortedValues
|
|
/// </summary>
|
|
/// <param name="o"></param>
|
|
/// <returns></returns>
|
|
protected virtual int GetObjectWeight(object o)
|
|
{
|
|
var type = o.GetType();
|
|
var attr = type.GetCustomAttribute<WeightedPluginAttribute>(true);
|
|
return attr == null ? DefaultPluginWeight : attr.Weight;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the resolved object instances.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException"><c>CanResolveBeforeFrozen</c> is false, and resolution is not frozen.</exception>
|
|
protected IEnumerable<TResolved> Values
|
|
{
|
|
get
|
|
{
|
|
// ensure we can
|
|
if (!CanResolveBeforeFrozen)
|
|
Resolution.EnsureIsFrozen();
|
|
|
|
// note: we apply .ToArray() to the output of CreateInstance() because that is an IEnumerable that
|
|
// comes from the PluginManager we want to be _sure_ that it's not a Linq of some sort, but the
|
|
// instances have actually been instanciated when we return.
|
|
|
|
switch (LifetimeScope)
|
|
{
|
|
case ObjectLifetimeScope.HttpRequest:
|
|
// create new instances per HttpContext
|
|
using (var l = new UpgradeableReadLock(_lock))
|
|
{
|
|
// create if not already there
|
|
if (CurrentHttpContext.Items[_httpContextKey] == null)
|
|
{
|
|
l.UpgradeToWriteLock();
|
|
CurrentHttpContext.Items[_httpContextKey] = CreateInstances().ToArray();
|
|
}
|
|
return (TResolved[])CurrentHttpContext.Items[_httpContextKey];
|
|
}
|
|
|
|
case ObjectLifetimeScope.Application:
|
|
// create new instances per application
|
|
using(var l = new UpgradeableReadLock(_lock))
|
|
{
|
|
// create if not already there
|
|
if (_applicationInstances == null)
|
|
{
|
|
l.UpgradeToWriteLock();
|
|
_applicationInstances = CreateInstances().ToArray();
|
|
}
|
|
return _applicationInstances;
|
|
}
|
|
|
|
case ObjectLifetimeScope.Transient:
|
|
default:
|
|
// create new instances each time
|
|
return CreateInstances().ToArray();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the object instances for the types contained in the types collection.
|
|
/// </summary>
|
|
/// <returns>A list of objects of type <typeparamref name="TResolved"/>.</returns>
|
|
protected virtual IEnumerable<TResolved> CreateInstances()
|
|
{
|
|
return PluginManager.Current.CreateInstances<TResolved>(InstanceTypes);
|
|
}
|
|
|
|
#region Types collection manipulation
|
|
|
|
/// <summary>
|
|
/// Removes a type.
|
|
/// </summary>
|
|
/// <param name="value">The type to remove.</param>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
|
|
/// the type is not a valid type for the resolver.</exception>
|
|
public virtual void RemoveType(Type value)
|
|
{
|
|
EnsureSupportsRemove();
|
|
|
|
using (Resolution.Configuration)
|
|
using (var l = new UpgradeableReadLock(_lock))
|
|
{
|
|
EnsureCorrectType(value);
|
|
|
|
l.UpgradeToWriteLock();
|
|
_instanceTypes.Remove(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a type.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to remove.</typeparam>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
|
|
/// the type is not a valid type for the resolver.</exception>
|
|
public void RemoveType<T>()
|
|
where T : TResolved
|
|
{
|
|
RemoveType(typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds types.
|
|
/// </summary>
|
|
/// <param name="types">The types to add.</param>
|
|
/// <remarks>The types are appended at the end of the list.</remarks>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
|
|
/// a type is not a valid type for the resolver, or a type is already in the collection of types.</exception>
|
|
protected void AddTypes(IEnumerable<Type> types)
|
|
{
|
|
EnsureSupportsAdd();
|
|
|
|
using (Resolution.Configuration)
|
|
using (new WriteLock(_lock))
|
|
{
|
|
foreach(var t in types)
|
|
{
|
|
EnsureCorrectType(t);
|
|
if (_instanceTypes.Contains(t))
|
|
{
|
|
throw new InvalidOperationException(string.Format(
|
|
"Type {0} is already in the collection of types.", t.FullName));
|
|
}
|
|
_instanceTypes.Add(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a type.
|
|
/// </summary>
|
|
/// <param name="value">The type to add.</param>
|
|
/// <remarks>The type is appended at the end of the list.</remarks>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
|
|
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
|
|
public virtual void AddType(Type value)
|
|
{
|
|
EnsureSupportsAdd();
|
|
|
|
using (Resolution.Configuration)
|
|
using (var l = new UpgradeableReadLock(_lock))
|
|
{
|
|
EnsureCorrectType(value);
|
|
if (_instanceTypes.Contains(value))
|
|
{
|
|
throw new InvalidOperationException(string.Format(
|
|
"Type {0} is already in the collection of types.", value.FullName));
|
|
}
|
|
|
|
l.UpgradeToWriteLock();
|
|
_instanceTypes.Add(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a type.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to add.</typeparam>
|
|
/// <remarks>The type is appended at the end of the list.</remarks>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
|
|
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
|
|
public void AddType<T>()
|
|
where T : TResolved
|
|
{
|
|
AddType(typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the list of types.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support clearing types.</exception>
|
|
public virtual void Clear()
|
|
{
|
|
EnsureSupportsClear();
|
|
|
|
using (Resolution.Configuration)
|
|
using (new WriteLock(_lock))
|
|
{
|
|
_instanceTypes.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a type at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The zero-based index at which the type should be inserted.</param>
|
|
/// <param name="value">The type to insert.</param>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
|
|
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
|
|
public virtual void InsertType(int index, Type value)
|
|
{
|
|
EnsureSupportsInsert();
|
|
|
|
using (Resolution.Configuration)
|
|
using (var l = new UpgradeableReadLock(_lock))
|
|
{
|
|
EnsureCorrectType(value);
|
|
if (_instanceTypes.Contains(value))
|
|
{
|
|
throw new InvalidOperationException(string.Format(
|
|
"Type {0} is already in the collection of types.", value.FullName));
|
|
}
|
|
|
|
l.UpgradeToWriteLock();
|
|
_instanceTypes.Insert(index, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a type at the beginning of the list.
|
|
/// </summary>
|
|
/// <param name="value">The type to insert.</param>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
|
|
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
|
|
public virtual void InsertType(Type value)
|
|
{
|
|
InsertType(0, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a type at the specified index.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to insert.</typeparam>
|
|
/// <param name="index">The zero-based index at which the type should be inserted.</param>
|
|
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
|
|
public void InsertType<T>(int index)
|
|
where T : TResolved
|
|
{
|
|
InsertType(index, typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a type at the beginning of the list.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to insert.</typeparam>
|
|
public void InsertType<T>()
|
|
where T : TResolved
|
|
{
|
|
InsertType(0, typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a type before a specified, already existing type.
|
|
/// </summary>
|
|
/// <param name="existingType">The existing type before which to insert.</param>
|
|
/// <param name="value">The type to insert.</param>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
|
|
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
|
|
/// or the new type is already in the collection of types.</exception>
|
|
public virtual void InsertTypeBefore(Type existingType, Type value)
|
|
{
|
|
EnsureSupportsInsert();
|
|
|
|
using (Resolution.Configuration)
|
|
using (var l = new UpgradeableReadLock(_lock))
|
|
{
|
|
EnsureCorrectType(existingType);
|
|
EnsureCorrectType(value);
|
|
if (!_instanceTypes.Contains(existingType))
|
|
{
|
|
throw new InvalidOperationException(string.Format(
|
|
"Type {0} is not in the collection of types.", existingType.FullName));
|
|
}
|
|
if (_instanceTypes.Contains(value))
|
|
{
|
|
throw new InvalidOperationException(string.Format(
|
|
"Type {0} is already in the collection of types.", value.FullName));
|
|
}
|
|
int index = _instanceTypes.IndexOf(existingType);
|
|
|
|
l.UpgradeToWriteLock();
|
|
_instanceTypes.Insert(index, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a type before a specified, already existing type.
|
|
/// </summary>
|
|
/// <typeparam name="TExisting">The existing type before which to insert.</typeparam>
|
|
/// <typeparam name="T">The type to insert.</typeparam>
|
|
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
|
|
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
|
|
/// or the new type is already in the collection of types.</exception>
|
|
public void InsertTypeBefore<TExisting, T>()
|
|
where TExisting : TResolved
|
|
where T : TResolved
|
|
{
|
|
InsertTypeBefore(typeof(TExisting), typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified type is already in the collection of types.
|
|
/// </summary>
|
|
/// <param name="value">The type to look for.</param>
|
|
/// <returns>A value indicating whether the type is already in the collection of types.</returns>
|
|
public virtual bool ContainsType(Type value)
|
|
{
|
|
using (new ReadLock(_lock))
|
|
{
|
|
return _instanceTypes.Contains(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value indicating whether the specified type is already in the collection of types.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to look for.</typeparam>
|
|
/// <returns>A value indicating whether the type is already in the collection of types.</returns>
|
|
public bool ContainsType<T>()
|
|
where T : TResolved
|
|
{
|
|
return ContainsType(typeof(T));
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns a WriteLock to use when modifying collections
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected WriteLock GetWriteLock()
|
|
{
|
|
return new WriteLock(_lock);
|
|
}
|
|
|
|
#region Type utilities
|
|
|
|
/// <summary>
|
|
/// Ensures that a type is a valid type for the resolver.
|
|
/// </summary>
|
|
/// <param name="value">The type to test.</param>
|
|
/// <exception cref="InvalidOperationException">the type is not a valid type for the resolver.</exception>
|
|
protected void EnsureCorrectType(Type value)
|
|
{
|
|
if (!TypeHelper.IsTypeAssignableFrom<TResolved>(value))
|
|
throw new InvalidOperationException(string.Format(
|
|
"Type {0} is not an acceptable type for resolver {1}.", value.FullName, this.GetType().FullName));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Types collection manipulation support
|
|
|
|
/// <summary>
|
|
/// Ensures that the resolver supports removing types.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The resolver does not support removing types.</exception>
|
|
protected void EnsureSupportsRemove()
|
|
{
|
|
if (!SupportsRemove)
|
|
throw new InvalidOperationException("This resolver does not support removing types");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the resolver supports clearing types.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The resolver does not support clearing types.</exception>
|
|
protected void EnsureSupportsClear() {
|
|
if (!SupportsClear)
|
|
throw new InvalidOperationException("This resolver does not support clearing types");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the resolver supports adding types.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The resolver does not support adding types.</exception>
|
|
protected void EnsureSupportsAdd()
|
|
{
|
|
if (!SupportsAdd)
|
|
throw new InvalidOperationException("This resolver does not support adding new types");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the resolver supports inserting types.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The resolver does not support inserting types.</exception>
|
|
protected void EnsureSupportsInsert()
|
|
{
|
|
if (!SupportsInsert)
|
|
throw new InvalidOperationException("This resolver does not support inserting new types");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the resolver supports adding types.
|
|
/// </summary>
|
|
protected virtual bool SupportsAdd
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the resolver supports inserting types.
|
|
/// </summary>
|
|
protected virtual bool SupportsInsert
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the resolver supports clearing types.
|
|
/// </summary>
|
|
protected virtual bool SupportsClear
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the resolver supports removing types.
|
|
/// </summary>
|
|
protected virtual bool SupportsRemove
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |