Sunday, May 1, 2011

A framework for cross-cutting concerns

A multitude of frameworks can be leveraged by your application to address one or more cross-cutting application concerns (e.g. exception handling, caching, validation, etc.).  However, sometimes it behooves you to not couple your application to a specific framework (e.g. Enterprise Library, Log4Net, ELMAH).

I've decided to develop an example aspect-oriented programming (AOP) framework API to facilitate decoupling from AOP frameworks.  For each framework we wish to use for the concrete implementation of a cross-cutting concern, we'd have to develop an adapter.  I have posted below the interfaces for such a framework and, since I use Enterprise Library (EntLib) a lot, I've included the relevant adapters as well.

Disclaimer: The code below carries no guarantees-- heck, it may not even work-- so use at your own risk.

    
    public interface INotifyDisposed
    {
        event EventHandler Disposed;
    }


    public abstract class ScopeLimitedObject : IDisposable, INotifyDisposed
    {
        public event EventHandler Disposed = null;


        // We provide this version of the property name as it is most common in legacy .NET and applications.
        public bool Disposing
        { get { return IsDisposing; } }

        protected bool IsDisposing
        { get; private set; }

        public bool IsDisposed
        { get; protected set; }


        private void RaiseDisposed()
        {
            var @event = Disposed;
            if (@event != null)
                @event(this, null);
        }


        /// <summary>
        /// This is the dispose routine you want to normally handle.
        /// </summary>
        protected virtual void OnDisposeExplicit()
        { }

        /// <summary>
        /// This is the dispose routine that *typically* originates from garbage collection and where
        /// dispose was NEVER called explicitly.
        /// </summary>
        protected virtual void OnDisposeImplicit()
        { }

        /// <summary>
        /// This is a dispose routine that is always called, during both implicit and explicit calls to 
        /// Dispose.  WARNING: this method will likely (though not guaranteed) be executed twice, 
        /// so be careful of the side effects in your own overriding method.  You never need to provide
        /// an implementation of this method if you adequately provide implementations for both
        /// "OnDisposeExplicit" and "OnDisposeImplicit".
        /// </summary>
        protected virtual void OnDisposeRegardless()
        { }


        protected void Dispose(bool isDisposing)
        {
            if (!(IsDisposed || IsDisposing))
            {
                IsDisposing = true;

                if (isDisposing)
                {
                    IsDisposed = true;
                    OnDisposeExplicit();
                }
                else
                    OnDisposeImplicit();

                RaiseDisposed();
            }

            OnDisposeRegardless();
        }


        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        ~ScopeLimitedObject()
        { Dispose(false); }
    }


    public interface Indexable<TKey, TValue> : IEnumerable<TValue>
    {
        bool TryGet(TKey key, out TValue value);

        TValue this[TKey key]
        { get; }
    }


    public interface IndexableByName<TEntry> : Indexable<string, TEntry>
    {
        new bool TryGet(string name, out TEntry entry);

        new TEntry this[string name]
        { get; }
    }


    public interface ExceptionManager
    {
        /// <summary>
        /// For advanced scenarios (suggested you use "Process" over this method): Handles the 
        /// specified <see cref="Exception"/> object according to the rules configured for <paramref name="policyName"/>. 
        /// </summary>a
        /// <param name="exceptionToHandle">An <see cref="Exception"/> object.</param>
        /// <param name="policyName">The name of the policy to handle.</param>
        /// <param name="exceptionToThrow">The new <see cref="Exception"/> to throw, if any.</param>
        /// <remarks>
        /// If a rethrow is recommended and <paramref name="exceptionToThrow"/> is <see langword="null"/>,
        /// then the original exception <paramref name="exceptionToHandle"/> should be rethrown; otherwise,
        /// the exception returned in <paramref name="exceptionToThrow"/> should be thrown.
        /// </remarks>
        /// <returns>
        /// Whether or not a rethrow is recommended. 
        /// </returns>
        /// <example>
        /// The following code shows the usage of the 
        /// exception handling framework.
        /// <code>
        /// try
        /// {
        ///  Foo();
        /// }
        /// catch (Exception e)
        /// {
        ///     Exception exceptionToThrow;
        ///  if (exceptionManager.HandleException(e, "policy", out exceptionToThrow))
        ///  {
        ///    if(exceptionToThrow == null)
        ///      throw;
        ///    else
        ///      throw exceptionToThrow;
        ///  }
        /// }
        /// </code>
        /// </example>
        /// <seealso cref="ExceptionManager.HandleException(Exception, string)"/>
        bool HandleException(Exception exceptionToHandle, string policyName, out Exception exceptionToThrow);

        /// <summary>
        /// For advanced scenarios (suggested you use "Process" over this method): Handles the 
        /// specified <see cref="Exception"/> object according to the rules configured for <paramref name="policyName"/>. 
        /// </summary>
        /// <param name="exceptionToHandle">An <see cref="Exception"/> object.</param>
        /// <param name="policyName">The name of the policy to handle.</param>        
        /// <returns>
        /// Whether or not a rethrow is recommended.
        /// </returns>
        /// <example>
        /// The following code shows the usage of the 
        /// exception handling framework.
        /// <code>
        /// try
        /// {
        ///  Foo();
        /// }
        /// catch (Exception e)
        /// {
        ///  if (exceptionManager.HandleException(e, "policy")) throw;
        /// }
        /// </code>
        /// </example>
        /// <seealso cref="ExceptionManager.Process"/>
        bool HandleException(Exception exceptionToHandle, string policyName);

        /// <summary>
        /// Excecutes the supplied delegate <paramref name="action"/> and handles 
        /// any thrown exception according to the rules configured for <paramref name="policyName"/>.
        /// </summary>
        /// <param name="action">The delegate to execute.</param>
        /// <param name="policyName">The name of the policy to handle.</param>
        /// <param name="finallyAction">A delegate to execute as if in a "finally" block.</param>        
        /// <example>
        /// The following code shows one usage of this method.
        /// <code>
        ///  exceptionManager.Process(() => { Foo(); }, "policy", () => { CleanUpResources(); });
        /// </code>
        /// </example>
        /// <seealso cref="ExceptionManager.HandleException(Exception, string)"/>
        void Process(Action action, string policyName, Action finallyAction);

        /// <summary>
        /// Excecutes the supplied delegate <paramref name="action"/> and handles 
        /// any thrown exception according to the rules configured for <paramref name="policyName"/>.
        /// </summary>
        /// <param name="action">The delegate to execute.</param>
        /// <param name="policyName">The name of the policy to handle.</param>        
        /// <example>
        /// The following code shows one usage of this method.
        /// <code>
        ///  exceptionManager.Process(() => { Foo(); }, "policy");
        /// </code>
        /// </example>
        /// <seealso cref="ExceptionManager.HandleException(Exception, string)"/>
        void Process(Action action, string policyName);

        /// <summary>
        /// Excecutes the supplied delegate <paramref name="action"/> and handles 
        /// any thrown exception according to the rules configured for the systems default exception policy, ExceptionPolicy.Default
        /// (please note that, though calling this method may provide some desired benefit, a well-designed application will
        /// make liberal use of well-defined exception policies).
        /// </summary>
        /// <param name="action">The delegate to execute.</param>
        /// <example>
        /// The following code shows one usage of this method.
        /// <code>
        ///  exceptionManager.Process(() => { Foo(); });
        /// </code>
        /// </example>
        /// <seealso cref="ExceptionManager.HandleException(Exception, string)"/>
        void Process(Action action);
    }


    public class EntLibBasedExceptionManager : ExceptionManager
    {
        private readonly EntLibExceptionManager exceptionManager = null;


        public bool HandleException(Exception exceptionToHandle, string policyName, out Exception exceptionToThrow)
        { return exceptionManager.HandleException(exceptionToHandle, policyName, out exceptionToThrow); }

        public bool HandleException(Exception exceptionToHandle, string policyName)
        { return exceptionManager.HandleException(exceptionToHandle, policyName); }

        public void Process(Action action, string policyName, Action finallyAction)
        {
            try
            { action(); }
            catch (Exception ex)
            {
                var exToThrow = (Exception)null;
                if (HandleException(ex, policyName, out exToThrow))
                {
                    if (exToThrow == null)
                        throw;
                    else
                        throw exToThrow;
                }
            }
            finally
            {
                if (finallyAction != null)
                    Process(finallyAction, ExceptionPolicy.Default);
            }
        }

        public void Process(Action action, string policyName)
        { Process(action, policyName, null); }

        public void Process(Action action)
        { Process(action, ExceptionPolicy.Default); }


        public EntLibBasedExceptionManager(EntLibExceptionManager exceptionManager)
        {
            Guard.AgainstNull(exceptionManager, "exceptionManager");
            this.exceptionManager = exceptionManager;
        }
    }



    public abstract class CacheItem
    {
        public abstract string Key { get; }
        public abstract DateTime LastAccessedTime { get; }
        public abstract CacheItemPriority ScavengingPriority { get; }
        public abstract object Value { get; }


        public abstract bool HasExpired();
        public abstract void MakeEligibleForScavenging();        
        public abstract void MakeIneligibleForScavenging();        
    }


    /// <summary>
    /// Specifies the item priority levels.
    /// </summary>
    public enum CacheItemPriority
    {
        /// <summary>
        /// Should never be seen in nature.
        /// </summary>
        None = 0,
        /// <summary>
        /// Low priority for scavenging.
        /// </summary>
        Low = 1,
        /// <summary>
        /// Normal priority for scavenging.
        /// </summary>
        Normal,
        /// <summary>
        /// High priority for scavenging.
        /// </summary>
        High,
        /// <summary>
        /// Non-removable priority for scavenging.
        /// </summary>
        NotRemovable
    }

    
    /// <summary>
    /// Allows end users to implement their own cache item expiration schema.  Should be marked as Serializable.
    /// </summary>
    public interface CacheItemExpirationPolicy
    {
        /// <summary>
        /// Specifies if item has expired or not.
        /// </summary>
        /// <returns>Returns true if the item has expired, otherwise false.</returns>
        bool HasExpired();

        /// <summary>
        /// Called to tell the expiration that the CacheItem to which this expiration belongs has been touched by the user
        /// </summary>
        void Notify();

        /// <summary>
        /// Called to give the instance the opportunity to initialize itself from information contained in the CacheItem.
        /// </summary>
        /// <param name="owningCacheItem">CacheItem that owns this expiration policy</param>
        void Initialize(CacheItem owningCacheItem);
    }


    [Serializable]
    sealed public class AbsoluteTimeExpirationPolicy : CacheItemExpirationPolicy
    {
        private DateTime expirationDateTime;        

        
        public bool HasExpired()
        { return DateTime.Now.ToUniversalTime().Ticks >= this.expirationDateTime.Ticks; }

        public void Notify()
        { }

        public void Initialize(CacheItem owningCacheItem)
        { }


        public AbsoluteTimeExpirationPolicy(DateTime dateTime)
        {
            Guard.Against(dateTime, dt => dt <= DateTime.Now, Resources.ConstructedOrProvidedTimeCannotBeLessThanCurrentTime, "dateTime");
            this.expirationDateTime = dateTime.ToUniversalTime();
        }

        public AbsoluteTimeExpirationPolicy(TimeSpan duration) : this(DateTime.Now + duration)
        { }
    }


    [Serializable]
    sealed public class FileDependentExpirationPolicy : CacheItemExpirationPolicy
    {
        private string dependentFilePath = null;
        private DateTime dependentFileLastModifiedTime = default(DateTime);


        private static void EnsureFileIsAccessible(string filePath)
        { new FileIOPermission(FileIOPermissionAccess.Read, filePath).Demand(); }


        public bool HasExpired()
        {
            EnsureFileIsAccessible(dependentFilePath);
            return !File.Exists(dependentFilePath)
                || (DateTime.Compare(dependentFileLastModifiedTime, File.GetLastWriteTime(dependentFilePath)) != 0);
        }

        public void Notify()
        { }

        public void Initialize(CacheItem owningCacheItem)
        { }


        public FileDependentExpirationPolicy(string filePath)
        {
            Guard.AgainstNullOrEmpty(filePath, "filePath");
            EnsureFileIsAccessible(filePath);
            Guard.Against(filePath, path => !File.Exists(path), Resources.ProvidedFilePathMustExist, "filePath");

            this.dependentFilePath = Path.GetFullPath(filePath);
            this.dependentFileLastModifiedTime = File.GetLastWriteTime(filePath);
        }
    }


    [Serializable]
    sealed internal class NoExpirationPolicy : CacheItemExpirationPolicy
    {
        public bool HasExpired()
        { return false; }

        public void Notify()
        { }

        public void Initialize(CacheItem owningCacheItem)
        { }
    }


    [Serializable]
    sealed internal class SlidingTimeExpirationPolicy : CacheItemExpirationPolicy
    {
        private DateTime timeLastUsed = default(DateTime);
        private TimeSpan maxDurationOfNonUsageBeforeExpiration = default(TimeSpan);


        public bool HasExpired()
        { return DateTime.Now.ToUniversalTime().Ticks >= (timeLastUsed.ToUniversalTime().Ticks + maxDurationOfNonUsageBeforeExpiration.Ticks); }

        public void Notify()
        { timeLastUsed = DateTime.Now; }

        public void Initialize(CacheItem owningCacheItem)
        {
            Guard.AgainstNull(owningCacheItem, "owningCacheItem");
            timeLastUsed = owningCacheItem.LastAccessedTime;
        }


        public SlidingTimeExpirationPolicy(TimeSpan maxDurationOfNonUsageBeforeExpiration)
        {
            Guard.Against(
                maxDurationOfNonUsageBeforeExpiration, 
                timeSpan => timeSpan.TotalSeconds < 1,
                Resources.MaxDurationOfNonUseBeforeExpirationMustBeGreaterThanOrEqualToOneSecond,
                "maxDurationOfNonUsageBeforeExpiration"
            );

            this.maxDurationOfNonUsageBeforeExpiration = maxDurationOfNonUsageBeforeExpiration;
        }
    }


    public enum CacheItemRemovedReason
    {
        /// <summary>
        /// The item has expired.
        /// </summary>
        Expired,

        /// <summary>
        /// The item was manually removed from the cache.
        /// </summary>
        Removed,

        /// <summary>
        /// The item was removed by the scavenger because it had a lower priority that any other item in the cache.
        /// </summary>
        Scavenged,

        /// <summary>
        /// Reserved. Do not use.
        /// </summary>
        Unknown = 9999
    }

    
    /// <summary>
    /// This interface defines the contract that must be implemented to create an object that can be used to refresh 
    /// an expired item from the cache. The implementing class must be serializable. Care must be taken when implementing 
    /// this interface not to create an object that maintains too much state about its environment, as all portions of its
    /// environment will be serialized as well, creating possibly a huge object graph.
    /// </summary>
    public interface CacheItemRemovedHandler
    {
        /// <summary>
        /// Called when an item expires from the cache. This method can be used to notify an application that
        /// the expiration occured, cause the item to be refetched and refreshed from its original location, or 
        /// perform any other application-specific action. 
        /// </summary>
        /// <param name="removedKey">Key of item removed from cache. Will never be null.</param>
        /// <param name="expiredValue">Value from cache item that was just expired</param>
        /// <param name="removalReason">Reason the item was removed from the cache. See <see cref="CacheItemRemovedReason"/></param>
        /// <remarks>This method should catch and handle any exceptions thrown during its operation. No exceptions should leak
        /// out of it.</remarks>
        void OnCacheItemRemoved(string removedKey, object expiredValue, CacheItemRemovedReason removalReason);
    }


    public interface CacheManager
    {
        /// <summary>
        /// Adds new CacheItem to cache. If another item already exists with the same key, that item is removed before
        /// the new item is added. If any failure occurs during this process, the cache will not contain the item being added.
        /// </summary>
        /// <param name="key">Identifier for this CacheItem</param>
        /// <param name="value">Value to be stored in cache. May be null.</param>
        /// <param name="scavengingPriority">Specifies the new item's scavenging priority. See <see cref="CacheItemPriority" /> for more information.</param>
        /// <param name="cacheItemRemovedHandler">Object provided to allow the cache to handle the removal of an item that has been expired. May be null.</param>
        /// <param name="expirations">Param array specifying the expiration policies to be applied to this item. May be null or omitted.</param>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        void Add(string key, object value, CacheItemPriority scavengingPriority, CacheItemRemovedHandler cacheItemRemovedHandler, params CacheItemExpirationPolicy[] expirations);

        /// <summary>
        /// Adds new CacheItem to cache. If another item already exists with the same key, that item is removed before
        /// the new item is added. If any failure occurs during this process, the cache will not contain the item being added.
        /// </summary>
        /// <param name="key">Identifier for this CacheItem</param>
        /// <param name="value">Value to be stored in cache. May be null.</param>
        /// <param name="cacheItemRemovedHandler">Object provided to allow the cache to handle the removal of an item that has been expired. May be null.</param>
        /// <param name="expirations">Param array specifying the expiration policies to be applied to this item. May be null or omitted.</param>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        void Add(string key, object value, CacheItemRemovedHandler cacheItemRemovedHandler, params CacheItemExpirationPolicy[] expirations);

        /// <summary>
        /// Adds new CacheItem to cache. If another item already exists with the same key, that item is removed before
        /// the new item is added. If any failure occurs during this process, the cache will not contain the item being added.
        /// </summary>
        /// <param name="key">Identifier for this CacheItem</param>
        /// <param name="value">Value to be stored in cache. May be null.</param>
        /// <param name="scavengingPriority">Specifies the new item's scavenging priority. See <see cref="CacheItemPriority" /> for more information.</param>
        /// <param name="expirations">Param array specifying the expiration policies to be applied to this item. May be null or omitted.</param>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        void Add(string key, object value, CacheItemPriority scavengingPriority, params CacheItemExpirationPolicy[] expirations);

        /// <summary>
        /// Adds new CacheItem to cache. If another item already exists with the same key, that item is removed before
        /// the new item is added. If any failure occurs during this process, the cache will not contain the item being added.
        /// </summary>
        /// <param name="key">Identifier for this CacheItem</param>
        /// <param name="value">Value to be stored in cache. May be null.</param>
        /// <param name="expirations">Param array specifying the expiration policies to be applied to this item. May be null or omitted.</param>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        void Add(string key, object value, params CacheItemExpirationPolicy[] expirations);


        /// <summary>
        /// Returns true if key refers to item current stored in cache
        /// </summary>
        /// <param name="key">Key of item to check for</param>
        /// <returns>True if item referenced by key is in the cache</returns>
        bool Contains(string key);

        /// <summary>
        /// Returns the number of items currently in the cache.
        /// </summary>
        int Count { get; }

        /// <summary>
        /// Removes all items from the cache. If an error occurs during the removal, the cache is left unchanged.
        /// </summary>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        void Flush();

        /// <summary>
        /// Returns the value associated with the given key.
        /// </summary>
        /// <param name="key">Key of item to return from cache.</param>
        /// <returns>Value stored in cache</returns>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        T Get<T>(string key);

        /// <summary>
        /// Tries to return the value associated with the given key.
        /// </summary>
        /// <param name="key">Key of item to return from cache.</param>
        /// <param name="cachedItem">The cached item.</param>
        /// <returns>whether or not the key was found</returns>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        bool TryGet<T>(string key, out T cachedItem);

        /// <summary>
        /// Removes the given item from the cache. If no item exists with that key, this method does nothing.
        /// </summary>
        /// <param name="key">Key of item to remove from cache.</param>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the CacheItems.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        void Remove(string key);

        /// <summary>
        /// Returns the item identified by the provided key
        /// </summary>
        /// <param name="key">Key to retrieve from cache</param>
        /// <exception cref="ArgumentNullException">Provided key is null</exception>
        /// <exception cref="ArgumentException">Provided key is an empty string</exception>
        /// <remarks>The CacheManager can be configured to use different storage mechanisms in which to store the cache items.
        /// Each of these storage mechanisms can throw exceptions particular to their own implementations.</remarks>
        object this[string key] { get; }
    }


    public interface CacheManagers
    {
        CacheManager GetDefault();
        CacheManager Get(string name);
    }


    sealed public class ExtendableCacheItemExpirationPolicyResolver
    { }
    
    
    public static class CachedItem
    {
        public static readonly ExtendableCacheItemExpirationPolicyResolver Expires = new ExtendableCacheItemExpirationPolicyResolver();
    }


    internal static class CacheItemPriorityExtensions
    {
        public static EntLibCacheItemPriority ToEntLib(this CacheItemPriority priority)
        { return (EntLibCacheItemPriority)((int)priority); }

        public static CacheItemPriority ToFramework(this EntLibCacheItemPriority priority)
        { return (CacheItemPriority)((int)priority); }
    }


    internal static class CacheItemRemovedReasonExtensions
    {
        public static EntLibCacheItemRemovedReason ToEntLib(this CacheItemRemovedReason reason)
        { return (EntLibCacheItemRemovedReason)((int)reason); }

        public static CacheItemRemovedReason ToFramework(this EntLibCacheItemRemovedReason reason)
        { return (CacheItemRemovedReason)((int)reason); }
    }


    [Serializable]
    sealed internal class EntLibCacheItemExpirationWrapper : ICacheItemExpiration
    {
        private CacheItemExpirationPolicy policy = null;


        public bool HasExpired()
        { return policy.HasExpired(); }

        public void Initialize(EntLibCacheItem owningCacheItem)
        { policy.Initialize(new EntLibCacheItemWrapper(owningCacheItem)); }

        public void Notify()
        { policy.Notify(); }


        public EntLibCacheItemExpirationWrapper(CacheItemExpirationPolicy policy)
        {
            Guard.AgainstNull(policy, "policy");
            this.policy = policy;
        }
    }


    [Serializable]
    sealed internal class EntLibCacheItemRemovedHandlerWrapper : ICacheItemRefreshAction
    {
        private CacheItemRemovedHandler removedHandler = null;


        public void Refresh(string removedKey, object expiredValue, EntLibCacheItemRemovedReason removalReason)
        { removedHandler.OnCacheItemRemoved(removedKey, expiredValue, removalReason.ToFramework()); }


        public EntLibCacheItemRemovedHandlerWrapper(CacheItemRemovedHandler removedHandler)
        {
            Guard.AgainstNull(removedHandler, "removedHandler");
            this.removedHandler = removedHandler;
        }
    }


    sealed internal class EntLibCacheItemWrapper : CacheItem
    {
        private readonly EntLibCacheItem cacheItem = null;

        internal EntLibCacheItem WrappedCacheItem
        { get { return cacheItem; } }
        
        public override string Key
        { get { return cacheItem.Key; } }

        public override DateTime LastAccessedTime
        { get { return cacheItem.LastAccessedTime; } }

        public override CacheItemPriority ScavengingPriority
        { get { return cacheItem.ScavengingPriority.ToFramework(); } }

        public override object Value
        { get { return cacheItem.Value; } }


        public override bool HasExpired()
        { return cacheItem.HasExpired(); }

        public override void MakeEligibleForScavenging()
        { cacheItem.MakeEligibleForScavenging(); }

        public override void MakeIneligibleForScavenging()
        { cacheItem.MakeNotEligibleForScavenging(); }


        internal EntLibCacheItemWrapper(EntLibCacheItem cacheItem)
        {
            Guard.AgainstNull(cacheItem, "cacheItem");
            this.cacheItem = cacheItem;
        }
    }


    sealed internal class EntLibCacheManagerAdapter : CacheManager
    {
        private readonly ICacheManager cacheManager = null;
        private readonly ICacheItemExpiration[] EmptyExpirations = new ICacheItemExpiration[] { };


        public void Add(string key, object value, CacheItemPriority scavengingPriority, CacheItemRemovedHandler cacheItemRemovedHandler, params CacheItemExpirationPolicy[] expirations)
        {
            cacheManager.Add(
                key,
                value,
                scavengingPriority.ToEntLib(),
                cacheItemRemovedHandler == null ? null : new EntLibCacheItemRemovedHandlerWrapper(cacheItemRemovedHandler),
                expirations == null ? EmptyExpirations : expirations.Select(expiration => new EntLibCacheItemExpirationWrapper(expiration)).ToArray()
            );
        }

        public void Add(string key, object value, CacheItemRemovedHandler cacheItemRemovedHandler, params CacheItemExpirationPolicy[] expirations)
        { Add(key, value, CacheItemPriority.Normal, cacheItemRemovedHandler, expirations); }

        public void Add(string key, object value, CacheItemPriority scavengingPriority, params CacheItemExpirationPolicy[] expirations)
        { Add(key, value, scavengingPriority, null, expirations); }

        public void Add(string key, object value, params CacheItemExpirationPolicy[] expirations)
        { Add(key, value, (CacheItemRemovedHandler) null, expirations); }
        

        public bool Contains(string key)
        { return cacheManager.Contains(key); }

        public int Count
        { get { return cacheManager.Count; } }

        public void Flush()
        { cacheManager.Flush(); }

        public T Get<T>(string key)
        { return (T) cacheManager.GetData(key); }

        public bool TryGet<T>(string key, out T cachedItem)
        {
            var couldOrNot = Contains(key);
            cachedItem = couldOrNot ? Get<T>(key) : default(T);
            return couldOrNot;
        }

        public void Remove(string key)
        { cacheManager.Remove(key); }

        public object this[string key]
        { get { return cacheManager[key]; } }


        public EntLibCacheManagerAdapter(ICacheManager cacheManager)
        { this.cacheManager = cacheManager; }
    }


    sealed internal class EntLibCacheManagersAdapter : CacheManagers
    {
        private readonly Components components = null;


        public CacheManager GetDefault()
        { return new EntLibCacheManagerAdapter(components.Get<EntLibCacheManager>()); }

        public CacheManager Get(string name)
        { return new EntLibCacheManagerAdapter(components.Get<EntLibCacheManager>(name)); }


        public EntLibCacheManagersAdapter(Components components)
        {
            Guard.AgainstNull(components, "components");
            this.components = components;
        }
    }


    public interface Logger
    {
        /// <summary>
        /// Writes a new log entry to the default category.
        /// </summary>
        /// <param name="message">message to log; value from ToString() method from message object.</param>
        void Log(object message);
        
        /// <summary>
        /// Writes a new log entry to the default category with a dictionary of extended properties
        /// </summary>
        /// <param name="message">message to log; value from ToString() method from message object.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, IDictionary<string, object> properties);
        
        /// <summary>
        /// Writes a new log entry to a specific collection of categories.
        /// </summary>
        /// <param name="message">message to log; value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        void Log(object message, IEnumerable<string> categories);
        
        /// <summary>
        /// Writes a new log entry to a specific category.
        /// </summary>
        /// <param name="message">message to log; value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        void Log(object message, string category);

        /// <summary>
        /// Write a new log entry to a specific collection of categories with a dictionary of extended properties.
        /// </summary>
        /// <param name="message">message to log; value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, IEnumerable<string> categories, IDictionary<string, object> properties);
        
        /// <summary>
        /// Write a new log entry with a specific collection of categories and priority.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        void Log(object message, IEnumerable<string> categories, int priority);

        /// <summary>
        /// Write a new log entry to a specific category with a dictionary of extended properties.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, string category, IDictionary<string, object> properties);

        /// <summary>
        /// Write a new log entry with a specific category and priority.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        void Log(object message, string category, int priority);

        /// <summary>
        /// Write a new log entry to with a specific collection of categories, priority and a dictionary of extended properties.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, IEnumerable<string> categories, int priority, IDictionary<string, object> properties);

        /// <summary>
        /// Write a new log entry with a specific collection of categories, priority and event id.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        void Log(object message, IEnumerable<string> categories, int priority, int eventId);

        /// <summary>
        /// Write a new log entry to with a specific category, priority and a dictionary of extended properties.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, string category, int priority, IDictionary<string, object> properties);

        /// <summary>
        /// Write a new log entry with a specific category, priority and event id.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        void Log(object message, string category, int priority, int eventId);

        /// <summary>
        /// Write a new log entry with a specific collection of categories, priority, event id and severity.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        /// <param name="severity">Log entry severity as a System.Diagnostics.TraceEventType enumeration. (Unspecified, Information, Warning or Error).</param>
        void Log(object message, IEnumerable<string> categories, int priority, int eventId, TraceEventType severity);

        /// <summary>
        /// Write a new log entry with a specific category, priority, event id and severity.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        /// <param name="severity">Log entry severity as a System.Diagnostics.TraceEventType enumeration. (Unspecified, Information, Warning or Error).</param>
        void Log(object message, string category, int priority, int eventId, TraceEventType severity);

        /// <summary>
        /// Write a new log entry with a specific collection of categories, priority, event id, severity and title.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        /// <param name="severity">Log message severity as a System.Diagnostics.TraceEventType enumeration. (Unspecified, Information, Warning or Error).</param>
        /// <param name="title">Additional description of the log entry message</param>
        void Log(object message, IEnumerable<string> categories, int priority, int eventId, TraceEventType severity, string title);

        /// <summary>
        /// Write a new log entry with a specific category, priority, event id, severity and title.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        /// <param name="severity">Log message severity as a System.Diagnostics.TraceEventType enumeration. (Unspecified, Information, Warning or Error).</param>
        /// <param name="title">Additional description of the log entry message</param>
        void Log(object message, string category, int priority, int eventId, TraceEventType severity, string title);

        /// <summary>
        /// Write a new log entry with a specific category, priority, event Id, severity title and dictionary of extended properties.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="categories">Category names used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        /// <param name="severity">Log message severity as a System.Diagnostics.TraceEventType enumeration. (Unspecified, Information, Warning or Error).</param>
        /// <param name="title">Additional description of the log entry message.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, IEnumerable<string> categories, int priority, int eventId, TraceEventType severity, string title, IDictionary<string, object> properties);

        /// <summary>
        /// Write a new log entry with a specific category, priority, event Id, severity title and dictionary of extended properties.
        /// </summary>
        /// <param name="message">Message body to log. Value from ToString() method from message object.</param>
        /// <param name="category">Category name used to route the log entry to a one or more trace listeners.</param>
        /// <param name="priority">Only messages must be above the minimum priority are processed.</param>
        /// <param name="eventId">Event number or identifier.</param>
        /// <param name="severity">Log message severity as a System.Diagnostics.TraceEventType enumeration. (Unspecified, Information, Warning or Error).</param>
        /// <param name="title">Additional description of the log entry message.</param>
        /// <param name="properties">Dictionary of key/value pairs to log.</param>
        void Log(object message, string category, int priority, int eventId, TraceEventType severity, string title, IDictionary<string, object> properties);
    }


    internal class EntLibLogWriterAdapter : Logger
    {
        private readonly LogWriter logWriter = null;


        public void Log(object message)
        { logWriter.Write(message); }

        public void Log(object message, IDictionary<string, object> properties)
        { logWriter.Write(message, properties); }

        public void Log(object message, IEnumerable<string> categories)
        { logWriter.Write(message, categories); }

        public void Log(object message, string category)
        { logWriter.Write(message, category); }

        public void Log(object message, IEnumerable<string> categories, IDictionary<string, object> properties)
        { logWriter.Write(message, categories, properties); }

        public void Log(object message, IEnumerable<string> categories, int priority)
        { logWriter.Write(message, categories, priority); }

        public void Log(object message, string category, IDictionary<string, object> properties)
        { logWriter.Write(message, category, properties); }

        public void Log(object message, string category, int priority)
        { logWriter.Write(message, category, priority); }

        public void Log(object message, IEnumerable<string> categories, int priority, IDictionary<string, object> properties)
        { logWriter.Write(message, categories, priority, properties); }

        public void Log(object message, IEnumerable<string> categories, int priority, int eventId)
        { logWriter.Write(message, categories, priority, eventId); }

        public void Log(object message, string category, int priority, IDictionary<string, object> properties)
        { logWriter.Write(message, category, priority, properties); }

        public void Log(object message, string category, int priority, int eventId)
        { logWriter.Write(message, category, priority, eventId); }

        public void Log(object message, IEnumerable<string> categories, int priority, int eventId, TraceEventType severity)
        { logWriter.Write(message, categories, priority, eventId, severity); }

        public void Log(object message, string category, int priority, int eventId, TraceEventType severity)
        { logWriter.Write(message, category, priority, eventId, severity); }

        public void Log(object message, IEnumerable<string> categories, int priority, int eventId, TraceEventType severity, string title)
        { logWriter.Write(message, categories, priority, eventId, severity, title); }

        public void Log(object message, string category, int priority, int eventId, TraceEventType severity, string title)
        { logWriter.Write(message, category, priority, eventId, severity, title); }

        public void Log(object message, IEnumerable<string> categories, int priority, int eventId, TraceEventType severity, string title, IDictionary<string, object> properties)
        { logWriter.Write(message, categories, priority, eventId, severity, title, properties); }

        public void Log(object message, string category, int priority, int eventId, TraceEventType severity, string title, IDictionary<string, object> properties)
        { logWriter.Write(message, category, priority, eventId, severity, title, properties); }


        public EntLibLogWriterAdapter(LogWriter logWriter)
        {
            Guard.AgainstNull(logWriter, "logWriter");
            this.logWriter = logWriter;
        }
    }


    [ConfigurationElementType(typeof(CustomTraceListenerData))]
    public class DebugTraceListener : CustomTraceListener
    {
        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
        {
            if ((data is LogEntry) &amp;&amp; (Formatter != null))
                WriteLine(Formatter.Format((LogEntry)data));
            else
                WriteLine(data.ToString());
        }


        public override void Write(string message)
        { Debug.Write(message); }

        public override void WriteLine(string message)
        { Debug.WriteLine(message); }
    }


    public interface Authorizer
    {
        bool IsAuthorizedTo(IPrincipal user, string context);
    }

    
    public abstract class PrincipalToken
    { }


    public interface PrincipalCache
    {
        IPrincipal GetPrincipal(PrincipalToken token);
        PrincipalToken SavePrincipal(IPrincipal principal);
        void SavePrincipal(IPrincipal principal, PrincipalToken token);
        void ExpirePrincipal(PrincipalToken token);
    }

    
    internal class EntLibPrincipalToken : PrincipalToken
    {
        internal IToken Token
        { get; private set; }

        internal EntLibPrincipalToken(IToken token)
        { Token = token; }
    }


    sealed internal class EntLibAuthorizationRuleProviderAdapter : Authorizer
    {
        private readonly IAuthorizationProvider authorizationProvider = null;
        private readonly ExceptionManager exceptionManager = null;


        public bool IsAuthorizedTo(IPrincipal user, string context)
        { 
            var isAuthorizedOrNot = Settings.Default.UserShouldBeAuthorizedForUnconfiguredAuthorizationContext;

            exceptionManager.Process(
                () => isAuthorizedOrNot = authorizationProvider.Authorize(user, context),
                ExceptionPolicy.AuthorizingUserInContext
            );

            return isAuthorizedOrNot;
        }


        public EntLibAuthorizationRuleProviderAdapter(IAuthorizationProvider authorizationProvider, ExceptionManager exceptionManager)
        {
            Guard.AgainstNull(authorizationProvider, "authorizationProvider");
            Guard.AgainstNull(exceptionManager, "exceptionManager");

            this.authorizationProvider = authorizationProvider;
            this.exceptionManager = exceptionManager;
        }
    }


    sealed internal class EntLibSecurityCacheProviderAdapter : PrincipalCache
    {
        private ISecurityCacheProvider securityCacheProvider = null;

        
        public IPrincipal GetPrincipal(PrincipalToken token)
        { return securityCacheProvider.GetPrincipal(((EntLibPrincipalToken)token).Token); }

        public PrincipalToken SavePrincipal(IPrincipal principal)
        { return new EntLibPrincipalToken(securityCacheProvider.SavePrincipal(principal)); }

        public void SavePrincipal(IPrincipal principal, PrincipalToken token)
        { securityCacheProvider.SavePrincipal(principal, ((EntLibPrincipalToken)token).Token); }

        public void ExpirePrincipal(PrincipalToken token)
        { securityCacheProvider.ExpirePrincipal(((EntLibPrincipalToken)token).Token); }


        public EntLibSecurityCacheProviderAdapter(ISecurityCacheProvider securityCacheProvider)
        { this.securityCacheProvider = securityCacheProvider; }
    }


    public interface Cryptographer
    {
        /// <summary>
        /// Compares plain text input with a computed hash using the given hash provider instance.
        /// </summary>
        /// <remarks>
        /// Use this method to compare hash values. Since hashes contain a random "salt" value, two seperately generated
        /// hashes of the same plain text will result in different values. 
        /// </remarks>
        /// <param name="hashInstance">A hash instance from configuration.</param>
        /// <param name="plaintext">The input as a string for which you want to compare the hash to.</param>
        /// <param name="hashedText">The hash as a string for which you want to compare the input to.</param>
        /// <returns><c>true</c> if plainText hashed is equal to the hashedText. Otherwise, <c>false</c>.</returns>
         bool CompareHash(string hashInstance, string plaintext, string hashedText);

        /// <overrides>
        /// Compares plain text input with a computed hash using the given hash provider instance.
        /// </overrides>
        /// <summary>
        /// Compares plain text input with a computed hash using the given hash provider instance.
        /// </summary>
        /// <remarks>
        /// Use this method to compare hash values. Since hashes may contain a random "salt" value, two seperately generated
        /// hashes of the same plain text may result in different values. 
        /// </remarks>
        /// <param name="hashInstance">A hash instance from configuration.</param>
        /// <param name="plaintext">The input for which you want to compare the hash to.</param>
        /// <param name="hashedText">The hash value for which you want to compare the input to.</param>
        /// <returns><c>true</c> if plainText hashed is equal to the hashedText. Otherwise, <c>false</c>.</returns>
         bool CompareHash(string hashInstance, byte[] plaintext, byte[] hashedText);

        /// <overrides>
        /// Computes the hash value of plain text using the given hash provider instance
        /// </overrides>
        /// <summary>
        /// Computes the hash value of plain text using the given hash provider instance
        /// </summary>
        /// <param name="hashInstance">A hash instance from configuration.</param>
        /// <param name="plaintext">The input for which to compute the hash.</param>
        /// <returns>The computed hash code.</returns>
         byte[] CreateHash(string hashInstance, byte[] plaintext);

        /// <summary>
        /// Computes the hash value of plain text using the given hash provider instance
        /// </summary>
        /// <param name="hashInstance">A hash instance from configuration.</param>
        /// <param name="plaintext">The input for which to compute the hash.</param>
        /// <returns>The computed hash code.</returns>
         string CreateHash(string hashInstance, string plaintext);

        /// <summary>
        /// Decrypts a cipher text using a specified symmetric cryptography provider.
        /// </summary>
        /// <param name="symmetricInstance">A symmetric instance from configuration.</param>
        /// <param name="ciphertext">The cipher text for which you want to decrypt.</param>
        /// <returns>The resulting plain text.</returns>
         byte[] DecryptSymmetric(string symmetricInstance, byte[] ciphertext);

        /// <summary>
        /// Decrypts a cipher text using a specified symmetric cryptography provider.
        /// </summary>
        /// <param name="symmetricInstance">A symmetric instance from configuration.</param>
        /// <param name="ciphertextBase64">The cipher text as a base64 encoded string for which you want to decrypt.</param>
        /// <returns>The resulting plain text as a string.</returns>
         string DecryptSymmetric(string symmetricInstance, string ciphertextBase64);


        /// <summary>
        /// Encrypts a secret using a specified symmetric cryptography provider.
        /// </summary>
        /// <param name="symmetricInstance">A symmetric instance from configuration.</param>
        /// <param name="plaintext">The input for which you want to encrypt.</param>
        /// <returns>The resulting cipher text.</returns>
         byte[] EncryptSymmetric(string symmetricInstance, byte[] plaintext);

        /// <summary>
        /// Encrypts a secret using a specified symmetric cryptography provider.
        /// </summary>
        /// <param name="symmetricInstance">A symmetric instance from configuration.</param>
        /// <param name="plaintext">The input as a base64 encoded string for which you want to encrypt.</param>
        /// <returns>The resulting cipher text as a base64 encoded string.</returns>
         string EncryptSymmetric(string symmetricInstance, string plaintext);
    }


    sealed internal class EntLibCryptographyManagerAdapter : Cryptographer
    {
        private readonly CryptographyManager cryptographyManager = null;


        public bool CompareHash(string hashInstance, string plaintext, string hashedText)
        { return cryptographyManager.CompareHash(hashInstance, plaintext, hashedText); }

        public bool CompareHash(string hashInstance, byte[] plaintext, byte[] hashedText)
        { return cryptographyManager.CompareHash(hashInstance, plaintext, hashedText); }

        public byte[] CreateHash(string hashInstance, byte[] plaintext)
        { return cryptographyManager.CreateHash(hashInstance, plaintext); }

        public string CreateHash(string hashInstance, string plaintext)
        { return cryptographyManager.CreateHash(hashInstance, plaintext); }

        public byte[] DecryptSymmetric(string symmetricInstance, byte[] ciphertext)
        { return cryptographyManager.DecryptSymmetric(symmetricInstance, ciphertext); }

        public string DecryptSymmetric(string symmetricInstance, string ciphertextBase64)
        { return cryptographyManager.DecryptSymmetric(symmetricInstance, ciphertextBase64); }

        public byte[] EncryptSymmetric(string symmetricInstance, byte[] plaintext)
        { return cryptographyManager.EncryptSymmetric(symmetricInstance, plaintext); }

        public string EncryptSymmetric(string symmetricInstance, string plaintext)
        { return cryptographyManager.EncryptSymmetric(symmetricInstance, plaintext); }


        public EntLibCryptographyManagerAdapter(CryptographyManager cryptographyManager)
        { this.cryptographyManager = cryptographyManager; }
    }


    [ConfigurationElementType(typeof(CustomSymmetricCryptoProviderData))]
    public class GeneralPurposeSymmetricCryptoProvider : ISymmetricCryptoProvider
    {
        private const string key = "!@#$FS3423fwwrt2";
        private static readonly byte[] keyAsBytes = UTF8Encoding.UTF8.GetBytes(key);

        private readonly AesManaged cryptographer = new AesManaged()
        {
            Key = keyAsBytes,
            IV = keyAsBytes
        };


        public byte[] Decrypt(byte[] ciphertext)
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var cryptoStream = new CryptoStream(memoryStream, cryptographer.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cryptoStream.Write(ciphertext, 0, ciphertext.Length);
                    cryptoStream.Flush();
                    cryptoStream.Close();
                    return memoryStream.ToArray();
                }
            }
        }

        public byte[] Encrypt(byte[] plaintext)
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var cryptoStream = new CryptoStream(memoryStream, cryptographer.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cryptoStream.Write(plaintext, 0, plaintext.Length);
                    cryptoStream.FlushFinalBlock();
                    cryptoStream.Close();
                    return memoryStream.ToArray();
                }
            }
        }


        public GeneralPurposeSymmetricCryptoProvider(NameValueCollection ignored)
     { }
    }
    

    sealed public class QueryResultAccessor : ScopeLimitedObject, IEnumerable<QueryResultAccessor.DataRecord>
    {
        sealed public class DataField
        {
            public string Name
            { get; private set; }

            public object Value
            { get; private set; }


            internal DataField(string name, object value)
            {
                Name = name;
                Value = value;
            }
        }


        sealed public class DataRecord : IndexableByName<object>, IEnumerable<DataField>
        {
            private readonly IDictionary<string, DataField> fields = null;


            public bool TryGet(string name, out object entry)
            {
                var field = (DataField)null;
                var couldOrNot = fields.TryGetValue(name, out field);
                entry = couldOrNot ? field.Value : null;
                return couldOrNot;
            }

            public object this[string name]
            { get { return fields[name].Value; } }

            public IEnumerator<DataField> GetEnumerator()
            { return fields.Values.GetEnumerator(); }

            IEnumerator IEnumerable.GetEnumerator()
            { return GetEnumerator(); }

            IEnumerator<object> IEnumerable<object>.GetEnumerator()
            {
                foreach (var dataField in this)
                    yield return dataField.Value;
            }


            internal DataRecord(IDictionary<int, string> fieldNamesByNum, Func<int, object> getFieldValue)
            {
                var sortedFields = new SortedDictionary<string, DataField>();

                var fieldName = (string)null;
                var numFields = fieldNamesByNum.Count; // separated out for performance
                for (var i = 0; i < numFields; i++)
                {
                    fieldName = fieldNamesByNum[i];
                    sortedFields[fieldName] = new DataField(fieldName, getFieldValue(i));
                }

                this.fields = sortedFields;
            }
        }


        private class DataRecordEnumerator : ScopeLimitedObject, IEnumerator<DataRecord>
        {
            private const int InitialIndex = -1;

            private Func<int, DataRecord> tryGetRecord = null;
            private IDictionary<int, string> fieldNamesByNum = null;

            private int currentIndex = InitialIndex;
            

            public DataRecord Current
            { get; private set; }

            object IEnumerator.Current
            { get { return Current; } }


            protected override void OnDisposeExplicit()
            {
                base.OnDisposeExplicit();
                tryGetRecord = null;
                fieldNamesByNum = null;
            }
            
            public bool MoveNext()
            { return (Current = tryGetRecord(++currentIndex)) != null; }

            public void Reset()
            { currentIndex = InitialIndex; }


            public DataRecordEnumerator(Func<int, DataRecord> tryGetRecord, IDictionary<int, string> fieldNamesByNum)
            {
                Guard.AgainstNull(tryGetRecord, "tryGetRecord");
                Guard.AgainstNull(fieldNamesByNum, "fieldNamesByNum");
                this.tryGetRecord = tryGetRecord;
                this.fieldNamesByNum = fieldNamesByNum;
            }
        }


        private const int InitialIndex = -1;

        private int maxIndexRead = InitialIndex;
        private IList<DataRecord> readRecords = new List<DataRecord>();
        private IDictionary<int, string> fieldNamesByNum = new SortedDictionary<int, string>();
        private IDataReader reader = null;


        protected override void OnDisposeExplicit()
        {
            base.OnDisposeExplicit();
            readRecords = null;
            fieldNamesByNum = null;
            reader.Dispose();
            reader = null;
        }


        public IEnumerator<DataRecord> GetEnumerator()
        { return new DataRecordEnumerator(TryGetRecord, fieldNamesByNum); }

        IEnumerator IEnumerable.GetEnumerator()
        { return GetEnumerator(); }


        private object GetFieldValue(int fieldNum)
        { return reader[fieldNum]; }

        private DataRecord TryGetRecord(int index)
        {
            var record = (DataRecord)null;

            if (index <= maxIndexRead)
                record = readRecords[index];
            else
            {
                if (reader.Read())
                    readRecords.Insert(maxIndexRead = index, record = new DataRecord(fieldNamesByNum, GetFieldValue));
            }

            return record;
        }


        public QueryResultAccessor(IDataReader reader)
        {
            Guard.AgainstNull(reader, "reader");
            Guard.Against(reader, r => r.IsClosed, Resources.CannotUtilizeClosedIDataReader, "reader");

            this.reader = reader;

            var fieldCount = reader.FieldCount; // separated out for performance reasons
            for (var i = 0; i < fieldCount; i++)
                fieldNamesByNum[i] = reader.GetName(i);
        }
    }


    public interface Database
    {
        //
        // Summary:
        //     Does this Database object support asynchronous execution?
        bool SupportsAsync { get; }

        //
        // Summary:
        //     Initiates the asynchronous execution of the storedProcedureName using the
        //     given parameterValues which will return the number of rows affected.
        //
        // Parameters:
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteNonQuery(System.IAsyncResult),
        //     which returns the number of affected records.
        IAsyncResult BeginExecuteNonQuery(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of the storedProcedureName using the
        //     given parameterValues inside a transaction which will return the number of
        //     rows affected.
        //
        // Parameters:
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   transaction:
        //     The System.Data.Common.DbTransaction to execute the command within.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteNonQuery(System.IAsyncResult),
        //     which returns the number of affected records.
        IAsyncResult BeginExecuteNonQuery(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of storedProcedureName using the given
        //     parameterValues which will return a System.Data.IDataReader.
        //
        // Parameters:
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteReader(System.IAsyncResult),
        //     which returns the System.Data.IDataReader.
        IAsyncResult BeginExecuteReader(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of storedProcedureName using the given
        //     parameterValues inside a transaction which will return a System.Data.IDataReader.
        //
        // Parameters:
        //   transaction:
        //     The System.Data.Common.DbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteReader(System.IAsyncResult),
        //     which returns the System.Data.IDataReader.
        IAsyncResult BeginExecuteReader(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of storedProcedureName using the given
        //     parameterValues which will return a query result accessor.
        //
        // Parameters:
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteQuery(System.IAsyncResult),
        //     which returns the query result accessor.
        IAsyncResult BeginExecuteQuery(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of storedProcedureName using the given
        //     parameterValues inside a transaction which will return a query result accessor.
        //
        // Parameters:
        //   transaction:
        //     The System.Data.Common.DbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteQuery(System.IAsyncResult),
        //     which returns the query result accessor.
        IAsyncResult BeginExecuteQuery(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of storedProcedureName using the given
        //     parameterValues which will return a single value.
        //
        // Parameters:
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteScalar(System.IAsyncResult),
        //     which returns the actual result.
        IAsyncResult BeginExecuteScalar(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Initiates the asynchronous execution of storedProcedureName using the given
        //     parameterValues inside a transaction which will return a single value.
        //
        // Parameters:
        //   transaction:
        //     The System.Data.Common.DbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   callback:
        //     The async callback to execute when the result of the operation is available.
        //     Pass null if you don't want to use a callback.
        //
        //   state:
        //     Additional state object to pass to the callback.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.IAsyncResult that can be used to poll or wait for results, or both;
        //     this value is also needed when invoking Microsoft.Practices.EnterpriseLibrary.Data.Database.EndExecuteScalar(System.IAsyncResult),
        //     which returns the actual result.
        IAsyncResult BeginExecuteScalar(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues);
        //
        // Summary:
        //     Finishes asynchronous execution of a SQL statement, returning the number
        //     of affected records.
        //
        // Parameters:
        //   asyncResult:
        //     The System.IAsyncResult returned by a call to any overload of Microsoft.Practices.EnterpriseLibrary.Data.Database.BeginExecuteNonQuery(System.Data.Common.DbCommand,System.AsyncCallback,System.Object).
        //
        // Returns:
        //     The number of affected records.
        int EndExecuteNonQuery(IAsyncResult asyncResult);
        //
        // Summary:
        //     Finishes asynchronous execution of a Transact-SQL statement, returning an
        //     System.Data.IDataReader.
        //
        // Parameters:
        //   asyncResult:
        //     The System.IAsyncResult returned by a call to any overload of BeginExecuteReader.
        //
        // Returns:
        //     An System.Data.IDataReader object that can be used to consume the queried
        //     information.
        IDataReader EndExecuteReader(IAsyncResult asyncResult);
        //
        // Summary:
        //     Finishes asynchronous execution of a Transact-SQL statement, returning a
        //     query result accessor.
        //
        // Parameters:
        //   asyncResult:
        //     The System.IAsyncResult returned by a call to any overload of BeginExecuteReader.
        //
        // Returns:
        //     A query result accessor that can be used to consume the queried
        //     information.
        QueryResultAccessor EndExecuteQuery(IAsyncResult asyncResult);
        //
        // Summary:
        //     Finishes asynchronous execution of a Transact-SQL statement, returning the
        //     first column of the first row in the result set returned by the query. Extra
        //     columns or rows are ignored.
        //
        // Parameters:
        //   asyncResult:
        //     The System.IAsyncResult returned by a call to any overload of BeginExecuteScalar.
        //
        // Returns:
        //     The value of the first column of the first row in the result set returned
        //     by the query.  If the result didn't have any columns or rows null (Nothing
        //     in Visual Basic).
        object EndExecuteScalar(IAsyncResult asyncResult);
        //
        // Summary:
        //     Executes the storedProcedureName using the given parameterValues and returns
        //     the number of rows affected.
        //
        // Parameters:
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     The number of rows affected
        int ExecuteNonQuery(string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName using the given parameterValues within a
        //     transaction and returns the number of rows affected.
        //
        // Parameters:
        //   transaction:
        //     The System.Data.IDbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The name of the stored procedure to execute.
        //
        //   parameterValues:
        //     An array of parameters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     The number of rows affected.
        int ExecuteNonQuery(DbTransaction transaction, string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName with the given parameterValues and returns
        //     an System.Data.IDataReader through which the result can be read.  It is the
        //     responsibility of the caller to close the connection and reader when finished.
        //
        // Parameters:
        //   storedProcedureName:
        //     The command that contains the query to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.Data.IDataReader object.
        IDataReader ExecuteReader(string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName with the given parameterValues within the
        //     given transaction and returns an System.Data.IDataReader through which the
        //     result can be read.  It is the responsibility of the caller to close the
        //     connection and reader when finished.
        //
        // Parameters:
        //   transaction:
        //     The System.Data.IDbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The command that contains the query to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     An System.Data.IDataReader object.
        IDataReader ExecuteReader(DbTransaction transaction, string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName with the given parameterValues and returns
        //     the first column of the first row in the result set returned by the query.
        //     Extra columns or rows are ignored.
        //
        // Parameters:
        //   storedProcedureName:
        //     The stored procedure to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     The first column of the first row in the result set.
        object ExecuteScalar(string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName with the given parameterValues within a
        //     transaction and returns the first column of the first row in the result set
        //     returned by the query. Extra columns or rows are ignored.
        //
        // Parameters:
        //   transaction:
        //     The System.Data.IDbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The stored procedure to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     The first column of the first row in the result set.
        object ExecuteScalar(DbTransaction transaction, string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName with the given parameterValues and returns
        //     a result accessor through which the result can be read.  It is the
        //     responsibility of the caller to close the accessor when finished (using Dispose).
        //
        // Parameters:
        //   storedProcedureName:
        //     The command that contains the query to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     a query result accessor
        QueryResultAccessor ExecuteQuery(string storedProcedureName, params object[] parameterValues);
        //
        // Summary:
        //     Executes the storedProcedureName with the given parameterValues within the
        //     given transaction and returns a result accessor through which the
        //     result can be read.  It is the responsibility of the caller to close the
        //     accessor when finished (using dispose).
        //
        // Parameters:
        //   transaction:
        //     The System.Data.IDbTransaction to execute the command within.
        //
        //   storedProcedureName:
        //     The command that contains the query to execute.
        //
        //   parameterValues:
        //     An array of paramters to pass to the stored procedure. The parameter values
        //     must be in call order as they appear in the stored procedure.
        //
        // Returns:
        //     a query result accessor
        QueryResultAccessor ExecuteQuery(DbTransaction transaction, string storedProcedureName, params object[] parameterValues);
    }


    public interface Databases
    {
        Database GetDefault();
        Database Get(string name);
    }


    sealed internal class EntLibDatabaseAdapter : Database
    {
        private readonly EntLibDatabase database = null;


        public bool SupportsAsync
        { get { return database.SupportsAsync; } }

        public IAsyncResult BeginExecuteNonQuery(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return database.BeginExecuteNonQuery(storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteNonQuery(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return database.BeginExecuteNonQuery(transaction, storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteReader(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return database.BeginExecuteReader(storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteReader(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return database.BeginExecuteReader(transaction, storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteScalar(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return database.BeginExecuteScalar(storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteScalar(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return database.BeginExecuteScalar(transaction, storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteQuery(string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return BeginExecuteReader(storedProcedureName, callback, state, parameterValues); }

        public IAsyncResult BeginExecuteQuery(DbTransaction transaction, string storedProcedureName, AsyncCallback callback, object state, params object[] parameterValues)
        { return BeginExecuteReader(transaction, storedProcedureName, callback, state, parameterValues); }

        public int EndExecuteNonQuery(IAsyncResult asyncResult)
        { return database.EndExecuteNonQuery(asyncResult); }

        public IDataReader EndExecuteReader(IAsyncResult asyncResult)
        { return database.EndExecuteReader(asyncResult); }

        public QueryResultAccessor EndExecuteQuery(IAsyncResult asyncResult)
        { return new QueryResultAccessor(EndExecuteReader(asyncResult)); }

        public object EndExecuteScalar(IAsyncResult asyncResult)
        { return database.EndExecuteScalar(asyncResult); }

        public int ExecuteNonQuery(string storedProcedureName, params object[] parameterValues)
        { return database.ExecuteNonQuery(storedProcedureName, parameterValues); }

        public int ExecuteNonQuery(DbTransaction transaction, string storedProcedureName, params object[] parameterValues)
        { return database.ExecuteNonQuery(transaction, storedProcedureName, parameterValues); }

        public IDataReader ExecuteReader(string storedProcedureName, params object[] parameterValues)
        { return database.ExecuteReader(storedProcedureName, parameterValues); }

        public IDataReader ExecuteReader(DbTransaction transaction, string storedProcedureName, params object[] parameterValues)
        { return database.ExecuteReader(transaction, storedProcedureName, parameterValues); }

        public QueryResultAccessor ExecuteQuery(string storedProcedureName, params object[] parameterValues)
        { return new QueryResultAccessor(ExecuteReader(storedProcedureName, parameterValues)); }

        public QueryResultAccessor ExecuteQuery(DbTransaction transaction, string storedProcedureName, params object[] parameterValues)
        { return new QueryResultAccessor(ExecuteReader(transaction, storedProcedureName, parameterValues)); }

        public object ExecuteScalar(string storedProcedureName, params object[] parameterValues)
        { return database.ExecuteScalar(storedProcedureName, parameterValues); }

        public object ExecuteScalar(DbTransaction transaction, string storedProcedureName, params object[] parameterValues)
        { return database.ExecuteScalar(transaction, storedProcedureName, parameterValues); }


        public EntLibDatabaseAdapter(EntLibDatabase database)
        {
            Guard.AgainstNull(database, "database");
            this.database = database;
        }
    }


    sealed internal class EntLibDatabasesAdapter : Databases
    {
        private readonly Components components = null;


        public Database GetDefault()
        { return new EntLibDatabaseAdapter(components.Get<EntLibDatabase>()); }

        public Database Get(string name)
        { return new EntLibDatabaseAdapter(components.Get<EntLibDatabase>(name)); }


        public EntLibDatabasesAdapter(Components components)
        {
            Guard.AgainstNull(components, "components");
            this.components = components;
        }
    }
    

Thursday, April 21, 2011

How to properly implement IDisposable

A lot of junior developers don't know how to implement IDisposable properly. I've provided below an example base class and a possibly useful interface which can assist in this effort. It is the accumulation of knowledge I've gathered from reading books and blogs.

Disclaimer: The code below carries no guarantees-- heck, it may not even work-- so use at your own risk.



    public interface INotifyDisposed
    {
        event EventHandler Disposed;
    }


    public abstract class ScopeLimitedObject : IDisposable, INotifyDisposed
    {
        public event EventHandler Disposed = null;


        // We provide this version of the property name as it is most common in legacy .NET and applications.
        public bool Disposing
        { get { return IsDisposing; } }

        protected bool IsDisposing
        { get; private set; }

        public bool IsDisposed
        { get; protected set; }


        private void RaiseDisposed()
        {
            var @event = Disposed;
            if (@event != null)
                @event(this, null);
        }


        /// <summary>
        /// This is the dispose routine you want to normally handle.
        /// </summary>
        protected virtual void OnDisposeExplicit()
        { }

        /// <summary>
        /// This is the dispose routine that *typically* originates from garbage collection and where
        /// dispose was NEVER called explicitly.
        /// </summary>
        protected virtual void OnDisposeImplicit()
        { }

        /// <summary>
        /// This is a dispose routine that is always called, during both implicit and explicit calls to 
        /// Dispose.  WARNING: this method will likely (though not guaranteed) be executed twice, 
        /// so be careful of the side effects in your own overriding method.  You never need to provide
        /// an implementation of this method if you adequately provide implementations for both
        /// "OnDisposeExplicit" and "OnDisposeImplicit".
        /// </summary>
        protected virtual void OnDisposeRegardless()
        { }


        protected void Dispose(bool isDisposing)
        {
            if (!(IsDisposed || IsDisposing))
            {
                IsDisposing = true;

                if (isDisposing)
                {
                    IsDisposed = true;
                    OnDisposeExplicit();
                }
                else
                    OnDisposeImplicit();

                RaiseDisposed();
            }

            OnDisposeRegardless();
        }


        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        ~ScopeLimitedObject()
        { Dispose(false); }
    }


Tuesday, April 19, 2011

How to properly publish events

A lot of junior developers don't know how to publish events properly. I've provided an API below that can standardize this mundane task in your code. It is the accumulation of knowledge I've gathered from reading books and blogs.

Disclaimer: The code below carries no guarantees-- heck, it may not even work-- so use at your own risk.

    public static class Publish
    {
        public const bool CatchesExceptionsThrownByEventHandlersByDefault = false;


        static void Event(EventHandler eventPublishingDelegate, object sender, EventArgs eventArgs, bool catchExceptionsThrownByEventHandlers)
        {
            var eventPublishingDelegate_referenceHolder = eventPublishingDelegate;

            if (eventPublishingDelegate_referenceHolder != null)
            {
                if (!catchExceptionsThrownByEventHandlers)
                    eventPublishingDelegate_referenceHolder(sender, eventArgs);
                else
                    foreach (EventHandler eventHandler in eventPublishingDelegate_referenceHolder.GetInvocationList().Cast<EventHandler>())
                    {
                        try
                        { eventHandler(sender, eventArgs); }
                        catch
                        { }
                    }
            }
        }


        static void Event<TEventArgs>(EventHandler<TEventArgs> eventPublishingDelegate, object sender, TEventArgs eventArgs, bool catchExceptionsThrownByEventHandlers) where TEventArgs : EventArgs
        {
            var eventPublishingDelegate_referenceHolder = eventPublishingDelegate;

            if (eventPublishingDelegate_referenceHolder != null)
            {
                if (!catchExceptionsThrownByEventHandlers)
                    eventPublishingDelegate_referenceHolder(sender, eventArgs);
                else
                    foreach (EventHandler<TEventArgs> eventHandler in eventPublishingDelegate_referenceHolder.GetInvocationList().Cast<EventHandler<TEventArgs>>())
                    {
                        try
                        { eventHandler(sender, eventArgs); }
                        catch
                        { }
                    }
            }
        }

        static void Event<TEventArgs>(Delegate eventPublishingDelegate, object sender, TEventArgs eventArgs, bool catchExceptionsThrownByEventHandlers)
            where TEventArgs : EventArgs
        {
            if (!(eventPublishingDelegate is Delegate))
                return;

            var eventPublishingDelegate_referenceHolder = eventPublishingDelegate;
            var delegateInvocationArguments = new object[] { sender, eventArgs };

            if (eventPublishingDelegate_referenceHolder != null)
            {
                foreach (Delegate eventHandler in eventPublishingDelegate_referenceHolder.GetInvocationList())
                {
                    if (!catchExceptionsThrownByEventHandlers)
                        eventHandler.DynamicInvoke(delegateInvocationArguments);
                    else
                    {
                        try
                        { eventHandler.DynamicInvoke(delegateInvocationArguments); }
                        catch
                        { }
                    }
                }
            }
        }



        public static void Event(EventHandler eventPublishingDelegate, object sender, EventArgs eventArgs)
        { Event(eventPublishingDelegate, sender, eventArgs, CatchesExceptionsThrownByEventHandlersByDefault); }

        public static void Event<TEventArgs>(EventHandler<TEventArgs> eventPublishingDelegate, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs
        { Event<TEventArgs>(eventPublishingDelegate, sender, eventArgs, CatchesExceptionsThrownByEventHandlersByDefault); }

        public static void Event<TEventArgs>(Delegate eventPublishingDelegate, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs
        { Event<TEventArgs>(eventPublishingDelegate, sender, eventArgs, CatchesExceptionsThrownByEventHandlersByDefault); }


        public static void EventWithExceptions(EventHandler eventPublishingDelegate, object sender, EventArgs eventArgs)
        { Event(eventPublishingDelegate, sender, eventArgs, false); }

        public static void EventWithExceptions<TEventArgs>(EventHandler<TEventArgs> eventPublishingDelegate, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs
        { Event<TEventArgs>(eventPublishingDelegate, sender, eventArgs, false); }

        public static void EventWithExceptions<TEventArgs>(Delegate eventPublishingDelegate, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs
        { Event<TEventArgs>(eventPublishingDelegate, sender, eventArgs, false); }


        public static void EventWithoutExceptions(EventHandler eventPublishingDelegate, object sender, EventArgs eventArgs)
        { Event(eventPublishingDelegate, sender, eventArgs, true); }

        public static void EventWithoutExceptions<TEventArgs>(EventHandler<TEventArgs> eventPublishingDelegate, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs
        { Event<TEventArgs>(eventPublishingDelegate, sender, eventArgs, true); }

        public static void EventWithoutExceptions<TEventArgs>(Delegate eventPublishingDelegate, object sender, TEventArgs eventArgs) where TEventArgs : EventArgs
        { Event<TEventArgs>(eventPublishingDelegate, sender, eventArgs, true); }
    }