From 388f55d7fd0cddf796f2dabe5a7c6959565b11fa Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2013 14:23:56 +1000 Subject: [PATCH] Moved application logic into Core which are now 'Sections'. Have proxied all calls from the legacy application to sections. Streamlined how automapper configs are registered (much better now). Updated some unit tests to use the new classes instead of the legacy ones. Created the sections controller to return the sections from the back office. Changed the TypeFinder to search all types not just public ones, changed the boot managers to not have to explicitly modify the resolvers with internal types because now internal types are automatically found. --- src/Umbraco.Core/CoreBootManager.cs | 23 +- src/Umbraco.Core/ModelMapperHelper.cs | 4 +- .../Models/Mapping/IMapperConfiguration.cs | 22 ++ .../Models/Mapping/MapperConfiguration.cs | 15 ++ src/Umbraco.Core/Sections/Section.cs | 23 ++ .../Sections/SectionCollection.cs | 202 ++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 4 + src/Umbraco.Tests/BusinessLogic/BaseTest.cs | 20 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 8 +- src/Umbraco.Tests/packages.config | 1 + .../src/common/resources/section.resource.js | 34 +++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 - .../Cache/CacheRefresherEventHandler.cs | 9 +- .../Editors/BackOfficeController.cs | 3 +- src/Umbraco.Web/Editors/SectionController.cs | 21 ++ .../Models/ContentEditing/Section.cs | 28 ++ .../Models/Mapping/SectionModelMapper.cs | 15 ++ .../Models/Mapping/UserModelMapper.cs | 19 +- ...roller.cs => ApplicationTreeController.cs} | 246 +++++++++--------- src/Umbraco.Web/Umbraco.Web.csproj | 5 +- src/Umbraco.Web/WebBootManager.cs | 19 +- src/umbraco.businesslogic/Application.cs | 169 +----------- .../ApplicationRegistrar.cs | 97 +++---- .../ApplicationTreeRegistrar.cs | 8 +- 24 files changed, 607 insertions(+), 389 deletions(-) create mode 100644 src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs create mode 100644 src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs create mode 100644 src/Umbraco.Core/Sections/Section.cs create mode 100644 src/Umbraco.Core/Sections/SectionCollection.cs create mode 100644 src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js create mode 100644 src/Umbraco.Web/Editors/SectionController.cs create mode 100644 src/Umbraco.Web/Models/ContentEditing/Section.cs create mode 100644 src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs rename src/Umbraco.Web/Trees/{ApplicationTreeApiController.cs => ApplicationTreeController.cs} (78%) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index d4aa83e342..8f962ae7c7 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; +using AutoMapper; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models.Mapping; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; @@ -100,9 +103,19 @@ namespace Umbraco.Core /// /// This method allows for configuration of model mappers /// - protected virtual void InitializeModelMappers() + /// + /// Model mappers MUST be defined on ApplicationEventHandler instances with the interface IMapperConfiguration. + /// This allows us to search for less types on startup. + /// + protected void InitializeModelMappers() { - //TODO: There will most likely be AutoMapper configs to put in here, we know they exist in web for now so we'll leave this here for future use + Mapper.Initialize(configuration => + { + foreach (var m in ApplicationEventsResolver.Current.ApplicationEventHandlers.OfType()) + { + m.ConfigureMappings(configuration); + } + }); } /// @@ -135,8 +148,6 @@ namespace Umbraco.Core { CanResolveBeforeFrozen = true }; - //add custom types here that are internal - ApplicationEventsResolver.Current.AddType(); } /// @@ -280,10 +291,6 @@ namespace Umbraco.Core PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( PluginManager.Current.ResolvePropertyEditorValueConverters()); - //add the internal ones, these are not public currently so need to add them manually - PropertyEditorValueConvertersResolver.Current.AddType(); - PropertyEditorValueConvertersResolver.Current.AddType(); - PropertyEditorValueConvertersResolver.Current.AddType(); // this is how we'd switch over to DefaultShortStringHelper _and_ still use // UmbracoSettings UrlReplaceCharacters... diff --git a/src/Umbraco.Core/ModelMapperHelper.cs b/src/Umbraco.Core/ModelMapperHelper.cs index efd843a059..584ce5b108 100644 --- a/src/Umbraco.Core/ModelMapperHelper.cs +++ b/src/Umbraco.Core/ModelMapperHelper.cs @@ -7,9 +7,9 @@ namespace Umbraco.Core /// internal static class ModelMapperHelper { - internal static IMappingExpression SelfMap() + internal static IMappingExpression SelfMap(this IConfiguration config) { - return Mapper.CreateMap(); + return config.CreateMap(); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs new file mode 100644 index 0000000000..0ca76417de --- /dev/null +++ b/src/Umbraco.Core/Models/Mapping/IMapperConfiguration.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; + +namespace Umbraco.Core.Models.Mapping +{ + /// + /// This is used to explicitly decorate any ApplicationEventHandler class if there are + /// AutoMapper configurations defined. + /// + /// + /// All automapper configurations are done during startup + /// inside an Automapper Initialize call which is better for performance + /// + internal interface IMapperConfiguration : IApplicationEventHandler + { + void ConfigureMappings(IConfiguration config); + } +} diff --git a/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs new file mode 100644 index 0000000000..8bde82d13f --- /dev/null +++ b/src/Umbraco.Core/Models/Mapping/MapperConfiguration.cs @@ -0,0 +1,15 @@ +using AutoMapper; + +namespace Umbraco.Core.Models.Mapping +{ + /// + /// Used to declare a mapper configuration + /// + /// + /// Use this class if your mapper configuration isn't also explicitly an ApplicationEventHandler. + /// + internal abstract class MapperConfiguration : ApplicationEventHandler, IMapperConfiguration + { + public abstract void ConfigureMappings(IConfiguration config); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sections/Section.cs b/src/Umbraco.Core/Sections/Section.cs new file mode 100644 index 0000000000..b39cbe009d --- /dev/null +++ b/src/Umbraco.Core/Sections/Section.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Core.Sections +{ + public class Section + { + public Section(string name, string @alias, string icon, int sortOrder) + { + Name = name; + Alias = alias; + Icon = icon; + SortOrder = sortOrder; + } + + public Section() + { + + } + + public string Name { get; set; } + public string Alias { get; set; } + public string Icon { get; set; } + public int SortOrder { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Sections/SectionCollection.cs b/src/Umbraco.Core/Sections/SectionCollection.cs new file mode 100644 index 0000000000..8b3f51c49f --- /dev/null +++ b/src/Umbraco.Core/Sections/SectionCollection.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Trees; + +namespace Umbraco.Core.Sections +{ + public class SectionCollection + { + internal const string AppConfigFileName = "applications.config"; + private static string _appConfig; + private static readonly object Locker = new object(); + + /// + /// gets/sets the application.config file path + /// + /// + /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath + /// + internal static string AppConfigFilePath + { + get + { + if (string.IsNullOrWhiteSpace(_appConfig)) + { + _appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName); + } + return _appConfig; + } + set { _appConfig = value; } + } + + /// + /// The cache storage for all applications + /// + internal static IEnumerable
Sections + { + get + { + return ApplicationContext.Current.ApplicationCache.GetCacheItem( + CacheKeys.ApplicationsCacheKey, + () => + { + ////used for unit tests + //if (_testApps != null) + // return _testApps; + + var tmp = new List
(); + + LoadXml(doc => + { + foreach (var addElement in doc.Root.Elements("add").OrderBy(x => + { + var sortOrderAttr = x.Attribute("sortOrder"); + return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; + })) + { + var sortOrderAttr = addElement.Attribute("sortOrder"); + tmp.Add(new Section(addElement.Attribute("name").Value, + addElement.Attribute("alias").Value, + addElement.Attribute("icon").Value, + sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0)); + } + + }, false); + return tmp; + }); + } + } + + internal static void LoadXml(Action callback, bool saveAfterCallback) + { + lock (Locker) + { + var doc = File.Exists(AppConfigFilePath) + ? XDocument.Load(AppConfigFilePath) + : XDocument.Parse(""); + + if (doc.Root != null) + { + callback.Invoke(doc); + + if (saveAfterCallback) + { + //ensure the folder is created! + Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath)); + + doc.Save(AppConfigFilePath); + + //remove the cache so it gets re-read ... SD: I'm leaving this here even though it + // is taken care of by events as well, I think unit tests may rely on it being cleared here. + ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + } + } + } + } + + /// + /// Gets the application by its alias. + /// + /// The application alias. + /// + public static Section GetByAlias(string appAlias) + { + return Sections.FirstOrDefault(t => t.Alias == appAlias); + } + + /// + /// Creates a new applcation if no application with the specified alias is found. + /// + /// The application name. + /// The application alias. + /// The application icon, which has to be located in umbraco/images/tray folder. + [MethodImpl(MethodImplOptions.Synchronized)] + public static void MakeNew(string name, string alias, string icon) + { + MakeNew(name, alias, icon, Sections.Max(x => x.SortOrder) + 1); + } + + /// + /// Makes the new. + /// + /// The name. + /// The alias. + /// The icon. + /// The sort order. + [MethodImpl(MethodImplOptions.Synchronized)] + public static void MakeNew(string name, string alias, string icon, int sortOrder) + { + if (Sections.All(x => x.Alias != alias)) + { + LoadXml(doc => + { + doc.Root.Add(new XElement("add", + new XAttribute("alias", alias), + new XAttribute("name", name), + new XAttribute("icon", icon), + new XAttribute("sortOrder", sortOrder))); + }, true); + + //raise event + OnNew(new Section(name, alias, icon, sortOrder), new EventArgs()); + } + } + + /// + /// Deletes the section + /// + public static void DeleteSection(Section section) + { + lock (Locker) + { + //delete the assigned applications + ApplicationContext.Current.DatabaseContext.Database.Execute( + "delete from umbracoUser2App where app = @appAlias", + new { appAlias = section.Alias }); + + //delete the assigned trees + var trees = ApplicationTreeCollection.GetApplicationTree(section.Alias); + foreach (var t in trees) + { + ApplicationTreeCollection.DeleteTree(t); + } + + LoadXml(doc => + { + doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias).Remove(); + }, true); + + //raise event + OnDeleted(section, new EventArgs()); + } + } + + internal static event TypedEventHandler Deleted; + private static void OnDeleted(Section app, EventArgs args) + { + if (Deleted != null) + { + Deleted(app, args); + } + } + + internal static event TypedEventHandler New; + private static void OnNew(Section app, EventArgs args) + { + if (New != null) + { + New(app, args); + } + } + + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3581d15a00..e3de933b23 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -211,6 +211,8 @@ + + @@ -688,6 +690,8 @@ + + diff --git a/src/Umbraco.Tests/BusinessLogic/BaseTest.cs b/src/Umbraco.Tests/BusinessLogic/BaseTest.cs index c6efefc474..1f3173b926 100644 --- a/src/Umbraco.Tests/BusinessLogic/BaseTest.cs +++ b/src/Umbraco.Tests/BusinessLogic/BaseTest.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; using System.Configuration; +using AutoMapper; using NUnit.Framework; using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Models.Mapping; +using Umbraco.Core.Sections; using Umbraco.Tests.TestHelpers; using umbraco.BusinessLogic; using umbraco.DataLayer; @@ -33,12 +36,25 @@ namespace Umbraco.Tests.BusinessLogic public void Initialize() { ApplicationContext.Current = new ApplicationContext(false){IsReady = true}; + InitializeMappers(); InitializeDatabase(); InitializeApps(); InitializeAppConfigFile(); InitializeTreeConfigFile(); } + private void InitializeMappers() + { + Mapper.Initialize(configuration => + { + var mappers = PluginManager.Current.FindAndCreateInstances(); + foreach (var mapper in mappers) + { + mapper.ConfigureMappings(configuration); + } + }); + } + private void ClearDatabase() { var databaseSettings = ConfigurationManager.ConnectionStrings[Core.Configuration.GlobalSettings.UmbracoConnectionName]; @@ -71,7 +87,7 @@ namespace Umbraco.Tests.BusinessLogic private void InitializeApps() { - Application.MakeNew("content", "content", "file", 0); + SectionCollection.MakeNew("content", "content", "file", 0); //Application.SetTestApps(new List() // { // new Application(Constants.Applications.Content, "content", "content", 0) @@ -80,7 +96,7 @@ namespace Umbraco.Tests.BusinessLogic private void InitializeAppConfigFile() { - Application.AppConfigFilePath = IOHelper.MapPath(SystemDirectories.Config + "/" + Application.AppConfigFileName, false); + SectionCollection.AppConfigFilePath = IOHelper.MapPath(SystemDirectories.Config + "/" + SectionCollection.AppConfigFileName, false); } private void InitializeTreeConfigFile() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d9565a9b18..423a8c2410 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -51,6 +51,10 @@ false + + False + ..\packages\AutoMapper.2.2.1\lib\net40\AutoMapper.dll + False ..\packages\Examine.0.1.51.2941\lib\Examine.dll @@ -288,8 +292,8 @@ - - + + diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 4f69076931..07ac8de73d 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js new file mode 100644 index 0000000000..d4f1d19e38 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/section.resource.js @@ -0,0 +1,34 @@ +/** + * @ngdoc factory + * @name umbraco.resources.section + * @description Loads in data for section + **/ +function sectionResource($q, $http) { + + /** internal method to get the tree app url */ + function getSectionsUrl(section) { + return Umbraco.Sys.ServerVariables.sectionApiBaseUrl + "GetSections"; + } + + //the factory object returned + return { + /** Loads in the data to display the section list */ + getSections: function (options) { + + var deferred = $q.defer(); + + //go and get the tree data + $http.get(getSectionsUrl()). + success(function (data, status, headers, config) { + deferred.resolve(data); + }). + error(function (data, status, headers, config) { + deferred.reject('Failed to retreive data for sections'); + }); + + return deferred.promise; + } + }; +} + +angular.module('umbraco.resources').factory('treeResource', treeResource); \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 6ec0aa4b4f..8999407607 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2558,7 +2558,6 @@ - diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 37be376381..cbfb321f55 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Sections; using Umbraco.Core.Services; using Umbraco.Core.Trees; using umbraco; @@ -30,8 +31,8 @@ namespace Umbraco.Web.Cache ApplicationTreeCollection.New += ApplicationTreeNew; //bind to application events - Application.Deleted += ApplicationDeleted; - Application.New += ApplicationNew; + SectionCollection.Deleted += ApplicationDeleted; + SectionCollection.New += ApplicationNew; //bind to user type events UserType.Deleted += UserTypeDeleted; @@ -145,12 +146,12 @@ namespace Umbraco.Web.Cache #endregion #region Application event handlers - static void ApplicationNew(Application sender, System.EventArgs e) + static void ApplicationNew(Section sender, System.EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } - static void ApplicationDeleted(Application sender, System.EventArgs e) + static void ApplicationDeleted(Section sender, System.EventArgs e) { DistributedCache.Instance.RefreshAllApplicationCache(); } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index ec1486988f..7d4968e429 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -58,7 +58,8 @@ namespace Umbraco.Web.Editors {"umbracoPath", GlobalSettings.Path}, {"contentApiBaseUrl", Url.GetUmbracoApiService("PostSave").TrimEnd("PostSave")}, {"mediaApiBaseUrl", Url.GetUmbracoApiService("GetRootMedia").TrimEnd("GetRootMedia")}, - {"treeApplicationApiBaseUrl", Url.GetUmbracoApiService("GetTreeData").TrimEnd("GetTreeData")}, + {"sectionApiBaseUrl", Url.GetUmbracoApiService("GetSections").TrimEnd("GetSections")}, + {"treeApplicationApiBaseUrl", Url.GetUmbracoApiService("GetApplicationTrees").TrimEnd("GetApplicationTrees")}, {"contentTypeApiBaseUrl", Url.GetUmbracoApiService("GetAllowedChildren").TrimEnd("GetAllowedChildren")}, {"mediaTypeApiBaseUrl", Url.GetUmbracoApiService("GetAllowedChildren").TrimEnd("GetAllowedChildren")}, {"authenticationApiBaseUrl", Url.GetUmbracoApiService("PostLogin").TrimEnd("PostLogin")} diff --git a/src/Umbraco.Web/Editors/SectionController.cs b/src/Umbraco.Web/Editors/SectionController.cs new file mode 100644 index 0000000000..f80525c227 --- /dev/null +++ b/src/Umbraco.Web/Editors/SectionController.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using AutoMapper; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using System.Linq; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for using the list of sections + /// + [PluginController("UmbracoApi")] + public class SectionController : UmbracoAuthorizedApiController + { + public IEnumerable
GetSections() + { + return Core.Sections.SectionCollection.Sections.Select(Mapper.Map); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/Section.cs b/src/Umbraco.Web/Models/ContentEditing/Section.cs new file mode 100644 index 0000000000..4ffc7a8b3a --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/Section.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Web.Models.ContentEditing +{ + + /// + /// Represents a section (application) in the back office + /// + [DataContract(Name = "section", Namespace = "")] + public class Section + { + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "cssclass")] + public string CssClass { get; set; } + + [DataMember(Name = "alias")] + public string Alias { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs b/src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs new file mode 100644 index 0000000000..7453e7487b --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/SectionModelMapper.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Umbraco.Core.Models.Mapping; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class SectionModelMapper : MapperConfiguration + { + public override void ConfigureMappings(IConfiguration config) + { + config.CreateMap() + .ReverseMap(); //backwards too! + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index bada4d4e12..9ea7f082f4 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -1,26 +1,27 @@ using System; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class UserModelMapper + internal class UserModelMapper : MapperConfiguration { - /// - /// Configures the automapper mappings - /// - internal static void Configure() + + #region Mapper config + public override void ConfigureMappings(IConfiguration config) { - Mapper.CreateMap() + config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) .ForMember( detail => detail.EmailHash, opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().ToMd5())); - Mapper.CreateMap() + config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id))); - } + } + #endregion private static int GetIntId(object id) { @@ -41,6 +42,6 @@ namespace Umbraco.Web.Models.Mapping public UserBasic ToUserBasic(IProfile profile) { return Mapper.Map(profile); - } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ApplicationTreeApiController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs similarity index 78% rename from src/Umbraco.Web/Trees/ApplicationTreeApiController.cs rename to src/Umbraco.Web/Trees/ApplicationTreeController.cs index 005ec172a8..c8dda9b6e0 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeApiController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -1,123 +1,123 @@ -using System; -using System.Linq; -using System.Management.Instrumentation; -using System.Net.Http.Formatting; -using System.Web.Mvc; -using Umbraco.Core; -using Umbraco.Core.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Web.Trees -{ - - [PluginController("UmbracoTrees")] - public class ApplicationTreeApiController : UmbracoAuthorizedApiController - { - - /// - /// Remove the xml formatter... only support JSON! - /// - /// - protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) - { - base.Initialize(controllerContext); - controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter); - } - - /// - /// Returns the tree nodes for an application - /// - /// - /// - /// - [HttpQueryStringFilter("queryStrings")] - public TreeNodeCollection GetApplicationTrees(string application, FormDataCollection queryStrings) - { - if (application == null) throw new ArgumentNullException("application"); - - //find all tree definitions that have the current application alias - var appTrees = ApplicationTreeCollection.GetApplicationTree(application).Where(x => x.Initialize).ToArray(); - if (appTrees.Count() == 1) - { - //return the nodes for the one tree assigned - return GetNodeCollection(appTrees.Single(), "-1", queryStrings); - } - - var collection = new TreeNodeCollection(); - foreach (var tree in appTrees) - { - //return the root nodes for each tree in the app - var rootNode = GetRoot(tree, queryStrings); - collection.Add(rootNode); - } - return collection; - } - - /// - /// Returns the tree data for a specific tree for the children of the id - /// - /// - /// - /// - /// - [HttpQueryStringFilter("queryStrings")] - public TreeNodeCollection GetTreeData(string treeType, string id, FormDataCollection queryStrings) - { - if (treeType == null) throw new ArgumentNullException("treeType"); - - //get the configured tree - var foundConfigTree = ApplicationTreeCollection.GetByAlias(treeType); - if (foundConfigTree == null) - throw new InstanceNotFoundException("Could not find tree of type " + treeType + " in the trees.config"); - - return GetNodeCollection(foundConfigTree, id, queryStrings); - } - - private TreeNode GetRoot(ApplicationTree configTree, FormDataCollection queryStrings) - { - if (configTree == null) throw new ArgumentNullException("configTree"); - var byControllerAttempt = configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext, Request); - if (byControllerAttempt.Success) - { - return byControllerAttempt.Result; - } - var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url); - if (legacyAttempt.Success) - { - return legacyAttempt.Result; - } - - throw new ApplicationException("Could not get root node for tree type " + configTree.Alias); - } - - /// - /// Get the node collection for the tree, try loading from new controllers first, then from legacy trees - /// - /// - /// - /// - /// - private TreeNodeCollection GetNodeCollection(ApplicationTree configTree, string id, FormDataCollection queryStrings) - { - if (configTree == null) throw new ArgumentNullException("configTree"); - var byControllerAttempt = configTree.TryLoadFromControllerTree(id, queryStrings, ControllerContext, Request); - if (byControllerAttempt.Success) - { - return byControllerAttempt.Result; - } - var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url); - if (legacyAttempt.Success) - { - return legacyAttempt.Result; - } - - throw new ApplicationException("Could not render a tree for type " + configTree.Alias); - } - - - } - - -} +using System; +using System.Linq; +using System.Management.Instrumentation; +using System.Net.Http.Formatting; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Core.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Trees +{ + + [PluginController("UmbracoTrees")] + public class ApplicationTreeController : UmbracoAuthorizedApiController + { + + /// + /// Remove the xml formatter... only support JSON! + /// + /// + protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) + { + base.Initialize(controllerContext); + controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter); + } + + /// + /// Returns the tree nodes for an application + /// + /// + /// + /// + [HttpQueryStringFilter("queryStrings")] + public TreeNodeCollection GetApplicationTrees(string application, FormDataCollection queryStrings) + { + if (application == null) throw new ArgumentNullException("application"); + + //find all tree definitions that have the current application alias + var appTrees = ApplicationTreeCollection.GetApplicationTree(application).Where(x => x.Initialize).ToArray(); + if (appTrees.Count() == 1) + { + //return the nodes for the one tree assigned + return GetNodeCollection(appTrees.Single(), "-1", queryStrings); + } + + var collection = new TreeNodeCollection(); + foreach (var tree in appTrees) + { + //return the root nodes for each tree in the app + var rootNode = GetRoot(tree, queryStrings); + collection.Add(rootNode); + } + return collection; + } + + ///// + ///// Returns the tree data for a specific tree for the children of the id + ///// + ///// + ///// + ///// + ///// + //[HttpQueryStringFilter("queryStrings")] + //public TreeNodeCollection GetTreeData(string treeType, string id, FormDataCollection queryStrings) + //{ + // if (treeType == null) throw new ArgumentNullException("treeType"); + + // //get the configured tree + // var foundConfigTree = ApplicationTreeCollection.GetByAlias(treeType); + // if (foundConfigTree == null) + // throw new InstanceNotFoundException("Could not find tree of type " + treeType + " in the trees.config"); + + // return GetNodeCollection(foundConfigTree, id, queryStrings); + //} + + private TreeNode GetRoot(ApplicationTree configTree, FormDataCollection queryStrings) + { + if (configTree == null) throw new ArgumentNullException("configTree"); + var byControllerAttempt = configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext, Request); + if (byControllerAttempt.Success) + { + return byControllerAttempt.Result; + } + var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url); + if (legacyAttempt.Success) + { + return legacyAttempt.Result; + } + + throw new ApplicationException("Could not get root node for tree type " + configTree.Alias); + } + + /// + /// Get the node collection for the tree, try loading from new controllers first, then from legacy trees + /// + /// + /// + /// + /// + private TreeNodeCollection GetNodeCollection(ApplicationTree configTree, string id, FormDataCollection queryStrings) + { + if (configTree == null) throw new ArgumentNullException("configTree"); + var byControllerAttempt = configTree.TryLoadFromControllerTree(id, queryStrings, ControllerContext, Request); + if (byControllerAttempt.Success) + { + return byControllerAttempt.Result; + } + var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url); + if (legacyAttempt.Success) + { + return legacyAttempt.Result; + } + + throw new ApplicationException("Could not render a tree for type " + configTree.Alias); + } + + + } + + +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 16140a2f6b..e481ad5a80 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -298,10 +298,12 @@ + + @@ -311,6 +313,7 @@ + @@ -331,7 +334,7 @@ - + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index a166b96702..66b5dda7a6 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -112,23 +112,12 @@ namespace Umbraco.Web ProfilerResolver.Current.SetProfiler(new WebProfiler()); } - /// - /// Configure the model mappers - /// - protected override void InitializeModelMappers() - { - base.InitializeModelMappers(); - UserModelMapper.Configure(); - } - /// /// Adds custom types to the ApplicationEventsResolver /// protected override void InitializeApplicationEventsResolver() { base.InitializeApplicationEventsResolver(); - ApplicationEventsResolver.Current.AddType(); - ApplicationEventsResolver.Current.AddType(); //We need to remove these types because we've obsoleted them and we don't want them executing: ApplicationEventsResolver.Current.RemoveType(); } @@ -291,10 +280,10 @@ namespace Umbraco.Web UmbracoApiControllerResolver.Current = new UmbracoApiControllerResolver( PluginManager.Current.ResolveUmbracoApiControllers()); - //the base creates the PropertyEditorValueConvertersResolver but we want to modify it in the web app and replace - //the TinyMcePropertyEditorValueConverter with the RteMacroRenderingPropertyEditorValueConverter - PropertyEditorValueConvertersResolver.Current.RemoveType(); - PropertyEditorValueConvertersResolver.Current.AddType(); + //the base creates the PropertyEditorValueConvertersResolver but we want to modify it in the web app and remove + //the TinyMcePropertyEditorValueConverter since when the web app is loaded the RteMacroRenderingPropertyEditorValueConverter + //is found and we'll use that instead. + PropertyEditorValueConvertersResolver.Current.RemoveType(); PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches( new PublishedCache.XmlPublishedCache.PublishedContentCache(), diff --git a/src/umbraco.businesslogic/Application.cs b/src/umbraco.businesslogic/Application.cs index efa7131196..d21238b8cb 100644 --- a/src/umbraco.businesslogic/Application.cs +++ b/src/umbraco.businesslogic/Application.cs @@ -5,11 +5,13 @@ using System.IO; using System.Linq; using System.Web; using System.Xml.Linq; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Sections; using umbraco.DataLayer; using System.Runtime.CompilerServices; using umbraco.businesslogic; @@ -19,88 +21,18 @@ namespace umbraco.BusinessLogic /// /// Class for handling all registered applications in Umbraco. /// + [Obsolete("Use Umbraco.Core.Sections.SectionCollection instead")] public class Application { private static ISqlHelper _sqlHelper; - - internal const string AppConfigFileName = "applications.config"; - private static string _appConfig; - private static readonly object Locker = new object(); - - /// - /// gets/sets the application.config file path - /// - /// - /// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath - /// - internal static string AppConfigFilePath - { - get - { - if (string.IsNullOrWhiteSpace(_appConfig)) - { - _appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName); - } - return _appConfig; - } - set { _appConfig = value; } - } - - /// - /// The cache storage for all applications - /// - internal static List Apps - { - get - { - return ApplicationContext.Current.ApplicationCache.GetCacheItem( - CacheKeys.ApplicationsCacheKey, - () => - { - ////used for unit tests - //if (_testApps != null) - // return _testApps; - - var tmp = new List(); - - try - { - LoadXml(doc => - { - foreach (var addElement in doc.Root.Elements("add").OrderBy(x => - { - var sortOrderAttr = x.Attribute("sortOrder"); - return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; - })) - { - var sortOrderAttr = addElement.Attribute("sortOrder"); - tmp.Add(new Application(addElement.Attribute("name").Value, - addElement.Attribute("alias").Value, - addElement.Attribute("icon").Value, - sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0)); - } - - }, false); - return tmp; - } - catch - { - //this is a bit of a hack that just ensures the application doesn't crash when the - //installer is run and there is no database or connection string defined. - //the reason this method may get called during the installation is that the - //SqlHelper of this class is shared amongst everything "Application" wide. - - //TODO: Perhaps we should log something here?? - return null; - } - }); - } - } + + /// /// Gets the SQL helper. /// /// The SQL helper. + [Obsolete("Do not use SqlHelper anymore, if database querying needs to be done use the DatabaseContext instead")] public static ISqlHelper SqlHelper { get @@ -197,10 +129,9 @@ namespace umbraco.BusinessLogic /// The application name. /// The application alias. /// The application icon, which has to be located in umbraco/images/tray folder. - [MethodImpl(MethodImplOptions.Synchronized)] public static void MakeNew(string name, string alias, string icon) { - MakeNew(name, alias, icon, Apps.Max(x => x.sortOrder) + 1); + SectionCollection.MakeNew(name, alias, icon); } /// @@ -210,25 +141,9 @@ namespace umbraco.BusinessLogic /// The alias. /// The icon. /// The sort order. - [MethodImpl(MethodImplOptions.Synchronized)] public static void MakeNew(string name, string alias, string icon, int sortOrder) { - var exist = getAll().Any(x => x.alias == alias); - - if (!exist) - { - LoadXml(doc => - { - doc.Root.Add(new XElement("add", - new XAttribute("alias", alias), - new XAttribute("name", name), - new XAttribute("icon", icon), - new XAttribute("sortOrder", sortOrder))); - }, true); - - //raise event - OnNew(new Application(name, alias, icon, sortOrder), new EventArgs()); - } + SectionCollection.MakeNew(name, alias, icon, sortOrder); } /// @@ -238,7 +153,8 @@ namespace umbraco.BusinessLogic /// public static Application getByAlias(string appAlias) { - return Apps.Find(t => t.alias == appAlias); + return Mapper.Map( + SectionCollection.GetByAlias(appAlias)); } /// @@ -246,23 +162,7 @@ namespace umbraco.BusinessLogic /// public void Delete() { - //delete the assigned applications - SqlHelper.ExecuteNonQuery("delete from umbracoUser2App where app = @appAlias", SqlHelper.CreateParameter("@appAlias", this.alias)); - - //delete the assigned trees - var trees = ApplicationTree.getApplicationTree(this.alias); - foreach (var t in trees) - { - t.Delete(); - } - - LoadXml(doc => - { - doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == this.alias).Remove(); - }, true); - - //raise event - OnDeleted(this, new EventArgs()); + SectionCollection.DeleteSection(Mapper.Map(this)); } /// @@ -271,7 +171,7 @@ namespace umbraco.BusinessLogic /// Returns a Application Array public static List getAll() { - return Apps; + return SectionCollection.Sections.Select(Mapper.Map).ToList(); } /// @@ -283,49 +183,6 @@ namespace umbraco.BusinessLogic ApplicationStartupHandler.RegisterHandlers(); } - internal static void LoadXml(Action callback, bool saveAfterCallback) - { - lock (Locker) - { - var doc = File.Exists(AppConfigFilePath) - ? XDocument.Load(AppConfigFilePath) - : XDocument.Parse(""); - - if (doc.Root != null) - { - callback.Invoke(doc); - - if (saveAfterCallback) - { - //ensure the folder is created! - Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath)); - - doc.Save(AppConfigFilePath); - - //remove the cache so it gets re-read ... SD: I'm leaving this here even though it - // is taken care of by events as well, I think unit tests may rely on it being cleared here. - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); - } - } - } - } - - internal static event TypedEventHandler Deleted; - private static void OnDeleted(Application app, EventArgs args) - { - if (Deleted != null) - { - Deleted(app, args); - } - } - - internal static event TypedEventHandler New; - private static void OnNew(Application app, EventArgs args) - { - if (New != null) - { - New(app, args); - } - } + } } diff --git a/src/umbraco.businesslogic/ApplicationRegistrar.cs b/src/umbraco.businesslogic/ApplicationRegistrar.cs index 8eb695eebb..6cb962bab2 100644 --- a/src/umbraco.businesslogic/ApplicationRegistrar.cs +++ b/src/umbraco.businesslogic/ApplicationRegistrar.cs @@ -3,8 +3,11 @@ using System.Configuration; using System.Data.SqlClient; using System.Linq; using System.Xml.Linq; +using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Models.Mapping; using Umbraco.Core.Persistence; +using Umbraco.Core.Sections; using umbraco.BusinessLogic.Utils; using umbraco.DataLayer; using umbraco.businesslogic; @@ -12,75 +15,47 @@ using umbraco.interfaces; namespace umbraco.BusinessLogic { - public class ApplicationRegistrar : IApplicationStartupHandler + public class ApplicationRegistrar : ApplicationEventHandler, IMapperConfiguration { - private ISqlHelper _sqlHelper; - protected ISqlHelper SqlHelper + protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - get + // Load all Applications by attribute and add them to the XML config + var types = PluginManager.Current.ResolveApplications(); + + //since applications don't populate their metadata from the attribute and because it is an interface, + //we need to interrogate the attributes for the data. Would be better to have a base class that contains + //metadata populated by the attribute. Oh well i guess. + var attrs = types.Select(x => x.GetCustomAttributes(false).Single()) + .Where(x => SectionCollection.GetByAlias(x.Alias) == null) + .ToArray(); + + var allAliases = SectionCollection.Sections.Select(x => x.Alias).Concat(attrs.Select(x => x.Alias)); + + SectionCollection.LoadXml(doc => { - if (_sqlHelper == null) + foreach (var attr in attrs) { - try - { - var databaseSettings = ConfigurationManager.ConnectionStrings[Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName]; - _sqlHelper = DataLayerHelper.CreateSqlHelper(databaseSettings.ConnectionString, false); - } - catch { } + doc.Root.Add(new XElement("add", + new XAttribute("alias", attr.Alias), + new XAttribute("name", attr.Name), + new XAttribute("icon", attr.Icon), + new XAttribute("sortOrder", attr.SortOrder))); } - return _sqlHelper; - } + }, true); } - public ApplicationRegistrar() + public void ConfigureMappings(IConfiguration config) { - - //don't do anything if the application is not configured! - if (!ApplicationContext.Current.IsConfigured) - return; - - // Load all Applications by attribute and add them to the XML config - var types = PluginManager.Current.ResolveApplications(); - - //since applications don't populate their metadata from the attribute and because it is an interface, - //we need to interrogate the attributes for the data. Would be better to have a base class that contains - //metadata populated by the attribute. Oh well i guess. - var attrs = types.Select(x => x.GetCustomAttributes(false).Single()) - .Where(x => Application.getByAlias(x.Alias) == null); - - var allAliases = Application.getAll().Select(x => x.alias).Concat(attrs.Select(x => x.Alias)); - var inString = "'" + string.Join("','", allAliases) + "'"; - - Application.LoadXml(doc => - { - foreach (var attr in attrs) - { - doc.Root.Add(new XElement("add", - new XAttribute("alias", attr.Alias), - new XAttribute("name", attr.Name), - new XAttribute("icon", attr.Icon), - new XAttribute("sortOrder", attr.SortOrder))); - } - - var db = ApplicationContext.Current.DatabaseContext.Database; - var exist = db.TableExist("umbracoApp"); - if (exist) - { - var dbApps = SqlHelper.ExecuteReader("SELECT * FROM umbracoApp WHERE appAlias NOT IN (" + inString + ")"); - while (dbApps.Read()) - { - doc.Root.Add(new XElement("add", - new XAttribute("alias", dbApps.GetString("appAlias")), - new XAttribute("name", dbApps.GetString("appName")), - new XAttribute("icon", dbApps.GetString("appIcon")), - new XAttribute("sortOrder", dbApps.GetByte("sortOrder")))); - } - } - - }, true); - - //TODO Shouldn't this be enabled and then delete the whole table? - //SqlHelper.ExecuteNonQuery("DELETE FROM umbracoApp"); + config.CreateMap() + .ForMember(x => x.alias, expression => expression.MapFrom(x => x.Alias)) + .ForMember(x => x.icon, expression => expression.MapFrom(x => x.Icon)) + .ForMember(x => x.name, expression => expression.MapFrom(x => x.Name)) + .ForMember(x => x.sortOrder, expression => expression.MapFrom(x => x.SortOrder)).ReverseMap(); + config.CreateMap() + .ForMember(x => x.Alias, expression => expression.MapFrom(x => x.alias)) + .ForMember(x => x.Icon, expression => expression.MapFrom(x => x.icon)) + .ForMember(x => x.Name, expression => expression.MapFrom(x => x.name)) + .ForMember(x => x.SortOrder, expression => expression.MapFrom(x => x.sortOrder)); } } } \ No newline at end of file diff --git a/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs b/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs index 341ba12d11..c9ef52410e 100644 --- a/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs +++ b/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Xml.Linq; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using umbraco.businesslogic; @@ -17,21 +18,20 @@ namespace umbraco.BusinessLogic /// /// A startup handler for dealing with trees /// - public class ApplicationTreeRegistrar : ApplicationEventHandler + public class ApplicationTreeRegistrar : ApplicationEventHandler, IMapperConfiguration { protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - ConfigureMappings(); ScanTrees(); } /// /// Configures automapper model mappings /// - private static void ConfigureMappings() + public void ConfigureMappings(IConfiguration config) { - Mapper.CreateMap() + config.CreateMap() .ReverseMap(); //two way }