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 }