From ca6356170c5dc26674b1341dc5d05e910f16bfe1 Mon Sep 17 00:00:00 2001 From: "Morten@Thinkpad-X220" Date: Fri, 5 Oct 2012 12:59:59 -0200 Subject: [PATCH] Adds Repository Resolver and backing config section implementation U4-988 Will need a bit of refactoring because of the way UnitOfWork is currently implemented. --- .../RepositoryConfigurationSection.cs | 117 +++++++++++++ .../RepositoryMappingCollection.cs | 61 +++++++ .../RepositoryMappingConstants.cs | 12 ++ .../Repositories/RepositoryMappingElement.cs | 49 ++++++ .../Repositories/RepositorySettings.cs | 14 ++ .../Persistence/RepositoryResolver.cs | 164 ++++++++++++++++++ .../Persistence/UnitOfWork/IUnitOfWork.cs | 6 +- .../UnitOfWork/IUnitOfWorkProvider.cs | 4 + src/Umbraco.Core/Umbraco.Core.csproj | 6 + 9 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Configuration/Repositories/RepositoryConfigurationSection.cs create mode 100644 src/Umbraco.Core/Configuration/Repositories/RepositoryMappingCollection.cs create mode 100644 src/Umbraco.Core/Configuration/Repositories/RepositoryMappingConstants.cs create mode 100644 src/Umbraco.Core/Configuration/Repositories/RepositoryMappingElement.cs create mode 100644 src/Umbraco.Core/Configuration/Repositories/RepositorySettings.cs create mode 100644 src/Umbraco.Core/Persistence/RepositoryResolver.cs diff --git a/src/Umbraco.Core/Configuration/Repositories/RepositoryConfigurationSection.cs b/src/Umbraco.Core/Configuration/Repositories/RepositoryConfigurationSection.cs new file mode 100644 index 0000000000..1902e87ae7 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Repositories/RepositoryConfigurationSection.cs @@ -0,0 +1,117 @@ +using System; +using System.Configuration; + +namespace Umbraco.Core.Configuration.Repositories +{ + internal class RepositoryConfigurationSection : ConfigurationSection + { + [ConfigurationProperty("repositories", IsDefaultCollection = false)] + [ConfigurationCollection(typeof(RepositoryCollection), + AddItemName = "add", + ClearItemsName = "clear", + RemoveItemName = "remove")] + public RepositoryCollection Repositories + { + get + { + return (RepositoryCollection)base["repositories"]; + } + } + } + + internal class RepositoryCollection : ConfigurationElementCollection + { + public RepositoryCollection() + { + Console.WriteLine("RepositoryCollection Constructor"); + } + + public RepositoryElement this[int index] + { + get { return (RepositoryElement)BaseGet(index); } + set + { + if (BaseGet(index) != null) + { + BaseRemoveAt(index); + } + BaseAdd(index, value); + } + } + + public void Add(RepositoryElement repositoryElement) + { + BaseAdd(repositoryElement); + } + + public void Clear() + { + BaseClear(); + } + + protected override ConfigurationElement CreateNewElement() + { + return new RepositoryElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((RepositoryElement)element).Name; + } + + public void Remove(RepositoryElement repositoryElement) + { + BaseRemove(repositoryElement.Name); + } + + public void RemoveAt(int index) + { + BaseRemoveAt(index); + } + + public void Remove(string name) + { + BaseRemove(name); + } + } + + internal class RepositoryElement : ConfigurationElement + { + private const string NameKey = "name"; + private const string ModelTypeKey = "modelType"; + private const string RepositoryTypeKey = "repositoryType"; + + public RepositoryElement() { } + + public RepositoryElement(string name, string modelType, string repositoryType) + { + Name = name; + ModelType = modelType; + RepositoryType = repositoryType; + } + + [ConfigurationProperty(NameKey, IsRequired = true, IsKey = true)] + public string Name + { + get { return (string)this[NameKey]; } + + set { this[NameKey] = value; } + } + + [ConfigurationProperty(ModelTypeKey, IsRequired = true, IsKey = false)] + public string ModelType + { + get { return (string)this[ModelTypeKey]; } + + set { this[ModelTypeKey] = value; } + } + + [ConfigurationProperty(RepositoryTypeKey, IsRequired = true, IsKey = false)] + public string RepositoryType + { + get { return (string)this[RepositoryTypeKey]; } + + set { this[RepositoryTypeKey] = value; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingCollection.cs b/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingCollection.cs new file mode 100644 index 0000000000..e612444fa1 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingCollection.cs @@ -0,0 +1,61 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration.Repositories +{ + internal sealed class RepositoryMappingCollection : ConfigurationElementCollection + { + protected override ConfigurationElement CreateNewElement() + { + return new RepositoryMappingElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((RepositoryMappingElement)element).InterfaceShortTypeName; + } + + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + protected override string ElementName + { + get { return RepositoryMappingConstants.ConfigurationElementName; } + } + + public RepositoryMappingElement this[int index] + { + get { return (RepositoryMappingElement)this.BaseGet(index); } + set + { + if (this.BaseGet(index) != null) + { + this.BaseRemoveAt(index); + } + this.BaseAdd(index, value); + } + } + + public new RepositoryMappingElement this[string interfaceShortTypeName] + { + get { return (RepositoryMappingElement)this.BaseGet(interfaceShortTypeName); } + } + + public bool ContainsKey(string keyName) + { + bool result = false; + object[] keys = this.BaseGetAllKeys(); + foreach (object key in keys) + { + if ((string)key == keyName) + { + result = true; + break; + + } + } + return result; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingConstants.cs b/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingConstants.cs new file mode 100644 index 0000000000..f9b602e1f3 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingConstants.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Configuration.Repositories +{ + internal static class RepositoryMappingConstants + { + internal const string CacheProviderFullTypeNameAttributeName = "cacheProviderFullTypeName"; + internal const string ConfigurationPropertyName = "repositoryMappings"; + internal const string ConfigurationElementName = "repositoryMapping"; + internal const string InterfaceShortTypeNameAttributeName = "interfaceShortTypeName"; + internal const string RepositoryFullTypeNameAttributeName = "repositoryFullTypeName"; + internal const string RepositoryMappingsConfigurationSectionName = "repositoryMappingsConfiguration"; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingElement.cs b/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingElement.cs new file mode 100644 index 0000000000..c126ec501e --- /dev/null +++ b/src/Umbraco.Core/Configuration/Repositories/RepositoryMappingElement.cs @@ -0,0 +1,49 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration.Repositories +{ + internal sealed class RepositoryMappingElement : ConfigurationElement + { + [ConfigurationProperty(RepositoryMappingConstants.InterfaceShortTypeNameAttributeName, + IsKey = true, IsRequired = true)] + public string InterfaceShortTypeName + { + get + { + return (string)this[RepositoryMappingConstants.InterfaceShortTypeNameAttributeName]; + } + set + { + this[RepositoryMappingConstants.InterfaceShortTypeNameAttributeName] = value; + } + } + + [ConfigurationProperty(RepositoryMappingConstants.RepositoryFullTypeNameAttributeName, + IsRequired = true)] + public string RepositoryFullTypeName + { + get + { + return (string)this[RepositoryMappingConstants.RepositoryFullTypeNameAttributeName]; + } + set + { + this[RepositoryMappingConstants.RepositoryFullTypeNameAttributeName] = value; + } + } + + [ConfigurationProperty(RepositoryMappingConstants.CacheProviderFullTypeNameAttributeName, + IsRequired = true)] + public string CacheProviderFullTypeName + { + get + { + return (string)this[RepositoryMappingConstants.CacheProviderFullTypeNameAttributeName]; + } + set + { + this[RepositoryMappingConstants.CacheProviderFullTypeNameAttributeName] = value; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/Repositories/RepositorySettings.cs b/src/Umbraco.Core/Configuration/Repositories/RepositorySettings.cs new file mode 100644 index 0000000000..5f97d993b6 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Repositories/RepositorySettings.cs @@ -0,0 +1,14 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration.Repositories +{ + internal class RepositorySettings : ConfigurationSection + { + [ConfigurationProperty(RepositoryMappingConstants.ConfigurationPropertyName, + IsDefaultCollection = true)] + public RepositoryMappingCollection RepositoryMappings + { + get { return (RepositoryMappingCollection)base[RepositoryMappingConstants.ConfigurationPropertyName]; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryResolver.cs b/src/Umbraco.Core/Persistence/RepositoryResolver.cs new file mode 100644 index 0000000000..57d7c15984 --- /dev/null +++ b/src/Umbraco.Core/Persistence/RepositoryResolver.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using Umbraco.Core.Configuration.Repositories; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence +{ + internal class RepositoryResolver + { + private static readonly ConcurrentDictionary Repositories = new ConcurrentDictionary(); + + //GetRepository based on Repository Interface and Model Entity. + //Check if Dictionary contains the interface name of the Repository, ea. IContentRepository + //- If it does return the repository from the dictionary and set the new UnitOfWork object + //Otherwise look for the full type for the repository in config + //- If type exists check depedencies, create new object, add it to dictionary and return it + //Otherwise look for an entity type in the config + //- If type exists check dependencies, create new object, add it to dictionary and return it + //If we have come this far the correct types wasn't found and we throw an exception + internal static TRepository ResolveByType(IUnitOfWork unitOfWork) + where TRepository : class, IRepository + where TEntity : class, IAggregateRoot + { + //Initialize the provider's default value + TRepository repository = default(TRepository); + + string interfaceShortName = typeof(TRepository).Name; + string entityTypeName = typeof(TEntity).Name; + + //Check if the repository has already been created and is in the cache + if (Repositories.ContainsKey(interfaceShortName)) + { + repository = (TRepository)Repositories[interfaceShortName]; + if (unitOfWork != null && repository.GetType().IsSubclassOf(typeof(IRepository))) + { + repository.SetUnitOfWork(unitOfWork); + } + return repository; + } + + var settings = + (RepositorySettings) + ConfigurationManager.GetSection(RepositoryMappingConstants.RepositoryMappingsConfigurationSectionName); + + Type repositoryType = null; + + //Check if a valid interfaceShortName was passed in + if (settings.RepositoryMappings.ContainsKey(interfaceShortName)) + { + repositoryType = Type.GetType(settings.RepositoryMappings[interfaceShortName].RepositoryFullTypeName); + } + else + { + foreach (RepositoryMappingElement element in settings.RepositoryMappings) + { + if (element.InterfaceShortTypeName.Contains(entityTypeName)) + { + repositoryType = Type.GetType(settings.RepositoryMappings[element.InterfaceShortTypeName].RepositoryFullTypeName); + break; + } + } + } + + //If the repository type is null we should stop and throw an exception + if (repositoryType == null) + { + throw new Exception(string.Format("No repository matching the Repository interface '{0}' or Entity type '{1}' could be resolved", + interfaceShortName, entityTypeName)); + } + + //Resolve the repository with its constructor dependencies + repository = Resolve(repositoryType, unitOfWork) as TRepository; + + //Add the new repository instance to the cache + Repositories.AddOrUpdate(interfaceShortName, repository, (x, y) => repository); + + return repository; + } + + //Recursive create and dependency check + private static object Resolve(Type repositoryType, IUnitOfWork unitOfWork) + { + var constructor = repositoryType.GetConstructors().SingleOrDefault(); + if (constructor == null) + { + throw new Exception(string.Format("No public constructor was found on {0}", repositoryType.FullName)); + } + + var constructorArgs = new List(); + var settings = (RepositorySettings)ConfigurationManager.GetSection(RepositoryMappingConstants.RepositoryMappingsConfigurationSectionName); + + var parameters = constructor.GetParameters(); + foreach (var parameter in parameters) + { + if (parameter.ParameterType.Name.Equals("IUnitOfWork")) + { + constructorArgs.Add(unitOfWork); + } + else if (Repositories.ContainsKey(parameter.ParameterType.Name)) + { + var repo = Repositories[parameter.ParameterType.Name]; + constructorArgs.Add(repo); + } + else + { + if (settings.RepositoryMappings.ContainsKey(parameter.ParameterType.Name)) + { + //Get the Type of the repository and resolve the object + var repoType = Type.GetType(settings.RepositoryMappings[parameter.ParameterType.Name].RepositoryFullTypeName); + var repo = Resolve(repoType, unitOfWork); + + // Add the new repository instance to the cache + Repositories.AddOrUpdate(parameter.ParameterType.Name, repo, (x, y) => repo); + + //Add the new repository to the constructor + constructorArgs.Add(repo); + } + else + { + throw new Exception("Cannot create the Repository. There was one or more invalid repositoryMapping configuration settings."); + } + } + } + + var repositoryObj = Activator.CreateInstance(repositoryType, constructorArgs.ToArray()); + return repositoryObj; + } + + /// + /// Register all repositories by interating the configuration and adding the repositories to the internal cache (Dictionary) + /// + internal static void RegisterRepositories() + { + var settings = + (RepositorySettings) + ConfigurationManager.GetSection(RepositoryMappingConstants.RepositoryMappingsConfigurationSectionName); + + foreach (RepositoryMappingElement element in settings.RepositoryMappings) + { + if (Repositories.ContainsKey(element.InterfaceShortTypeName)) continue; + + var repositoryType = Type.GetType(settings.RepositoryMappings[element.InterfaceShortTypeName].RepositoryFullTypeName); + var repository = Resolve(repositoryType, null); + + //Add the new repository instance to the cache + Repositories.AddOrUpdate(element.InterfaceShortTypeName, repository, (x, y) => repository); + } + } + + /// + /// Returns the number of repositories that has been registered + /// + /// + internal static int RegisteredRepositories() + { + return Repositories.Count; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs index 326c561b65..801cbd08af 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs @@ -2,9 +2,13 @@ namespace Umbraco.Core.Persistence.UnitOfWork { + /// + /// Defines a Unit Of Work + /// + /// public interface IUnitOfWork : IDisposable { void Commit(); - T Storage { get; } + T Storage { get; }//TODO This won't work! Need to change it } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs index d9bcd25cdf..443b5421aa 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs @@ -1,5 +1,9 @@ namespace Umbraco.Core.Persistence.UnitOfWork { + /// + /// Defines a Unit of Work Provider + /// + /// public interface IUnitOfWorkProvider { IUnitOfWork GetUnitOfWork(); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 32978895b0..c4a83197a6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -58,6 +58,11 @@ + + + + + @@ -98,6 +103,7 @@ +