using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
namespace Umbraco.Core.ObjectResolution
{
///
/// The base class for all many-objects resolvers.
///
/// The type of the concrete resolver class.
/// The type of the resolved objects.
public abstract class ManyObjectsResolverBase : ResolverBase
where TResolved : class
where TResolver : ResolverBase
{
private IEnumerable _applicationInstances = null;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private readonly string _httpContextKey;
private readonly List _instanceTypes = new List();
private IEnumerable _sortedValues = null;
private int _defaultPluginWeight = 10;
#region Constructors
///
/// Initializes a new instance of the class with an empty list of objects,
/// and an optional lifetime scope.
///
/// The lifetime scope of instantiated objects, default is per Application.
/// If is per HttpRequest then there must be a current HttpContext.
/// is per HttpRequest but the current HttpContext is null.
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();
}
///
/// Initializes a new instance of the class with an empty list of objects,
/// with creation of objects based on an HttpRequest lifetime scope.
///
/// The HttpContextBase corresponding to the HttpRequest.
/// is null.
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();
}
///
/// Initializes a new instance of the class with an initial list of object types,
/// and an optional lifetime scope.
///
/// The list of object types.
/// The lifetime scope of instantiated objects, default is per Application.
/// If is per HttpRequest then there must be a current HttpContext.
/// is per HttpRequest but the current HttpContext is null.
protected ManyObjectsResolverBase(IEnumerable value, ObjectLifetimeScope scope = ObjectLifetimeScope.Application)
: this(scope)
{
_instanceTypes = value.ToList();
}
///
/// Initializes a new instance of the class with an initial list of objects,
/// with creation of objects based on an HttpRequest lifetime scope.
///
/// The HttpContextBase corresponding to the HttpRequest.
/// The list of object types.
/// is null.
protected ManyObjectsResolverBase(HttpContextBase httpContext, IEnumerable value)
: this(httpContext)
{
_instanceTypes = value.ToList();
}
#endregion
///
/// Gets or sets a value indicating whether the resolver can resolve objects before resolution is frozen.
///
/// This is false by default and is used for some special internal resolvers.
internal bool CanResolveBeforeFrozen { get; set; }
///
/// Gets the list of types to create instances from.
///
protected virtual IEnumerable InstanceTypes
{
get { return _instanceTypes; }
}
///
/// Gets or sets the used to initialize this object, if any.
///
/// If not null, then LifetimeScope will be ObjectLifetimeScope.HttpRequest.
protected HttpContextBase CurrentHttpContext { get; private set; }
///
/// Gets or sets the lifetime scope of resolved objects.
///
protected ObjectLifetimeScope LifetimeScope { get; private set; }
///
/// Gets the resolved object instances, sorted by weight.
///
/// The sorted resolved object instances.
///
/// The order is based upon the WeightedPluginAttribute and DefaultPluginWeight.
/// Weights are sorted ascendingly (lowest weights come first).
///
protected IEnumerable GetSortedValues()
{
if (_sortedValues == null)
{
var values = Values.ToList();
values.Sort((f1, f2) => GetObjectWeight(f1).CompareTo(GetObjectWeight(f2)));
_sortedValues = values;
}
return _sortedValues;
}
///
/// Gets or sets the default type weight.
///
/// Determines the weight of types that do not have a WeightedPluginAttribute set on
/// them, when calling GetSortedValues.
protected virtual int DefaultPluginWeight
{
get { return _defaultPluginWeight; }
set { _defaultPluginWeight = value; }
}
///
/// Returns the weight of an object for user with GetSortedValues
///
///
///
protected virtual int GetObjectWeight(object o)
{
var type = o.GetType();
var attr = type.GetCustomAttribute(true);
return attr == null ? DefaultPluginWeight : attr.Weight;
}
///
/// Gets the resolved object instances.
///
/// CanResolveBeforeFrozen is false, and resolution is not frozen.
protected IEnumerable Values
{
get
{
using (Resolution.Reader(CanResolveBeforeFrozen))
{
// 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();
}
}
}
}
///
/// Creates the object instances for the types contained in the types collection.
///
/// A list of objects of type .
protected virtual IEnumerable CreateInstances()
{
return PluginManager.Current.CreateInstances(InstanceTypes);
}
#region Types collection manipulation
///
/// Removes a type.
///
/// The type to remove.
/// the resolver does not support removing types, or
/// the type is not a valid type for the resolver.
public virtual void RemoveType(Type value)
{
EnsureSupportsRemove();
using (Resolution.Configuration)
using (var l = new UpgradeableReadLock(_lock))
{
EnsureCorrectType(value);
l.UpgradeToWriteLock();
_instanceTypes.Remove(value);
}
}
///
/// Removes a type.
///
/// The type to remove.
/// the resolver does not support removing types, or
/// the type is not a valid type for the resolver.
public void RemoveType()
where T : TResolved
{
RemoveType(typeof(T));
}
///
/// Adds types.
///
/// The types to add.
/// The types are appended at the end of the list.
/// 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.
protected void AddTypes(IEnumerable 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);
}
}
}
///
/// Adds a type.
///
/// The type to add.
/// The type is appended at the end of the list.
/// 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.
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);
}
}
///
/// Adds a type.
///
/// The type to add.
/// The type is appended at the end of the list.
/// 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.
public void AddType()
where T : TResolved
{
AddType(typeof(T));
}
///
/// Clears the list of types.
///
/// the resolver does not support clearing types.
public virtual void Clear()
{
EnsureSupportsClear();
using (Resolution.Configuration)
using (new WriteLock(_lock))
{
_instanceTypes.Clear();
}
}
///
/// Inserts a type at the specified index.
///
/// The zero-based index at which the type should be inserted.
/// The type to insert.
/// 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.
/// is out of range.
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);
}
}
///
/// Inserts a type at the beginning of the list.
///
/// The type to insert.
/// 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.
public virtual void InsertType(Type value)
{
InsertType(0, value);
}
///
/// Inserts a type at the specified index.
///
/// The type to insert.
/// The zero-based index at which the type should be inserted.
/// is out of range.
public void InsertType(int index)
where T : TResolved
{
InsertType(index, typeof(T));
}
///
/// Inserts a type at the beginning of the list.
///
/// The type to insert.
public void InsertType()
where T : TResolved
{
InsertType(0, typeof(T));
}
///
/// Inserts a type before a specified, already existing type.
///
/// The existing type before which to insert.
/// The type to insert.
/// 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.
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);
}
}
///
/// Inserts a type before a specified, already existing type.
///
/// The existing type before which to insert.
/// The type to insert.
/// 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.
public void InsertTypeBefore()
where TExisting : TResolved
where T : TResolved
{
InsertTypeBefore(typeof(TExisting), typeof(T));
}
///
/// Returns a value indicating whether the specified type is already in the collection of types.
///
/// The type to look for.
/// A value indicating whether the type is already in the collection of types.
public virtual bool ContainsType(Type value)
{
using (new ReadLock(_lock))
{
return _instanceTypes.Contains(value);
}
}
///
/// Gets the types in the collection of types.
///
/// The types in the collection of types.
/// Returns an enumeration, the list cannot be modified.
public virtual IEnumerable GetTypes()
{
Type[] types;
using (new ReadLock(_lock))
{
types = _instanceTypes.ToArray();
}
return types;
}
///
/// Returns a value indicating whether the specified type is already in the collection of types.
///
/// The type to look for.
/// A value indicating whether the type is already in the collection of types.
public bool ContainsType()
where T : TResolved
{
return ContainsType(typeof(T));
}
#endregion
///
/// Returns a WriteLock to use when modifying collections
///
///
protected WriteLock GetWriteLock()
{
return new WriteLock(_lock);
}
#region Type utilities
///
/// Ensures that a type is a valid type for the resolver.
///
/// The type to test.
/// the type is not a valid type for the resolver.
protected virtual void EnsureCorrectType(Type value)
{
if (!TypeHelper.IsTypeAssignableFrom(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
///
/// Ensures that the resolver supports removing types.
///
/// The resolver does not support removing types.
protected void EnsureSupportsRemove()
{
if (!SupportsRemove)
throw new InvalidOperationException("This resolver does not support removing types");
}
///
/// Ensures that the resolver supports clearing types.
///
/// The resolver does not support clearing types.
protected void EnsureSupportsClear() {
if (!SupportsClear)
throw new InvalidOperationException("This resolver does not support clearing types");
}
///
/// Ensures that the resolver supports adding types.
///
/// The resolver does not support adding types.
protected void EnsureSupportsAdd()
{
if (!SupportsAdd)
throw new InvalidOperationException("This resolver does not support adding new types");
}
///
/// Ensures that the resolver supports inserting types.
///
/// The resolver does not support inserting types.
protected void EnsureSupportsInsert()
{
if (!SupportsInsert)
throw new InvalidOperationException("This resolver does not support inserting new types");
}
///
/// Gets a value indicating whether the resolver supports adding types.
///
protected virtual bool SupportsAdd
{
get { return true; }
}
///
/// Gets a value indicating whether the resolver supports inserting types.
///
protected virtual bool SupportsInsert
{
get { return true; }
}
///
/// Gets a value indicating whether the resolver supports clearing types.
///
protected virtual bool SupportsClear
{
get { return true; }
}
///
/// Gets a value indicating whether the resolver supports removing types.
///
protected virtual bool SupportsRemove
{
get { return true; }
}
#endregion
}
}