WIP - moving tree and section service implementations to Web because they are web based and they need web based objects, there's no sense in coying all of the non-web objects to Core. Now we need to use IoC for the ServiceContext

This commit is contained in:
Shannon
2016-02-17 16:45:26 +01:00
parent 6e7f3b5500
commit 4fa1ce42dc
17 changed files with 920 additions and 851 deletions

View File

@@ -1,8 +0,0 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Interface for created applications in the umbraco backoffice
/// </summary>
public interface IApplication
{ }
}

View File

@@ -5,18 +5,6 @@ namespace Umbraco.Core.Services
{
public interface IApplicationTreeService
{
/// <summary>
/// Initializes the service with any trees found in plugins
/// </summary>
/// <param name="allAvailableTrees">
/// A collection of all available tree found in assemblies in the application
/// </param>
/// <remarks>
/// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first
/// access is made to resolve the tree collection
/// </remarks>
void Intitialize(IEnumerable<ApplicationTree> allAvailableTrees);
/// <summary>
/// Creates a new application tree.
/// </summary>
@@ -68,4 +56,82 @@ namespace Umbraco.Core.Services
/// <returns>Returns a ApplicationTree Array</returns>
IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias, bool onlyInitialized);
}
/// <summary>
/// Purely used to allow a service context to create the default services
/// </summary>
internal class EmptyApplicationTreeService : IApplicationTreeService
{
/// <summary>
/// Creates a new application tree.
/// </summary>
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
/// <param name="sortOrder">The sort order.</param>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="alias">The alias.</param>
/// <param name="title">The title.</param>
/// <param name="iconClosed">The icon closed.</param>
/// <param name="iconOpened">The icon opened.</param>
/// <param name="type">The type.</param>
public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Saves this instance.
/// </summary>
public void SaveTree(ApplicationTree tree)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Deletes this instance.
/// </summary>
public void DeleteTree(ApplicationTree tree)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets an ApplicationTree by it's tree alias.
/// </summary>
/// <param name="treeAlias">The tree alias.</param>
/// <returns>An ApplicationTree instance</returns>
public ApplicationTree GetByAlias(string treeAlias)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets all applicationTrees registered in umbraco from the umbracoAppTree table..
/// </summary>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetAll()
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets the application tree for the applcation with the specified alias
/// </summary>
/// <param name="applicationAlias">The application alias.</param>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets the application tree for the applcation with the specified alias
/// </summary>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="onlyInitialized"></param>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias, bool onlyInitialized)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -46,4 +46,69 @@ namespace Umbraco.Core.Services
/// </summary>
void DeleteSection(Section section);
}
/// <summary>
/// Purely used to allow a service context to create the default services
/// </summary>
internal class EmptySectionService : ISectionService
{
/// <summary>
/// The cache storage for all applications
/// </summary>
public IEnumerable<Section> GetSections()
{
throw new System.NotImplementedException();
}
/// <summary>
/// Get the user's allowed sections
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public IEnumerable<Section> GetAllowedSections(int userId)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Gets the application by its alias.
/// </summary>
/// <param name="appAlias">The application alias.</param>
/// <returns></returns>
public Section GetByAlias(string appAlias)
{
throw new System.NotImplementedException();
}
/// <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>
public void MakeNew(string name, string alias, string icon)
{
throw new System.NotImplementedException();
}
/// <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>
public void MakeNew(string name, string alias, string icon, int sortOrder)
{
throw new System.NotImplementedException();
}
/// <summary>
/// Deletes the section
/// </summary>
public void DeleteSection(Section section)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -279,10 +279,10 @@ namespace Umbraco.Core.Services
_relationService = new Lazy<IRelationService>(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, _entityService.Value));
if (_treeService == null)
_treeService = new Lazy<IApplicationTreeService>(() => new ApplicationTreeService(logger, cache));
_treeService = new Lazy<IApplicationTreeService>(() => new EmptyApplicationTreeService());
if (_sectionService == null)
_sectionService = new Lazy<ISectionService>(() => new SectionService(_userService.Value, _treeService.Value, provider, cache));
_sectionService = new Lazy<ISectionService>(() => new EmptySectionService());
if (_macroService == null)
_macroService = new Lazy<IMacroService>(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory));

View File

@@ -342,13 +342,11 @@
<Compile Include="Logging\OwinLogger.cs" />
<Compile Include="Logging\OwinLoggerFactory.cs" />
<Compile Include="Logging\ParallelForwardingAppender.cs" />
<Compile Include="Mandate.cs" />
<Compile Include="Manifest\GridEditorConverter.cs" />
<Compile Include="Models\ApplicationAttribute.cs" />
<Compile Include="Models\ApplicationDefinitions.cs" />
<Compile Include="Models\AuditItem.cs" />
<Compile Include="Models\AuditType.cs" />
<Compile Include="Models\EntityContainer.cs" />
<Compile Include="Models\IApplication.cs" />
<Compile Include="ObjectResolution\ContainerLazyManyObjectsResolver.cs" />
<Compile Include="ObjectResolution\ContainerManyObjectsResolver.cs" />
<Compile Include="Models\Identity\IdentityModelMappings.cs" />
@@ -1095,7 +1093,6 @@
<Compile Include="IO\FileSystemExtensions.cs" />
<Compile Include="IO\MediaFileSystem.cs" />
<Compile Include="Macros\MacroTagParser.cs" />
<Compile Include="Mandate.cs" />
<Compile Include="Models\Content.cs" />
<Compile Include="Models\ContentStatus.cs" />
<Compile Include="Models\ContentType.cs" />
@@ -1262,7 +1259,6 @@
<Compile Include="Services\NotificationService.cs" />
<Compile Include="Packaging\ConflictingPackageData.cs" />
<Compile Include="Services\RelationService.cs" />
<Compile Include="Services\SectionService.cs" />
<Compile Include="Services\ServerRegistrationService.cs" />
<Compile Include="Services\PackagingService.cs" />
<Compile Include="Services\ServiceContext.cs" />
@@ -1313,7 +1309,6 @@
<Compile Include="Strings\StringAliasCaseTypeExtensions.cs" />
<Compile Include="Strings\DefaultShortStringHelper.cs" />
<Compile Include="Models\ApplicationTree.cs" />
<Compile Include="Services\ApplicationTreeService.cs" />
<Compile Include="TypeExtensions.cs" />
<Compile Include="ReadLock.cs" />
<Compile Include="TypeFinder.cs" />

View File

@@ -20,6 +20,8 @@ using Umbraco.Core.IO;
using umbraco.DataLayer;
using umbraco.interfaces;
using umbraco.uicontrols;
using Umbraco.Web.Models.Trees;
using Umbraco.Web.Trees;
namespace Umbraco.Tests.Plugins
{
@@ -80,7 +82,7 @@ namespace Umbraco.Tests.Plugins
[Test]
public void Find_Classes_With_Attribute()
{
var typesFound = TypeFinder.FindClassesWithAttribute<Umbraco.Web.Trees.TreeAttribute>(_assemblies);
var typesFound = TypeFinder.FindClassesWithAttribute<TreeAttribute>(_assemblies);
//TODO: Fix this with the correct count
Assert.AreEqual(1, typesFound.Count());
}

View File

@@ -4,6 +4,7 @@ using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using System;
using System.Linq;
using Umbraco.Web.Services;
namespace Umbraco.Tests.TreesAndSections
{

View File

@@ -6,6 +6,7 @@ using umbraco.BusinessLogic;
using System;
using System.Linq;
using Umbraco.Core;
using Umbraco.Web.Services;
namespace Umbraco.Tests.TreesAndSections
{

View File

@@ -12,6 +12,7 @@ using System.Linq;
using umbraco.cms.businesslogic.web;
using Umbraco.Core.Logging;
using Umbraco.Core.Publishing;
using Umbraco.Web.Services;
using Content = Umbraco.Core.Models.Content;
using ApplicationTree = Umbraco.Core.Models.ApplicationTree;
using DeleteEventArgs = umbraco.cms.businesslogic.DeleteEventArgs;

View File

@@ -1,6 +1,6 @@
using System;
namespace Umbraco.Core.Models
namespace Umbraco.Web.Models.Trees
{
/// <summary>
/// Identifies an application tree

View File

@@ -1,4 +1,7 @@
namespace Umbraco.Core.Models
using Umbraco.Core;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models.Trees
{
// The application definitions are intended as a means to auto populate
// the application.config. On app startup, Umbraco will look for any

View File

@@ -9,6 +9,7 @@ using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
using umbraco;
using umbraco.interfaces;
using Umbraco.Web.Models.Trees;
namespace Umbraco.Web
{
@@ -28,7 +29,7 @@ namespace Umbraco.Web
return resolver.ResolveTypesWithAttribute<TreeController, TreeAttribute>();
}
internal static IEnumerable<Type> ResolveSurfaceControllers(this PluginManager resolver)
internal static IEnumerable<Type> ResolveSurfaceControllers(this PluginManager resolver)
{
return resolver.ResolveTypes<SurfaceController>();
}

View File

@@ -21,12 +21,6 @@ using System.Security;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ce9d3539-299e-40d3-b605-42ac423e24fa")]
//This is required so that Medium trust works and this is because of this class:
// umbraco.presentation.templateControls.ItemDesigner since this class cannot inherit from
// the System.Web.UI.Design.ControlDesigner in partial trust (or something along those lines)
// if we remove this class then we won't need to do this.
[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1)]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
[assembly: InternalsVisibleTo("umbraco.MacroEngines")]
[assembly: InternalsVisibleTo("Umbraco.Web.UI")]

View File

@@ -1,400 +1,423 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Cache;
using File = System.IO.File;
namespace Umbraco.Core.Services
{
internal class ApplicationTreeService : IApplicationTreeService
{
private readonly ILogger _logger;
private readonly CacheHelper _cache;
private IEnumerable<ApplicationTree> _allAvailableTrees;
private volatile bool _isInitialized = false;
internal const string TreeConfigFileName = "trees.config";
private static string _treeConfig;
private static readonly object Locker = new object();
public ApplicationTreeService(ILogger logger, CacheHelper cache)
{
_logger = logger;
_cache = cache;
}
/// <summary>
/// gets/sets the trees.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 TreeConfigFilePath
{
get
{
if (string.IsNullOrWhiteSpace(_treeConfig))
{
_treeConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + TreeConfigFileName);
}
return _treeConfig;
}
set { _treeConfig = value; }
}
/// <summary>
/// The main entry point to get application trees
/// </summary>
/// <remarks>
/// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins
/// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned.
/// </remarks>
private List<ApplicationTree> GetAppTrees()
{
return _cache.RuntimeCache.GetCacheItem<List<ApplicationTree>>(
CacheKeys.ApplicationTreeCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
//On first access we need to do some initialization
if (_isInitialized == false)
{
lock (Locker)
{
if (_isInitialized == false)
{
//now we can check the non-volatile flag
if (_allAvailableTrees != null)
{
var hasChanges = false;
LoadXml(doc =>
{
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy
// based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableTrees
.Where(x => list.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
if (hasChanges == false) return false;
//add the unregistered ones to the list and re-save the file if any changes were found
var count = 0;
foreach (var tree in unregistered)
{
doc.Root.Add(new XElement("add",
new XAttribute("initialize", tree.Initialize),
new XAttribute("sortOrder", tree.SortOrder),
new XAttribute("alias", tree.Alias),
new XAttribute("application", tree.ApplicationAlias),
new XAttribute("title", tree.Title),
new XAttribute("iconClosed", tree.IconClosed),
new XAttribute("iconOpen", tree.IconOpened),
new XAttribute("type", tree.Type)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
_isInitialized = true;
}
}
}
return list;
}, new TimeSpan(0, 10, 0));
}
/// <summary>
/// Initializes the service with any trees found in plugins
/// </summary>
/// <param name="allAvailableTrees">
/// A collection of all available tree found in assemblies in the application
/// </param>
/// <remarks>
/// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first
/// access is made to resolve the tree collection
/// </remarks>
public void Intitialize(IEnumerable<ApplicationTree> allAvailableTrees)
{
_allAvailableTrees = allAvailableTrees;
}
/// <summary>
/// Creates a new application tree.
/// </summary>
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
/// <param name="sortOrder">The sort order.</param>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="alias">The alias.</param>
/// <param name="title">The title.</param>
/// <param name="iconClosed">The icon closed.</param>
/// <param name="iconOpened">The icon opened.</param>
/// <param name="type">The type.</param>
public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type)
{
LoadXml(doc =>
{
var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == alias && x.Attribute("application").Value == applicationAlias);
if (el == null)
{
doc.Root.Add(new XElement("add",
new XAttribute("initialize", initialize),
new XAttribute("sortOrder", sortOrder),
new XAttribute("alias", alias),
new XAttribute("application", applicationAlias),
new XAttribute("title", title),
new XAttribute("iconClosed", iconClosed),
new XAttribute("iconOpen", iconOpened),
new XAttribute("type", type)));
}
return true;
}, true);
OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs());
}
/// <summary>
/// Saves this instance.
/// </summary>
public void SaveTree(ApplicationTree tree)
{
LoadXml(doc =>
{
var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == tree.Alias && x.Attribute("application").Value == tree.ApplicationAlias);
if (el != null)
{
el.RemoveAttributes();
el.Add(new XAttribute("initialize", tree.Initialize));
el.Add(new XAttribute("sortOrder", tree.SortOrder));
el.Add(new XAttribute("alias", tree.Alias));
el.Add(new XAttribute("application", tree.ApplicationAlias));
el.Add(new XAttribute("title", tree.Title));
el.Add(new XAttribute("iconClosed", tree.IconClosed));
el.Add(new XAttribute("iconOpen", tree.IconOpened));
el.Add(new XAttribute("type", tree.Type));
}
return true;
}, true);
OnUpdated(tree, new EventArgs());
}
/// <summary>
/// Deletes this instance.
/// </summary>
public void DeleteTree(ApplicationTree tree)
{
LoadXml(doc =>
{
doc.Root.Elements("add")
.Where(x => x.Attribute("application") != null
&& x.Attribute("application").Value == tree.ApplicationAlias
&& x.Attribute("alias") != null && x.Attribute("alias").Value == tree.Alias).Remove();
return true;
}, true);
OnDeleted(tree, new EventArgs());
}
/// <summary>
/// Gets an ApplicationTree by it's tree alias.
/// </summary>
/// <param name="treeAlias">The tree alias.</param>
/// <returns>An ApplicationTree instance</returns>
public ApplicationTree GetByAlias(string treeAlias)
{
return GetAppTrees().Find(t => (t.Alias == treeAlias));
}
/// <summary>
/// Gets all applicationTrees registered in umbraco from the umbracoAppTree table..
/// </summary>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetAll()
{
return GetAppTrees().OrderBy(x => x.SortOrder);
}
/// <summary>
/// Gets the application tree for the applcation with the specified alias
/// </summary>
/// <param name="applicationAlias">The application alias.</param>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias)
{
return GetApplicationTrees(applicationAlias, false);
}
/// <summary>
/// Gets the application tree for the applcation with the specified alias
/// </summary>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="onlyInitialized"></param>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias, bool onlyInitialized)
{
var list = GetAppTrees().FindAll(
t =>
{
if (onlyInitialized)
return (t.ApplicationAlias == applicationAlias && t.Initialize);
return (t.ApplicationAlias == applicationAlias);
}
);
return list.OrderBy(x => x.SortOrder).ToArray();
}
/// <summary>
/// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the
/// callback with the xml document and saves the structure back to disk if saveAfterCallback is true.
/// </summary>
/// <param name="callback"></param>
/// <param name="saveAfterCallbackIfChanges"></param>
internal void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanges)
{
lock (Locker)
{
var doc = File.Exists(TreeConfigFilePath)
? XDocument.Load(TreeConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><trees />");
if (doc.Root != null)
{
var hasChanges = callback.Invoke(doc);
if (saveAfterCallbackIfChanges && hasChanges
//Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process
// in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and
// more about the IO trying to save the XML doc, but it doesn't hurt to check.
&& doc.Root != null && doc.Root.Elements().Any())
{
//ensures the folder exists
Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath));
//saves it
doc.Save(TreeConfigFilePath);
//remove the cache now that it has changed 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.
_cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey);
}
}
}
}
private List<ApplicationTree> ReadFromXmlAndSort()
{
var list = new List<ApplicationTree>();
//read in the xml file containing trees and convert them all to ApplicationTree instances
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 applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
var clrType = Type.GetType(type);
if (clrType == null)
{
_logger.Warn<ApplicationTreeService>("The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type");
continue;
}
//check if the tree definition (applicationAlias + type + assembly) is already in the list
if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false)
{
list.Add(new ApplicationTree(
addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value),
addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0,
addElement.Attribute("application").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("title").Value,
addElement.Attribute("iconClosed").Value,
addElement.Attribute("iconOpen").Value,
addElement.Attribute("type").Value));
}
}
return false;
}, false);
return list;
}
internal static event TypedEventHandler<ApplicationTree, EventArgs> Deleted;
private static void OnDeleted(ApplicationTree app, EventArgs args)
{
if (Deleted != null)
{
Deleted(app, args);
}
}
internal static event TypedEventHandler<ApplicationTree, EventArgs> New;
private static void OnNew(ApplicationTree app, EventArgs args)
{
if (New != null)
{
New(app, args);
}
}
internal static event TypedEventHandler<ApplicationTree, EventArgs> Updated;
private static void OnUpdated(ApplicationTree app, EventArgs args)
{
if (Updated != null)
{
Updated(app, args);
}
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Services
{
internal class ApplicationTreeService : IApplicationTreeService
{
private readonly ILogger _logger;
private readonly CacheHelper _cache;
private Lazy<IEnumerable<ApplicationTree>> _allAvailableTrees;
internal const string TreeConfigFileName = "trees.config";
private static string _treeConfig;
private static readonly object Locker = new object();
public ApplicationTreeService(ILogger logger, CacheHelper cache)
{
_logger = logger;
_cache = cache;
}
/// <summary>
/// gets/sets the trees.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 TreeConfigFilePath
{
get
{
if (string.IsNullOrWhiteSpace(_treeConfig))
{
_treeConfig = IOHelper.MapPath(SystemDirectories.Config + "/" + TreeConfigFileName);
}
return _treeConfig;
}
set { _treeConfig = value; }
}
/// <summary>
/// The main entry point to get application trees
/// </summary>
/// <remarks>
/// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins
/// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned.
/// </remarks>
private List<ApplicationTree> GetAppTrees()
{
return _cache.RuntimeCache.GetCacheItem<List<ApplicationTree>>(
CacheKeys.ApplicationTreeCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
//now we can check the non-volatile flag
if (_allAvailableTrees != null)
{
var hasChanges = false;
LoadXml(doc =>
{
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy
// based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableTrees.Value
.Where(x => list.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
if (hasChanges == false) return false;
//add the unregistered ones to the list and re-save the file if any changes were found
var count = 0;
foreach (var tree in unregistered)
{
doc.Root.Add(new XElement("add",
new XAttribute("initialize", tree.Initialize),
new XAttribute("sortOrder", tree.SortOrder),
new XAttribute("alias", tree.Alias),
new XAttribute("application", tree.ApplicationAlias),
new XAttribute("title", tree.Title),
new XAttribute("iconClosed", tree.IconClosed),
new XAttribute("iconOpen", tree.IconOpened),
new XAttribute("type", tree.Type)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
return list;
}, new TimeSpan(0, 10, 0));
}
/// <summary>
/// Creates a new application tree.
/// </summary>
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
/// <param name="sortOrder">The sort order.</param>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="alias">The alias.</param>
/// <param name="title">The title.</param>
/// <param name="iconClosed">The icon closed.</param>
/// <param name="iconOpened">The icon opened.</param>
/// <param name="type">The type.</param>
public void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type)
{
LoadXml(doc =>
{
var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == alias && x.Attribute("application").Value == applicationAlias);
if (el == null)
{
doc.Root.Add(new XElement("add",
new XAttribute("initialize", initialize),
new XAttribute("sortOrder", sortOrder),
new XAttribute("alias", alias),
new XAttribute("application", applicationAlias),
new XAttribute("title", title),
new XAttribute("iconClosed", iconClosed),
new XAttribute("iconOpen", iconOpened),
new XAttribute("type", type)));
}
return true;
}, true);
OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs());
}
/// <summary>
/// Saves this instance.
/// </summary>
public void SaveTree(ApplicationTree tree)
{
LoadXml(doc =>
{
var el = doc.Root.Elements("add").SingleOrDefault(x => x.Attribute("alias").Value == tree.Alias && x.Attribute("application").Value == tree.ApplicationAlias);
if (el != null)
{
el.RemoveAttributes();
el.Add(new XAttribute("initialize", tree.Initialize));
el.Add(new XAttribute("sortOrder", tree.SortOrder));
el.Add(new XAttribute("alias", tree.Alias));
el.Add(new XAttribute("application", tree.ApplicationAlias));
el.Add(new XAttribute("title", tree.Title));
el.Add(new XAttribute("iconClosed", tree.IconClosed));
el.Add(new XAttribute("iconOpen", tree.IconOpened));
el.Add(new XAttribute("type", tree.Type));
}
return true;
}, true);
OnUpdated(tree, new EventArgs());
}
/// <summary>
/// Deletes this instance.
/// </summary>
public void DeleteTree(ApplicationTree tree)
{
LoadXml(doc =>
{
doc.Root.Elements("add")
.Where(x => x.Attribute("application") != null
&& x.Attribute("application").Value == tree.ApplicationAlias
&& x.Attribute("alias") != null && x.Attribute("alias").Value == tree.Alias).Remove();
return true;
}, true);
OnDeleted(tree, new EventArgs());
}
/// <summary>
/// Gets an ApplicationTree by it's tree alias.
/// </summary>
/// <param name="treeAlias">The tree alias.</param>
/// <returns>An ApplicationTree instance</returns>
public ApplicationTree GetByAlias(string treeAlias)
{
return GetAppTrees().Find(t => (t.Alias == treeAlias));
}
/// <summary>
/// Gets all applicationTrees registered in umbraco from the umbracoAppTree table..
/// </summary>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetAll()
{
return GetAppTrees().OrderBy(x => x.SortOrder);
}
/// <summary>
/// Gets the application tree for the applcation with the specified alias
/// </summary>
/// <param name="applicationAlias">The application alias.</param>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias)
{
return GetApplicationTrees(applicationAlias, false);
}
/// <summary>
/// Gets the application tree for the applcation with the specified alias
/// </summary>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="onlyInitialized"></param>
/// <returns>Returns a ApplicationTree Array</returns>
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias, bool onlyInitialized)
{
var list = GetAppTrees().FindAll(
t =>
{
if (onlyInitialized)
return (t.ApplicationAlias == applicationAlias && t.Initialize);
return (t.ApplicationAlias == applicationAlias);
}
);
return list.OrderBy(x => x.SortOrder).ToArray();
}
/// <summary>
/// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the
/// callback with the xml document and saves the structure back to disk if saveAfterCallback is true.
/// </summary>
/// <param name="callback"></param>
/// <param name="saveAfterCallbackIfChanges"></param>
internal void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanges)
{
lock (Locker)
{
var doc = System.IO.File.Exists(TreeConfigFilePath)
? XDocument.Load(TreeConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><trees />");
if (doc.Root != null)
{
var hasChanges = callback.Invoke(doc);
if (saveAfterCallbackIfChanges && hasChanges
//Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process
// in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and
// more about the IO trying to save the XML doc, but it doesn't hurt to check.
&& doc.Root != null && doc.Root.Elements().Any())
{
//ensures the folder exists
Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath));
//saves it
doc.Save(TreeConfigFilePath);
//remove the cache now that it has changed 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.
_cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey);
}
}
}
}
private List<ApplicationTree> ReadFromXmlAndSort()
{
var list = new List<ApplicationTree>();
//read in the xml file containing trees and convert them all to ApplicationTree instances
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 applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
var clrType = Type.GetType(type);
if (clrType == null)
{
_logger.Warn<ApplicationTreeService>("The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type");
continue;
}
//check if the tree definition (applicationAlias + type + assembly) is already in the list
if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false)
{
list.Add(new ApplicationTree(
addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value),
addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0,
addElement.Attribute("application").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("title").Value,
addElement.Attribute("iconClosed").Value,
addElement.Attribute("iconOpen").Value,
addElement.Attribute("type").Value));
}
}
return false;
}, false);
return list;
}
internal static event TypedEventHandler<ApplicationTree, EventArgs> Deleted;
private static void OnDeleted(ApplicationTree app, EventArgs args)
{
if (Deleted != null)
{
Deleted(app, args);
}
}
internal static event TypedEventHandler<ApplicationTree, EventArgs> New;
private static void OnNew(ApplicationTree app, EventArgs args)
{
if (New != null)
{
New(app, args);
}
}
internal static event TypedEventHandler<ApplicationTree, EventArgs> Updated;
private static void OnUpdated(ApplicationTree app, EventArgs args)
{
if (Updated != null)
{
Updated(app, args);
}
}
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableTrees : IEnumerable<ApplicationTree>
{
public LazyEnumerableTrees()
{
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
{
var added = new List<string>();
// Load all Controller Trees by attribute
var types = PluginManager.Current.ResolveTypesWithAttribute<TreeController, TreeAttribute>();
//convert them to ApplicationTree instances
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly()))
.ToArray();
added.AddRange(items.Select(x => x.Alias));
return items.ToArray();
});
}
private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<ApplicationTree> GetEnumerator()
{
return _lazyTrees.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

View File

@@ -1,329 +1,330 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.UnitOfWork;
using File = System.IO.File;
namespace Umbraco.Core.Services
{
internal class SectionService : ISectionService
{
private readonly IUserService _userService;
private readonly Lazy<IEnumerable<Section>> _allAvailableSections;
private readonly IApplicationTreeService _applicationTreeService;
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly CacheHelper _cache;
internal const string AppConfigFileName = "applications.config";
private static string _appConfig;
private static readonly object Locker = new object();
public SectionService(
IUserService userService,
IApplicationTreeService applicationTreeService,
IDatabaseUnitOfWorkProvider uowProvider,
CacheHelper cache)
{
if (applicationTreeService == null) throw new ArgumentNullException("applicationTreeService");
if (cache == null) throw new ArgumentNullException("cache");
_userService = userService;
_applicationTreeService = applicationTreeService;
_uowProvider = uowProvider;
_cache = cache;
_allAvailableSections = new Lazy<IEnumerable<Section>>(() => new LazyEnumerableSections());
}
/// <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>
public IEnumerable<Section> GetSections()
{
return _cache.RuntimeCache.GetCacheItem<IEnumerable<Section>>(
CacheKeys.ApplicationsCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
var hasChanges = false;
var localCopyList = list;
LoadXml(doc =>
{
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy
//Get all the trees not registered in the config
var unregistered = _allAvailableSections.Value
.Where(x => localCopyList.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
var count = 0;
foreach (var attr in unregistered)
{
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)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
return list;
}, new TimeSpan(0, 10, 0));
}
internal void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanged)
{
lock (Locker)
{
var doc = File.Exists(AppConfigFilePath)
? XDocument.Load(AppConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><applications />");
if (doc.Root != null)
{
var changed = callback.Invoke(doc);
if (saveAfterCallbackIfChanged && changed)
{
//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.
_cache.ClearCacheItem(CacheKeys.ApplicationsCacheKey);
}
}
}
}
/// <summary>
/// Get the user's allowed sections
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public IEnumerable<Section> GetAllowedSections(int userId)
{
var user = _userService.GetUserById(userId);
if (user == null)
{
throw new InvalidOperationException("No user found with id " + userId);
}
return GetSections().Where(x => user.AllowedSections.Contains(x.Alias));
}
/// <summary>
/// Gets the application by its alias.
/// </summary>
/// <param name="appAlias">The application alias.</param>
/// <returns></returns>
public Section GetByAlias(string appAlias)
{
return GetSections().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>
public void MakeNew(string name, string alias, string icon)
{
MakeNew(name, alias, icon, GetSections().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>
public void MakeNew(string name, string alias, string icon, int sortOrder)
{
if (GetSections().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)));
return true;
}, true);
//raise event
OnNew(new Section(name, alias, icon, sortOrder), new EventArgs());
}
}
/// <summary>
/// Deletes the section
/// </summary>
public void DeleteSection(Section section)
{
lock (Locker)
{
//delete the assigned applications
_uowProvider.GetUnitOfWork().Database.Execute(
"delete from umbracoUser2App where app = @appAlias",
new { appAlias = section.Alias });
//delete the assigned trees
var trees = _applicationTreeService.GetApplicationTrees(section.Alias);
foreach (var t in trees)
{
_applicationTreeService.DeleteTree(t);
}
LoadXml(doc =>
{
doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias)
.Remove();
return true;
}, true);
//raise event
OnDeleted(section, new EventArgs());
}
}
private List<Section> ReadFromXmlAndSort()
{
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));
}
return false;
}, false);
return tmp;
}
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);
}
}
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableSections : IEnumerable<Section>
{
public LazyEnumerableSections()
{
_lazySections = new Lazy<IEnumerable<Section>>(() =>
{
// Load all Applications by attribute and add them to the XML config
//don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason
var types = PluginManager.Current.ResolveTypesWithAttribute<IApplication, ApplicationAttribute>(cacheResult: false);
//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());
return attrs.Select(x => new Section(x.Name, x.Alias, x.Icon, x.SortOrder)).ToArray();
});
}
private readonly Lazy<IEnumerable<Section>> _lazySections;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<Section> GetEnumerator()
{
return _lazySections.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
using Umbraco.Web.Models.Trees;
using File = System.IO.File;
namespace Umbraco.Web.Services
{
internal class SectionService : ISectionService
{
private readonly IUserService _userService;
private readonly Lazy<IEnumerable<Section>> _allAvailableSections;
private readonly IApplicationTreeService _applicationTreeService;
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
private readonly CacheHelper _cache;
internal const string AppConfigFileName = "applications.config";
private static string _appConfig;
private static readonly object Locker = new object();
public SectionService(
IUserService userService,
IApplicationTreeService applicationTreeService,
IDatabaseUnitOfWorkProvider uowProvider,
CacheHelper cache)
{
if (applicationTreeService == null) throw new ArgumentNullException("applicationTreeService");
if (cache == null) throw new ArgumentNullException("cache");
_userService = userService;
_applicationTreeService = applicationTreeService;
_uowProvider = uowProvider;
_cache = cache;
_allAvailableSections = new Lazy<IEnumerable<Section>>(() => new LazyEnumerableSections());
}
/// <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>
public IEnumerable<Section> GetSections()
{
return _cache.RuntimeCache.GetCacheItem<IEnumerable<Section>>(
CacheKeys.ApplicationsCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
var hasChanges = false;
var localCopyList = list;
LoadXml(doc =>
{
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy
//Get all the trees not registered in the config
var unregistered = _allAvailableSections.Value
.Where(x => localCopyList.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
var count = 0;
foreach (var attr in unregistered)
{
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)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
return list;
}, new TimeSpan(0, 10, 0));
}
internal void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanged)
{
lock (Locker)
{
var doc = File.Exists(AppConfigFilePath)
? XDocument.Load(AppConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><applications />");
if (doc.Root != null)
{
var changed = callback.Invoke(doc);
if (saveAfterCallbackIfChanged && changed)
{
//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.
_cache.ClearCacheItem(CacheKeys.ApplicationsCacheKey);
}
}
}
}
/// <summary>
/// Get the user's allowed sections
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public IEnumerable<Section> GetAllowedSections(int userId)
{
var user = _userService.GetUserById(userId);
if (user == null)
{
throw new InvalidOperationException("No user found with id " + userId);
}
return GetSections().Where(x => user.AllowedSections.Contains(x.Alias));
}
/// <summary>
/// Gets the application by its alias.
/// </summary>
/// <param name="appAlias">The application alias.</param>
/// <returns></returns>
public Section GetByAlias(string appAlias)
{
return GetSections().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>
public void MakeNew(string name, string alias, string icon)
{
MakeNew(name, alias, icon, GetSections().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>
public void MakeNew(string name, string alias, string icon, int sortOrder)
{
if (GetSections().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)));
return true;
}, true);
//raise event
OnNew(new Section(name, alias, icon, sortOrder), new EventArgs());
}
}
/// <summary>
/// Deletes the section
/// </summary>
public void DeleteSection(Section section)
{
lock (Locker)
{
//delete the assigned applications
_uowProvider.GetUnitOfWork().Database.Execute(
"delete from umbracoUser2App where app = @appAlias",
new { appAlias = section.Alias });
//delete the assigned trees
var trees = _applicationTreeService.GetApplicationTrees(section.Alias);
foreach (var t in trees)
{
_applicationTreeService.DeleteTree(t);
}
LoadXml(doc =>
{
doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias)
.Remove();
return true;
}, true);
//raise event
OnDeleted(section, new EventArgs());
}
}
private List<Section> ReadFromXmlAndSort()
{
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));
}
return false;
}, false);
return tmp;
}
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);
}
}
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableSections : IEnumerable<Section>
{
public LazyEnumerableSections()
{
_lazySections = new Lazy<IEnumerable<Section>>(() =>
{
// Load all Applications by attribute and add them to the XML config
//don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason
var types = PluginManager.Current.ResolveTypesWithAttribute<IApplication, ApplicationAttribute>(cacheResult: false);
//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());
return Enumerable.ToArray<Section>(attrs.Select(x => new Section(x.Name, x.Alias, x.Icon, x.SortOrder)));
});
}
private readonly Lazy<IEnumerable<Section>> _lazySections;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<Section> GetEnumerator()
{
return _lazySections.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

View File

@@ -1,80 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core;
using Umbraco.Core.Models;
using umbraco.businesslogic;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
/// <summary>
/// A startup handler for putting the tree config in the config file based on attributes found
/// </summary>
/// <remarks>
/// TODO: This is really not a very ideal process but the code is found here because tree plugins are in the Web project or the legacy business logic project.
/// Moving forward we can put the base tree plugin classes in the core and then this can all just be taken care of normally within the service.
/// </remarks>
public sealed class ApplicationTreeRegistrar : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//Call initialize on the tree service with the lazy enumerable class below, when the tree service needs to resolve,
// it will lazily do the scanning and comparing so it's not actually done on app start.
applicationContext.Services.ApplicationTreeService.Intitialize(new LazyEnumerableTrees());
}
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableTrees : IEnumerable<ApplicationTree>
{
public LazyEnumerableTrees()
{
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
{
var added = new List<string>();
// Load all Controller Trees by attribute
var types = PluginManager.Current.ResolveAttributedTreeControllers();
//convert them to ApplicationTree instances
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Select(x => new ApplicationTree(x.Item2.Initialize, x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen, x.Item1.GetFullNameWithAssembly()))
.ToArray();
added.AddRange(items.Select(x => x.Alias));
return items.ToArray();
});
}
private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<ApplicationTree> GetEnumerator()
{
return _lazyTrees.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

View File

@@ -348,6 +348,13 @@
<Compile Include="LegacyActions\ActionUpdate.cs" />
<Compile Include="LegacyActions\ContextMenuSeperator.cs" />
<Compile Include="LegacyActions\LegacyActionMenuItemAttribute.cs" />
<Compile Include="Models\ContentEditing\EntityBasic.cs" />
<Compile Include="Models\Trees\ApplicationAttribute.cs" />
<Compile Include="Models\Trees\ApplicationDefinitions.cs" />
<Compile Include="Models\Trees\IApplication.cs" />
<Compile Include="Services\SectionService.cs" />
<Compile Include="Trees\TreeAttribute.cs" />
<Compile Include="Models\Trees\TreeNode.cs" />
<Compile Include="Routing\NotFoundHandlerHelper.cs" />
<Compile Include="Install\InstallSteps\Version73FileCleanup.cs" />
<Compile Include="Models\ContentEditing\ContentTypeDisplay.cs" />
@@ -378,6 +385,7 @@
<Compile Include="Media\EmbedProviders\OEmbedPhoto.cs" />
<Compile Include="Security\Identity\IUmbracoBackOfficeTwoFactorOptions.cs" />
<Compile Include="Models\Mapping\PropertyTypeGroupResolver.cs" />
<Compile Include="Services\ApplicationTreeService.cs" />
<Compile Include="Trees\ContentTypeTreeController.cs" />
<Compile Include="Trees\MediaTypeTreeController.cs" />
<Compile Include="Trees\MemberTypeTreeController.cs" />
@@ -590,7 +598,6 @@
<Compile Include="Models\ContentEditing\DashboardControl.cs" />
<Compile Include="Models\ContentEditing\DataTypeDisplay.cs" />
<Compile Include="Models\ContentEditing\DataTypeSave.cs" />
<Compile Include="Models\ContentEditing\EntityBasic.cs" />
<Compile Include="Models\ContentEditing\PreValueFieldDisplay.cs" />
<Compile Include="Models\ContentEditing\PreValueFieldSave.cs" />
<Compile Include="Models\ContentEditing\PropertyEditorBasic.cs" />
@@ -777,7 +784,6 @@
<Compile Include="Models\Trees\ActionMenuItem.cs" />
<Compile Include="Models\Trees\ActionMenuItemAttribute.cs" />
<Compile Include="Trees\ActionUrlMethod.cs" />
<Compile Include="Trees\ApplicationTreeRegistrar.cs" />
<Compile Include="Trees\ContentTreeControllerBase.cs" />
<Compile Include="Trees\LegacyTreeController.cs" />
<Compile Include="Trees\ISearchableTree.cs" />
@@ -791,8 +797,6 @@
<Compile Include="Models\Trees\SectionRootNode.cs" />
<Compile Include="Trees\TreeController.cs" />
<Compile Include="Trees\ApplicationTreeExtensions.cs" />
<Compile Include="Trees\TreeAttribute.cs" />
<Compile Include="Models\Trees\TreeNode.cs" />
<Compile Include="Models\Trees\TreeNodeCollection.cs" />
<Compile Include="Models\Trees\TreeNodeExtensions.cs" />
<Compile Include="Trees\TreeNodeRenderingEventArgs.cs" />