diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs
index e88a51021a..a8307044a1 100644
--- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs
+++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs
@@ -1,218 +1,204 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-using System.Threading;
namespace Umbraco.Core.Cache
{
internal abstract class DictionaryCacheProviderBase : ICacheProvider
{
- protected static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
- protected abstract DictionaryCacheWrapper DictionaryCache { get; }
-
- ///
- /// Clears everything in umbraco's runtime cache
- ///
- ///
- /// Does not clear other stuff the user has put in httpruntime.cache!
- ///
- public virtual void ClearAllCache()
- {
- using (new WriteLock(Locker))
- {
- var keysToRemove = DictionaryCache.Cast()
- .Select(item => new DictionaryItemWrapper(item))
- .Where(c => c.Key is string && ((string)c.Key).StartsWith(CacheItemPrefix) && DictionaryCache[c.Key.ToString()] != null)
- .Select(c => c.Key)
- .ToList();
-
- foreach (var k in keysToRemove)
- {
- DictionaryCache.Remove(k);
- }
- }
- }
-
- ///
- /// Clears the item in umbraco's runtime cache with the given key
- ///
- /// Key
- public virtual void ClearCacheItem(string key)
- {
- using (new WriteLock(Locker))
- {
- if (DictionaryCache[GetCacheKey(key)] == null) return;
- DictionaryCache.Remove(GetCacheKey(key)); ;
- }
- }
-
- ///
- /// Clears all objects in the System.Web.Cache with the System.Type name as the
- /// input parameter. (using [object].GetType())
- ///
- /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument"
- public virtual void ClearCacheObjectTypes(string typeName)
- {
- using (new WriteLock(Locker))
- {
- var keysToRemove = DictionaryCache
- .Cast()
- .Select(item => new DictionaryItemWrapper(item))
- .Where(c =>
- {
- var k = c.Key.ToString();
- var v = DictionaryCache[k];
- return v != null && v.GetType().ToString().InvariantEquals(typeName);
- })
- .Select(c => c.Key)
- .ToList();
-
- foreach (var k in keysToRemove)
- DictionaryCache.Remove(k);
- }
- }
-
- public virtual void ClearCacheObjectTypes()
- {
- using (new WriteLock(Locker))
- {
- var typeOfT = typeof(T);
- var keysToRemove = DictionaryCache
- .Cast()
- .Select(item => new DictionaryItemWrapper(item))
- .Where(c =>
- {
- var k = c.Key.ToString();
- var v = DictionaryCache[k];
- return v != null && v.GetType() == typeOfT;
- })
- .Select(c => c.Key)
- .ToList();
-
- foreach (var k in keysToRemove)
- DictionaryCache.Remove(k);
- }
- }
-
- public virtual void ClearCacheObjectTypes(Func predicate)
- {
- using (new WriteLock(Locker))
- {
- var typeOfT = typeof(T);
- var keysToRemove = DictionaryCache
- .Cast()
- .Select(item => new DictionaryItemWrapper(item))
- .Where(c =>
- {
- var k = c.Key.ToString();
- var v = DictionaryCache[k];
- return v != null && v.GetType() == typeOfT && predicate(k, (T)v);
- })
- .Select(c => c.Key)
- .ToList();
-
- foreach (var k in keysToRemove)
- DictionaryCache.Remove(k);
- }
- }
-
- ///
- /// Clears all cache items that starts with the key passed.
- ///
- /// The start of the key
- public virtual void ClearCacheByKeySearch(string keyStartsWith)
- {
- var keysToRemove = DictionaryCache.Cast()
- .Select(item => new DictionaryItemWrapper(item))
- .Where(c => c.Key is string && ((string)c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith)))
- .Select(c => c.Key)
- .ToList();
-
- foreach (var k in keysToRemove)
- {
- DictionaryCache.Remove(k);
- }
- }
-
- ///
- /// Clears all cache items that have a key that matches the regular expression
- ///
- ///
- public virtual void ClearCacheByKeyExpression(string regexString)
- {
- var keysToRemove = new List();
- foreach (var item in DictionaryCache)
- {
- var c = new DictionaryItemWrapper(item);
- var s = c.Key as string;
- if (s != null)
- {
- var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
- if (Regex.IsMatch(withoutPrefix, regexString))
- {
- keysToRemove.Add(c.Key);
- }
- }
- }
-
- foreach (var k in keysToRemove)
- {
- DictionaryCache.Remove(k);
- }
- }
-
- public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
- {
- return (from object item in DictionaryCache
- select new DictionaryItemWrapper(item)
- into c
- where c.Key is string && ((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith))
- select c.Value).ToList();
- }
-
- public IEnumerable GetCacheItemsByKeyExpression(string regexString)
- {
- var found = new List();
- foreach (var item in DictionaryCache)
- {
- var c = new DictionaryItemWrapper(item);
- var s = c.Key as string;
- if (s != null)
- {
- var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
- if (Regex.IsMatch(withoutPrefix, regexString))
- {
- found.Add(c.Value);
- }
- }
- }
-
- return found;
- }
-
- ///
- /// Returns a cache item by key, does not update the cache if it isn't there.
- ///
- ///
- ///
- public virtual object GetCacheItem(string cacheKey)
- {
- var result = DictionaryCache.Get(GetCacheKey(cacheKey));
- return result;
- }
-
- public abstract object GetCacheItem(string cacheKey, Func getCacheItem);
-
- ///
- /// We prefix all cache keys with this so that we know which ones this class has created when
- /// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create.
- ///
+ // prefix cache keys so we know which one are ours
protected const string CacheItemPrefix = "umbrtmche";
+ // an object that represent a value that has not been created yet
+ protected readonly object ValueNotCreated = new object();
+
+ // manupulate the underlying cache entries
+ // these *must* be called from within the appropriate locks
+ // and use the full prefixed cache keys
+ protected abstract IEnumerable GetDictionaryEntries();
+ protected abstract void RemoveEntry(string key);
+ protected abstract object GetEntry(string key);
+
+ // read-write lock the underlying cache
+ protected abstract IDisposable ReadLock { get; }
+ protected abstract IDisposable WriteLock { get; }
+
protected string GetCacheKey(string key)
{
return string.Format("{0}-{1}", CacheItemPrefix, key);
}
+
+ protected object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false)
+ {
+ try
+ {
+ // if onlyIfValueIsCreated, do not trigger value creation
+ // must return something, though, to differenciate from null values
+ if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
+ return lazy.Value;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ #region Clear
+
+ public virtual void ClearAllCache()
+ {
+ using (WriteLock)
+ {
+ foreach (var entry in GetDictionaryEntries()
+ .ToArray())
+ RemoveEntry((string) entry.Key);
+ }
+ }
+
+ public virtual void ClearCacheItem(string key)
+ {
+ var cacheKey = GetCacheKey(key);
+ using (WriteLock)
+ {
+ RemoveEntry(cacheKey);
+ }
+ }
+
+ public virtual void ClearCacheObjectTypes(string typeName)
+ {
+ using (WriteLock)
+ {
+ foreach (var entry in GetDictionaryEntries()
+ .Where(x =>
+ {
+ // entry.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = GetSafeLazyValue((Lazy)x.Value, true);
+ return value == null || value.GetType().ToString().InvariantEquals(typeName);
+ })
+ .ToArray())
+ RemoveEntry((string) entry.Key);
+ }
+ }
+
+ public virtual void ClearCacheObjectTypes()
+ {
+ var typeOfT = typeof(T);
+ using (WriteLock)
+ {
+ foreach (var entry in GetDictionaryEntries()
+ .Where(x =>
+ {
+ // entry.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // compare on exact type, don't use "is"
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = GetSafeLazyValue((Lazy)x.Value, true);
+ return value == null || value.GetType() == typeOfT;
+ })
+ .ToArray())
+ RemoveEntry((string) entry.Key);
+ }
+ }
+
+ public virtual void ClearCacheObjectTypes(Func predicate)
+ {
+ var typeOfT = typeof(T);
+ var plen = CacheItemPrefix.Length + 1;
+ using (WriteLock)
+ {
+ foreach (var entry in GetDictionaryEntries()
+ .Where(x =>
+ {
+ // entry.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // compare on exact type, don't use "is"
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = GetSafeLazyValue((Lazy)x.Value, true);
+ if (value == null) return true;
+ return value.GetType() == typeOfT
+ // run predicate on the 'public key' part only, ie without prefix
+ && predicate(((string)x.Key).Substring(plen), (T)value);
+ }))
+ RemoveEntry((string) entry.Key);
+ }
+ }
+
+ public virtual void ClearCacheByKeySearch(string keyStartsWith)
+ {
+ var plen = CacheItemPrefix.Length + 1;
+ using (WriteLock)
+ {
+ foreach (var entry in GetDictionaryEntries()
+ .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
+ .ToArray())
+ RemoveEntry((string) entry.Key);
+ }
+ }
+
+ public virtual void ClearCacheByKeyExpression(string regexString)
+ {
+ var plen = CacheItemPrefix.Length + 1;
+ using (WriteLock)
+ {
+ foreach (var entry in GetDictionaryEntries()
+ .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
+ .ToArray())
+ RemoveEntry((string) entry.Key);
+ }
+ }
+
+ #endregion
+
+ #region Get
+
+ public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
+ {
+ var plen = CacheItemPrefix.Length + 1;
+ IEnumerable entries;
+ using (ReadLock)
+ {
+ entries = GetDictionaryEntries()
+ .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
+ .ToArray(); // evaluate while locked
+ }
+ return entries
+ .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null); // backward compat, don't store null values in the cache
+ }
+
+ public virtual IEnumerable GetCacheItemsByKeyExpression(string regexString)
+ {
+ const string prefix = CacheItemPrefix + "-";
+ var plen = prefix.Length;
+ IEnumerable entries;
+ using (ReadLock)
+ {
+ entries = GetDictionaryEntries()
+ .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
+ .ToArray(); // evaluate while locked
+ }
+ return entries
+ .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null); // backward compat, don't store null values in the cache
+ }
+
+ public virtual object GetCacheItem(string cacheKey)
+ {
+ cacheKey = GetCacheKey(cacheKey);
+ Lazy result;
+ using (ReadLock)
+ {
+ result = GetEntry(cacheKey) as Lazy; // null if key not found
+ }
+ return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
+ }
+
+ public abstract object GetCacheItem(string cacheKey, Func getCacheItem);
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs b/src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs
deleted file mode 100644
index 840a1c01b3..0000000000
--- a/src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Collections;
-
-namespace Umbraco.Core.Cache
-{
- internal class DictionaryCacheWrapper : IEnumerable
- {
- private readonly IEnumerable _inner;
- private readonly Func _get;
- private readonly Action _remove;
-
- public DictionaryCacheWrapper(
- IEnumerable inner,
- Func get,
- Action remove)
- {
- _inner = inner;
- _get = get;
- _remove = remove;
- }
-
- public object this[object key]
- {
- get
- {
- return Get(key);
- }
- }
-
- public object Get(object key)
- {
- return _get(key);
- }
-
- public void Remove(object key)
- {
- _remove(key);
- }
-
- public IEnumerator GetEnumerator()
- {
- return _inner.GetEnumerator();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/DictionaryItemWrapper.cs b/src/Umbraco.Core/Cache/DictionaryItemWrapper.cs
deleted file mode 100644
index 724f5f43b0..0000000000
--- a/src/Umbraco.Core/Cache/DictionaryItemWrapper.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Umbraco.Core.Cache
-{
- internal class DictionaryItemWrapper
- {
- public DictionaryItemWrapper(dynamic item)
- {
- Key = item.Key;
- Value = item.Value;
- }
-
- public object Key { get; private set; }
- public object Value { get; private set; }
- }
-}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs
index b71317b31d..0a95ff6fd2 100644
--- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs
@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
-using System.Text.RegularExpressions;
using System.Web;
namespace Umbraco.Core.Cache
@@ -12,36 +11,115 @@ namespace Umbraco.Core.Cache
///
internal class HttpRequestCacheProvider : DictionaryCacheProviderBase
{
- private readonly Func _context;
+ // context provider
+ // the idea is that there is only one, application-wide HttpRequestCacheProvider instance,
+ // that is initialized with a method that returns the "current" context.
+ // NOTE
+ // but then it is initialized with () => new HttpContextWrapper(HttpContent.Current)
+ // which is higly inefficient because it creates a new wrapper each time we refer to _context()
+ // so replace it with _context1 and _context2 below + a way to get context.Items.
+ //private readonly Func _context;
- public HttpRequestCacheProvider(HttpContext context)
+ // NOTE
+ // and then in almost 100% cases _context2 will be () => HttpContext.Current
+ // so why not bring that logic in here and fallback on to HttpContext.Current when
+ // _context1 is null?
+ //private readonly HttpContextBase _context1;
+ //private readonly Func _context2;
+ private readonly HttpContextBase _context;
+
+ private IDictionary ContextItems
{
- _context = () => new HttpContextWrapper(context);
+ //get { return _context1 != null ? _context1.Items : _context2().Items; }
+ get { return _context != null ? _context.Items : HttpContext.Current.Items; }
}
- public HttpRequestCacheProvider(Func context)
+ // for unit tests
+ public HttpRequestCacheProvider(HttpContextBase context)
{
_context = context;
}
- protected override DictionaryCacheWrapper DictionaryCache
+ // main constructor
+ // will use HttpContext.Current
+ public HttpRequestCacheProvider(/*Func context*/)
{
+ //_context2 = context;
+ }
+
+ protected override IEnumerable GetDictionaryEntries()
+ {
+ const string prefix = CacheItemPrefix + "-";
+ return ContextItems.Cast()
+ .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
+ }
+
+ protected override void RemoveEntry(string key)
+ {
+ ContextItems.Remove(key);
+ }
+
+ protected override object GetEntry(string key)
+ {
+ return ContextItems[key];
+ }
+
+ #region Lock
+
+ protected override IDisposable ReadLock
+ {
+ // there's no difference between ReadLock and WriteLock here
+ get { return WriteLock; }
+ }
+
+ protected override IDisposable WriteLock
+ {
+ // NOTE
+ // could think about just overriding base.Locker to return a different
+ // object but then we'd create a ReaderWriterLockSlim per request,
+ // which is less efficient than just using a basic monitor lock.
+
get
{
- var ctx = _context();
- return new DictionaryCacheWrapper(
- ctx.Items,
- o => ctx.Items[o],
- o => ctx.Items.Remove(o));
+ return new MonitorLock(ContextItems.SyncRoot);
}
}
+ #endregion
+
+ #region Get
+
public override object GetCacheItem(string cacheKey, Func getCacheItem)
{
- var ctx = _context();
- var ck = GetCacheKey(cacheKey);
- return ctx.Items[ck] ?? (ctx.Items[ck] = getCacheItem());
+ cacheKey = GetCacheKey(cacheKey);
+
+ Lazy result;
+
+ using (WriteLock)
+ {
+ result = ContextItems[cacheKey] as Lazy; // null if key not found
+
+ // cannot create value within the lock, so if result.IsValueCreated is false, just
+ // do nothing here - means that if creation throws, a race condition could cause
+ // more than one thread to reach the return statement below and throw - accepted.
+
+ if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
+ {
+ result = new Lazy(getCacheItem);
+ ContextItems[cacheKey] = result;
+ }
+ }
+
+ // this may throw if getCacheItem throws, but this is the only place where
+ // it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
+ // and pretend exceptions were never inserted into cache to begin with.
+ return result.Value;
}
+
+ #endregion
+
+ #region Insert
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs
index c52d27f24a..c5870f26e8 100644
--- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs
@@ -1,10 +1,9 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
-using System.Web;
using System.Web.Caching;
-using Umbraco.Core.Logging;
using CacheItemPriority = System.Web.Caching.CacheItemPriority;
namespace Umbraco.Core.Cache
@@ -14,48 +13,52 @@ namespace Umbraco.Core.Cache
///
internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider
{
+ // locker object that supports upgradeable read locking
+ // does not need to support recursion if we implement the cache correctly and ensure
+ // that methods cannot be reentrant, ie we do NOT create values while holding a lock.
+ private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
+
private readonly System.Web.Caching.Cache _cache;
- private readonly DictionaryCacheWrapper _wrapper;
-
+
public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache)
{
_cache = cache;
- _wrapper = new DictionaryCacheWrapper(_cache, s => _cache.Get(s.ToString()), o => _cache.Remove(o.ToString()));
}
- protected override DictionaryCacheWrapper DictionaryCache
+ protected override IEnumerable GetDictionaryEntries()
{
- get { return _wrapper; }
+ const string prefix = CacheItemPrefix + "-";
+ return _cache.Cast()
+ .Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix));
}
+ protected override void RemoveEntry(string key)
+ {
+ _cache.Remove(key);
+ }
+
+ protected override object GetEntry(string key)
+ {
+ return _cache.Get(key);
+ }
+
+ #region Lock
+
+ protected override IDisposable ReadLock
+ {
+ get { return new ReadLock(_locker); }
+ }
+
+ protected override IDisposable WriteLock
+ {
+ get { return new WriteLock(_locker); }
+ }
+
+ #endregion
+
+ #region Get
+
///
- /// Clears all objects in the System.Web.Cache with the System.Type specified that satisfy the predicate
- ///
- public override void ClearCacheObjectTypes(Func predicate)
- {
- try
- {
- lock (Locker)
- {
- foreach (DictionaryEntry c in _cache)
- {
- var key = c.Key.ToString();
- if (_cache[key] != null
- && _cache[key] is T
- && predicate(key, (T)_cache[key]))
- {
- _cache.Remove(c.Key.ToString());
- }
- }
- }
- }
- catch (Exception e)
- {
- LogHelper.Error("Cache clearing error", e);
- }
- }
-
- ///
/// Gets (and adds if necessary) an item from the cache with all of the default parameters
///
///
@@ -81,25 +84,64 @@ namespace Umbraco.Core.Cache
{
cacheKey = GetCacheKey(cacheKey);
- using (var lck = new UpgradeableReadLock(Locker))
+ // NOTE - because we don't know what getCacheItem does, how long it will take and whether it will hang,
+ // getCacheItem should run OUTSIDE of the global application lock else we run into lock contention and
+ // nasty performance issues.
+
+ // So.... we insert a Lazy in the cache while holding the global application lock, and then rely
+ // on the Lazy lock to ensure that getCacheItem runs once and everybody waits on it, while the global
+ // application lock has been released.
+
+ // NOTE
+ // The Lazy value creation may produce a null value.
+ // Must make sure (for backward compatibility) that we pretend they are not in the cache.
+ // So if we find an entry in the cache that already has its value created and is null,
+ // pretend it was not there. If value is not already created, wait... and return null, that's
+ // what prior code did.
+
+ // NOTE
+ // The Lazy value creation may throw.
+
+ // So... the null value _will_ be in the cache but never returned
+
+ Lazy result;
+
+ // Fast!
+ // Only one thread can enter an UpgradeableReadLock at a time, but it does not prevent other
+ // threads to enter a ReadLock in the meantime -- only upgrading to WriteLock will prevent all
+ // reads. We first try with a normal ReadLock for maximum concurrency and take the penalty of
+ // having to re-lock in case there's no value. Would need to benchmark to figure out whether
+ // it's worth it, though...
+ using (new ReadLock(_locker))
{
- var result = DictionaryCache.Get(cacheKey);
- if (result == null)
- {
- lck.UpgradeToWriteLock();
-
- result = getCacheItem();
- if (result != null)
- {
- var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
- var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
-
- _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
- }
-
- }
- return result;
+ result = _cache.Get(cacheKey) as Lazy; // null if key not found
}
+ var value = result == null ? null : GetSafeLazyValue(result);
+ if (value != null) return value;
+
+ using (var lck = new UpgradeableReadLock(_locker))
+ {
+ result = _cache.Get(cacheKey) as Lazy; // null if key not found
+
+ // cannot create value within the lock, so if result.IsValueCreated is false, just
+ // do nothing here - means that if creation throws, a race condition could cause
+ // more than one thread to reach the return statement below and throw - accepted.
+
+ if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
+ {
+ result = new Lazy(getCacheItem);
+ var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
+ var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
+
+ lck.UpgradeToWriteLock();
+ _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
+ }
+ }
+
+ // this may throw if getCacheItem throws, but this is the only place where
+ // it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
+ // and pretend exceptions were never inserted into cache to begin with.
+ return result.Value;
}
public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
@@ -112,6 +154,10 @@ namespace Umbraco.Core.Cache
return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
}
+ #endregion
+
+ #region Insert
+
///
/// This overload is here for legacy purposes
///
@@ -124,15 +170,22 @@ namespace Umbraco.Core.Cache
///
internal void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null)
{
- var result = getCacheItem();
- if (result == null) return;
+ // NOTE - here also we must insert a Lazy but we can evaluate it right now
+ // and make sure we don't store a null value.
+
+ var result = new Lazy(getCacheItem);
+ var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
+ if (value == null) return; // do not store null values (backward compat)
cacheKey = GetCacheKey(cacheKey);
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
- _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
+ using (new WriteLock(_locker))
+ {
+ _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
+ }
}
public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
@@ -144,5 +197,7 @@ namespace Umbraco.Core.Cache
}
InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
}
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
index 7b1053c8a4..edf1ba5aa6 100644
--- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
@@ -16,17 +16,37 @@ namespace Umbraco.Core.Cache
///
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
{
- private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
+ private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
internal ObjectCache MemoryCache;
+ // an object that represent a value that has not been created yet
+ protected readonly object ValueNotCreated = new object();
+
public ObjectCacheRuntimeCacheProvider()
{
MemoryCache = new MemoryCache("in-memory");
}
+ protected object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false)
+ {
+ try
+ {
+ // if onlyIfValueIsCreated, do not trigger value creation
+ // must return something, though, to differenciate from null values
+ if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
+ return lazy.Value;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ #region Clear
+
public virtual void ClearAllCache()
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
MemoryCache.DisposeIfDisposable();
MemoryCache = new MemoryCache("in-memory");
@@ -35,7 +55,7 @@ namespace Umbraco.Core.Cache
public virtual void ClearCacheItem(string key)
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
if (MemoryCache[key] == null) return;
MemoryCache.Remove(key);
@@ -44,131 +64,179 @@ namespace Umbraco.Core.Cache
public virtual void ClearCacheObjectTypes(string typeName)
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
- var keysToRemove = MemoryCache
- .Where(c => c.Value != null && c.Value.GetType().ToString().InvariantEquals(typeName))
- .Select(c => c.Key)
- .ToArray();
- foreach (var k in keysToRemove)
- MemoryCache.Remove(k);
+ foreach (var key in MemoryCache
+ .Where(x =>
+ {
+ // x.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = GetSafeLazyValue((Lazy)x.Value, true);
+ return value == null || value.GetType().ToString().InvariantEquals(typeName);
+ })
+ .Select(x => x.Key)
+ .ToArray()) // ToArray required to remove
+ MemoryCache.Remove(key);
}
}
public virtual void ClearCacheObjectTypes()
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
var typeOfT = typeof (T);
- var keysToRemove = MemoryCache
- .Where(c => c.Value != null && c.Value.GetType() == typeOfT)
- .Select(c => c.Key)
- .ToArray();
- foreach (var k in keysToRemove)
- MemoryCache.Remove(k);
+ foreach (var key in MemoryCache
+ .Where(x =>
+ {
+ // x.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = GetSafeLazyValue((Lazy)x.Value, true);
+ return value == null || value.GetType() == typeOfT;
+ })
+ .Select(x => x.Key)
+ .ToArray()) // ToArray required to remove
+ MemoryCache.Remove(key);
}
}
public virtual void ClearCacheObjectTypes(Func predicate)
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
var typeOfT = typeof(T);
- var keysToRemove = MemoryCache
- .Where(c => c.Value != null && c.Value.GetType() == typeOfT && predicate(c.Key, (T)c.Value))
- .Select(c => c.Key)
- .ToArray();
- foreach (var k in keysToRemove)
- MemoryCache.Remove(k);
+ foreach (var key in MemoryCache
+ .Where(x =>
+ {
+ // x.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = GetSafeLazyValue((Lazy)x.Value, true);
+ if (value == null) return true;
+ return value.GetType() == typeOfT
+ && predicate(x.Key, (T) value);
+ })
+ .Select(x => x.Key)
+ .ToArray()) // ToArray required to remove
+ MemoryCache.Remove(key);
}
}
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
- var keysToRemove = (from c in MemoryCache where c.Key.InvariantStartsWith(keyStartsWith) select c.Key).ToList();
- foreach (var k in keysToRemove)
- {
- MemoryCache.Remove(k);
- }
+ foreach (var key in MemoryCache
+ .Where(x => x.Key.InvariantStartsWith(keyStartsWith))
+ .Select(x => x.Key)
+ .ToArray()) // ToArray required to remove
+ MemoryCache.Remove(key);
}
}
public virtual void ClearCacheByKeyExpression(string regexString)
{
- using (new WriteLock(Locker))
+ using (new WriteLock(_locker))
{
- var keysToRemove = (from c in MemoryCache where Regex.IsMatch(c.Key, regexString) select c.Key).ToList();
- foreach (var k in keysToRemove)
- {
- MemoryCache.Remove(k);
- }
+ foreach (var key in MemoryCache
+ .Where(x => Regex.IsMatch(x.Key, regexString))
+ .Select(x => x.Key)
+ .ToArray()) // ToArray required to remove
+ MemoryCache.Remove(key);
}
}
- public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
+ #endregion
+
+ #region Get
+
+ public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
{
- return (from c in MemoryCache
- where c.Key.InvariantStartsWith(keyStartsWith)
- select c.Value).ToList();
+ KeyValuePair[] entries;
+ using (new ReadLock(_locker))
+ {
+ entries = MemoryCache
+ .Where(x => x.Key.InvariantStartsWith(keyStartsWith))
+ .ToArray(); // evaluate while locked
+ }
+ return entries
+ .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null) // backward compat, don't store null values in the cache
+ .ToList();
}
public IEnumerable GetCacheItemsByKeyExpression(string regexString)
{
- return (from c in MemoryCache
- where Regex.IsMatch(c.Key, regexString)
- select c.Value).ToList();
+ KeyValuePair[] entries;
+ using (new ReadLock(_locker))
+ {
+ entries = MemoryCache
+ .Where(x => Regex.IsMatch(x.Key, regexString))
+ .ToArray(); // evaluate while locked
+ }
+ return entries
+ .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null) // backward compat, don't store null values in the cache
+ .ToList();
}
- public virtual object GetCacheItem(string cacheKey)
+ public object GetCacheItem(string cacheKey)
{
- var result = MemoryCache.Get(cacheKey);
- return result;
+ Lazy result;
+ using (new ReadLock(_locker))
+ {
+ result = MemoryCache.Get(cacheKey) as Lazy; // null if key not found
+ }
+ return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
}
- public virtual object GetCacheItem(string cacheKey, Func getCacheItem)
+ public object GetCacheItem(string cacheKey, Func getCacheItem)
{
return GetCacheItem(cacheKey, getCacheItem, null);
}
- public object GetCacheItem(
- string cacheKey,
- Func getCacheItem,
- TimeSpan? timeout,
- bool isSliding = false,
- CacheItemPriority priority = CacheItemPriority.Normal,
- CacheItemRemovedCallback removedCallback = null,
- string[] dependentFiles = null)
+ public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
- using (var lck = new UpgradeableReadLock(Locker))
- {
- var result = MemoryCache.Get(cacheKey);
- if (result == null)
- {
- lck.UpgradeToWriteLock();
+ // see notes in HttpRuntimeCacheProvider
- result = getCacheItem();
- if (result != null)
- {
- var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
- MemoryCache.Set(cacheKey, result, policy);
- }
+ Lazy result;
+
+ using (var lck = new UpgradeableReadLock(_locker))
+ {
+ result = MemoryCache.Get(cacheKey) as Lazy;
+ if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
+ {
+ result = new Lazy(getCacheItem);
+ var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
+
+ lck.UpgradeToWriteLock();
+ MemoryCache.Set(cacheKey, result, policy);
}
- return result;
}
+
+ return result.Value;
}
+ #endregion
+
+ #region Insert
+
public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
- object result = getCacheItem();
- if (result != null)
- {
- var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
- MemoryCache.Set(cacheKey, result, policy);
- }
+ // NOTE - here also we must insert a Lazy but we can evaluate it right now
+ // and make sure we don't store a null value.
+
+ var result = new Lazy(getCacheItem);
+ var value = result.Value; // force evaluation now
+ if (value == null) return; // do not store null values (backward compat)
+
+ var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
+ MemoryCache.Set(cacheKey, result, policy);
}
+ #endregion
+
private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs
index 601e2819ff..51cf37aa23 100644
--- a/src/Umbraco.Core/CacheHelper.cs
+++ b/src/Umbraco.Core/CacheHelper.cs
@@ -44,7 +44,7 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(HttpRuntime.Cache),
new StaticCacheProvider(),
- new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current)))
+ new HttpRequestCacheProvider())
{
}
@@ -56,7 +56,7 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(cache),
new StaticCacheProvider(),
- new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current)))
+ new HttpRequestCacheProvider())
{
}
diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs
index 768ae37c9b..dceed0e259 100644
--- a/src/Umbraco.Core/Models/ContentExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentExtensions.cs
@@ -240,6 +240,28 @@ namespace Umbraco.Core.Models
return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Contains(recycleBinId.ToInvariantString());
}
+
+ ///
+ /// Removes characters that are not valide XML characters from all entity properties
+ /// of type string. See: http://stackoverflow.com/a/961504/5018
+ ///
+ ///
+ ///
+ /// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it.
+ ///
+ ///
+ public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity)
+ {
+ entity.Name = entity.Name.ToValidXmlString();
+ foreach (var property in entity.Properties)
+ {
+ if (property.Value is string)
+ {
+ var value = (string)property.Value;
+ property.Value = value.ToValidXmlString();
+ }
+ }
+ }
///
/// Checks if the IContentBase has children
@@ -734,10 +756,6 @@ namespace Umbraco.Core.Models
{
return ((PackagingService)(ApplicationContext.Current.Services.PackagingService)).Export(member);
}
-
#endregion
}
-
-
-
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/EntityExtensions.cs b/src/Umbraco.Core/Models/EntityExtensions.cs
index 6daf99a58d..9fbd4ce592 100644
--- a/src/Umbraco.Core/Models/EntityExtensions.cs
+++ b/src/Umbraco.Core/Models/EntityExtensions.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using System.Linq;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
@@ -23,5 +20,5 @@ namespace Umbraco.Core.Models
var dirty = (IRememberBeingDirty)entity;
return dirty.WasPropertyDirty("Id");
}
- }
-}
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ITag.cs b/src/Umbraco.Core/Models/ITag.cs
index fe5eaf4a9a..53b23d65c5 100644
--- a/src/Umbraco.Core/Models/ITag.cs
+++ b/src/Umbraco.Core/Models/ITag.cs
@@ -3,6 +3,9 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
+ ///
+ /// Represents a Tag, which is composed of a Text, Group and NodeCount property.
+ ///
public interface ITag : IAggregateRoot
{
[DataMember]
diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs
index b304f72777..1aead03726 100644
--- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs
+++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs
@@ -1,9 +1,11 @@
using System;
-using System.Collections.Generic;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models.Membership
{
+ ///
+ /// Defines the base contract for and
+ ///
public interface IMembershipUser : IAggregateRoot
{
object ProviderUserKey { get; set; }
diff --git a/src/Umbraco.Core/Models/TaggedEntity.cs b/src/Umbraco.Core/Models/TaggedEntity.cs
index decd4220fe..890412a6b6 100644
--- a/src/Umbraco.Core/Models/TaggedEntity.cs
+++ b/src/Umbraco.Core/Models/TaggedEntity.cs
@@ -2,6 +2,11 @@
namespace Umbraco.Core.Models
{
+ ///
+ /// Represents a tagged entity.
+ ///
+ /// Note that it is the properties of an entity (like Content, Media, Members, etc.) that is tagged,
+ /// which is why this class is composed of a list of tagged properties and an Id reference to the actual entity.
public class TaggedEntity
{
public TaggedEntity(int entityId, IEnumerable taggedProperties)
@@ -10,7 +15,14 @@ namespace Umbraco.Core.Models
TaggedProperties = taggedProperties;
}
+ ///
+ /// Id of the entity, which is tagged
+ ///
public int EntityId { get; private set; }
+
+ ///
+ /// An enumerable list of tagged properties
+ ///
public IEnumerable TaggedProperties { get; private set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/TaggedProperty.cs b/src/Umbraco.Core/Models/TaggedProperty.cs
index 3b92413cdb..9013c25887 100644
--- a/src/Umbraco.Core/Models/TaggedProperty.cs
+++ b/src/Umbraco.Core/Models/TaggedProperty.cs
@@ -2,6 +2,9 @@
namespace Umbraco.Core.Models
{
+ ///
+ /// Represents a tagged property on an entity.
+ ///
public class TaggedProperty
{
public TaggedProperty(int propertyTypeId, string propertyTypeAlias, IEnumerable tags)
@@ -11,8 +14,19 @@ namespace Umbraco.Core.Models
Tags = tags;
}
+ ///
+ /// Id of the PropertyType, which this tagged property is based on
+ ///
public int PropertyTypeId { get; private set; }
+
+ ///
+ /// Alias of the PropertyType, which this tagged property is based on
+ ///
public string PropertyTypeAlias { get; private set; }
+
+ ///
+ /// An enumerable list of Tags for the property
+ ///
public IEnumerable Tags { get; private set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/MonitorLock.cs b/src/Umbraco.Core/MonitorLock.cs
new file mode 100644
index 0000000000..9d17c86be8
--- /dev/null
+++ b/src/Umbraco.Core/MonitorLock.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Umbraco.Core
+{
+ ///
+ /// Provides an equivalent to the c# lock statement, to be used in a using block.
+ ///
+ /// Ie replace lock (o) {...} by using (new MonitorLock(o)) { ... }
+ public class MonitorLock : IDisposable
+ {
+ private readonly object _locker;
+ private readonly bool _entered;
+
+ ///
+ /// Initializes a new instance of the class with an object to lock.
+ ///
+ /// The object to lock.
+ /// Should always be used within a using block.
+ public MonitorLock(object locker)
+ {
+ _locker = locker;
+ _entered = false;
+ System.Threading.Monitor.Enter(_locker, ref _entered);
+ }
+
+ void IDisposable.Dispose()
+ {
+ if (_entered)
+ System.Threading.Monitor.Exit(_locker);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs
index 17097ee7a5..87eb06e295 100644
--- a/src/Umbraco.Core/ObjectResolution/Resolution.cs
+++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs
@@ -63,6 +63,11 @@ namespace Umbraco.Core.ObjectResolution
}
}
+ // NOTE - the ugly code below exists only because of umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers
+ // which wants to re-register actions and handlers instead of properly restarting the application. Don't even think
+ // about using it for anything else. Also, while the backdoor is open, the resolution system is locked so nothing
+ // can work properly => deadlocks. Therefore, open the backdoor, do resolution changes EXCLUSIVELY, and close the door!
+
///
/// Returns a disposable object that reprents dirty access to temporarily unfrozen resolution configuration.
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index c1633daa75..42bec777aa 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -255,6 +255,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
+
+ //Ensure that strings don't contain characters that are invalid in XML
+ entity.SanitizeEntityPropertiesForXmlStorage();
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
@@ -366,6 +369,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
+ //Ensure that strings don't contain characters that are invalid in XML
+ entity.SanitizeEntityPropertiesForXmlStorage();
+
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
index f98c0f1e71..2609231c63 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
@@ -146,6 +146,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
((DictionaryItem)entity).AddingEntity();
+ foreach (var translation in entity.Translations)
+ translation.Value = translation.Value.ToValidXmlString();
+
var factory = new DictionaryItemFactory();
var dto = factory.BuildDto(entity);
@@ -167,6 +170,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
((Entity)entity).UpdatingEntity();
+ foreach (var translation in entity.Translations)
+ translation.Value = translation.Value.ToValidXmlString();
+
var factory = new DictionaryItemFactory();
var dto = factory.BuildDto(entity);
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
index d916c91e96..a736f16e3c 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
@@ -220,6 +220,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
+ //Ensure that strings don't contain characters that are invalid in XML
+ entity.SanitizeEntityPropertiesForXmlStorage();
+
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
@@ -289,6 +292,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure unique name on the same level
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
+ //Ensure that strings don't contain characters that are invalid in XML
+ entity.SanitizeEntityPropertiesForXmlStorage();
+
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
index 2bbb3439ca..f84cb9c1b6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
@@ -209,6 +209,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
((Member)entity).AddingEntity();
+ //Ensure that strings don't contain characters that are invalid in XML
+ entity.SanitizeEntityPropertiesForXmlStorage();
+
var factory = new MemberFactory(NodeObjectTypeId, entity.Id);
var dto = factory.BuildDto(entity);
@@ -284,6 +287,9 @@ namespace Umbraco.Core.Persistence.Repositories
//Updates Modified date
((Member)entity).UpdatingEntity();
+ //Ensure that strings don't contain characters that are invalid in XML
+ entity.SanitizeEntityPropertiesForXmlStorage();
+
var dirtyEntity = (ICanBeDirty) entity;
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs
index b89b13630f..ebcd967cc2 100644
--- a/src/Umbraco.Core/Security/MembershipProviderBase.cs
+++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs
@@ -211,7 +211,7 @@ namespace Umbraco.Core.Security
_enablePasswordReset = config.GetValue("enablePasswordReset", false);
_requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false);
_requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true);
- _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
+ _maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 20, false, 0);
_passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
_minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80);
_minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80);
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index 5d78f6df86..82c21dd2fc 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -5,128 +5,180 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services
{
-
///
/// Defines the MemberService, which is an easy access to operations involving (umbraco) members.
///
public interface IMemberService : IMembershipMemberService
{
- int Count(string contentTypeAlias = null);
-
+ ///
+ /// Creates an object without persisting it
+ ///
+ /// This method is convenient for when you need to add properties to a new Member
+ /// before persisting it in order to limit the amount of times its saved.
+ /// Also note that the returned will not have an Id until its saved.
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// Alias of the MemberType the Member should be based on
+ ///
IMember CreateMember(string username, string email, string name, string memberTypeAlias);
+
+ ///
+ /// Creates an object without persisting it
+ ///
+ /// This method is convenient for when you need to add properties to a new Member
+ /// before persisting it in order to limit the amount of times its saved.
+ /// Also note that the returned will not have an Id until its saved.
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// MemberType the Member should be based on
+ ///
IMember CreateMember(string username, string email, string name, IMemberType memberType);
+
+ ///
+ /// Creates and persists a Member
+ ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// Alias of the MemberType the Member should be based on
+ ///
IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias);
+
+ ///
+ /// Creates and persists a Member
+ ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// MemberType the Member should be based on
+ ///
IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType);
///
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
///
- /// The member to save the password for
- ///
- ///
- /// This method exists so that Umbraco developers can use one entry point to create/update members if they choose to.
- ///
+ /// This method exists so that Umbraco developers can use one entry point to create/update
+ /// Members if they choose to.
+ /// The Member to save the password for
+ /// The password to encrypt and save
void SavePassword(IMember member, string password);
///
- /// Checks if a member with the id exists
+ /// Gets the count of Members by an optional MemberType alias
///
- ///
- ///
+ /// If no alias is supplied then the count for all Member will be returned
+ /// Optional alias for the MemberType when counting number of Members
+ /// with number of Members
+ int Count(string memberTypeAlias = null);
+
+ ///
+ /// Checks if a Member with the id exists
+ ///
+ /// Id of the Member
+ /// True if the Member exists otherwise False
bool Exists(int id);
///
- /// Get a member by the unique key
+ /// Gets a Member by the unique key
///
- ///
- ///
+ /// The guid key corresponds to the unique id in the database
+ /// and the user id in the membership provider.
+ /// Id
+ ///
IMember GetByKey(Guid id);
///
- /// Gets a member by it's id
+ /// Gets a Member by its integer id
///
- ///
- ///
+ /// Id
+ ///
IMember GetById(int id);
///
- /// Get all members for the member type alias
+ /// Gets all Members for the specified MemberType alias
///
- ///
- ///
+ /// Alias of the MemberType
+ ///
IEnumerable GetMembersByMemberType(string memberTypeAlias);
///
- /// Get all members for the member type id
+ /// Gets all Members for the MemberType id
///
- ///
- ///
+ /// Id of the MemberType
+ ///
IEnumerable GetMembersByMemberType(int memberTypeId);
///
- /// Get all members in the member group name specified
+ /// Gets all Members within the specified MemberGroup name
///
- ///
- ///
+ /// Name of the MemberGroup
+ ///
IEnumerable GetMembersByGroup(string memberGroupName);
///
- /// Get all members with the ids specified
+ /// Gets all Members with the ids specified
///
- ///
- ///
+ /// If no Ids are specified all Members will be retrieved
+ /// Optional list of Member Ids
+ ///
IEnumerable GetAllMembers(params int[] ids);
///
- /// Delete members of the specified member type id
+ /// Delete Members of the specified MemberType id
///
- ///
+ /// Id of the MemberType
void DeleteMembersOfType(int memberTypeId);
///
- /// Find members based on their display name
+ /// Finds Members based on their display name
///
- ///
- ///
- ///
- ///
- ///
- ///
+ /// Display name to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
///
- /// Get members based on a property search
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ /// The type of match to make as . Default is
+ ///
IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact);
///
- /// Get members based on a property search
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ /// The type of match to make as . Default is
+ ///
IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
///
- /// Get members based on a property search
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ ///
IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value);
///
- /// Get members based on a property search
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ /// The type of match to make as . Default is
+ ///
IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs
index d4d865614a..801f88d9ac 100644
--- a/src/Umbraco.Core/Services/IMembershipMemberService.cs
+++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
-using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Querying;
@@ -13,12 +12,21 @@ namespace Umbraco.Core.Services
/// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl.
///
public interface IMembershipMemberService : IMembershipMemberService, IMembershipRoleService
- {
+ {
+ ///
+ /// Creates and persists a new Member
+ ///
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// which the Member should be based on
+ ///
IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType);
}
///
/// Defines part of the UserService/MemberService, which is specific to methods used by the membership provider.
+ /// The generic type is restricted to . The implementation of this interface uses
+ /// either for the MemberService or for the UserService.
///
///
/// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl.
@@ -27,69 +35,124 @@ namespace Umbraco.Core.Services
where T : class, IMembershipUser
{
///
- /// Returns the default member type alias
+ /// Gets the total number of Members or Users based on the count type
///
- ///
- string GetDefaultMemberType();
-
- ///
- /// Checks if a member with the username exists
- ///
- ///
- ///
- bool Exists(string username);
-
- ///
- /// Creates and persists a new member
- ///
- ///
- ///
- ///
- /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
- ///
- ///
- ///
- T CreateWithIdentity(string username, string email, string rawPasswordValue, string memberTypeAlias);
-
- ///
- /// Gets the member by the provider key
- ///
- ///
- ///
- T GetByProviderKey(object id);
-
- ///
- /// Get a member by email
- ///
- ///
- ///
- T GetByEmail(string email);
-
- T GetByUsername(string login);
-
- void Delete(T membershipUser);
-
- void Save(T entity, bool raiseEvents = true);
-
- void Save(IEnumerable entities, bool raiseEvents = true);
-
- IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
-
- IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
-
- ///
- /// Gets the total number of members based on the count type
- ///
- ///
+ ///
+ /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
+ /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
+ /// but that is how MS have made theirs so we'll follow that principal.
+ ///
+ /// to count by
+ /// with number of Members or Users for passed in type
int GetCount(MemberCountType countType);
///
- /// Gets a list of paged member data
+ /// Gets the default MemberType alias
///
- ///
- ///
- ///
- ///
+ /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
+ /// return the first type that is not an admin, otherwise if there's only one we will return that one.
+ /// Alias of the default MemberType
+ string GetDefaultMemberType();
+
+ ///
+ /// Checks if a Member with the username exists
+ ///
+ /// Username to check
+ /// True if the Member exists otherwise False
+ bool Exists(string username);
+
+ ///
+ /// Creates and persists a new
+ ///
+ /// An can be of type or
+ /// Username of the to create
+ /// Email of the to create
+ /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
+ /// Alias of the Type
+ ///
+ T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias);
+
+ ///
+ /// Gets an by its provider key
+ ///
+ /// An can be of type or
+ /// Id to use for retrieval
+ ///
+ T GetByProviderKey(object id);
+
+ ///
+ /// Get an by email
+ ///
+ /// An can be of type or
+ /// Email to use for retrieval
+ ///
+ T GetByEmail(string email);
+
+ ///
+ /// Get an by username
+ ///
+ /// An can be of type or
+ /// Username to use for retrieval
+ ///
+ T GetByUsername(string username);
+
+ ///
+ /// Deletes an
+ ///
+ /// An can be of type or
+ /// or to Delete
+ void Delete(T membershipUser);
+
+ ///
+ /// Saves an
+ ///
+ /// An can be of type or
+ /// or to Save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
+ void Save(T entity, bool raiseEvents = true);
+
+ ///
+ /// Saves a list of objects
+ ///
+ /// An can be of type or
+ /// to save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
+ void Save(IEnumerable entities, bool raiseEvents = true);
+
+ ///
+ /// Finds a list of objects by a partial email string
+ ///
+ /// An can be of type or
+ /// Partial email string to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
+ IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
+
+ ///
+ /// Finds a list of objects by a partial username
+ ///
+ /// An can be of type or
+ /// Partial username to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
+ IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
+
+ ///
+ /// Gets a list of paged objects
+ ///
+ /// An can be of type or
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ ///
IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/IMembershipUserService.cs b/src/Umbraco.Core/Services/IMembershipUserService.cs
index 86dc09d225..182d08f9bd 100644
--- a/src/Umbraco.Core/Services/IMembershipUserService.cs
+++ b/src/Umbraco.Core/Services/IMembershipUserService.cs
@@ -10,6 +10,16 @@ namespace Umbraco.Core.Services
///
public interface IMembershipUserService : IMembershipMemberService
{
+ ///
+ /// Creates and persists a new User
+ ///
+ /// The user will be saved in the database and returned with an Id.
+ /// This method is convenient when you need to perform operations, which needs the
+ /// Id of the user once its been created.
+ /// Username of the User to create
+ /// Email of the User to create
+ /// which the User should be based on
+ ///
IUser CreateUserWithIdentity(string username, string email, IUserType userType);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs
index 68a53d1b7f..9dcc6df615 100644
--- a/src/Umbraco.Core/Services/IRelationService.cs
+++ b/src/Umbraco.Core/Services/IRelationService.cs
@@ -237,6 +237,15 @@ namespace Umbraco.Core.Services
/// Returns True if any relations exist between the entities, otherwise False
bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
+ ///
+ /// Checks whether two items are related
+ ///
+ /// Id of the Parent relation
+ /// Id of the Child relation
+ /// Alias of the type of relation to create
+ /// Returns True if any relations exist between the entities, otherwise False
+ bool AreRelated(int parentId, int childId, string relationTypeAlias);
+
///
/// Saves a
///
diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs
index 20043327e6..02ac6f79ff 100644
--- a/src/Umbraco.Core/Services/ITagService.cs
+++ b/src/Umbraco.Core/Services/ITagService.cs
@@ -1,6 +1,4 @@
using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
@@ -16,55 +14,110 @@ namespace Umbraco.Core.Services
///
public interface ITagService : IService
{
-
+ ///
+ /// Gets tagged Content by a specific 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Content, not the actual Content item.
+ /// Name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTaggedContentByTagGroup(string tagGroup);
+
+ ///
+ /// Gets tagged Content by a specific 'Tag' and optional 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Content, not the actual Content item.
+ /// Tag
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTaggedContentByTag(string tag, string tagGroup = null);
+
+ ///
+ /// Gets tagged Media by a specific 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Media, not the actual Media item.
+ /// Name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTaggedMediaByTagGroup(string tagGroup);
+
+ ///
+ /// Gets tagged Media by a specific 'Tag' and optional 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Media, not the actual Media item.
+ /// Tag
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTaggedMediaByTag(string tag, string tagGroup = null);
+
+ ///
+ /// Gets tagged Members by a specific 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Member, not the actual Member item.
+ /// Name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTaggedMembersByTagGroup(string tagGroup);
+
+ ///
+ /// Gets tagged Members by a specific 'Tag' and optional 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Member, not the actual Member item.
+ /// Tag
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTaggedMembersByTag(string tag, string tagGroup = null);
///
- /// Get every tag stored in the database (with optional group)
+ /// Gets every tag stored in the database
///
- IEnumerable GetAllTags(string group = null);
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ IEnumerable GetAllTags(string tagGroup = null);
///
- /// Get all tags for content items (with optional group)
+ /// Gets all tags for content items
///
- /// Optional group
- ///
- IEnumerable GetAllContentTags(string group = null);
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ IEnumerable GetAllContentTags(string tagGroup = null);
///
- /// Get all tags for media items (with optional group)
+ /// Gets all tags for media items
///
- /// Optional group
- ///
- IEnumerable GetAllMediaTags(string group = null);
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ IEnumerable GetAllMediaTags(string tagGroup = null);
///
- /// Get all tags for member items (with optional group)
+ /// Gets all tags for member items
///
- /// Optional group
- ///
- IEnumerable GetAllMemberTags(string group = null);
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ IEnumerable GetAllMemberTags(string tagGroup = null);
///
- /// Returns all tags attached to a property by entity id
+ /// Gets all tags attached to a property by entity id
///
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
/// The content item id to get tags for
/// Property type alias
- /// Optional tag group
- ///
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null);
///
- /// Returns all tags attached to an entity (content, media or member) by entity id
+ /// Gets all tags attached to an entity (content, media or member) by entity id
///
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
/// The content item id to get tags for
- /// Optional tag group
- ///
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
IEnumerable GetTagsForEntity(int contentId, string tagGroup = null);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs
index 191f9085f6..559bf100d0 100644
--- a/src/Umbraco.Core/Services/IUserService.cs
+++ b/src/Umbraco.Core/Services/IUserService.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Web;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Services
@@ -12,18 +11,18 @@ namespace Umbraco.Core.Services
///
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
///
- /// The user to save the password for
- ///
///
/// This method exists so that Umbraco developers can use one entry point to create/update users if they choose to.
///
+ /// The user to save the password for
+ /// The password to save
void SavePassword(IUser user, string password);
///
- /// To permanently delete the user pass in true, otherwise they will just be disabled
+ /// Deletes or disables a User
///
- ///
- ///
+ /// to delete
+ /// True to permanently delete the user, False to disable the user
void Delete(IUser user, bool deletePermanently);
///
@@ -34,79 +33,86 @@ namespace Umbraco.Core.Services
IProfile GetProfileById(int id);
///
- /// Get profile by username
+ /// Gets a profile by username
///
- ///
- ///
+ /// Username
+ ///
IProfile GetProfileByUserName(string username);
///
- /// Get user by Id
+ /// Gets a user by Id
///
- ///
- ///
+ /// Id of the user to retrieve
+ ///
IUser GetUserById(int id);
///
- /// This is useful when an entire section is removed from config
+ /// Removes a specific section from all users
///
- ///
+ /// This is useful when an entire section is removed from config
+ /// Alias of the section to remove
void DeleteSectionFromAllUsers(string sectionAlias);
///
- /// Get permissions set for user and specified node ids
+ /// Get permissions set for a user and optional node ids
///
- ///
- ///
- /// Specifiying nothing will return all user permissions for all nodes
- ///
- ///
+ /// If no permissions are found for a particular entity then the user's default permissions will be applied
+ /// User to retrieve permissions for
+ /// Specifiying nothing will return all user permissions for all nodes
+ /// An enumerable list of
IEnumerable GetPermissions(IUser user, params int[] nodeIds);
///
/// Replaces the same permission set for a single user to any number of entities
///
- ///
- ///
- ///
+ /// If no 'entityIds' are specified all permissions will be removed for the specified user.
+ /// Id of the user
+ /// Permissions as enumerable list of
+ /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed.
void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds);
#region User types
+ ///
+ /// Gets all UserTypes or thosed specified as parameters
+ ///
+ /// Optional Ids of UserTypes to retrieve
+ /// An enumerable list of
IEnumerable GetAllUserTypes(params int[] ids);
///
- /// Gets an IUserType by its Alias
+ /// Gets a UserType by its Alias
///
/// Alias of the UserType to retrieve
///
IUserType GetUserTypeByAlias(string alias);
///
- /// Gets an IUserType by its Id
+ /// Gets a UserType by its Id
///
- ///
- ///
+ /// Id of the UserType to retrieve
+ ///
IUserType GetUserTypeById(int id);
///
- /// Gets an IUserType by its Name
+ /// Gets a UserType by its Name
///
/// Name of the UserType to retrieve
///
IUserType GetUserTypeByName(string name);
///
- /// Saves an IUserType
+ /// Saves a UserType
///
- ///
- ///
+ /// UserType to save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
void SaveUserType(IUserType userType, bool raiseEvents = true);
///
- /// Deletes an IUserType
+ /// Deletes a UserType
///
- ///
+ /// UserType to delete
void DeleteUserType(IUserType userType);
#endregion
diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs
index ecf2874ea6..8a8e99f255 100644
--- a/src/Umbraco.Core/Services/MemberService.cs
+++ b/src/Umbraco.Core/Services/MemberService.cs
@@ -1,21 +1,15 @@
using System;
using System.Collections.Generic;
-using System.Runtime.CompilerServices;
using System.Threading;
using System.Web.Security;
using System.Xml.Linq;
-using System.Xml.Linq;
-using Umbraco.Core.Auditing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
-using Umbraco.Core.Events;
using Umbraco.Core.Models;
-using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using System.Linq;
@@ -23,7 +17,6 @@ using Umbraco.Core.Security;
namespace Umbraco.Core.Services
{
-
///
/// Represents the MemberService.
///
@@ -77,9 +70,11 @@ namespace Umbraco.Core.Services
#region IMemberService Implementation
///
- /// Get the default member type from the database - first check if the type "Member" is there, if not choose the first one found
+ /// Gets the default MemberType alias
///
- ///
+ /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
+ /// return the first type that is not an admin, otherwise if there's only one we will return that one.
+ /// Alias of the default MemberType
public string GetDefaultMemberType()
{
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
@@ -101,10 +96,10 @@ namespace Umbraco.Core.Services
}
///
- /// Checks if a member with the username exists
+ /// Checks if a Member with the username exists
///
- ///
- ///
+ /// Username to check
+ /// True if the Member exists otherwise False
public bool Exists(string username)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -116,11 +111,10 @@ namespace Umbraco.Core.Services
///
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
///
- /// The member to save the password for
- ///
- ///
- /// This method exists so that Umbraco developers can use one entry point to create/update members if they choose to.
- ///
+ /// This method exists so that Umbraco developers can use one entry point to create/update
+ /// Members if they choose to.
+ /// The Member to save the password for
+ /// The password to encrypt and save
public void SavePassword(IMember member, string password)
{
if (member == null) throw new ArgumentNullException("member");
@@ -148,10 +142,10 @@ namespace Umbraco.Core.Services
}
///
- /// Checks if a member with the id exists
+ /// Checks if a Member with the id exists
///
- ///
- ///
+ /// Id of the Member
+ /// True if the Member exists otherwise False
public bool Exists(int id)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -161,10 +155,10 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a Member by its integer Id
+ /// Gets a Member by its integer id
///
- ///
- ///
+ /// Id
+ ///
public IMember GetById(int id)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -174,14 +168,12 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a Member by its Guid key
+ /// Gets a Member by the unique key
///
- ///
- /// The guid key corresponds to the unique id in the database
- /// and the user id in the membership provider.
- ///
- ///
- ///
+ /// The guid key corresponds to the unique id in the database
+ /// and the user id in the membership provider.
+ /// Id
+ ///
public IMember GetByKey(Guid id)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -193,10 +185,10 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members by their MemberType
+ /// Gets all Members for the specified MemberType alias
///
- ///
- ///
+ /// Alias of the MemberType
+ ///
public IEnumerable GetMembersByMemberType(string memberTypeAlias)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -208,10 +200,10 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members by their MemberType
+ /// Gets all Members for the MemberType id
///
- ///
- ///
+ /// Id of the MemberType
+ ///
public IEnumerable GetMembersByMemberType(int memberTypeId)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -224,10 +216,10 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members by the MemberGroup they are part of
+ /// Gets all Members within the specified MemberGroup name
///
- ///
- ///
+ /// Name of the MemberGroup
+ ///
public IEnumerable GetMembersByGroup(string memberGroupName)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -237,10 +229,11 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of all Members
+ /// Gets all Members with the ids specified
///
- ///
- ///
+ /// If no Ids are specified all Members will be retrieved
+ /// Optional list of Member Ids
+ ///
public IEnumerable GetAllMembers(params int[] ids)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -249,6 +242,10 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Delete Members of the specified MemberType id
+ ///
+ /// Id of the MemberType
public void DeleteMembersOfType(int memberTypeId)
{
using (new WriteLock(Locker))
@@ -272,6 +269,15 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Finds Members based on their display name
+ ///
+ /// Display name to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
public IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -314,14 +320,14 @@ namespace Umbraco.Core.Services
}
///
- /// Does a search for members that contain the specified string in their email address
+ /// Finds a list of objects by a partial email string
///
- ///
- ///
- ///
- ///
- ///
- ///
+ /// Partial email string to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -354,6 +360,15 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Finds a list of objects by a partial username
+ ///
+ /// Partial username to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -387,12 +402,12 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members with a certain string property value
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ /// The type of match to make as . Default is
+ ///
public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -443,12 +458,12 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members with a certain integer property value
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ /// The type of match to make as . Default is
+ ///
public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -502,11 +517,11 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members with a certain boolean property value
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ ///
public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -523,12 +538,12 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a list of Members with a certain date time property value
+ /// Gets a list of Members based on a property search
///
- ///
- ///
- ///
- ///
+ /// Alias of the PropertyType to search for
+ /// Value to match
+ /// The type of match to make as . Default is
+ ///
public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -586,15 +601,15 @@ namespace Umbraco.Core.Services
#region IMembershipMemberService Implementation
///
- /// Returns the count of members based on the countType
+ /// Gets the total number of Members based on the count type
///
- ///
- ///
///
/// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
/// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
/// but that is how MS have made theirs so we'll follow that principal.
///
+ /// to count by
+ /// with number of Members for passed in type
public int GetCount(MemberCountType countType)
{
using (var repository = _repositoryFactory.CreateMemberRepository(_uowProvider.GetUnitOfWork()))
@@ -635,6 +650,13 @@ namespace Umbraco.Core.Services
}
+ ///
+ /// Gets a list of paged objects
+ ///
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ ///
public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -644,23 +666,32 @@ namespace Umbraco.Core.Services
}
}
- public int Count(string contentTypeAlias = null)
+ ///
+ /// Gets the count of Members by an optional MemberType alias
+ ///
+ /// If no alias is supplied then the count for all Member will be returned
+ /// Optional alias for the MemberType when counting number of Members
+ /// with number of Members
+ public int Count(string memberTypeAlias = null)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMemberRepository(uow))
{
- return repository.Count(contentTypeAlias);
+ return repository.Count(memberTypeAlias);
}
}
///
- /// Creates a member object
+ /// Creates an object without persisting it
///
- ///
- ///
- ///
- ///
- ///
+ /// This method is convenient for when you need to add properties to a new Member
+ /// before persisting it in order to limit the amount of times its saved.
+ /// Also note that the returned will not have an Id until its saved.
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// Alias of the MemberType the Member should be based on
+ ///
public IMember CreateMember(string username, string email, string name, string memberTypeAlias)
{
var memberType = FindMemberTypeByAlias(memberTypeAlias);
@@ -668,13 +699,16 @@ namespace Umbraco.Core.Services
}
///
- /// Creates a new member object
+ /// Creates an object without persisting it
///
- ///
- ///
- ///
- ///
- ///
+ /// This method is convenient for when you need to add properties to a new Member
+ /// before persisting it in order to limit the amount of times its saved.
+ /// Also note that the returned will not have an Id until its saved.
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// MemberType the Member should be based on
+ ///
public IMember CreateMember(string username, string email, string name, IMemberType memberType)
{
var member = new Member(name, email.ToLower().Trim(), username, memberType);
@@ -685,13 +719,15 @@ namespace Umbraco.Core.Services
}
///
- /// Creates a member with an Id
+ /// Creates and persists a Member
///
- ///
- ///
- ///
- ///
- ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// Alias of the MemberType the Member should be based on
+ ///
public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias)
{
var memberType = FindMemberTypeByAlias(memberTypeAlias);
@@ -699,49 +735,65 @@ namespace Umbraco.Core.Services
}
///
- /// Creates a member with an Id, the username will be used as their name
+ /// Creates and persists a Member
///
- ///
- ///
- ///
- ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// MemberType the Member should be based on
+ ///
public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType)
{
return CreateMemberWithIdentity(username, email, username, memberType);
}
///
- /// Creates a member with an Id
+ /// Creates and persists a Member
///
- ///
- ///
- ///
- ///
- ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// MemberType the Member should be based on
+ ///
public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType)
{
return CreateMemberWithIdentity(username, email, name, "", memberType);
}
///
- /// Creates and persists a new Member
+ /// Creates and persists a new
///
- ///
- ///
- ///
- ///
- ///
- IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string rawPasswordValue, string memberTypeAlias)
+ /// An can be of type or
+ /// Username of the to create
+ /// Email of the to create
+ /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
+ /// Alias of the Type
+ ///
+ IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias)
{
var memberType = FindMemberTypeByAlias(memberTypeAlias);
- return CreateMemberWithIdentity(username, email, username, rawPasswordValue, memberType);
+ return CreateMemberWithIdentity(username, email, username, passwordValue, memberType);
}
- private IMember CreateMemberWithIdentity(string username, string email, string name, string rawPasswordValue, IMemberType memberType)
+ ///
+ /// Creates and persists a Member
+ ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// Name of the Member to create
+ /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
+ /// MemberType the Member should be based on
+ ///
+ private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType)
{
if (memberType == null) throw new ArgumentNullException("memberType");
- var member = new Member(name, email.ToLower().Trim(), username, rawPasswordValue, memberType);
+ var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType);
if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this))
{
@@ -771,13 +823,10 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a Member by its Id
+ /// Gets an by its provider key
///
- ///
- /// The Id should be an integer or Guid.
- ///
- ///
- ///
+ /// Id to use for retrieval
+ ///
public IMember GetByProviderKey(object id)
{
var asGuid = id.TryConvertTo();
@@ -795,10 +844,10 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a Member by its Email
+ /// Get an by email
///
- ///
- ///
+ /// Email to use for retrieval
+ ///
public IMember GetByEmail(string email)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -812,16 +861,16 @@ namespace Umbraco.Core.Services
}
///
- /// Gets a Member by its Username
+ /// Get an by username
///
- ///
- ///
- public IMember GetByUsername(string userName)
+ /// Username to use for retrieval
+ ///
+ public IMember GetByUsername(string username)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMemberRepository(uow))
{
- var query = Query.Builder.Where(x => x.Username.Equals(userName));
+ var query = Query.Builder.Where(x => x.Username.Equals(username));
var member = repository.GetByQuery(query).FirstOrDefault();
return member;
@@ -829,9 +878,9 @@ namespace Umbraco.Core.Services
}
///
- /// Deletes a Member
+ /// Deletes an
///
- ///
+ /// to Delete
public void Delete(IMember member)
{
if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(member), this))
@@ -846,12 +895,13 @@ namespace Umbraco.Core.Services
Deleted.RaiseEvent(new DeleteEventArgs(member, false), this);
}
-
+
///
- /// Saves an updated Member
+ /// Saves an
///
- ///
- ///
+ /// to Save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
public void Save(IMember entity, bool raiseEvents = true)
{
if (raiseEvents)
@@ -860,7 +910,6 @@ namespace Umbraco.Core.Services
{
return;
}
-
}
var uow = _uowProvider.GetUnitOfWork();
@@ -881,6 +930,12 @@ namespace Umbraco.Core.Services
Saved.RaiseEvent(new SaveEventArgs(entity, false), this);
}
+ ///
+ /// Saves a list of objects
+ ///
+ /// to save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
public void Save(IEnumerable entities, bool raiseEvents = true)
{
var asArray = entities.ToArray();
@@ -1261,7 +1316,5 @@ namespace Umbraco.Core.Services
return new Member(name, email, username, password, memType);
}
-
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs
index fa7a094b04..ebe39377ad 100644
--- a/src/Umbraco.Core/Services/TagService.cs
+++ b/src/Umbraco.Core/Services/TagService.cs
@@ -1,10 +1,7 @@
using System.Collections.Generic;
-using System.Linq;
using Umbraco.Core.Models;
-using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -17,7 +14,6 @@ namespace Umbraco.Core.Services
///
public class TagService : ITagService
{
-
private readonly RepositoryFactory _repositoryFactory;
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
@@ -41,6 +37,12 @@ namespace Umbraco.Core.Services
_uowProvider = provider;
}
+ ///
+ /// Gets tagged Content by a specific 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Content, not the actual Content item.
+ /// Name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTaggedContentByTagGroup(string tagGroup)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -49,6 +51,13 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets tagged Content by a specific 'Tag' and optional 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Content, not the actual Content item.
+ /// Tag
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTaggedContentByTag(string tag, string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -57,6 +66,12 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets tagged Media by a specific 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Media, not the actual Media item.
+ /// Name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTaggedMediaByTagGroup(string tagGroup)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -65,6 +80,13 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets tagged Media by a specific 'Tag' and optional 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Media, not the actual Media item.
+ /// Tag
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTaggedMediaByTag(string tag, string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -73,6 +95,12 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets tagged Members by a specific 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Member, not the actual Member item.
+ /// Name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTaggedMembersByTagGroup(string tagGroup)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -81,6 +109,13 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets tagged Members by a specific 'Tag' and optional 'Tag Group'.
+ ///
+ /// The contains the Id and Tags of the Member, not the actual Member item.
+ /// Tag
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTaggedMembersByTag(string tag, string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -90,69 +125,79 @@ namespace Umbraco.Core.Services
}
///
- /// Get every tag stored in the database (with optional group)
+ /// Gets every tag stored in the database
///
- public IEnumerable GetAllTags(string group = null)
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ public IEnumerable GetAllTags(string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
{
- if (group.IsNullOrWhiteSpace())
+ if (tagGroup.IsNullOrWhiteSpace())
{
return repository.GetAll();
}
- var query = Query.Builder.Where(x => x.Group == group);
+ var query = Query.Builder.Where(x => x.Group == tagGroup);
var definitions = repository.GetByQuery(query);
return definitions;
}
}
///
- /// Get all tags for content items (with optional group)
+ /// Gets all tags for content items
///
- /// Optional group
- ///
- public IEnumerable GetAllContentTags(string group = null)
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ public IEnumerable GetAllContentTags(string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
{
- return repository.GetTagsForEntityType(TaggableObjectTypes.Content, group);
+ return repository.GetTagsForEntityType(TaggableObjectTypes.Content, tagGroup);
}
}
///
- /// Get all tags for media items (with optional group)
+ /// Gets all tags for media items
///
- /// Optional group
- ///
- public IEnumerable GetAllMediaTags(string group = null)
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ public IEnumerable GetAllMediaTags(string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
{
- return repository.GetTagsForEntityType(TaggableObjectTypes.Media, group);
+ return repository.GetTagsForEntityType(TaggableObjectTypes.Media, tagGroup);
}
}
///
- /// Get all tags for member items (with optional group)
+ /// Gets all tags for member items
///
- /// Optional group
- ///
- public IEnumerable GetAllMemberTags(string group = null)
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
+ public IEnumerable GetAllMemberTags(string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
{
- return repository.GetTagsForEntityType(TaggableObjectTypes.Member, group);
+ return repository.GetTagsForEntityType(TaggableObjectTypes.Member, tagGroup);
}
}
///
- /// Returns all tags attached to a property by entity id
+ /// Gets all tags attached to a property by entity id
///
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
/// The content item id to get tags for
/// Property type alias
- /// Optional tag group
- ///
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
@@ -162,11 +207,13 @@ namespace Umbraco.Core.Services
}
///
- /// Returns all tags attached to an entity (content, media or member) by entity id
+ /// Gets all tags attached to an entity (content, media or member) by entity id
///
+ /// Use the optional tagGroup parameter to limit the
+ /// result to a specific 'Tag Group'.
/// The content item id to get tags for
- /// Optional tag group
- ///
+ /// Optional name of the 'Tag Group'
+ /// An enumerable list of
public IEnumerable GetTagsForEntity(int contentId, string tagGroup = null)
{
using (var repository = _repositoryFactory.CreateTagsRepository(_uowProvider.GetUnitOfWork()))
diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs
index 3b426a2a0d..1a523835c8 100644
--- a/src/Umbraco.Core/Services/UserService.cs
+++ b/src/Umbraco.Core/Services/UserService.cs
@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using Umbraco.Core.Events;
-using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
@@ -13,8 +10,6 @@ using Umbraco.Core.Security;
namespace Umbraco.Core.Services
{
-
-
///
/// Represents the UserService, which is an easy access to operations involving , and eventually Backoffice Users.
///
@@ -40,10 +35,11 @@ namespace Umbraco.Core.Services
#region Implementation of IMembershipUserService
///
- /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll return the first type that is not an admin, otherwise if there's only one
- /// we will return that one.
+ /// Gets the default MemberType alias
///
- ///
+ /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll
+ /// return the first type that is not an admin, otherwise if there's only one we will return that one.
+ /// Alias of the default MemberType
public string GetDefaultMemberType()
{
using (var repository = _repositoryFactory.CreateUserTypeRepository(_uowProvider.GetUnitOfWork()))
@@ -70,6 +66,11 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Checks if a User with the username exists
+ ///
+ /// Username to check
+ /// True if the User exists otherwise False
public bool Exists(string username)
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
@@ -78,12 +79,28 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Creates a new User
+ ///
+ /// The user will be saved in the database and returned with an Id
+ /// Username of the user to create
+ /// Email of the user to create
+ /// which the User should be based on
+ ///
public IUser CreateUserWithIdentity(string username, string email, IUserType userType)
{
return CreateUserWithIdentity(username, email, "", userType);
}
- IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string rawPasswordValue, string memberTypeAlias)
+ ///
+ /// Creates and persists a new
+ ///
+ /// Username of the to create
+ /// Email of the to create
+ /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
+ /// Alias of the Type
+ ///
+ IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias)
{
var userType = GetUserTypeByAlias(memberTypeAlias);
if (userType == null)
@@ -91,10 +108,20 @@ namespace Umbraco.Core.Services
throw new EntityNotFoundException("The user type " + memberTypeAlias + " could not be resolved");
}
- return CreateUserWithIdentity(username, email, rawPasswordValue, userType);
+ return CreateUserWithIdentity(username, email, passwordValue, userType);
}
- private IUser CreateUserWithIdentity(string username, string email, string rawPasswordValue, IUserType userType)
+ ///
+ /// Creates and persists a Member
+ ///
+ /// Using this method will persist the Member object before its returned
+ /// meaning that it will have an Id available (unlike the CreateMember method)
+ /// Username of the Member to create
+ /// Email of the Member to create
+ /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
+ /// MemberType the Member should be based on
+ ///
+ private IUser CreateUserWithIdentity(string username, string email, string passwordValue, IUserType userType)
{
if (userType == null) throw new ArgumentNullException("userType");
@@ -113,7 +140,7 @@ namespace Umbraco.Core.Services
Email = email,
Language = Configuration.GlobalSettings.DefaultUILanguage,
Name = username,
- RawPasswordValue = rawPasswordValue,
+ RawPasswordValue = passwordValue,
Username = username,
StartContentId = -1,
StartMediaId = -1,
@@ -133,6 +160,11 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets a User by its integer id
+ ///
+ /// Id
+ ///
public IUser GetById(int id)
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
@@ -143,6 +175,11 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets an by its provider key
+ ///
+ /// Id to use for retrieval
+ ///
public IUser GetByProviderKey(object id)
{
var asInt = id.TryConvertTo();
@@ -154,6 +191,11 @@ namespace Umbraco.Core.Services
return null;
}
+ ///
+ /// Get an by email
+ ///
+ /// Email to use for retrieval
+ ///
public IUser GetByEmail(string email)
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
@@ -165,19 +207,24 @@ namespace Umbraco.Core.Services
}
}
- public IUser GetByUsername(string login)
+ ///
+ /// Get an by username
+ ///
+ /// Username to use for retrieval
+ ///
+ public IUser GetByUsername(string username)
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
{
- var query = Query.Builder.Where(x => x.Username.Equals(login));
+ var query = Query.Builder.Where(x => x.Username.Equals(username));
return repository.GetByQuery(query).FirstOrDefault();
}
}
///
- /// This disables and renames the user, it does not delete them, use the overload to delete them
+ /// Deletes an
///
- ///
+ /// to Delete
public void Delete(IUser membershipUser)
{
//disable
@@ -197,11 +244,11 @@ namespace Umbraco.Core.Services
///
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
///
- /// The user to save the password for
- ///
///
/// This method exists so that Umbraco developers can use one entry point to create/update users if they choose to.
///
+ /// The user to save the password for
+ /// The password to save
public void SavePassword(IUser user, string password)
{
if (user == null) throw new ArgumentNullException("user");
@@ -225,10 +272,10 @@ namespace Umbraco.Core.Services
}
///
- /// To permanently delete the user pass in true, otherwise they will just be disabled
+ /// Deletes or disables a User
///
- ///
- ///
+ /// to delete
+ /// True to permanently delete the user, False to disable the user
public void Delete(IUser user, bool deletePermanently)
{
if (deletePermanently == false)
@@ -251,6 +298,12 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Saves an
+ ///
+ /// to Save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
public void Save(IUser entity, bool raiseEvents = true)
{
if (raiseEvents)
@@ -270,6 +323,12 @@ namespace Umbraco.Core.Services
SavedUser.RaiseEvent(new SaveEventArgs(entity, false), this);
}
+ ///
+ /// Saves a list of objects
+ ///
+ /// to save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
public void Save(IEnumerable entities, bool raiseEvents = true)
{
if (raiseEvents)
@@ -293,6 +352,15 @@ namespace Umbraco.Core.Services
SavedUser.RaiseEvent(new SaveEventArgs(entities, false), this);
}
+ ///
+ /// Finds a list of objects by a partial email string
+ ///
+ /// Partial email string to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -325,6 +393,15 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Finds a list of objects by a partial username
+ ///
+ /// Partial username to match
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ /// The type of match to make as . Default is
+ ///
public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -357,6 +434,16 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets the total number of Users based on the count type
+ ///
+ ///
+ /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
+ /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
+ /// but that is how MS have made theirs so we'll follow that principal.
+ ///
+ /// to count by
+ /// with number of Users for passed in type
public int GetCount(MemberCountType countType)
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
@@ -393,6 +480,13 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets a list of paged objects
+ ///
+ /// Current page index
+ /// Size of the page
+ /// Total number of records found (out)
+ ///
public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -417,12 +511,22 @@ namespace Umbraco.Core.Services
return user.ProfileData;
}
- public IProfile GetProfileByUserName(string login)
+ ///
+ /// Gets a profile by username
+ ///
+ /// Username
+ ///
+ public IProfile GetProfileByUserName(string username)
{
- var user = GetByUsername(login);
+ var user = GetByUsername(username);
return user.ProfileData;
}
-
+
+ ///
+ /// Gets a user by Id
+ ///
+ /// Id of the user to retrieve
+ ///
public IUser GetUserById(int id)
{
using (var repository = _repositoryFactory.CreateUserRepository(_uowProvider.GetUnitOfWork()))
@@ -430,13 +534,14 @@ namespace Umbraco.Core.Services
return repository.Get(id);
}
}
-
+
///
/// Replaces the same permission set for a single user to any number of entities
///
- ///
- ///
- ///
+ /// If no 'entityIds' are specified all permissions will be removed for the specified user.
+ /// Id of the user
+ /// Permissions as enumerable list of
+ /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed.
public void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -446,6 +551,11 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets all UserTypes or thosed specified as parameters
+ ///
+ /// Optional Ids of UserTypes to retrieve
+ /// An enumerable list of
public IEnumerable GetAllUserTypes(params int[] ids)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -456,7 +566,7 @@ namespace Umbraco.Core.Services
}
///
- /// Gets an IUserType by its Alias
+ /// Gets a UserType by its Alias
///
/// Alias of the UserType to retrieve
///
@@ -470,6 +580,11 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Gets a UserType by its Id
+ ///
+ /// Id of the UserType to retrieve
+ ///
public IUserType GetUserTypeById(int id)
{
using (var repository = _repositoryFactory.CreateUserTypeRepository(_uowProvider.GetUnitOfWork()))
@@ -479,7 +594,7 @@ namespace Umbraco.Core.Services
}
///
- /// Gets an IUserType by its Name
+ /// Gets a UserType by its Name
///
/// Name of the UserType to retrieve
///
@@ -493,6 +608,12 @@ namespace Umbraco.Core.Services
}
}
+ ///
+ /// Saves a UserType
+ ///
+ /// UserType to save
+ /// Optional parameter to raise events.
+ /// Default is True otherwise set to False to not raise events
public void SaveUserType(IUserType userType, bool raiseEvents = true)
{
if (raiseEvents)
@@ -512,6 +633,10 @@ namespace Umbraco.Core.Services
SavedUserType.RaiseEvent(new SaveEventArgs(userType, false), this);
}
+ ///
+ /// Deletes a UserType
+ ///
+ /// UserType to delete
public void DeleteUserType(IUserType userType)
{
if (DeletingUserType.IsRaisedEventCancelled(new DeleteEventArgs(userType), this))
@@ -528,9 +653,10 @@ namespace Umbraco.Core.Services
}
///
- /// This is useful for when a section is removed from config
+ /// Removes a specific section from all users
///
- ///
+ /// This is useful when an entire section is removed from config
+ /// Alias of the section to remove
public void DeleteSectionFromAllUsers(string sectionAlias)
{
var uow = _uowProvider.GetUnitOfWork();
@@ -548,14 +674,12 @@ namespace Umbraco.Core.Services
}
///
- /// Returns permissions for a given user for any number of nodes
+ /// Get permissions set for a user and optional node ids
///
- ///
- ///
- ///
- ///
- /// If no permissions are found for a particular entity then the user's default permissions will be applied
- ///
+ /// If no permissions are found for a particular entity then the user's default permissions will be applied
+ /// User to retrieve permissions for
+ /// Specifiying nothing will return all user permissions for all nodes
+ /// An enumerable list of
public IEnumerable GetPermissions(IUser user, params int[] nodeIds)
{
var uow = _uowProvider.GetUnitOfWork();
diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs
index 3eedb69eb4..10ba5fab32 100644
--- a/src/Umbraco.Core/StringExtensions.cs
+++ b/src/Umbraco.Core/StringExtensions.cs
@@ -31,7 +31,7 @@ namespace Umbraco.Core
private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray();
private static readonly char[] ToCSharpEscapeChars;
-
+
static StringExtensions()
{
var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" };
@@ -56,7 +56,7 @@ namespace Umbraco.Core
}
return fileName;
-
+
}
///
@@ -158,7 +158,7 @@ namespace Umbraco.Core
//remove any prefixed '&' or '?'
for (var i = 0; i < queryStrings.Length; i++)
{
- queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&');
+ queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&');
}
var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
@@ -169,66 +169,66 @@ namespace Umbraco.Core
}
return url + string.Join("&", nonEmpty).EnsureStartsWith('?');
}
-
- ///
- /// Encrypt the string using the MachineKey in medium trust
- ///
- /// The string value to be encrypted.
- /// The encrypted string.
- public static string EncryptWithMachineKey(this string value)
+
+ ///
+ /// Encrypt the string using the MachineKey in medium trust
+ ///
+ /// The string value to be encrypted.
+ /// The encrypted string.
+ public static string EncryptWithMachineKey(this string value)
{
- if (value == null)
- return null;
+ if (value == null)
+ return null;
- string valueToEncrypt = value;
- List parts = new List();
+ string valueToEncrypt = value;
+ List parts = new List();
- const int EncrpytBlockSize = 500;
+ const int EncrpytBlockSize = 500;
- while (valueToEncrypt.Length > EncrpytBlockSize)
- {
- parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
- valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
- }
+ while (valueToEncrypt.Length > EncrpytBlockSize)
+ {
+ parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
+ valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
+ }
- if (valueToEncrypt.Length > 0)
- {
- parts.Add(valueToEncrypt);
- }
+ if (valueToEncrypt.Length > 0)
+ {
+ parts.Add(valueToEncrypt);
+ }
- StringBuilder encrpytedValue = new StringBuilder();
+ StringBuilder encrpytedValue = new StringBuilder();
- foreach (var part in parts)
- {
- var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
- encrpytedValue.AppendLine(encrpytedBlock);
- }
+ foreach (var part in parts)
+ {
+ var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
+ encrpytedValue.AppendLine(encrpytedBlock);
+ }
- return encrpytedValue.ToString().TrimEnd();
+ return encrpytedValue.ToString().TrimEnd();
}
///
- /// Decrypt the encrypted string using the Machine key in medium trust
- ///
- /// The string value to be decrypted
- /// The decrypted string.
- public static string DecryptWithMachineKey(this string value)
+ /// Decrypt the encrypted string using the Machine key in medium trust
+ ///
+ /// The string value to be decrypted
+ /// The decrypted string.
+ public static string DecryptWithMachineKey(this string value)
{
- if (value == null)
- return null;
+ if (value == null)
+ return null;
- string[] parts = value.Split('\n');
+ string[] parts = value.Split('\n');
- StringBuilder decryptedValue = new StringBuilder();
+ StringBuilder decryptedValue = new StringBuilder();
- foreach (var part in parts)
- {
- decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
- }
+ foreach (var part in parts)
+ {
+ decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
+ }
- return decryptedValue.ToString();
+ return decryptedValue.ToString();
}
-
+
//this is from SqlMetal and just makes it a bit of fun to allow pluralisation
public static string MakePluralName(this string name)
{
@@ -974,8 +974,8 @@ namespace Umbraco.Core
{
var helper = ShortStringHelper;
var legacy = helper as LegacyShortStringHelper;
- return legacy != null
- ? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
+ return legacy != null
+ ? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
: helper.CleanStringForUrlSegment(value);
}
@@ -1082,7 +1082,7 @@ namespace Umbraco.Core
}
// the new methods to get a url segment
-
+
///
/// Cleans a string to produce a string that can safely be used in an url segment.
///
@@ -1220,7 +1220,7 @@ namespace Umbraco.Core
{
return ShortStringHelper.CleanStringForSafeFileName(text, culture);
}
-
+
///
/// An extension method that returns a new string in which all occurrences of a
/// specified string in the current instance are replaced with another specified string.
@@ -1239,7 +1239,7 @@ namespace Umbraco.Core
int index = -1 * newString.Length;
// Determine if there are any matches left in source, starting from just after the result of replacing the last match.
- while((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
+ while ((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
{
// Remove the old text.
source = source.Remove(index, oldString.Length);
@@ -1310,5 +1310,28 @@ namespace Umbraco.Core
var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
return idCheckList.Contains(value);
}
+
+ // From: http://stackoverflow.com/a/961504/5018
+ // filters control characters but allows only properly-formed surrogate sequences
+ private static readonly Regex InvalidXmlChars =
+ new Regex(
+ @"(?
+ /// An extension method that returns a new string in which all occurrences of an
+ /// unicode characters that are invalid in XML files are replaced with an empty string.
+ ///
+ /// Current instance of the string
+ /// Updated string
+ ///
+ ///
+ /// removes any unusual unicode characters that can't be encoded into XML
+ ///
+ internal static string ToValidXmlString(this string text)
+ {
+ return string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Replace(text, "");
+ }
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 096ee04948..08cad1a5c7 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -149,8 +149,6 @@
-
-
@@ -371,6 +369,7 @@
+
diff --git a/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs b/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs
index 5483a4341b..87a468ddd5 100644
--- a/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs
+++ b/src/Umbraco.Tests/Cache/HttpRequestCacheProviderTests.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.Cache
{
base.Setup();
_ctx = new FakeHttpContextFactory("http://localhost/test");
- _provider = new HttpRequestCacheProvider(() => _ctx.HttpContext);
+ _provider = new HttpRequestCacheProvider(_ctx.HttpContext);
}
internal override ICacheProvider Provider
diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs
index c786a61996..609452978c 100644
--- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs
+++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs
@@ -148,7 +148,7 @@ namespace Umbraco.Tests.Membership
Assert.AreEqual(false, provider.EnablePasswordReset);
Assert.AreEqual(false, provider.RequiresQuestionAndAnswer);
Assert.AreEqual(true, provider.RequiresUniqueEmail);
- Assert.AreEqual(5, provider.MaxInvalidPasswordAttempts);
+ Assert.AreEqual(20, provider.MaxInvalidPasswordAttempts);
Assert.AreEqual(10, provider.PasswordAttemptWindow);
Assert.AreEqual(provider.DefaultMinPasswordLength, provider.MinRequiredPasswordLength);
Assert.AreEqual(provider.DefaultMinNonAlphanumericChars, provider.MinRequiredNonAlphanumericCharacters);
diff --git a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs
index c6ee114bb9..31bdb616b0 100644
--- a/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs
+++ b/src/Umbraco.Tests/TestHelpers/FakeHttpContextFactory.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security;
@@ -96,7 +97,9 @@ namespace Umbraco.Tests.TestHelpers
var httpContextMock = new Mock();
httpContextMock.Setup(x => x.Cache).Returns(HttpRuntime.Cache);
- httpContextMock.Setup(x => x.Items).Returns(new Dictionary());
+ //note: foreach on Items should return DictionaryEntries!
+ //httpContextMock.Setup(x => x.Items).Returns(new Dictionary());
+ httpContextMock.Setup(x => x.Items).Returns(new Hashtable());
httpContextMock.Setup(x => x.Request).Returns(requestMock.Object);
httpContextMock.Setup(x => x.Server).Returns(serverMock.Object);
httpContextMock.Setup(x => x.Response).Returns(responseMock.Object);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
index d96a60cd87..ff4d7184fe 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js
@@ -94,7 +94,7 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
if(selectedElm.nodeName === 'IMG'){
var img = $(selectedElm);
currentTarget = {
- name: img.attr("alt"),
+ altText: img.attr("alt"),
url: img.attr("src"),
id: img.attr("rel")
};
@@ -109,7 +109,7 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
if (img) {
var data = {
- alt: img.name,
+ alt: img.altText,
src: (img.url) ? img.url : "nothing.jpg",
rel: img.id,
id: '__mcenew'
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html
index d8ce5e0b8c..d3f9a5f724 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.html
@@ -19,14 +19,10 @@ data-file-upload="options" data-file-upload-progress="" data-ng-class="{'fileupl
ng-disabled="target.id"/>
-
-
+
-
+ ng-model="target.altText" />
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
index 16b943481b..b034f935b2 100644
--- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
+++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml
@@ -150,6 +150,7 @@
To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting
Statistics
Title (optional)
+ Alternative text (optional)
Type
Unpublish
Last edited
@@ -175,7 +176,7 @@
Browse your website
- Hide
- If umbraco isn't opening, you might need to allow popups from this site
+ If Umbraco isn't opening, you might need to allow popups from this site
has opened in a new window
Restart
Visit
@@ -203,7 +204,7 @@
Last Edited
Link
Internal link:
- When using local links, insert "#" infront of link
+ When using local links, insert "#" in front of link
Open in new window?
Macro Settings
This macro does not contain any properties you can edit
@@ -245,6 +246,7 @@
Allowed child node types
+ Document Type Compositions
Create
Delete tab
Description
@@ -436,12 +438,12 @@
proceed. ]]>
next to continue the configuration wizard]]>
The Default users’ password needs to be changed!]]>
- The Default user has been disabled or has no access to umbraco!
No further actions needs to be taken. Click Next to proceed.]]>
+ The Default user has been disabled or has no access to Umbraco!
No further actions needs to be taken. Click Next to proceed.]]>
The Default user's password has been successfully changed since the installation!
No further actions needs to be taken. Click Next to proceed.]]>
The password is changed!
- umbraco creates a default user with a login ('admin') and password ('default') . It's important that the password is
+ Umbraco creates a default user with a login ('admin') and password ('default') . It's important that the password is
changed to something unique.
@@ -449,29 +451,29 @@
]]>
Get a great start, watch our introduction videos
- By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this umbraco distribution consists of two different licenses, the open source MIT license for the framework and the umbraco freeware license that covers the UI.
+ By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI.
Not installed yet.
Affected files and folders
- More information on setting up permissions for umbraco here
+ More information on setting up permissions for Umbraco here
You need to grant ASP.NET modify permissions to the following files/folders
Your permission settings are almost perfect!
- You can run umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of umbraco.]]>
+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
How to Resolve
Click here to read the text version
- video tutorial on setting up folder permissions for umbraco or read the text version.]]>
+ video tutorial on setting up folder permissions for Umbraco or read the text version.]]>
Your permission settings might be an issue!
- You can run umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of umbraco.]]>
- Your permission settings are not ready for umbraco!
+ You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
+ Your permission settings are not ready for Umbraco!
- In order to run umbraco, you'll need to update your permission settings.]]>
+ In order to run Umbraco, you'll need to update your permission settings.]]>
Your permission settings are perfect!
- You are ready to run umbraco and install packages!]]>
+ You are ready to run Umbraco and install packages!]]>
Resolving folder issue
Follow this link for more information on problems with ASP.NET and creating folders
Setting up folder permissions
I want to start from scratch
@@ -504,25 +506,25 @@
Step 1/5 Accept license
Step 2/5: Database configuration
Step 3/5: Validating File Permissions
- Step 4/5: Check umbraco security
+ Step 4/5: Check Umbraco security
Step 5/5: Umbraco is ready to get you started
- Thank you for choosing umbraco
+ Thank you for choosing Umbraco
Browse your new site
You installed Runway, so why not see how your new website looks.]]>
Further help and information
-Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the umbraco terminology]]>
+Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]>
Umbraco %0% is installed and ready for use
/web.config file and update the AppSetting key umbracoConfigurationStatus in the bottom to the value of '%0%' .]]>
- started instantly by clicking the "Launch Umbraco" button below. If you are new to umbraco ,
+ started instantly by clicking the "Launch Umbraco" button below. If you are new to Umbraco ,
you can find plenty of resources on our getting started pages.]]>
Launch Umbraco
-To manage your website, simply open the umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]>
+To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]>
Connection to database failed.
Umbraco Version 3
Umbraco Version 4
Watch
- umbraco %0% for a fresh install or upgrading from version 3.0.
+ Umbraco %0% for a fresh install or upgrading from version 3.0.
Press "next" to start the wizard.]]>
@@ -544,7 +546,7 @@ To manage your website, simply open the umbraco back office and start adding con
Happy Caturday
Log in below
Session timed out
- © 2001 - %0% umbraco.org ]]>
+ © 2001 - %0% Umbraco.com ]]>
Dashboard
@@ -578,7 +580,7 @@ To manage your website, simply open the umbraco back office and start adding con
Have a nice day!
- Cheers from the umbraco robot
+ Cheers from the Umbraco robot
]]>
Hi %0%
@@ -605,7 +607,7 @@ To manage your website, simply open the umbraco back office and start adding con
Have a nice day!
- Cheers from the umbraco robot
+ Cheers from the Umbraco robot
]]>
[%0%] Notification about %1% performed on %2%
Notifications
@@ -613,7 +615,7 @@ To manage your website, simply open the umbraco back office and start adding con
- button and locating the package. umbraco packages usually have a ".umb" or ".zip" extension.
+ button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension.
]]>
Author
Demonstration
@@ -637,20 +639,20 @@ To manage your website, simply open the umbraco back office and start adding con
Download update from the repository
Upgrade package
Upgrade instructions
- There's an upgrade available for this package. You can download it directly from the umbraco package repository.
+ There's an upgrade available for this package. You can download it directly from the Umbraco package repository.
Package version
Package version history
View package website
Paste with full formatting (Not recommended)
- The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.
+ The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.
Paste as raw text without any formatting at all
Paste, but remove formatting (Recommended)
Role based protection
- using umbraco's member groups.]]>
+ using Umbraco's member groups.]]>
role-based authentication.]]>
Error Page
Used when people are logged on, but do not have access
@@ -658,7 +660,7 @@ To manage your website, simply open the umbraco back office and start adding con
%0% is now protected
Protection removed from %0%
Login Page
- Choose the page that has the login formular
+ Choose the page that contains the login form
Remove Protection
Select the pages that contain login form and error messages
Pick the roles who have access to this page
@@ -667,16 +669,14 @@ To manage your website, simply open the umbraco back office and start adding con
If you just want to setup simple protection using a single login and password
-
-
-
+ ]]>
%0% and subpages have been published
Publish %0% and all its subpages
ok to publish %0% and thereby making its content publicly available.
- You can publish this page and all its sub-pages by checking publish all children below.
+ You can publish this page and all it's sub-pages by checking publish all children below.
]]>
@@ -710,7 +710,7 @@ To manage your website, simply open the umbraco back office and start adding con
Current version
Red text will not be shown in the selected version. , green means added ]]>
Document has been rolled back
- This displays the selected version as html, if you wish to see the difference between 2 versions at the same time, use the diff view
+ This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view
Rollback to
Select version
View
@@ -732,6 +732,7 @@ To manage your website, simply open the umbraco back office and start adding con
Translation
Users
Help
+ Analytics
Default template
@@ -825,9 +826,9 @@ To manage your website, simply open the umbraco back office and start adding con
Insert content area placeholder
Insert dictionary item
Insert Macro
- Insert umbraco page field
+ Insert Umbraco page field
Master template
- Quick Guide to umbraco template tags
+ Quick Guide to Umbraco template tags
Template
@@ -868,9 +869,9 @@ To manage your website, simply open the umbraco back office and start adding con
]]>
close task
Translation details
- Download all translation tasks as xml
- Download xml
- Download xml DTD
+ Download all translation tasks as XML
+ Download XML
+ Download XML DTD
Fields
Include subpages
[%0%] Translation task for %1%
No translator users found. Please create a translator user before you start sending content to translation
@@ -903,10 +904,10 @@ To manage your website, simply open the umbraco back office and start adding con
Translate to
Translation completed.
You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages.
- Translation failed, the xml file might be corrupt
+ Translation failed, the XML file might be corrupt
Translation options
Translator
- Upload translation xml
+ Upload translation XML
Cache Browser
@@ -937,6 +938,7 @@ To manage your website, simply open the umbraco back office and start adding con
Stylesheets
Templates
XSLT Files
+ Analytics
New update ready
@@ -986,4 +988,4 @@ To manage your website, simply open the umbraco back office and start adding con
Your recent history
Session expires in
-
+
\ No newline at end of file
diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs
index 94ce82799c..386f7c7032 100644
--- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs
+++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs
@@ -15,7 +15,7 @@ namespace Umbraco.Web.Models
///
/// Represents a collection of DynamicPublishedContent items.
///
- public class DynamicPublishedContentList : DynamicObject, IEnumerable, IEnumerable
+ public class DynamicPublishedContentList : DynamicObject, IEnumerable
{
private readonly List _content;
private readonly PublishedContentSet _contentSet;
@@ -574,11 +574,6 @@ namespace Umbraco.Web.Models
#region Enumerate inner IPublishedContent items
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
public IEnumerator GetEnumerator()
{
return Items.GetEnumerator();
diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs
index a4e7c097ce..075cd84436 100644
--- a/src/umbraco.cms/Actions/Action.cs
+++ b/src/umbraco.cms/Actions/Action.cs
@@ -33,7 +33,6 @@ namespace umbraco.BusinessLogic.Actions
[Obsolete("Actions and ActionHandlers are obsolete and should no longer be used")]
public class Action
{
-
private static readonly Dictionary ActionJs = new Dictionary();
private static readonly object Lock = new object();
@@ -49,21 +48,25 @@ namespace umbraco.BusinessLogic.Actions
///
///
/// TODO: this shouldn't be needed... we should restart the app pool when a package is installed!
- ///
+ ///
public static void ReRegisterActionsAndHandlers()
{
- lock (Lock)
- {
+ lock (Lock)
+ {
+ // NOTE use the DirtyBackdoor to change the resolution configuration EXCLUSIVELY
+ // ie do NOT do ANYTHING else while holding the backdoor, because while it is open
+ // the whole resolution system is locked => nothing can work properly => deadlocks
+
+ var newResolver = new ActionsResolver(
+ () => TypeFinder.FindClassesOfType(PluginManager.Current.AssembliesToScan));
+
using (Umbraco.Core.ObjectResolution.Resolution.DirtyBackdoorToConfiguration)
{
- //TODO: Based on the above, this is a big hack as types should all be cleared on package install!
ActionsResolver.Reset(false); // and do NOT reset the whole resolution!
-
- //TODO: Based on the above, this is a big hack as types should all be cleared on package install!
- ActionsResolver.Current = new ActionsResolver(
- () => TypeFinder.FindClassesOfType(PluginManager.Current.AssembliesToScan));
+ ActionsResolver.Current = newResolver;
}
- }
+
+ }
}
///