Have the macro repository working, just need to write more unit tests regarding macro properties.

This commit is contained in:
Shannon
2013-09-17 17:18:54 +10:00
parent 44d01e3868
commit f2f8004599
17 changed files with 978 additions and 121 deletions

View File

@@ -0,0 +1,93 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Macro
/// </summary>
internal interface IMacro : IAggregateRoot
{
/// <summary>
/// Gets or sets the alias of the Macro
/// </summary>
[DataMember]
string Alias { get; set; }
/// <summary>
/// Gets or sets the name of the Macro
/// </summary>
[DataMember]
string Name { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro can be used in an Editor
/// </summary>
[DataMember]
bool UseInEditor { get; set; }
/// <summary>
/// Gets or sets the Cache Duration for the Macro
/// </summary>
[DataMember]
int CacheDuration { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached by Page
/// </summary>
[DataMember]
bool CacheByPage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached Personally
/// </summary>
[DataMember]
bool CacheByMember { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor
/// </summary>
[DataMember]
bool DontRender { get; set; }
/// <summary>
/// Gets or sets the path to user control or the Control Type to render
/// </summary>
[DataMember]
string ControlType { get; set; }
/// <summary>
/// Gets or sets the name of the assembly, which should be used by the Macro
/// </summary>
/// <remarks>Will usually only be filled if the ScriptFile is a Usercontrol</remarks>
[DataMember]
string ControlAssembly { get; set; }
/// <summary>
/// Gets or set the path to the Python file in use
/// </summary>
/// <remarks>Optional: Can only be one of three Script, Python or Xslt</remarks>
[DataMember]
string ScriptPath { get; set; }
/// <summary>
/// Gets or sets the path to the Xslt file in use
/// </summary>
/// <remarks>Optional: Can only be one of three Script, Python or Xslt</remarks>
[DataMember]
string XsltPath { get; set; }
/// <summary>
/// Gets or sets a list of Macro Properties
/// </summary>
[DataMember]
MacroPropertyCollection Properties { get; }
/// <summary>
/// Returns an enum <see cref="MacroTypes"/> based on the properties on the Macro
/// </summary>
/// <returns><see cref="MacroTypes"/></returns>
MacroTypes MacroType();
}
}

View File

@@ -0,0 +1,39 @@
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Property for a Macro
/// </summary>
internal interface IMacroProperty : IValueObject
{
/// <summary>
/// Gets or sets the Alias of the Property
/// </summary>
[DataMember]
string Alias { get; set; }
/// <summary>
/// Gets or sets the Name of the Property
/// </summary>
[DataMember]
string Name { get; set; }
/// <summary>
/// Gets or sets the Sort Order of the Property
/// </summary>
[DataMember]
int SortOrder { get; set; }
/// <summary>
/// Gets or sets the Type for this Property
/// </summary>
/// <remarks>
/// The MacroPropertyTypes acts as a plugin for Macros.
/// All types was previously contained in the database, but has been ported to code.
/// </remarks>
[DataMember]
IMacroPropertyType PropertyType { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a PropertyType (plugin) for a Macro
/// </summary>
internal interface IMacroPropertyType
{
/// <summary>
/// Gets the unique Alias of the Property Type
/// </summary>
string Alias { get; }
/// <summary>
/// Gets the name of the Assembly used to render the Property Type
/// </summary>
string RenderingAssembly { get; }
/// <summary>
/// Gets the name of the Type used to render the Property Type
/// </summary>
string RenderingType { get; }
/// <summary>
/// Gets the Base Type for storing the PropertyType (Int32, String, Boolean)
/// </summary>
MacroPropertyTypeBaseTypes BaseType { get; }
}
}

View File

@@ -0,0 +1,400 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Umbraco.Core.IO;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a Macro
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class Macro : Entity, IMacro
{
public Macro()
{
_properties = new MacroPropertyCollection();
_properties.CollectionChanged += PropertiesChanged;
_addedProperties = new List<string>();
_removedProperties = new List<string>();
}
/// <summary>
/// Creates an item with pre-filled properties
/// </summary>
/// <param name="id"></param>
/// <param name="useInEditor"></param>
/// <param name="cacheDuration"></param>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="controlType"></param>
/// <param name="controlAssembly"></param>
/// <param name="xsltPath"></param>
/// <param name="cacheByPage"></param>
/// <param name="cacheByMember"></param>
/// <param name="dontRender"></param>
/// <param name="scriptPath"></param>
public Macro(int id, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
: this()
{
Id = id;
UseInEditor = useInEditor;
CacheDuration = cacheDuration;
Alias = alias;
Name = name;
ControlType = controlType;
ControlAssembly = controlAssembly;
XsltPath = xsltPath;
CacheByPage = cacheByPage;
CacheByMember = cacheByMember;
DontRender = dontRender;
ScriptPath = scriptPath;
}
/// <summary>
/// Creates an instance for persisting a new item
/// </summary>
/// <param name="useInEditor"></param>
/// <param name="cacheDuration"></param>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="controlType"></param>
/// <param name="controlAssembly"></param>
/// <param name="xsltPath"></param>
/// <param name="cacheByPage"></param>
/// <param name="cacheByMember"></param>
/// <param name="dontRender"></param>
/// <param name="scriptPath"></param>
public Macro(string @alias, string name,
string controlType = "",
string controlAssembly = "",
string xsltPath = "",
string scriptPath = "",
bool cacheByPage = false,
bool cacheByMember = false,
bool dontRender = true,
bool useInEditor = false,
int cacheDuration = 0)
: this()
{
UseInEditor = useInEditor;
CacheDuration = cacheDuration;
Alias = alias;
Name = name;
ControlType = controlType;
ControlAssembly = controlAssembly;
XsltPath = xsltPath;
CacheByPage = cacheByPage;
CacheByMember = cacheByMember;
DontRender = dontRender;
ScriptPath = scriptPath;
}
private string _alias;
private string _name;
private bool _useInEditor;
private int _cacheDuration;
private bool _cacheByPage;
private bool _cacheByMember;
private bool _dontRender;
private string _scriptFile;
private string _scriptAssembly;
private string _python;
private string _xslt;
private readonly MacroPropertyCollection _properties;
private readonly List<string> _addedProperties;
private readonly List<string> _removedProperties;
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.Alias);
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.Name);
private static readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.UseInEditor);
private static readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo<Macro, int>(x => x.CacheDuration);
private static readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.CacheByPage);
private static readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.CacheByMember);
private static readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.DontRender);
private static readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ControlType);
private static readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ControlAssembly);
private static readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ScriptPath);
private static readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.XsltPath);
private static readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo<Macro, MacroPropertyCollection>(x => x.Properties);
protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(PropertiesSelector);
if (e.Action == NotifyCollectionChangedAction.Add)
{
var alias = e.NewItems.Cast<IMacroProperty>().Select(x => x.Alias).First();
//remove from the removed/added props (since people could add/remove all they want in one request)
_removedProperties.RemoveAll(s => s == alias);
_addedProperties.RemoveAll(s => s == alias);
//add to the added props
_addedProperties.Add(alias);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
var alias = e.OldItems.Cast<IMacroProperty>().Select(x => x.Alias).First();
//remove from the removed/added props (since people could add/remove all they want in one request)
_removedProperties.RemoveAll(s => s == alias);
_addedProperties.RemoveAll(s => s == alias);
//add to the added props
_removedProperties.Add(alias);
}
}
/// <summary>
/// Used internally to check if we need to add a section in the repository to the db
/// </summary>
internal IEnumerable<string> AddedProperties
{
get { return _addedProperties; }
}
/// <summary>
/// Used internally to check if we need to remove a section in the repository to the db
/// </summary>
internal IEnumerable<string> RemovedProperties
{
get { return _removedProperties; }
}
/// <summary>
/// Gets or sets the alias of the Macro
/// </summary>
[DataMember]
public string Alias
{
get { return _alias; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_alias = value;
return _alias;
}, _alias, AliasSelector);
}
}
/// <summary>
/// Gets or sets the name of the Macro
/// </summary>
[DataMember]
public string Name
{
get { return _name; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
/// Gets or sets a boolean indicating whether the Macro can be used in an Editor
/// </summary>
[DataMember]
public bool UseInEditor
{
get { return _useInEditor; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_useInEditor = value;
return _useInEditor;
}, _useInEditor, UseInEditorSelector);
}
}
/// <summary>
/// Gets or sets the Cache Duration for the Macro
/// </summary>
[DataMember]
public int CacheDuration
{
get { return _cacheDuration; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cacheDuration = value;
return _cacheDuration;
}, _cacheDuration, CacheDurationSelector);
}
}
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached by Page
/// </summary>
[DataMember]
public bool CacheByPage
{
get { return _cacheByPage; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cacheByPage = value;
return _cacheByPage;
}, _cacheByPage, CacheByPageSelector);
}
}
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached Personally
/// </summary>
[DataMember]
public bool CacheByMember
{
get { return _cacheByMember; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cacheByMember = value;
return _cacheByMember;
}, _cacheByMember, CacheByMemberSelector);
}
}
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor
/// </summary>
[DataMember]
public bool DontRender
{
get { return _dontRender; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_dontRender = value;
return _dontRender;
}, _dontRender, DontRenderSelector);
}
}
/// <summary>
/// Gets or sets the path to user control or the Control Type to render
/// </summary>
[DataMember]
public string ControlType
{
get { return _scriptFile; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_scriptFile = value;
return _scriptFile;
}, _scriptFile, ControlPathSelector);
}
}
/// <summary>
/// Gets or sets the name of the assembly, which should be used by the Macro
/// </summary>
/// <remarks>Will usually only be filled if the ControlType is a Usercontrol</remarks>
[DataMember]
public string ControlAssembly
{
get { return _scriptAssembly; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_scriptAssembly = value;
return _scriptAssembly;
}, _scriptAssembly, ControlAssemblySelector);
}
}
/// <summary>
/// Gets or set the path to the Python file in use
/// </summary>
/// <remarks>Optional: Can only be one of three Script, Python or Xslt</remarks>
[DataMember]
public string ScriptPath
{
get { return _python; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_python = value;
return _python;
}, _python, ScriptPathSelector);
}
}
/// <summary>
/// Gets or sets the path to the Xslt file in use
/// </summary>
/// <remarks>Optional: Can only be one of three Script, Python or Xslt</remarks>
[DataMember]
public string XsltPath
{
get { return _xslt; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_xslt = value;
return _xslt;
}, _xslt, XsltPathSelector);
}
}
/// <summary>
/// Gets or sets a list of Macro Properties
/// </summary>
[DataMember]
public MacroPropertyCollection Properties
{
get { return _properties; }
}
/// <summary>
/// Returns an enum <see cref="MacroTypes"/> based on the properties on the Macro
/// </summary>
/// <returns><see cref="MacroTypes"/></returns>
public MacroTypes MacroType()
{
if (string.IsNullOrEmpty(XsltPath) == false)
return MacroTypes.Xslt;
if (string.IsNullOrEmpty(ScriptPath) == false)
{
//we need to check if the file path saved is a virtual path starting with ~/Views/MacroPartials, if so then this is
//a partial view macro, not a script macro
//we also check if the file exists in ~/App_Plugins/[Packagename]/Views/MacroPartials, if so then it is also a partial view.
return (ScriptPath.InvariantStartsWith(SystemDirectories.MvcViews + "/MacroPartials/")
|| (Regex.IsMatch(ScriptPath, "~/App_Plugins/.+?/Views/MacroPartials", RegexOptions.Compiled | RegexOptions.IgnoreCase)))
? MacroTypes.PartialView
: MacroTypes.Script;
}
if (string.IsNullOrEmpty(ControlType) == false && ControlType.InvariantContains(".ascx"))
return MacroTypes.UserControl;
if (string.IsNullOrEmpty(ControlType) == false && string.IsNullOrEmpty(ControlAssembly) == false)
return MacroTypes.CustomControl;
return MacroTypes.Unknown;
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a Macro Property
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class MacroProperty : TracksChangesEntityBase, IMacroProperty, IRememberBeingDirty
{
private string _alias;
private string _name;
private int _sortOrder;
private IMacroPropertyType _propertyType;
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Alias);
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Name);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, int>(x => x.SortOrder);
private static readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, IMacroPropertyType>(x => x.PropertyType);
/// <summary>
/// Gets or sets the Alias of the Property
/// </summary>
[DataMember]
public string Alias
{
get { return _alias; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_alias = value;
return _alias;
}, _alias, AliasSelector);
}
}
/// <summary>
/// Gets or sets the Name of the Property
/// </summary>
[DataMember]
public string Name
{
get { return _name; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
/// Gets or sets the Sort Order of the Property
/// </summary>
[DataMember]
public int SortOrder
{
get { return _sortOrder; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
/// Gets or sets the Type for this Property
/// </summary>
/// <remarks>
/// The MacroPropertyTypes acts as a plugin for Macros.
/// All types was previously contained in the database, but has been ported to code.
/// </remarks>
[DataMember]
public IMacroPropertyType PropertyType
{
get { return _propertyType; }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_propertyType = value;
return _propertyType;
}, _propertyType, PropertyTypeSelector);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Models
{
/// <summary>
/// A macro's property collection
/// </summary>
internal class MacroPropertyCollection : ObservableDictionary<string, IMacroProperty>
{
public MacroPropertyCollection()
: base(property => property.Alias)
{
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Enum for the three allowed BaseTypes
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal enum MacroPropertyTypeBaseTypes
{
[EnumMember]
Int32,
[EnumMember]
Boolean,
[EnumMember]
String
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Enum for the various types of Macros
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal enum MacroTypes
{
[EnumMember]
Xslt = 1,
[EnumMember]
CustomControl = 2,
[EnumMember]
UserControl = 3,
[EnumMember]
Unknown = 4,
[EnumMember]
Python = 5,
[EnumMember]
Script = 6,
[EnumMember]
PartialView = 7
}
}

View File

@@ -1,4 +1,5 @@
using Umbraco.Core.Persistence;
using System.Collections.Generic;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
@@ -54,5 +55,8 @@ namespace Umbraco.Core.Models.Rdbms
[Column("macroPython")]
[NullSetting(NullSetting = NullSettings.Null)]
public string Python { get; set; }
[ResultColumn]
public List<MacroPropertyDto> MacroPropertyDtos { get; set; }
}
}

View File

@@ -6,100 +6,6 @@ using Umbraco.Core.Sync;
namespace Umbraco.Core.Models
{
internal class Macro : Entity, IAggregateRoot
{
/// <summary>
/// Creates an item with pre-filled properties
/// </summary>
/// <param name="id"></param>
/// <param name="useInEditor"></param>
/// <param name="refreshRate"></param>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="controlType"></param>
/// <param name="controlAssembly"></param>
/// <param name="xsltPath"></param>
/// <param name="cacheByPage"></param>
/// <param name="cachePersonalized"></param>
/// <param name="dontRender"></param>
/// <param name="scriptPath"></param>
public Macro(int id, bool useInEditor, int refreshRate, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cachePersonalized, bool dontRender, string scriptPath)
{
Id = id;
UseInEditor = useInEditor;
RefreshRate = refreshRate;
Alias = alias;
Name = name;
ControlType = controlType;
ControlAssembly = controlAssembly;
XsltPath = xsltPath;
CacheByPage = cacheByPage;
CachePersonalized = cachePersonalized;
DontRender = dontRender;
ScriptPath = scriptPath;
}
/// <summary>
/// Creates an instance for persisting a new item
/// </summary>
/// <param name="useInEditor"></param>
/// <param name="refreshRate"></param>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="controlType"></param>
/// <param name="controlAssembly"></param>
/// <param name="xsltPath"></param>
/// <param name="cacheByPage"></param>
/// <param name="cachePersonalized"></param>
/// <param name="dontRender"></param>
/// <param name="scriptPath"></param>
public Macro(string @alias, string name,
string controlType = "",
string controlAssembly = "",
string xsltPath = "",
string scriptPath = "",
bool cacheByPage = false,
bool cachePersonalized = false,
bool dontRender = true,
bool useInEditor = false,
int refreshRate = 0)
{
UseInEditor = useInEditor;
RefreshRate = refreshRate;
Alias = alias;
Name = name;
ControlType = controlType;
ControlAssembly = controlAssembly;
XsltPath = xsltPath;
CacheByPage = cacheByPage;
CachePersonalized = cachePersonalized;
DontRender = dontRender;
ScriptPath = scriptPath;
}
public bool UseInEditor { get; set; }
public int RefreshRate { get; set; }
public string Alias { get; set; }
public string Name { get; set; }
public string ControlType { get; set; }
public string ControlAssembly { get; set; }
public string XsltPath { get; set; }
public bool CacheByPage { get; set; }
public bool CachePersonalized { get; set; }
public bool DontRender { get; set; }
public string ScriptPath { get; set; }
}
internal class ServerRegistration : Entity, IServerAddress, IAggregateRoot
{
private string _serverAddress;

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core
{
/// <summary>
/// An ObservableDictionary
/// </summary>
/// <remarks>
/// Assumes that the key will not change and is unique for each element in the collection.
/// Collection is not thread-safe, so calls should be made single-threaded.
/// </remarks>
/// <typeparam name="TValue">The type of elements contained in the BindableCollection</typeparam>
/// <typeparam name="TKey">The type of the indexing key</typeparam>
internal class ObservableDictionary<TKey, TValue> : ObservableCollection<TValue>
{
protected Dictionary<TKey, int> Indecies = new Dictionary<TKey, int>();
protected Func<TValue, TKey> KeySelector;
/// <summary>
/// Create new ObservableDictionary
/// </summary>
/// <param name="keySelector">Selector function to create key from value</param>
public ObservableDictionary(Func<TValue, TKey> keySelector)
: base()
{
if (keySelector == null) throw new ArgumentException("keySelector");
KeySelector = keySelector;
}
#region Protected Methods
protected override void InsertItem(int index, TValue item)
{
var key = KeySelector(item);
if (Indecies.ContainsKey(key))
throw new DuplicateKeyException(key.ToString());
if (index != this.Count)
{
foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList())
{
Indecies[k]++;
}
}
base.InsertItem(index, item);
Indecies[key] = index;
}
protected override void ClearItems()
{
base.ClearItems();
Indecies.Clear();
}
protected override void RemoveItem(int index)
{
var item = this[index];
var key = KeySelector(item);
base.RemoveItem(index);
Indecies.Remove(key);
foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList())
{
Indecies[k]--;
}
}
#endregion
public virtual bool ContainsKey(TKey key)
{
return Indecies.ContainsKey(key);
}
/// <summary>
/// Gets or sets the element with the specified key. If setting a new value, new value must have same key.
/// </summary>
/// <param name="key">Key of element to replace</param>
/// <returns></returns>
public virtual TValue this[TKey key]
{
get { return this[Indecies[key]]; }
set
{
//confirm key matches
if (!KeySelector(value).Equals(key))
throw new InvalidOperationException("Key of new value does not match");
if (!Indecies.ContainsKey(key))
{
this.Add(value);
}
else
{
this[Indecies[key]] = value;
}
}
}
/// <summary>
/// Replaces element at given key with new value. New value must have same key.
/// </summary>
/// <param name="key">Key of element to replace</param>
/// <param name="value">New value</param>
///
/// <exception cref="InvalidOperationException"></exception>
/// <returns>False if key not found</returns>
public virtual bool Replace(TKey key, TValue value)
{
if (!Indecies.ContainsKey(key)) return false;
//confirm key matches
if (!KeySelector(value).Equals(key))
throw new InvalidOperationException("Key of new value does not match");
this[Indecies[key]] = value;
return true;
}
public virtual bool Remove(TKey key)
{
if (!Indecies.ContainsKey(key)) return false;
this.RemoveAt(Indecies[key]);
return true;
}
internal class DuplicateKeyException : Exception
{
public string Key { get; private set; }
public DuplicateKeyException(string key)
: base("Attempted to insert duplicate key " + key + " in collection")
{
Key = key;
}
}
}
}

View File

@@ -4,11 +4,11 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
internal class MacroFactory : IEntityFactory<Macro, MacroDto>
internal class MacroFactory : IEntityFactory<IMacro, MacroDto>
{
#region Implementation of IEntityFactory<Language,LanguageDto>
public Macro BuildEntity(MacroDto dto)
public IMacro BuildEntity(MacroDto dto)
{
var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.Python);
//on initial construction we don't want to have dirty properties tracked
@@ -17,17 +17,17 @@ namespace Umbraco.Core.Persistence.Factories
return model;
}
public MacroDto BuildDto(Macro entity)
public MacroDto BuildDto(IMacro entity)
{
var dto = new MacroDto()
{
Alias = entity.Alias,
CacheByPage = entity.CacheByPage,
CachePersonalized = entity.CachePersonalized,
CachePersonalized = entity.CacheByMember,
DontRender = entity.DontRender,
Name = entity.Name,
Python = entity.ScriptPath,
RefreshRate = entity.RefreshRate,
RefreshRate = entity.CacheDuration,
ScriptAssembly = entity.ControlAssembly,
ScriptType = entity.ControlType,
UseInEditor = entity.UseInEditor,

View File

@@ -5,6 +5,7 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
[MapperFor(typeof(Macro))]
[MapperFor(typeof(IMacro))]
internal sealed class MacroMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
@@ -28,12 +29,12 @@ namespace Umbraco.Core.Persistence.Mappers
CacheMap<Macro, MacroDto>(src => src.Id, dto => dto.Id);
CacheMap<Macro, MacroDto>(src => src.Alias, dto => dto.Alias);
CacheMap<Macro, MacroDto>(src => src.CacheByPage, dto => dto.CacheByPage);
CacheMap<Macro, MacroDto>(src => src.CachePersonalized, dto => dto.CachePersonalized);
CacheMap<Macro, MacroDto>(src => src.CacheByMember, dto => dto.CachePersonalized);
CacheMap<Macro, MacroDto>(src => src.ControlAssembly, dto => dto.ScriptAssembly);
CacheMap<Macro, MacroDto>(src => src.ControlType, dto => dto.ScriptType);
CacheMap<Macro, MacroDto>(src => src.DontRender, dto => dto.DontRender);
CacheMap<Macro, MacroDto>(src => src.Name, dto => dto.Name);
CacheMap<Macro, MacroDto>(src => src.RefreshRate, dto => dto.RefreshRate);
CacheMap<Macro, MacroDto>(src => src.CacheDuration, dto => dto.RefreshRate);
CacheMap<Macro, MacroDto>(src => src.ScriptPath, dto => dto.Python);
CacheMap<Macro, MacroDto>(src => src.UseInEditor, dto => dto.UseInEditor);
CacheMap<Macro, MacroDto>(src => src.XsltPath, dto => dto.Xslt);

View File

@@ -0,0 +1,9 @@
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
internal interface IMacroRepository : IRepositoryQueryable<int, IMacro>
{
}
}

View File

@@ -7,11 +7,12 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
internal class MacroRepository : PetaPocoRepositoryBase<int, Macro>
internal class MacroRepository : PetaPocoRepositoryBase<int, IMacro>, IMacroRepository
{
public MacroRepository(IDatabaseUnitOfWork work) : base(work)
{
@@ -21,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
}
protected override Macro PerformGet(int id)
protected override IMacro PerformGet(int id)
{
var sql = GetBaseQuery(false);
sql.Where(GetBaseWhereClause(), new { Id = id });
@@ -35,12 +36,12 @@ namespace Umbraco.Core.Persistence.Repositories
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
entity.ResetDirtyProperties(false);
((TracksChangesEntityBase)entity).ResetDirtyProperties(false);
return entity;
}
protected override IEnumerable<Macro> PerformGetAll(params int[] ids)
protected override IEnumerable<IMacro> PerformGetAll(params int[] ids)
{
if (ids.Any())
{
@@ -59,10 +60,10 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
protected override IEnumerable<Macro> PerformGetByQuery(IQuery<Macro> query)
protected override IEnumerable<IMacro> PerformGetByQuery(IQuery<IMacro> query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<Macro>(sqlClause, query);
var translator = new SqlTranslator<IMacro>(sqlClause, query);
var sql = translator.Translate();
var dtos = Database.Fetch<Macro>(sql);
@@ -76,8 +77,24 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<MacroDto>();
if (isCount)
{
sql.Select("COUNT(*)").From<MacroDto>();
}
else
{
return GetBaseQuery("*");
}
return sql;
}
private static Sql GetBaseQuery(string columns)
{
var sql = new Sql();
sql.Select(columns)
.From<MacroDto>()
.LeftJoin<MacroPropertyDto>()
.On<MacroDto, MacroPropertyDto>(left => left.Id, right => right.Macro);
return sql;
}
@@ -100,7 +117,7 @@ namespace Umbraco.Core.Persistence.Repositories
get { throw new NotImplementedException(); }
}
protected override void PersistNewItem(Macro entity)
protected override void PersistNewItem(IMacro entity)
{
((Entity)entity).AddingEntity();
@@ -110,10 +127,17 @@ namespace Umbraco.Core.Persistence.Repositories
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
foreach (var propDto in dto.MacroPropertyDtos)
{
//need to set the id explicitly here
propDto.Macro = id;
Database.Insert(propDto);
}
((ICanBeDirty)entity).ResetDirtyProperties();
}
protected override void PersistUpdatedItem(Macro entity)
protected override void PersistUpdatedItem(IMacro entity)
{
((Entity)entity).UpdatingEntity();
@@ -122,6 +146,33 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Update(dto);
//update the sections if they've changed
var macro = (Macro)entity;
if (macro.IsPropertyDirty("Properties"))
{
//for any that exist on the object, we need to determine if we need to update or insert
foreach (var propDto in dto.MacroPropertyDtos)
{
if (macro.AddedProperties.Contains(propDto.Alias))
{
//we need to insert since this was added
Database.Insert(propDto);
}
else
{
Database.Update(propDto);
}
}
//now we need to delete any props that have been removed
foreach (var propAlias in macro.RemovedProperties)
{
//delete the property
Database.Delete<MacroPropertyDto>("WHERE macro=@macroId AND macroPropertyAlias=@propAlias",
new { macroId = macro.Id, propAlias = propAlias });
}
}
((ICanBeDirty)entity).ResetDirtyProperties();
}
}

View File

@@ -315,6 +315,14 @@
<Compile Include="Models\IDictionaryTranslation.cs" />
<Compile Include="Models\IFile.cs" />
<Compile Include="Models\ILanguage.cs" />
<Compile Include="Models\IMacro.cs" />
<Compile Include="Models\IMacroProperty.cs" />
<Compile Include="Models\IMacroPropertyType.cs" />
<Compile Include="Models\Macro.cs" />
<Compile Include="Models\MacroProperty.cs" />
<Compile Include="Models\MacroPropertyCollection.cs" />
<Compile Include="Models\MacroPropertyTypeBaseTypes.cs" />
<Compile Include="Models\MacroTypes.cs" />
<Compile Include="Models\Mapping\IMapperConfiguration.cs" />
<Compile Include="Models\Mapping\MapperConfiguration.cs" />
<Compile Include="Models\Membership\EntityPermission.cs" />
@@ -370,6 +378,7 @@
<Compile Include="Models\Validation\RequiredForPersistenceAttribute.cs" />
<Compile Include="ObjectResolution\ApplicationEventsResolver.cs" />
<Compile Include="ObjectResolution\ResolverCollection.cs" />
<Compile Include="ObservableDictionary.cs" />
<Compile Include="Persistence\Caching\InMemoryCacheProvider.cs" />
<Compile Include="Persistence\Caching\IRepositoryCacheProvider.cs" />
<Compile Include="Persistence\Caching\NullCacheProvider.cs" />
@@ -398,6 +407,7 @@
<Compile Include="Persistence\Mappers\MacroMapper.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSeven\RemoveDefaultPermissionUserColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSeven\UpdateControlIdToPropertyEditorAlias.cs" />
<Compile Include="Persistence\Repositories\Interfaces\IMacroRepository.cs" />
<Compile Include="Persistence\Repositories\MacroRepository.cs" />
<Compile Include="PropertyEditors\LegacyPropertyEditorIdToAliasConverter.cs" />
<Compile Include="Persistence\Mappers\MapperForAttribute.cs" />

View File

@@ -88,12 +88,12 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(macro.HasIdentity, Is.True);
Assert.That(macro.Alias, Is.EqualTo("test1"));
Assert.That(macro.CacheByPage, Is.EqualTo(false));
Assert.That(macro.CachePersonalized, Is.EqualTo(false));
Assert.That(macro.CacheByMember, Is.EqualTo(false));
Assert.That(macro.ControlAssembly, Is.EqualTo("MyAssembly1"));
Assert.That(macro.ControlType, Is.EqualTo("~/usercontrol/test1.ascx"));
Assert.That(macro.DontRender, Is.EqualTo(true));
Assert.That(macro.Name, Is.EqualTo("Test1"));
Assert.That(macro.RefreshRate, Is.EqualTo(0));
Assert.That(macro.CacheDuration, Is.EqualTo(0));
Assert.That(macro.ScriptPath, Is.EqualTo("~/views/macropartials/test1.cshtml"));
Assert.That(macro.UseInEditor, Is.EqualTo(false));
Assert.That(macro.XsltPath, Is.EqualTo("test1.xslt"));
@@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var repository = new MacroRepository(unitOfWork))
{
// Act
var query = Query<Macro>.Builder.Where(x => x.Alias.ToUpper() == "TEST1");
var query = Query<IMacro>.Builder.Where(x => x.Alias.ToUpper() == "TEST1");
var result = repository.GetByQuery(query);
// Assert
@@ -145,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var repository = new MacroRepository(unitOfWork))
{
// Act
var query = Query<Macro>.Builder.Where(x => x.Name.StartsWith("Test"));
var query = Query<IMacro>.Builder.Where(x => x.Name.StartsWith("Test"));
int count = repository.Count(query);
// Assert
@@ -183,9 +183,9 @@ namespace Umbraco.Tests.Persistence.Repositories
// Act
var macro = repository.Get(2);
macro.Name = "Hello";
macro.RefreshRate = 1234;
macro.CacheDuration = 1234;
macro.CacheByPage = true;
macro.CachePersonalized = true;
macro.CacheByMember = true;
macro.ControlAssembly = "";
macro.ControlType = "";
macro.DontRender = false;
@@ -201,9 +201,9 @@ namespace Umbraco.Tests.Persistence.Repositories
// Assert
Assert.That(macroUpdated, Is.Not.Null);
Assert.That(macroUpdated.Name, Is.EqualTo("Hello"));
Assert.That(macroUpdated.RefreshRate, Is.EqualTo(1234));
Assert.That(macroUpdated.CacheDuration, Is.EqualTo(1234));
Assert.That(macroUpdated.CacheByPage, Is.EqualTo(true));
Assert.That(macroUpdated.CachePersonalized, Is.EqualTo(true));
Assert.That(macroUpdated.CacheByMember, Is.EqualTo(true));
Assert.That(macroUpdated.ControlAssembly, Is.EqualTo(""));
Assert.That(macroUpdated.ControlType, Is.EqualTo(""));
Assert.That(macroUpdated.DontRender, Is.EqualTo(false));
@@ -266,7 +266,7 @@ namespace Umbraco.Tests.Persistence.Repositories
{
repository.AddOrUpdate(new Macro("test1", "Test1", "~/usercontrol/test1.ascx", "MyAssembly1", "test1.xslt", "~/views/macropartials/test1.cshtml"));
repository.AddOrUpdate(new Macro("test2", "Test2", "~/usercontrol/test2.ascx", "MyAssembly2", "test2.xslt", "~/views/macropartials/test2.cshtml"));
repository.AddOrUpdate(new Macro("test3", "Test3", "~/usercontrol/test3.ascx", "MyAssembly3", "test3.xslt", "~/views/macropartials/test3.cshtml"));
repository.AddOrUpdate(new Macro("test3", "Tet3", "~/usercontrol/test3.ascx", "MyAssembly3", "test3.xslt", "~/views/macropartials/test3.cshtml"));
unitOfWork.Commit();
}