working on U4-6030
This commit is contained in:
@@ -9,6 +9,7 @@ using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using umbraco.DataLayer;
|
||||
|
||||
namespace umbraco.BusinessLogic
|
||||
@@ -24,6 +25,23 @@ namespace umbraco.BusinessLogic
|
||||
internal const string TreeConfigFileName = "trees.config";
|
||||
private static string _treeConfig;
|
||||
private static readonly object Locker = new object();
|
||||
private static volatile bool _isInitialized = false;
|
||||
private static IEnumerable<ApplicationTree> _allAvailableTrees;
|
||||
|
||||
/// <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>
|
||||
internal static void Intitialize(IEnumerable<ApplicationTree> allAvailableTrees)
|
||||
{
|
||||
_allAvailableTrees = allAvailableTrees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gets/sets the trees.config file path
|
||||
@@ -45,58 +63,136 @@ namespace umbraco.BusinessLogic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The cache storage for all application trees
|
||||
/// The main entry point to get application trees
|
||||
/// </summary>
|
||||
private static List<ApplicationTree> AppTrees
|
||||
/// <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 static List<ApplicationTree> GetAppTrees()
|
||||
{
|
||||
get
|
||||
{
|
||||
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
|
||||
CacheKeys.ApplicationTreeCacheKey,
|
||||
() =>
|
||||
return ApplicationContext.Current.ApplicationCache.GetCacheItem<List<ApplicationTree>>(
|
||||
CacheKeys.ApplicationTreeCacheKey,
|
||||
() =>
|
||||
{
|
||||
var list = ReadFromXmlAndSort();
|
||||
|
||||
//On first access we need to do some initialization
|
||||
if (_isInitialized == false)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
var list = new List<ApplicationTree>();
|
||||
|
||||
LoadXml(doc =>
|
||||
if (_isInitialized == false)
|
||||
{
|
||||
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
|
||||
{
|
||||
var sortOrderAttr = x.Attribute("sortOrder");
|
||||
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
|
||||
}))
|
||||
//now we can check the non-volatile flag
|
||||
if (_allAvailableTrees != null)
|
||||
{
|
||||
var hasChanges = false;
|
||||
|
||||
var applicationAlias = (string)addElement.Attribute("application");
|
||||
var type = (string)addElement.Attribute("type");
|
||||
var assembly = (string)addElement.Attribute("assembly");
|
||||
|
||||
//check if the tree definition (applicationAlias + type + assembly) is already in the list
|
||||
|
||||
if (!list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias)
|
||||
&& tree.Type.InvariantEquals(type)
|
||||
&& tree.AssemblyName.InvariantEquals(assembly)))
|
||||
LoadXml(doc =>
|
||||
{
|
||||
list.Add(new ApplicationTree(
|
||||
addElement.Attribute("silent") != null ? Convert.ToBoolean(addElement.Attribute("silent").Value) : false,
|
||||
addElement.Attribute("initialize") != null ? Convert.ToBoolean(addElement.Attribute("initialize").Value) : true,
|
||||
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,
|
||||
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
|
||||
addElement.Attribute("type").Value,
|
||||
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
|
||||
//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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}, false);
|
||||
|
||||
return list;
|
||||
});
|
||||
}
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static 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 = System.Type.GetType(type);
|
||||
if (clrType == null)
|
||||
{
|
||||
LogHelper.Warn(typeof(ApplicationTree), "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("silent") != null && Convert.ToBoolean(addElement.Attribute("silent").Value),
|
||||
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,
|
||||
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
|
||||
addElement.Attribute("type").Value,
|
||||
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}, false);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -256,6 +352,9 @@ namespace umbraco.BusinessLogic
|
||||
new XAttribute("type", type),
|
||||
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}, true);
|
||||
|
||||
OnNew(new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action), new EventArgs());
|
||||
@@ -287,6 +386,8 @@ namespace umbraco.BusinessLogic
|
||||
el.Add(new XAttribute("action", string.IsNullOrEmpty(this.Action) ? "" : this.Action));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}, true);
|
||||
|
||||
OnUpdated(this, new EventArgs());
|
||||
@@ -304,6 +405,9 @@ namespace umbraco.BusinessLogic
|
||||
{
|
||||
doc.Root.Elements("add").Where(x => x.Attribute("application") != null && x.Attribute("application").Value == this.ApplicationAlias &&
|
||||
x.Attribute("alias") != null && x.Attribute("alias").Value == this.Alias).Remove();
|
||||
|
||||
return true;
|
||||
|
||||
}, true);
|
||||
|
||||
OnDeleted(this, new EventArgs());
|
||||
@@ -317,7 +421,7 @@ namespace umbraco.BusinessLogic
|
||||
/// <returns>An ApplicationTree instance</returns>
|
||||
public static ApplicationTree getByAlias(string treeAlias)
|
||||
{
|
||||
return AppTrees.Find(t => (t.Alias == treeAlias));
|
||||
return GetAppTrees().Find(t => (t.Alias == treeAlias));
|
||||
|
||||
}
|
||||
|
||||
@@ -327,7 +431,7 @@ namespace umbraco.BusinessLogic
|
||||
/// <returns>Returns a ApplicationTree Array</returns>
|
||||
public static ApplicationTree[] getAll()
|
||||
{
|
||||
return AppTrees.OrderBy(x => x.SortOrder).ToArray();
|
||||
return GetAppTrees().OrderBy(x => x.SortOrder).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -348,7 +452,7 @@ namespace umbraco.BusinessLogic
|
||||
/// <returns>Returns a ApplicationTree Array</returns>
|
||||
public static ApplicationTree[] getApplicationTree(string applicationAlias, bool onlyInitializedApplications)
|
||||
{
|
||||
var list = AppTrees.FindAll(
|
||||
var list = GetAppTrees().FindAll(
|
||||
t =>
|
||||
{
|
||||
if (onlyInitializedApplications)
|
||||
@@ -360,21 +464,34 @@ namespace umbraco.BusinessLogic
|
||||
return list.OrderBy(x => x.SortOrder).ToArray();
|
||||
}
|
||||
|
||||
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
|
||||
/// <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 static 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)
|
||||
{
|
||||
callback.Invoke(doc);
|
||||
var hasChanges = callback.Invoke(doc);
|
||||
|
||||
if (saveAfterCallback)
|
||||
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
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core;
|
||||
@@ -9,81 +11,159 @@ using umbraco.interfaces;
|
||||
|
||||
namespace umbraco.BusinessLogic
|
||||
{
|
||||
public class ApplicationTreeRegistrar : IApplicationStartupHandler
|
||||
public class ApplicationTreeRegistrar : ApplicationEventHandler
|
||||
{
|
||||
public ApplicationTreeRegistrar()
|
||||
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
|
||||
{
|
||||
//don't do anything if the application or database is not configured!
|
||||
if (ApplicationContext.Current == null
|
||||
|| !ApplicationContext.Current.IsConfigured
|
||||
|| !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
|
||||
return;
|
||||
//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.
|
||||
ApplicationTree.Intitialize(new LazyEnumerableTrees());
|
||||
}
|
||||
|
||||
// Load all Trees by attribute and add them to the XML config
|
||||
var types = PluginManager.Current.ResolveAttributedTrees();
|
||||
//public ApplicationTreeRegistrar()
|
||||
//{
|
||||
// //don't do anything if the application or database is not configured!
|
||||
// if (ApplicationContext.Current == null
|
||||
// || !ApplicationContext.Current.IsConfigured
|
||||
// || !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
|
||||
// return;
|
||||
|
||||
var items = types
|
||||
.Select(x =>
|
||||
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
|
||||
.Where(x => ApplicationTree.getByAlias(x.Item2.Alias) == null);
|
||||
// // Load all Trees by attribute and add them to the XML config
|
||||
// var types = PluginManager.Current.ResolveAttributedTrees();
|
||||
|
||||
var allAliases = ApplicationTree.getAll().Select(x => x.Alias).Concat(items.Select(x => x.Item2.Alias));
|
||||
var inString = "'" + string.Join("','", allAliases) + "'";
|
||||
// var items = types
|
||||
// .Select(x =>
|
||||
// new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
|
||||
// .Where(x => ApplicationTree.getByAlias(x.Item2.Alias) == null);
|
||||
|
||||
ApplicationTree.LoadXml(doc =>
|
||||
// var allAliases = ApplicationTree.getAll().Select(x => x.Alias).Concat(items.Select(x => x.Item2.Alias));
|
||||
// var inString = "'" + string.Join("','", allAliases) + "'";
|
||||
|
||||
// ApplicationTree.LoadXml(doc =>
|
||||
// {
|
||||
// foreach (var tuple in items)
|
||||
// {
|
||||
// var type = tuple.Item1;
|
||||
// var attr = tuple.Item2;
|
||||
|
||||
// //Add the new tree that doesn't exist in the config that was found by type finding
|
||||
|
||||
// doc.Root.Add(new XElement("add",
|
||||
// new XAttribute("silent", attr.Silent),
|
||||
// new XAttribute("initialize", attr.Initialize),
|
||||
// new XAttribute("sortOrder", attr.SortOrder),
|
||||
// new XAttribute("alias", attr.Alias),
|
||||
// new XAttribute("application", attr.ApplicationAlias),
|
||||
// new XAttribute("title", attr.Title),
|
||||
// new XAttribute("iconClosed", attr.IconClosed),
|
||||
// new XAttribute("iconOpen", attr.IconOpen),
|
||||
// // don't add the assembly, we don't need this:
|
||||
// // http://issues.umbraco.org/issue/U4-1360
|
||||
// //new XAttribute("assembly", assemblyName),
|
||||
// //new XAttribute("type", typeName),
|
||||
// // instead, store the assembly type name
|
||||
// new XAttribute("type", type.GetFullNameWithAssembly()),
|
||||
// new XAttribute("action", attr.Action)));
|
||||
// }
|
||||
|
||||
// //add any trees that were found in the database that don't exist in the config
|
||||
|
||||
// var db = ApplicationContext.Current.DatabaseContext.Database;
|
||||
// var exist = db.TableExist("umbracoAppTree");
|
||||
// if (exist)
|
||||
// {
|
||||
// var appTrees = db.Fetch<AppTreeDto>("WHERE treeAlias NOT IN (" + inString + ")");
|
||||
// foreach (var appTree in appTrees)
|
||||
// {
|
||||
// var action = appTree.Action;
|
||||
|
||||
// doc.Root.Add(new XElement("add",
|
||||
// new XAttribute("silent", appTree.Silent),
|
||||
// new XAttribute("initialize", appTree.Initialize),
|
||||
// new XAttribute("sortOrder", appTree.SortOrder),
|
||||
// new XAttribute("alias", appTree.Alias),
|
||||
// new XAttribute("application", appTree.AppAlias),
|
||||
// new XAttribute("title", appTree.Title),
|
||||
// new XAttribute("iconClosed", appTree.IconClosed),
|
||||
// new XAttribute("iconOpen", appTree.IconOpen),
|
||||
// new XAttribute("assembly", appTree.HandlerAssembly),
|
||||
// new XAttribute("type", appTree.HandlerType),
|
||||
// new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
|
||||
// }
|
||||
// }
|
||||
|
||||
// }, true);
|
||||
//}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
foreach (var tuple in items)
|
||||
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
|
||||
{
|
||||
var type = tuple.Item1;
|
||||
var attr = tuple.Item2;
|
||||
|
||||
//Add the new tree that doesn't exist in the config that was found by type finding
|
||||
var added = new List<string>();
|
||||
|
||||
doc.Root.Add(new XElement("add",
|
||||
new XAttribute("silent", attr.Silent),
|
||||
new XAttribute("initialize", attr.Initialize),
|
||||
new XAttribute("sortOrder", attr.SortOrder),
|
||||
new XAttribute("alias", attr.Alias),
|
||||
new XAttribute("application", attr.ApplicationAlias),
|
||||
new XAttribute("title", attr.Title),
|
||||
new XAttribute("iconClosed", attr.IconClosed),
|
||||
new XAttribute("iconOpen", attr.IconOpen),
|
||||
// don't add the assembly, we don't need this:
|
||||
// http://issues.umbraco.org/issue/U4-1360
|
||||
//new XAttribute("assembly", assemblyName),
|
||||
//new XAttribute("type", typeName),
|
||||
// instead, store the assembly type name
|
||||
new XAttribute("type", type.GetFullNameWithAssembly()),
|
||||
new XAttribute("action", attr.Action)));
|
||||
}
|
||||
// Load all Controller Trees by attribute
|
||||
var types = PluginManager.Current.ResolveAttributedTrees();
|
||||
//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.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
|
||||
"",
|
||||
x.Item1.GetFullNameWithAssembly(),
|
||||
x.Item2.Action))
|
||||
.ToArray();
|
||||
|
||||
//add any trees that were found in the database that don't exist in the config
|
||||
added.AddRange(items.Select(x => x.Alias));
|
||||
|
||||
var db = ApplicationContext.Current.DatabaseContext.Database;
|
||||
var exist = db.TableExist("umbracoAppTree");
|
||||
if (exist)
|
||||
{
|
||||
var appTrees = db.Fetch<AppTreeDto>("WHERE treeAlias NOT IN (" + inString + ")");
|
||||
foreach (var appTree in appTrees)
|
||||
{
|
||||
var action = appTree.Action;
|
||||
//find the legacy trees
|
||||
var legacyTreeTypes = PluginManager.Current.ResolveAttributedTrees();
|
||||
//convert them to ApplicationTree instances
|
||||
var legacyItems = legacyTreeTypes
|
||||
.Select(x =>
|
||||
new Tuple<Type, global::umbraco.businesslogic.TreeAttribute>(
|
||||
x,
|
||||
x.GetCustomAttributes<global::umbraco.businesslogic.TreeAttribute>(false).SingleOrDefault()))
|
||||
.Where(x => x.Item2 != null)
|
||||
//make sure the legacy tree isn't added on top of the controller tree!
|
||||
.Where(x => added.InvariantContains(x.Item2.Alias) == false)
|
||||
.Select(x => new ApplicationTree(x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
|
||||
"",
|
||||
x.Item1.GetFullNameWithAssembly(),
|
||||
x.Item2.Action));
|
||||
|
||||
doc.Root.Add(new XElement("add",
|
||||
new XAttribute("silent", appTree.Silent),
|
||||
new XAttribute("initialize", appTree.Initialize),
|
||||
new XAttribute("sortOrder", appTree.SortOrder),
|
||||
new XAttribute("alias", appTree.Alias),
|
||||
new XAttribute("application", appTree.AppAlias),
|
||||
new XAttribute("title", appTree.Title),
|
||||
new XAttribute("iconClosed", appTree.IconClosed),
|
||||
new XAttribute("iconOpen", appTree.IconOpen),
|
||||
new XAttribute("assembly", appTree.HandlerAssembly),
|
||||
new XAttribute("type", appTree.HandlerType),
|
||||
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
|
||||
}
|
||||
}
|
||||
return items.Concat(legacyItems).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
}, true);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user