WIP - moved out the tree and section service from Core, these are web only things, removes their xml parsing, fixes base controller ctor params with UmbracoContext, need to implement tree/section collections taking into account controllers and not scanning so much
This commit is contained in:
@@ -11,7 +11,9 @@ using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
@@ -19,276 +21,251 @@ namespace Umbraco.Web.Services
|
||||
internal class ApplicationTreeService : IApplicationTreeService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly CacheHelper _cache;
|
||||
private readonly TypeLoader _typeLoader;
|
||||
private Lazy<IEnumerable<ApplicationTree>> _allAvailableTrees;
|
||||
internal const string TreeConfigFileName = "trees.config";
|
||||
private static string _treeConfig;
|
||||
private readonly TreeCollection _treeCollection;
|
||||
private static readonly object Locker = new object();
|
||||
private readonly Lazy<IReadOnlyCollection<IGrouping<string, string>>> _groupedTrees;
|
||||
|
||||
public ApplicationTreeService(ILogger logger, CacheHelper cache, TypeLoader typeLoader)
|
||||
public ApplicationTreeService(ILogger logger, TreeCollection treeCollection)
|
||||
{
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
_typeLoader = typeLoader;
|
||||
_groupedTrees = new Lazy<IReadOnlyCollection<IGrouping<string, string>>>(InitGroupedTrees);
|
||||
_treeCollection = treeCollection;
|
||||
//_groupedTrees = new Lazy<IReadOnlyCollection<IGrouping<string, string>>>(InitGroupedTrees);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
///// 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();
|
||||
///// <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;
|
||||
// //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.
|
||||
// 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.
|
||||
// //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 (those not matching by alias casing will be detected as "unregistered")
|
||||
var unregistered = _allAvailableTrees.Value
|
||||
.Where(x => list.Any(l => l.Alias == x.Alias) == false)
|
||||
.ToArray();
|
||||
// //Get all the trees not registered in the config (those not matching by alias casing will be detected as "unregistered")
|
||||
// var unregistered = _allAvailableTrees.Value
|
||||
// .Where(x => list.Any(l => l.Alias == x.Alias) == false)
|
||||
// .ToArray();
|
||||
|
||||
hasChanges = unregistered.Any();
|
||||
// hasChanges = unregistered.Any();
|
||||
|
||||
if (hasChanges == false) return false;
|
||||
// if (hasChanges == false) return false;
|
||||
|
||||
//add or edit the unregistered ones and re-save the file if any changes were found
|
||||
var count = 0;
|
||||
foreach (var tree in unregistered)
|
||||
{
|
||||
var existingElement = doc.Root.Elements("add").SingleOrDefault(x =>
|
||||
string.Equals(x.Attribute("alias").Value, tree.Alias,
|
||||
StringComparison.InvariantCultureIgnoreCase) &&
|
||||
string.Equals(x.Attribute("application").Value, tree.ApplicationAlias,
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
if (existingElement != null)
|
||||
{
|
||||
existingElement.SetAttributeValue("alias", tree.Alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tree.Title.IsNullOrWhiteSpace())
|
||||
{
|
||||
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("iconClosed", tree.IconClosed),
|
||||
new XAttribute("iconOpen", tree.IconOpened),
|
||||
new XAttribute("type", tree.Type)));
|
||||
}
|
||||
else
|
||||
{
|
||||
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)));
|
||||
}
|
||||
// //add or edit the unregistered ones and re-save the file if any changes were found
|
||||
// var count = 0;
|
||||
// foreach (var tree in unregistered)
|
||||
// {
|
||||
// var existingElement = doc.Root.Elements("add").SingleOrDefault(x =>
|
||||
// string.Equals(x.Attribute("alias").Value, tree.Alias,
|
||||
// StringComparison.InvariantCultureIgnoreCase) &&
|
||||
// string.Equals(x.Attribute("application").Value, tree.ApplicationAlias,
|
||||
// StringComparison.InvariantCultureIgnoreCase));
|
||||
// if (existingElement != null)
|
||||
// {
|
||||
// existingElement.SetAttributeValue("alias", tree.Alias);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (tree.Title.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// 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("iconClosed", tree.IconClosed),
|
||||
// new XAttribute("iconOpen", tree.IconOpened),
|
||||
// new XAttribute("type", tree.Type)));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// 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++;
|
||||
}
|
||||
// }
|
||||
// count++;
|
||||
// }
|
||||
|
||||
//don't save if there's no changes
|
||||
return count > 0;
|
||||
}, true);
|
||||
// //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();
|
||||
}
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
// 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);
|
||||
///// <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)));
|
||||
}
|
||||
// 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;
|
||||
// return true;
|
||||
|
||||
}, true);
|
||||
// }, true);
|
||||
|
||||
OnNew(new ApplicationTree(initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, type), new EventArgs());
|
||||
}
|
||||
// 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);
|
||||
///// <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();
|
||||
// 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));
|
||||
}
|
||||
// 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;
|
||||
// return true;
|
||||
|
||||
}, true);
|
||||
// }, true);
|
||||
|
||||
OnUpdated(tree, new EventArgs());
|
||||
}
|
||||
// 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();
|
||||
///// <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;
|
||||
// return true;
|
||||
|
||||
}, true);
|
||||
// }, true);
|
||||
|
||||
OnDeleted(tree, new EventArgs());
|
||||
}
|
||||
// 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));
|
||||
/// <inheritdoc />
|
||||
public ApplicationTree GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(t => t.Alias == treeAlias);
|
||||
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ApplicationTree> GetAll() => _treeCollection;
|
||||
|
||||
/// <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>
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias)
|
||||
{
|
||||
return GetApplicationTrees(applicationAlias, false);
|
||||
}
|
||||
=> GetAll().Where(x => x.ApplicationAlias.InvariantEquals(applicationAlias)).OrderBy(x => x.SortOrder).ToList();
|
||||
|
||||
/// <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);
|
||||
}
|
||||
);
|
||||
///// <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();
|
||||
}
|
||||
// return list.OrderBy(x => x.SortOrder).ToArray();
|
||||
//}
|
||||
|
||||
public IDictionary<string, IEnumerable<ApplicationTree>> GetGroupedApplicationTrees(string applicationAlias, bool onlyInitialized)
|
||||
public IDictionary<string, IEnumerable<ApplicationTree>> GetGroupedApplicationTrees(string applicationAlias)
|
||||
{
|
||||
var result = new Dictionary<string, IEnumerable<ApplicationTree>>();
|
||||
var foundTrees = GetApplicationTrees(applicationAlias, onlyInitialized);
|
||||
var foundTrees = GetApplicationTrees(applicationAlias).ToList();
|
||||
foreach(var treeGroup in _groupedTrees.Value)
|
||||
{
|
||||
List<ApplicationTree> resultGroup = null;
|
||||
@@ -309,185 +286,185 @@ namespace Umbraco.Web.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a group of all tree groups and their tree aliases
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Used to initialize the <see cref="_groupedTrees"/> field
|
||||
/// </remarks>
|
||||
private IReadOnlyCollection<IGrouping<string, string>> InitGroupedTrees()
|
||||
{
|
||||
var result = GetAll()
|
||||
.Select(x => (treeAlias: x.Alias, treeGroup: x.GetRuntimeType().GetCustomAttribute<CoreTreeAttribute>(false)?.TreeGroup))
|
||||
.GroupBy(x => x.treeGroup, x => x.treeAlias)
|
||||
.ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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 />");
|
||||
///// <summary>
|
||||
///// Creates a group of all tree groups and their tree aliases
|
||||
///// </summary>
|
||||
///// <returns></returns>
|
||||
///// <remarks>
|
||||
///// Used to initialize the <see cref="_groupedTrees"/> field
|
||||
///// </remarks>
|
||||
//private IReadOnlyCollection<IGrouping<string, string>> InitGroupedTrees()
|
||||
//{
|
||||
// var result = GetAll()
|
||||
// .Select(x => (treeAlias: x.Alias, treeGroup: x.GetRuntimeType().GetCustomAttribute<CoreTreeAttribute>(false)?.TreeGroup))
|
||||
// .GroupBy(x => x.treeGroup, x => x.treeAlias)
|
||||
// .ToList();
|
||||
// return result;
|
||||
//}
|
||||
|
||||
if (doc.Root != null)
|
||||
{
|
||||
var hasChanges = callback.Invoke(doc);
|
||||
///// <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 (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));
|
||||
// if (doc.Root != null)
|
||||
// {
|
||||
// var hasChanges = callback.Invoke(doc);
|
||||
|
||||
//saves it
|
||||
doc.Save(TreeConfigFilePath);
|
||||
// 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));
|
||||
|
||||
//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.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// //saves it
|
||||
// doc.Save(TreeConfigFilePath);
|
||||
|
||||
private List<ApplicationTree> ReadFromXmlAndSort()
|
||||
{
|
||||
var list = new List<ApplicationTree>();
|
||||
// //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.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//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");
|
||||
//private List<ApplicationTree> ReadFromXmlAndSort()
|
||||
//{
|
||||
// var list = new List<ApplicationTree>();
|
||||
|
||||
var clrType = Type.GetType(type);
|
||||
if (clrType == null)
|
||||
{
|
||||
_logger.Warn<ApplicationTreeService>("The tree definition: {AddElement} could not be resolved to a .Net object type", addElement);
|
||||
continue;
|
||||
}
|
||||
// //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");
|
||||
|
||||
//check if the tree definition (applicationAlias + type + assembly) is already in the list
|
||||
// var clrType = Type.GetType(type);
|
||||
// if (clrType == null)
|
||||
// {
|
||||
// _logger.Warn<ApplicationTreeService>("The tree definition: {AddElement} could not be resolved to a .Net object type", addElement);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
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,
|
||||
(string)addElement.Attribute("application"),
|
||||
(string)addElement.Attribute("alias"),
|
||||
(string)addElement.Attribute("title"),
|
||||
(string)addElement.Attribute("iconClosed"),
|
||||
(string)addElement.Attribute("iconOpen"),
|
||||
(string)addElement.Attribute("type")));
|
||||
}
|
||||
}
|
||||
// //check if the tree definition (applicationAlias + type + assembly) is already in the list
|
||||
|
||||
return false;
|
||||
// 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,
|
||||
// (string)addElement.Attribute("application"),
|
||||
// (string)addElement.Attribute("alias"),
|
||||
// (string)addElement.Attribute("title"),
|
||||
// (string)addElement.Attribute("iconClosed"),
|
||||
// (string)addElement.Attribute("iconOpen"),
|
||||
// (string)addElement.Attribute("type")));
|
||||
// }
|
||||
// }
|
||||
|
||||
}, false);
|
||||
// return false;
|
||||
|
||||
return list;
|
||||
}
|
||||
// }, 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> 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> 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);
|
||||
}
|
||||
}
|
||||
//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(TypeLoader typeLoader)
|
||||
{
|
||||
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
|
||||
{
|
||||
var added = new List<string>();
|
||||
///// <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(TypeLoader typeLoader)
|
||||
// {
|
||||
// _lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
|
||||
// {
|
||||
// var added = new List<string>();
|
||||
|
||||
// Load all Controller Trees by attribute
|
||||
var types = typeLoader.GetTypesWithAttribute<TreeController, TreeAttribute>(); // fixme inject
|
||||
//convert them to ApplicationTree instances
|
||||
var items = types
|
||||
.Select(x => (tree: x, treeAttribute: x.GetCustomAttributes<TreeAttribute>(false).Single()))
|
||||
.Select(x => new ApplicationTree(x.treeAttribute.Initialize, x.treeAttribute.SortOrder, x.treeAttribute.ApplicationAlias, x.treeAttribute.Alias, x.treeAttribute.Title, x.treeAttribute.IconClosed, x.treeAttribute.IconOpen, x.tree.GetFullNameWithAssembly()))
|
||||
.ToArray();
|
||||
// // Load all Controller Trees by attribute
|
||||
// var types = typeLoader.GetTypesWithAttribute<TreeController, TreeAttribute>(); // fixme inject
|
||||
// //convert them to ApplicationTree instances
|
||||
// var items = types
|
||||
// .Select(x => (tree: x, treeAttribute: x.GetCustomAttributes<TreeAttribute>(false).Single()))
|
||||
// .Select(x => new ApplicationTree(x.treeAttribute.Initialize, x.treeAttribute.SortOrder, x.treeAttribute.ApplicationAlias, x.treeAttribute.Alias, x.treeAttribute.Title, x.treeAttribute.IconClosed, x.treeAttribute.IconOpen, x.tree.GetFullNameWithAssembly()))
|
||||
// .ToArray();
|
||||
|
||||
added.AddRange(items.Select(x => x.Alias));
|
||||
// added.AddRange(items.Select(x => x.Alias));
|
||||
|
||||
return items.ToArray();
|
||||
});
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
// private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
// /// <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();
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
153
src/Umbraco.Web/Services/IApplicationTreeService.cs
Normal file
153
src/Umbraco.Web/Services/IApplicationTreeService.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
public interface 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>
|
||||
//void MakeNew(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type);
|
||||
|
||||
///// <summary>
|
||||
///// Saves this instance.
|
||||
///// </summary>
|
||||
//void SaveTree(ApplicationTree tree);
|
||||
|
||||
///// <summary>
|
||||
///// Deletes this instance.
|
||||
///// </summary>
|
||||
//void DeleteTree(ApplicationTree tree);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ApplicationTree by it's tree alias.
|
||||
/// </summary>
|
||||
/// <param name="treeAlias">The tree alias.</param>
|
||||
/// <returns>An ApplicationTree instance</returns>
|
||||
ApplicationTree GetByAlias(string treeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all applicationTrees registered in umbraco from the umbracoAppTree table..
|
||||
/// </summary>
|
||||
/// <returns>Returns a ApplicationTree Array</returns>
|
||||
IEnumerable<ApplicationTree> GetAll();
|
||||
|
||||
/// <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>
|
||||
IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias);
|
||||
|
||||
///// <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>
|
||||
//IEnumerable<ApplicationTree> GetApplicationTrees(string applicationAlias, bool onlyInitialized);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grouped application trees for the application with the specified alias
|
||||
/// </summary>
|
||||
/// <param name="applicationAlias"></param>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, IEnumerable<ApplicationTree>> GetGroupedApplicationTrees(string applicationAlias);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
public IDictionary<string, IEnumerable<ApplicationTree>> GetGroupedApplicationTrees(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>
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
116
src/Umbraco.Web/Services/ISectionService.cs
Normal file
116
src/Umbraco.Web/Services/ISectionService.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
{
|
||||
public interface ISectionService
|
||||
{
|
||||
/// <summary>
|
||||
/// The cache storage for all applications
|
||||
/// </summary>
|
||||
IEnumerable<IBackOfficeSection> GetSections();
|
||||
|
||||
/// <summary>
|
||||
/// Get the user group's allowed sections
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IBackOfficeSection> GetAllowedSections(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application by its alias.
|
||||
/// </summary>
|
||||
/// <param name="appAlias">The application alias.</param>
|
||||
/// <returns></returns>
|
||||
IBackOfficeSection GetByAlias(string 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>
|
||||
//void MakeNew(string name, string alias, string icon);
|
||||
|
||||
///// <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>
|
||||
//void MakeNew(string name, string alias, string icon, int sortOrder);
|
||||
|
||||
///// <summary>
|
||||
///// Deletes the section
|
||||
///// </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<IBackOfficeSection> GetSections()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user's allowed sections
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IBackOfficeSection> 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 IBackOfficeSection 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(IBackOfficeSection section)
|
||||
//{
|
||||
// throw new System.NotImplementedException();
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,12 @@ using Umbraco.Core.Events;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Trees;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Web.Services
|
||||
@@ -20,130 +23,90 @@ namespace Umbraco.Web.Services
|
||||
internal class SectionService : ISectionService
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly Lazy<IEnumerable<Section>> _allAvailableSections;
|
||||
private readonly IApplicationTreeService _applicationTreeService;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly BackOfficeSectionCollection _sectionCollection;
|
||||
|
||||
private readonly Lazy<IEnumerable<IBackOfficeSection>> _allAvailableSections;
|
||||
//private readonly IApplicationTreeService _applicationTreeService;
|
||||
//private readonly IScopeProvider _scopeProvider;
|
||||
private readonly CacheHelper _cache;
|
||||
internal const string AppConfigFileName = "applications.config";
|
||||
private static string _appConfig;
|
||||
private static readonly object Locker = new object();
|
||||
//internal const string AppConfigFileName = "applications.config";
|
||||
//private static string _appConfig;
|
||||
//private static readonly object Locker = new object();
|
||||
|
||||
public SectionService(
|
||||
IUserService userService,
|
||||
IApplicationTreeService applicationTreeService,
|
||||
IScopeProvider scopeProvider,
|
||||
BackOfficeSectionCollection sectionCollection,
|
||||
CacheHelper cache)
|
||||
{
|
||||
_applicationTreeService = applicationTreeService ?? throw new ArgumentNullException(nameof(applicationTreeService));
|
||||
//_applicationTreeService = applicationTreeService ?? throw new ArgumentNullException(nameof(applicationTreeService));
|
||||
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
||||
_userService = userService;
|
||||
_scopeProvider = scopeProvider;
|
||||
_allAvailableSections = new Lazy<IEnumerable<Section>>(() => new LazyEnumerableSections());
|
||||
_sectionCollection = sectionCollection;
|
||||
//_scopeProvider = scopeProvider;
|
||||
//_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>
|
||||
///// 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()
|
||||
public IEnumerable<IBackOfficeSection> 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("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));
|
||||
return _sectionCollection;
|
||||
}
|
||||
|
||||
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 />");
|
||||
//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 (doc.Root != null)
|
||||
// {
|
||||
// var changed = callback.Invoke(doc);
|
||||
|
||||
if (saveAfterCallbackIfChanged && changed)
|
||||
{
|
||||
//ensure the folder is created!
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath));
|
||||
// if (saveAfterCallbackIfChanged && changed)
|
||||
// {
|
||||
// //ensure the folder is created!
|
||||
// Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath));
|
||||
|
||||
doc.Save(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.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// //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.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user's allowed sections
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Section> GetAllowedSections(int userId)
|
||||
public IEnumerable<IBackOfficeSection> GetAllowedSections(int userId)
|
||||
{
|
||||
|
||||
var user = _userService.GetUserById(userId);
|
||||
@@ -160,172 +123,172 @@ namespace Umbraco.Web.Services
|
||||
/// </summary>
|
||||
/// <param name="appAlias">The application alias.</param>
|
||||
/// <returns></returns>
|
||||
public Section GetByAlias(string appAlias)
|
||||
public IBackOfficeSection 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)
|
||||
{
|
||||
var sections = GetSections();
|
||||
var nextSortOrder = sections.Any() ? sections.Max(x => x.SortOrder) + 1 : 1;
|
||||
MakeNew(name, alias, icon, nextSortOrder);
|
||||
}
|
||||
///// <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)
|
||||
//{
|
||||
// var sections = GetSections();
|
||||
// var nextSortOrder = sections.Any() ? sections.Max(x => x.SortOrder) + 1 : 1;
|
||||
// MakeNew(name, alias, icon, nextSortOrder);
|
||||
//}
|
||||
|
||||
/// <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);
|
||||
///// <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(this /*new Section(name, alias, sortOrder)*/, new EventArgs());
|
||||
}
|
||||
}
|
||||
// //raise event
|
||||
// OnNew(this /*new Section(name, alias, sortOrder)*/, new EventArgs());
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the section
|
||||
/// </summary>
|
||||
public void DeleteSection(Section section)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
//delete the assigned applications
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
scope.Database.Execute("delete from umbracoUserGroup2App where app = @appAlias",
|
||||
new { appAlias = section.Alias });
|
||||
scope.Complete();
|
||||
}
|
||||
///// <summary>
|
||||
///// Deletes the section
|
||||
///// </summary>
|
||||
//public void DeleteSection(Section section)
|
||||
//{
|
||||
// lock (Locker)
|
||||
// {
|
||||
// //delete the assigned applications
|
||||
// using (var scope = _scopeProvider.CreateScope())
|
||||
// {
|
||||
// scope.Database.Execute("delete from umbracoUserGroup2App where app = @appAlias",
|
||||
// new { appAlias = section.Alias });
|
||||
// scope.Complete();
|
||||
// }
|
||||
|
||||
//delete the assigned trees
|
||||
var trees = _applicationTreeService.GetApplicationTrees(section.Alias);
|
||||
foreach (var t in trees)
|
||||
{
|
||||
_applicationTreeService.DeleteTree(t);
|
||||
}
|
||||
// //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();
|
||||
// LoadXml(doc =>
|
||||
// {
|
||||
// doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == section.Alias)
|
||||
// .Remove();
|
||||
|
||||
return true;
|
||||
}, true);
|
||||
// return true;
|
||||
// }, true);
|
||||
|
||||
//raise event
|
||||
OnDeleted(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
// //raise event
|
||||
// OnDeleted(this, new EventArgs());
|
||||
// }
|
||||
//}
|
||||
|
||||
private List<Section> ReadFromXmlAndSort()
|
||||
{
|
||||
var tmp = new List<Section>();
|
||||
//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,
|
||||
sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0));
|
||||
}
|
||||
return false;
|
||||
}, false);
|
||||
// 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,
|
||||
// sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0));
|
||||
// }
|
||||
// return false;
|
||||
// }, false);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
// return tmp;
|
||||
//}
|
||||
|
||||
internal static event TypedEventHandler<ISectionService, EventArgs> Deleted;
|
||||
private static void OnDeleted(ISectionService app, EventArgs args)
|
||||
{
|
||||
if (Deleted != null)
|
||||
{
|
||||
Deleted(app, args);
|
||||
}
|
||||
}
|
||||
//internal static event TypedEventHandler<ISectionService, EventArgs> Deleted;
|
||||
//private static void OnDeleted(ISectionService app, EventArgs args)
|
||||
//{
|
||||
// if (Deleted != null)
|
||||
// {
|
||||
// Deleted(app, args);
|
||||
// }
|
||||
//}
|
||||
|
||||
internal static event TypedEventHandler<ISectionService, EventArgs> New;
|
||||
private static void OnNew(ISectionService app, EventArgs args)
|
||||
{
|
||||
if (New != null)
|
||||
{
|
||||
New(app, args);
|
||||
}
|
||||
}
|
||||
//internal static event TypedEventHandler<ISectionService, EventArgs> New;
|
||||
//private static void OnNew(ISectionService 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
|
||||
///// <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 = Current.TypeLoader.GetTypesWithAttribute<IApplication, ApplicationAttribute>(cache: false); // fixme - inject
|
||||
// //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 = Current.TypeLoader.GetTypesWithAttribute<IBackOfficeSection, ApplicationAttribute>(cache: false); // fixme - inject
|
||||
|
||||
//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.SortOrder)).ToArray();
|
||||
});
|
||||
}
|
||||
// //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.SortOrder)).ToArray();
|
||||
// });
|
||||
// }
|
||||
|
||||
private readonly Lazy<IEnumerable<Section>> _lazySections;
|
||||
// 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 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();
|
||||
}
|
||||
}
|
||||
// /// <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