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.

This commit is contained in:
Shannon
2013-07-01 14:23:56 +10:00
parent ad766a2544
commit 388f55d7fd
24 changed files with 607 additions and 389 deletions

View File

@@ -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
/// <summary>
/// This method allows for configuration of model mappers
/// </summary>
protected virtual void InitializeModelMappers()
/// <remarks>
/// Model mappers MUST be defined on ApplicationEventHandler instances with the interface IMapperConfiguration.
/// This allows us to search for less types on startup.
/// </remarks>
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<IMapperConfiguration>())
{
m.ConfigureMappings(configuration);
}
});
}
/// <summary>
@@ -135,8 +148,6 @@ namespace Umbraco.Core
{
CanResolveBeforeFrozen = true
};
//add custom types here that are internal
ApplicationEventsResolver.Current.AddType<PublishedContentHelper>();
}
/// <summary>
@@ -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<DatePickerPropertyEditorValueConverter>();
PropertyEditorValueConvertersResolver.Current.AddType<TinyMcePropertyEditorValueConverter>();
PropertyEditorValueConvertersResolver.Current.AddType<YesNoPropertyEditorValueConverter>();
// this is how we'd switch over to DefaultShortStringHelper _and_ still use
// UmbracoSettings UrlReplaceCharacters...

View File

@@ -7,9 +7,9 @@ namespace Umbraco.Core
/// </summary>
internal static class ModelMapperHelper
{
internal static IMappingExpression<TSource, TSource> SelfMap<TSource>()
internal static IMappingExpression<TSource, TSource> SelfMap<TSource>(this IConfiguration config)
{
return Mapper.CreateMap<TSource, TSource>();
return config.CreateMap<TSource, TSource>();
}
}
}

View File

@@ -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
{
/// <summary>
/// This is used to explicitly decorate any ApplicationEventHandler class if there are
/// AutoMapper configurations defined.
/// </summary>
/// <remarks>
/// All automapper configurations are done during startup
/// inside an Automapper Initialize call which is better for performance
/// </remarks>
internal interface IMapperConfiguration : IApplicationEventHandler
{
void ConfigureMappings(IConfiguration config);
}
}

View File

@@ -0,0 +1,15 @@
using AutoMapper;
namespace Umbraco.Core.Models.Mapping
{
/// <summary>
/// Used to declare a mapper configuration
/// </summary>
/// <remarks>
/// Use this class if your mapper configuration isn't also explicitly an ApplicationEventHandler.
/// </remarks>
internal abstract class MapperConfiguration : ApplicationEventHandler, IMapperConfiguration
{
public abstract void ConfigureMappings(IConfiguration config);
}
}

View File

@@ -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; }
}
}

View File

@@ -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();
/// <summary>
/// gets/sets the application.config file path
/// </summary>
/// <remarks>
/// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath
/// </remarks>
internal static string AppConfigFilePath
{
get
{
if (string.IsNullOrWhiteSpace(_appConfig))
{
_appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName);
}
return _appConfig;
}
set { _appConfig = value; }
}
/// <summary>
/// The cache storage for all applications
/// </summary>
internal static IEnumerable<Section> Sections
{
get
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationsCacheKey,
() =>
{
////used for unit tests
//if (_testApps != null)
// return _testApps;
var tmp = new List<Section>();
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<XDocument> callback, bool saveAfterCallback)
{
lock (Locker)
{
var doc = File.Exists(AppConfigFilePath)
? XDocument.Load(AppConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><applications />");
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);
}
}
}
}
/// <summary>
/// Gets the application by its alias.
/// </summary>
/// <param name="appAlias">The application alias.</param>
/// <returns></returns>
public static Section GetByAlias(string appAlias)
{
return Sections.FirstOrDefault(t => t.Alias == appAlias);
}
/// <summary>
/// Creates a new applcation if no application with the specified alias is found.
/// </summary>
/// <param name="name">The application name.</param>
/// <param name="alias">The application alias.</param>
/// <param name="icon">The application icon, which has to be located in umbraco/images/tray folder.</param>
[MethodImpl(MethodImplOptions.Synchronized)]
public static void MakeNew(string name, string alias, string icon)
{
MakeNew(name, alias, icon, Sections.Max(x => x.SortOrder) + 1);
}
/// <summary>
/// Makes the new.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="alias">The alias.</param>
/// <param name="icon">The icon.</param>
/// <param name="sortOrder">The sort order.</param>
[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());
}
}
/// <summary>
/// Deletes the section
/// </summary>
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<Section, EventArgs> Deleted;
private static void OnDeleted(Section app, EventArgs args)
{
if (Deleted != null)
{
Deleted(app, args);
}
}
internal static event TypedEventHandler<Section, EventArgs> New;
private static void OnNew(Section app, EventArgs args)
{
if (New != null)
{
New(app, args);
}
}
}
}

View File

@@ -211,6 +211,8 @@
<Compile Include="Models\IDictionaryTranslation.cs" />
<Compile Include="Models\IFile.cs" />
<Compile Include="Models\ILanguage.cs" />
<Compile Include="Models\Mapping\IMapperConfiguration.cs" />
<Compile Include="Models\Mapping\MapperConfiguration.cs" />
<Compile Include="Models\Membership\UmbracoMembershipUser.cs" />
<Compile Include="Models\ServerRegistration.cs" />
<Compile Include="Models\ITemplate.cs" />
@@ -688,6 +690,8 @@
<Compile Include="Publishing\PublishStatus.cs" />
<Compile Include="Publishing\PublishStatusType.cs" />
<Compile Include="RenderingEngine.cs" />
<Compile Include="Sections\Section.cs" />
<Compile Include="Sections\SectionCollection.cs" />
<Compile Include="Serialization\AbstractSerializationService.cs" />
<Compile Include="Serialization\Formatter.cs" />
<Compile Include="Serialization\IFormatter.cs" />

View File

@@ -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<IMapperConfiguration>();
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<Application>()
// {
// 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()

View File

@@ -51,6 +51,10 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="AutoMapper, Version=2.2.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\AutoMapper.2.2.1\lib\net40\AutoMapper.dll</HintPath>
</Reference>
<Reference Include="Examine, Version=0.1.51.2941, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Examine.0.1.51.2941\lib\Examine.dll</HintPath>
@@ -288,8 +292,8 @@
<Compile Include="Publishing\PublishingStrategyTests.cs" />
<Compile Include="Resolvers\ActionsResolverTests.cs" />
<Compile Include="AsynchronousRollingFileAppenderTests.cs" />
<Compile Include="BusinessLogic\ApplicationTest.cs" />
<Compile Include="BusinessLogic\ApplicationTreeTest.cs" />
<Compile Include="TreesAndSections\SectionTests.cs" />
<Compile Include="TreesAndSections\ApplicationTreeTest.cs" />
<Compile Include="BusinessLogic\BaseTest.cs" />
<Compile Include="CacheRefresherFactoryTests.cs" />
<Compile Include="PublishedContent\DynamicPublishedContentCustomExtensionMethods.cs" />

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AutoMapper" version="2.2.1" targetFramework="net45" />
<package id="Examine" version="0.1.51.2941" targetFramework="net40" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />

View File

@@ -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);

View File

@@ -2558,7 +2558,6 @@
<Folder Include="Media\" />
<Folder Include="Scripts\" />
<Folder Include="Umbraco\assets\" />
<Folder Include="Umbraco\lib\" />
<Folder Include="Umbraco_Client\FolderBrowser\Images\" />
<Folder Include="Umbraco_Client\Tags\images\" />
<Folder Include="UserControls\" />

View File

@@ -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();
}

View File

@@ -58,7 +58,8 @@ namespace Umbraco.Web.Editors
{"umbracoPath", GlobalSettings.Path},
{"contentApiBaseUrl", Url.GetUmbracoApiService<ContentController>("PostSave").TrimEnd("PostSave")},
{"mediaApiBaseUrl", Url.GetUmbracoApiService<MediaController>("GetRootMedia").TrimEnd("GetRootMedia")},
{"treeApplicationApiBaseUrl", Url.GetUmbracoApiService<ApplicationTreeApiController>("GetTreeData").TrimEnd("GetTreeData")},
{"sectionApiBaseUrl", Url.GetUmbracoApiService<SectionController>("GetSections").TrimEnd("GetSections")},
{"treeApplicationApiBaseUrl", Url.GetUmbracoApiService<ApplicationTreeController>("GetApplicationTrees").TrimEnd("GetApplicationTrees")},
{"contentTypeApiBaseUrl", Url.GetUmbracoApiService<ContentTypeController>("GetAllowedChildren").TrimEnd("GetAllowedChildren")},
{"mediaTypeApiBaseUrl", Url.GetUmbracoApiService<MediaTypeApiController>("GetAllowedChildren").TrimEnd("GetAllowedChildren")},
{"authenticationApiBaseUrl", Url.GetUmbracoApiService<AuthenticationController>("PostLogin").TrimEnd("PostLogin")}

View File

@@ -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
{
/// <summary>
/// The API controller used for using the list of sections
/// </summary>
[PluginController("UmbracoApi")]
public class SectionController : UmbracoAuthorizedApiController
{
public IEnumerable<Section> GetSections()
{
return Core.Sections.SectionCollection.Sections.Select(Mapper.Map<Core.Sections.Section, Section>);
}
}
}

View File

@@ -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
{
/// <summary>
/// Represents a section (application) in the back office
/// </summary>
[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; }
}
}

View File

@@ -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<Section, Umbraco.Core.Sections.Section>()
.ReverseMap(); //backwards too!
}
}
}

View File

@@ -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
{
/// <summary>
/// Configures the automapper mappings
/// </summary>
internal static void Configure()
#region Mapper config
public override void ConfigureMappings(IConfiguration config)
{
Mapper.CreateMap<IUser, UserDetail>()
config.CreateMap<IUser, UserDetail>()
.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<IProfile, UserBasic>()
config.CreateMap<IProfile, UserBasic>()
.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<UserBasic>(profile);
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Remove the xml formatter... only support JSON!
/// </summary>
/// <param name="controllerContext"></param>
protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter);
}
/// <summary>
/// Returns the tree nodes for an application
/// </summary>
/// <param name="application"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
[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;
}
/// <summary>
/// Returns the tree data for a specific tree for the children of the id
/// </summary>
/// <param name="treeType"></param>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
[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);
}
/// <summary>
/// Get the node collection for the tree, try loading from new controllers first, then from legacy trees
/// </summary>
/// <param name="configTree"></param>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
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
{
/// <summary>
/// Remove the xml formatter... only support JSON!
/// </summary>
/// <param name="controllerContext"></param>
protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter);
}
/// <summary>
/// Returns the tree nodes for an application
/// </summary>
/// <param name="application"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
[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;
}
///// <summary>
///// Returns the tree data for a specific tree for the children of the id
///// </summary>
///// <param name="treeType"></param>
///// <param name="id"></param>
///// <param name="queryStrings"></param>
///// <returns></returns>
//[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);
}
/// <summary>
/// Get the node collection for the tree, try loading from new controllers first, then from legacy trees
/// </summary>
/// <param name="configTree"></param>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
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);
}
}
}

View File

@@ -298,10 +298,12 @@
<Compile Include="Editors\AuthenticationController.cs" />
<Compile Include="Editors\ContentController.cs" />
<Compile Include="Editors\ContentTypeController.cs" />
<Compile Include="Editors\SectionController.cs" />
<Compile Include="HttpCookieExtensions.cs" />
<Compile Include="Models\ContentEditing\ContentSaveAction.cs" />
<Compile Include="Models\ContentEditing\ContentTypeBasic.cs" />
<Compile Include="Models\ContentEditing\MediaItemDisplay.cs" />
<Compile Include="Models\ContentEditing\Section.cs" />
<Compile Include="Models\ContentEditing\Tab.cs" />
<Compile Include="Models\ContentEditing\TabbedContentItem.cs" />
<Compile Include="Models\ContentEditing\UserBasic.cs" />
@@ -311,6 +313,7 @@
<Compile Include="FormDataCollectionExtensions.cs" />
<Compile Include="Models\Mapping\ContentTypeModelMapper.cs" />
<Compile Include="Models\Mapping\MediaModelMapper.cs" />
<Compile Include="Models\Mapping\SectionModelMapper.cs" />
<Compile Include="Models\Mapping\UserModelMapper.cs" />
<Compile Include="PropertyEditors\ContentPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\FileUploadPropertyEditor.cs" />
@@ -331,7 +334,7 @@
<Compile Include="Trees\TreeNode.cs" />
<Compile Include="Trees\TreeNodeCollection.cs" />
<Compile Include="Trees\TreeQueryStringParameters.cs" />
<Compile Include="Trees\ApplicationTreeApiController.cs" />
<Compile Include="Trees\ApplicationTreeController.cs" />
<Compile Include="Editors\BackOfficeController.cs" />
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
<Compile Include="Security\Providers\UsersMembershipProvider.cs" />

View File

@@ -112,23 +112,12 @@ namespace Umbraco.Web
ProfilerResolver.Current.SetProfiler(new WebProfiler());
}
/// <summary>
/// Configure the model mappers
/// </summary>
protected override void InitializeModelMappers()
{
base.InitializeModelMappers();
UserModelMapper.Configure();
}
/// <summary>
/// Adds custom types to the ApplicationEventsResolver
/// </summary>
protected override void InitializeApplicationEventsResolver()
{
base.InitializeApplicationEventsResolver();
ApplicationEventsResolver.Current.AddType<CacheHelperExtensions.CacheHelperApplicationEventListener>();
ApplicationEventsResolver.Current.AddType<LegacyScheduledTasks>();
//We need to remove these types because we've obsoleted them and we don't want them executing:
ApplicationEventsResolver.Current.RemoveType<global::umbraco.LibraryCacheRefresher>();
}
@@ -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<TinyMcePropertyEditorValueConverter>();
PropertyEditorValueConvertersResolver.Current.AddType<RteMacroRenderingPropertyEditorValueConverter>();
//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<TinyMcePropertyEditorValueConverter>();
PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches(
new PublishedCache.XmlPublishedCache.PublishedContentCache(),

View File

@@ -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
/// <summary>
/// Class for handling all registered applications in Umbraco.
/// </summary>
[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();
/// <summary>
/// gets/sets the application.config file path
/// </summary>
/// <remarks>
/// The setter is generally only going to be used in unit tests, otherwise it will attempt to resolve it using the IOHelper.MapPath
/// </remarks>
internal static string AppConfigFilePath
{
get
{
if (string.IsNullOrWhiteSpace(_appConfig))
{
_appConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + AppConfigFileName);
}
return _appConfig;
}
set { _appConfig = value; }
}
/// <summary>
/// The cache storage for all applications
/// </summary>
internal static List<Application> Apps
{
get
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationsCacheKey,
() =>
{
////used for unit tests
//if (_testApps != null)
// return _testApps;
var tmp = new List<Application>();
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;
}
});
}
}
/// <summary>
/// Gets the SQL helper.
/// </summary>
/// <value>The SQL helper.</value>
[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
/// <param name="name">The application name.</param>
/// <param name="alias">The application alias.</param>
/// <param name="icon">The application icon, which has to be located in umbraco/images/tray folder.</param>
[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);
}
/// <summary>
@@ -210,25 +141,9 @@ namespace umbraco.BusinessLogic
/// <param name="alias">The alias.</param>
/// <param name="icon">The icon.</param>
/// <param name="sortOrder">The sort order.</param>
[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);
}
/// <summary>
@@ -238,7 +153,8 @@ namespace umbraco.BusinessLogic
/// <returns></returns>
public static Application getByAlias(string appAlias)
{
return Apps.Find(t => t.alias == appAlias);
return Mapper.Map<Section, Application>(
SectionCollection.GetByAlias(appAlias));
}
/// <summary>
@@ -246,23 +162,7 @@ namespace umbraco.BusinessLogic
/// </summary>
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<Application, Section>(this));
}
/// <summary>
@@ -271,7 +171,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a Application Array</returns>
public static List<Application> getAll()
{
return Apps;
return SectionCollection.Sections.Select(Mapper.Map<Section, Application>).ToList();
}
/// <summary>
@@ -283,49 +183,6 @@ namespace umbraco.BusinessLogic
ApplicationStartupHandler.RegisterHandlers();
}
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
{
lock (Locker)
{
var doc = File.Exists(AppConfigFilePath)
? XDocument.Load(AppConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><applications />");
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<Application, EventArgs> Deleted;
private static void OnDeleted(Application app, EventArgs args)
{
if (Deleted != null)
{
Deleted(app, args);
}
}
internal static event TypedEventHandler<Application, EventArgs> New;
private static void OnNew(Application app, EventArgs args)
{
if (New != null)
{
New(app, args);
}
}
}
}

View File

@@ -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<ApplicationAttribute>(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<ApplicationAttribute>(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<Umbraco.Core.Sections.Section, Application>()
.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<Application, Umbraco.Core.Sections.Section>()
.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));
}
}
}

View File

@@ -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
/// <summary>
/// A startup handler for dealing with trees
/// </summary>
public class ApplicationTreeRegistrar : ApplicationEventHandler
public class ApplicationTreeRegistrar : ApplicationEventHandler, IMapperConfiguration
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
ConfigureMappings();
ScanTrees();
}
/// <summary>
/// Configures automapper model mappings
/// </summary>
private static void ConfigureMappings()
public void ConfigureMappings(IConfiguration config)
{
Mapper.CreateMap<Umbraco.Core.Trees.ApplicationTree, ApplicationTree>()
config.CreateMap<Umbraco.Core.Trees.ApplicationTree, ApplicationTree>()
.ReverseMap(); //two way
}