diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Abstractions/GuidUdi.cs similarity index 69% rename from src/Umbraco.Core/GuidUdi.cs rename to src/Umbraco.Abstractions/GuidUdi.cs index d78a33a435..8acac61b32 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Abstractions/GuidUdi.cs @@ -39,29 +39,6 @@ namespace Umbraco.Core Guid = guid; } - /// - /// Converts the string representation of an entity identifier into the equivalent GuidUdi instance. - /// - /// The string to convert. - /// A GuidUdi instance that contains the value that was parsed. - public new static GuidUdi Parse(string s) - { - var udi = Udi.Parse(s); - if (udi is GuidUdi == false) - throw new FormatException("String \"" + s + "\" is not a guid entity id."); - - return (GuidUdi) udi; - } - - public static bool TryParse(string s, out GuidUdi udi) - { - Udi tmp; - udi = null; - if (TryParse(s, out tmp) == false) return false; - udi = tmp as GuidUdi; - return udi != null; - } - public override bool Equals(object obj) { var other = obj as GuidUdi; diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Abstractions/NamedUdiRange.cs similarity index 100% rename from src/Umbraco.Core/NamedUdiRange.cs rename to src/Umbraco.Abstractions/NamedUdiRange.cs diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Abstractions/StringUdi.cs similarity index 69% rename from src/Umbraco.Core/StringUdi.cs rename to src/Umbraco.Abstractions/StringUdi.cs index 7b32399599..7e067ad6f9 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Abstractions/StringUdi.cs @@ -49,29 +49,6 @@ namespace Umbraco.Core return string.Join("/", s.Split('/').Select(Uri.EscapeDataString)); } - /// - /// Converts the string representation of an entity identifier into the equivalent StringUdi instance. - /// - /// The string to convert. - /// A StringUdi instance that contains the value that was parsed. - public new static StringUdi Parse(string s) - { - var udi = Udi.Parse(s); - if (udi is StringUdi == false) - throw new FormatException("String \"" + s + "\" is not a string entity id."); - - return (StringUdi) udi; - } - - public static bool TryParse(string s, out StringUdi udi) - { - udi = null; - Udi tmp; - if (TryParse(s, out tmp) == false || tmp is StringUdi == false) return false; - udi = (StringUdi) tmp; - return true; - } - /// public override bool IsRoot { diff --git a/src/Umbraco.Abstractions/Udi.cs b/src/Umbraco.Abstractions/Udi.cs new file mode 100644 index 0000000000..80e58b2f86 --- /dev/null +++ b/src/Umbraco.Abstractions/Udi.cs @@ -0,0 +1,171 @@ +using System; +using System.ComponentModel; +using System.Linq; + +namespace Umbraco.Core +{ + + /// + /// Represents an entity identifier. + /// + /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. + [TypeConverter(typeof(UdiTypeConverter))] + public abstract class Udi : IComparable + { + public Uri UriValue { get; } + + /// + /// Initializes a new instance of the Udi class. + /// + /// The entity type part of the identifier. + /// The string value of the identifier. + protected Udi(string entityType, string stringValue) + { + EntityType = entityType; + UriValue = new Uri(stringValue); + } + + /// + /// Initializes a new instance of the Udi class. + /// + /// The uri value of the identifier. + protected Udi(Uri uriValue) + { + EntityType = uriValue.Host; + UriValue = uriValue; + } + + + + /// + /// Gets the entity type part of the identifier. + /// + public string EntityType { get; private set; } + + public int CompareTo(Udi other) + { + return string.Compare(UriValue.ToString(), other.UriValue.ToString(), StringComparison.InvariantCultureIgnoreCase); + } + + public override string ToString() + { + // UriValue is created in the ctor and is never null + // use AbsoluteUri here and not ToString else it's not encoded! + return UriValue.AbsoluteUri; + } + + + + /// + /// Creates a root Udi for an entity type. + /// + /// The entity type. + /// The root Udi for the entity type. + public static Udi Create(string entityType) + { + return UdiParser.GetRootUdi(entityType); + } + + /// + /// Creates a string Udi. + /// + /// The entity type. + /// The identifier. + /// The string Udi for the entity type and identifier. + public static Udi Create(string entityType, string id) + { + if (UdiParser.UdiTypes.TryGetValue(entityType, out var udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Value cannot be null or whitespace.", "id"); + if (udiType != UdiType.StringUdi) + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType)); + + return new StringUdi(entityType, id); + } + + /// + /// Creates a Guid Udi. + /// + /// The entity type. + /// The identifier. + /// The Guid Udi for the entity type and identifier. + public static Udi Create(string entityType, Guid id) + { + if (UdiParser.UdiTypes.TryGetValue(entityType, out var udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + + if (udiType != UdiType.GuidUdi) + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); + if (id == default(Guid)) + throw new ArgumentException("Cannot be an empty guid.", "id"); + + return new GuidUdi(entityType, id); + } + + public static Udi Create(Uri uri) + { + // if it's a know type go fast and use ctors + // else fallback to parsing the string (and guess the type) + + if (UdiParser.UdiTypes.TryGetValue(uri.Host, out var udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); + + if (udiType == UdiType.GuidUdi) + return new GuidUdi(uri); + if (udiType == UdiType.GuidUdi) + return new StringUdi(uri); + + throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); + } + + public void EnsureType(params string[] validTypes) + { + if (validTypes.Contains(EntityType) == false) + throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType)); + } + + /// + /// Gets a value indicating whether this Udi is a root Udi. + /// + /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root. + public abstract bool IsRoot { get; } + + /// + /// Ensures that this Udi is not a root Udi. + /// + /// This Udi. + /// When this Udi is a Root Udi. + public Udi EnsureNotRoot() + { + if (IsRoot) throw new Exception("Root Udi."); + return this; + } + + public override bool Equals(object obj) + { + var other = obj as Udi; + return other != null && GetType() == other.GetType() && UriValue == other.UriValue; + } + + public override int GetHashCode() + { + return UriValue.GetHashCode(); + } + + public static bool operator ==(Udi udi1, Udi udi2) + { + if (ReferenceEquals(udi1, udi2)) return true; + if ((object)udi1 == null || (object)udi2 == null) return false; + return udi1.Equals(udi2); + } + + public static bool operator !=(Udi udi1, Udi udi2) + { + return (udi1 == udi2) == false; + } + + + } +} diff --git a/src/Umbraco.Core/UdiDefinitionAttribute.cs b/src/Umbraco.Abstractions/UdiDefinitionAttribute.cs similarity index 100% rename from src/Umbraco.Core/UdiDefinitionAttribute.cs rename to src/Umbraco.Abstractions/UdiDefinitionAttribute.cs diff --git a/src/Umbraco.Abstractions/UdiParser.cs b/src/Umbraco.Abstractions/UdiParser.cs new file mode 100644 index 0000000000..08af2bf0cd --- /dev/null +++ b/src/Umbraco.Abstractions/UdiParser.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Umbraco.Core +{ + public sealed class UdiParser + { + private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); + internal static ConcurrentDictionary UdiTypes { get; private set; } + + static UdiParser() + { + // initialize with known (built-in) Udi types + // we will add scanned types later on + UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); + } + + /// + /// Internal API for tests to resets all udi types back to only the known udi types. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void ResetUdiTypes() + { + UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + public static Udi Parse(string s) + { + ParseInternal(s, false, false, out var udi); + return udi; + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method succeeds but sets udito an + /// value. + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + public static Udi Parse(string s, bool knownTypes) + { + ParseInternal(s, false, knownTypes, out var udi); + return udi; + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + public static bool TryParse(string s, out Udi udi) + { + return ParseInternal(s, true, false, out udi); + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + public static bool TryParse(string s, out T udi) + where T : Udi + { + var result = ParseInternal(s, true, false, out var parsed); + if (result && parsed is T) + { + udi = (T)parsed; + return true; + } + + udi = null; + return false; + } + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method returns false but still sets udi + /// to an value. + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + public static bool TryParse(string s, bool knownTypes, out Udi udi) + { + return ParseInternal(s, true, knownTypes, out udi); + } + + private static bool ParseInternal(string s, bool tryParse, bool knownTypes, out Udi udi) + { + udi = null; + Uri uri; + + if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false + || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) + { + if (tryParse) return false; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); + } + + var entityType = uri.Host; + if (UdiTypes.TryGetValue(entityType, out var udiType) == false) + { + if (knownTypes) + { + // not knowing the type is not an error + // just return the unknown type udi + udi = UnknownTypeUdi.Instance; + return false; + } + if (tryParse) return false; + throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); + } + + var path = uri.AbsolutePath.TrimStart('/'); + + if (udiType == UdiType.GuidUdi) + { + if (path == string.Empty) + { + udi = GetRootUdi(uri.Host); + return true; + } + if (Guid.TryParse(path, out var guid) == false) + { + if (tryParse) return false; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); + } + udi = new GuidUdi(uri.Host, guid); + return true; + } + + if (udiType == UdiType.StringUdi) + { + udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); + return true; + } + + if (tryParse) return false; + throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); + } + + internal static Udi GetRootUdi(string entityType) + { + return RootUdis.GetOrAdd(entityType, x => + { + if (UdiTypes.TryGetValue(x, out var udiType) == false) + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); + return udiType == UdiType.StringUdi + ? (Udi)new StringUdi(entityType, string.Empty) + : new GuidUdi(entityType, Guid.Empty); + }); + } + + + + /// + /// Registers a custom entity type. + /// + /// + /// + public static void RegisterUdiType(string entityType, UdiType udiType) => UdiTypes.TryAdd(entityType, udiType); + + public static Dictionary GetKnownUdiTypes() => + new Dictionary + { + { Constants.UdiEntityType.Unknown, UdiType.Unknown }, + + { Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi }, + { Constants.UdiEntityType.Document, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi }, + { Constants.UdiEntityType.Media, UdiType.GuidUdi }, + { Constants.UdiEntityType.Member, UdiType.GuidUdi }, + { Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi }, + { Constants.UdiEntityType.Macro, UdiType.GuidUdi }, + { Constants.UdiEntityType.Template, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentType, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi }, + { Constants.UdiEntityType.MediaType, UdiType.GuidUdi }, + { Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, + { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, + { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, + { Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi }, + + { Constants.UdiEntityType.AnyString, UdiType.StringUdi }, + { Constants.UdiEntityType.Language, UdiType.StringUdi }, + { Constants.UdiEntityType.MacroScript, UdiType.StringUdi }, + { Constants.UdiEntityType.MediaFile, UdiType.StringUdi }, + { Constants.UdiEntityType.TemplateFile, UdiType.StringUdi }, + { Constants.UdiEntityType.Script, UdiType.StringUdi }, + { Constants.UdiEntityType.PartialView, UdiType.StringUdi }, + { Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi }, + { Constants.UdiEntityType.Stylesheet, UdiType.StringUdi } + }; + } +} diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Abstractions/UdiRange.cs similarity index 100% rename from src/Umbraco.Core/UdiRange.cs rename to src/Umbraco.Abstractions/UdiRange.cs diff --git a/src/Umbraco.Core/UdiTypeConverter.cs b/src/Umbraco.Abstractions/UdiTypeConverter.cs similarity index 93% rename from src/Umbraco.Core/UdiTypeConverter.cs rename to src/Umbraco.Abstractions/UdiTypeConverter.cs index e63aa7f786..8da1b7bb5f 100644 --- a/src/Umbraco.Core/UdiTypeConverter.cs +++ b/src/Umbraco.Abstractions/UdiTypeConverter.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core if (value is string) { Udi udi; - if (Udi.TryParse((string)value, out udi)) + if (UdiParser.TryParse((string)value, out udi)) { return udi; } diff --git a/src/Umbraco.Abstractions/UnknownTypeUdi.cs b/src/Umbraco.Abstractions/UnknownTypeUdi.cs new file mode 100644 index 0000000000..ac42205cbc --- /dev/null +++ b/src/Umbraco.Abstractions/UnknownTypeUdi.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core +{ + internal class UnknownTypeUdi : Udi + { + private UnknownTypeUdi() + : base("unknown", "umb://unknown/") + { } + + public static readonly UnknownTypeUdi Instance = new UnknownTypeUdi(); + + public override bool IsRoot + { + get { return false; } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs index 1956876402..343557e3f5 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 GuidUdi udi = null; if (relatedLink.IsInternal) { - var linkIsUdi = GuidUdi.TryParse(relatedLink.Link, out udi); + var linkIsUdi = UdiParser.TryParse(relatedLink.Link, out udi); if (linkIsUdi == false) { // oh no.. probably an integer, yikes! diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 5449318e7a..a9bcbc2b65 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -1,6 +1,4 @@ -using System; -using System.ComponentModel; -using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.Models { diff --git a/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs b/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs index e6473e7f8e..79ff90c734 100644 --- a/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs +++ b/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Serialization { var jo = JToken.ReadFrom(reader); var val = jo.ToObject(); - return val == null ? null : Udi.Parse(val, true); + return val == null ? null : UdiParser.Parse(val, true); } } } diff --git a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs index 134faf3d1d..eac5dd8c26 100644 --- a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs +++ b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Serialization { var jo = JToken.ReadFrom(reader); var val = jo.ToObject(); - return val == null ? null : Udi.Parse(val); + return val == null ? null : UdiParser.Parse(val); } } } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs deleted file mode 100644 index 444cbda6f5..0000000000 --- a/src/Umbraco.Core/Udi.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using Umbraco.Core.Deploy; -using Umbraco.Core.Composing; - -namespace Umbraco.Core -{ - /// - /// Represents an entity identifier. - /// - /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. - [TypeConverter(typeof(UdiTypeConverter))] - public abstract class Udi : IComparable - { - // notes - see U4-10409 - // if this class is used during application pre-start it cannot scans the assemblies, - // this is addressed by lazily-scanning, with the following caveats: - // - parsing a root udi still requires a scan and therefore still breaks - // - parsing an invalid udi ("umb://should-be-guid/") corrupts KnowUdiTypes - - private static volatile bool _scanned; - private static readonly object ScanLocker = new object(); - private static ConcurrentDictionary _udiTypes; - private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); - internal readonly Uri UriValue; // internal for UdiRange - - /// - /// Initializes a new instance of the Udi class. - /// - /// The entity type part of the identifier. - /// The string value of the identifier. - protected Udi(string entityType, string stringValue) - { - EntityType = entityType; - UriValue = new Uri(stringValue); - } - - /// - /// Initializes a new instance of the Udi class. - /// - /// The uri value of the identifier. - protected Udi(Uri uriValue) - { - EntityType = uriValue.Host; - UriValue = uriValue; - } - - static Udi() - { - // initialize with known (built-in) Udi types - // we will add scanned types later on - _udiTypes = new ConcurrentDictionary(UdiEntityTypeHelper.GetTypes()); - } - - // for tests, totally unsafe - internal static void ResetUdiTypes() - { - _udiTypes = new ConcurrentDictionary(UdiEntityTypeHelper.GetTypes()); - _scanned = false; - } - - /// - /// Gets the entity type part of the identifier. - /// - public string EntityType { get; private set; } - - public int CompareTo(Udi other) - { - return string.Compare(UriValue.ToString(), other.UriValue.ToString(), StringComparison.InvariantCultureIgnoreCase); - } - - public override string ToString() - { - // UriValue is created in the ctor and is never null - // use AbsoluteUri here and not ToString else it's not encoded! - return UriValue.AbsoluteUri; - } - - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// An Udi instance that contains the value that was parsed. - public static Udi Parse(string s) - { - Udi udi; - ParseInternal(s, false, false, out udi); - return udi; - } - - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// A value indicating whether to only deal with known types. - /// An Udi instance that contains the value that was parsed. - /// - /// If is true, and the string could not be parsed because - /// the entity type was not known, the method succeeds but sets udito an - /// value. - /// If is true, assemblies are not scanned for types, - /// and therefore only builtin types may be known. Unless scanning already took place. - /// - public static Udi Parse(string s, bool knownTypes) - { - Udi udi; - ParseInternal(s, false, knownTypes, out udi); - return udi; - } - - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// An Udi instance that contains the value that was parsed. - /// A boolean value indicating whether the string could be parsed. - public static bool TryParse(string s, out Udi udi) - { - return ParseInternal(s, true, false, out udi); - } - - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// A value indicating whether to only deal with known types. - /// An Udi instance that contains the value that was parsed. - /// A boolean value indicating whether the string could be parsed. - /// - /// If is true, and the string could not be parsed because - /// the entity type was not known, the method returns false but still sets udi - /// to an value. - /// If is true, assemblies are not scanned for types, - /// and therefore only builtin types may be known. Unless scanning already took place. - /// - public static bool TryParse(string s, bool knownTypes, out Udi udi) - { - return ParseInternal(s, true, knownTypes, out udi); - } - - private static bool ParseInternal(string s, bool tryParse, bool knownTypes, out Udi udi) - { - if (knownTypes == false) - EnsureScanForUdiTypes(); - - udi = null; - Uri uri; - - if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false - || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) - { - if (tryParse) return false; - throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); - } - - var entityType = uri.Host; - UdiType udiType; - if (_udiTypes.TryGetValue(entityType, out udiType) == false) - { - if (knownTypes) - { - // not knowing the type is not an error - // just return the unknown type udi - udi = UnknownTypeUdi.Instance; - return false; - } - if (tryParse) return false; - throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); - } - - var path = uri.AbsolutePath.TrimStart('/'); - - if (udiType == UdiType.GuidUdi) - { - if (path == string.Empty) - { - udi = GetRootUdi(uri.Host); - return true; - } - Guid guid; - if (Guid.TryParse(path, out guid) == false) - { - if (tryParse) return false; - throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); - } - udi = new GuidUdi(uri.Host, guid); - return true; - } - - if (udiType == UdiType.StringUdi) - { - udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); - return true; - } - - if (tryParse) return false; - throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); - } - - private static Udi GetRootUdi(string entityType) - { - EnsureScanForUdiTypes(); - - return RootUdis.GetOrAdd(entityType, x => - { - UdiType udiType; - if (_udiTypes.TryGetValue(x, out udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); - return udiType == UdiType.StringUdi - ? (Udi)new StringUdi(entityType, string.Empty) - : new GuidUdi(entityType, Guid.Empty); - }); - } - - /// - /// When required scan assemblies for known UDI types based on instances - /// - /// - /// This is only required when needing to resolve root udis - /// - private static void EnsureScanForUdiTypes() - { - if (_scanned) return; - - lock (ScanLocker) - { - // Scan for unknown UDI types - // there is no way we can get the "registered" service connectors, as registration - // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we - // just pick every service connectors - just making sure that not two of them - // would register the same entity type, with different udi types (would not make - // much sense anyways). - var connectors = Current.TypeLoader.GetTypes(); - var result = new Dictionary(); - foreach (var connector in connectors) - { - var attrs = connector.GetCustomAttributes(false); - foreach (var attr in attrs) - { - UdiType udiType; - if (result.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType) - throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); - result[attr.EntityType] = attr.UdiType; - } - } - - // merge these into the known list - foreach (var item in result) - _udiTypes.TryAdd(item.Key, item.Value); - - _scanned = true; - } - } - - /// - /// Creates a root Udi for an entity type. - /// - /// The entity type. - /// The root Udi for the entity type. - public static Udi Create(string entityType) - { - return GetRootUdi(entityType); - } - - /// - /// Creates a string Udi. - /// - /// The entity type. - /// The identifier. - /// The string Udi for the entity type and identifier. - public static Udi Create(string entityType, string id) - { - UdiType udiType; - if (_udiTypes.TryGetValue(entityType, out udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); - - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentException("Value cannot be null or whitespace.", "id"); - if (udiType != UdiType.StringUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType)); - - return new StringUdi(entityType, id); - } - - /// - /// Creates a Guid Udi. - /// - /// The entity type. - /// The identifier. - /// The Guid Udi for the entity type and identifier. - public static Udi Create(string entityType, Guid id) - { - UdiType udiType; - if (_udiTypes.TryGetValue(entityType, out udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); - - if (udiType != UdiType.GuidUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); - if (id == default(Guid)) - throw new ArgumentException("Cannot be an empty guid.", "id"); - - return new GuidUdi(entityType, id); - } - - internal static Udi Create(Uri uri) - { - // if it's a know type go fast and use ctors - // else fallback to parsing the string (and guess the type) - - UdiType udiType; - if (_udiTypes.TryGetValue(uri.Host, out udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); - - if (udiType == UdiType.GuidUdi) - return new GuidUdi(uri); - if (udiType == UdiType.GuidUdi) - return new StringUdi(uri); - - throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); - } - - public void EnsureType(params string[] validTypes) - { - if (validTypes.Contains(EntityType) == false) - throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType)); - } - - /// - /// Gets a value indicating whether this Udi is a root Udi. - /// - /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root. - public abstract bool IsRoot { get; } - - /// - /// Ensures that this Udi is not a root Udi. - /// - /// This Udi. - /// When this Udi is a Root Udi. - public Udi EnsureNotRoot() - { - if (IsRoot) throw new Exception("Root Udi."); - return this; - } - - public override bool Equals(object obj) - { - var other = obj as Udi; - return other != null && GetType() == other.GetType() && UriValue == other.UriValue; - } - - public override int GetHashCode() - { - return UriValue.GetHashCode(); - } - - public static bool operator ==(Udi udi1, Udi udi2) - { - if (ReferenceEquals(udi1, udi2)) return true; - if ((object)udi1 == null || (object)udi2 == null) return false; - return udi1.Equals(udi2); - } - - public static bool operator !=(Udi udi1, Udi udi2) - { - return (udi1 == udi2) == false; - } - - private class UnknownTypeUdi : Udi - { - private UnknownTypeUdi() - : base("unknown", "umb://unknown/") - { } - - public static readonly UnknownTypeUdi Instance = new UnknownTypeUdi(); - - public override bool IsRoot - { - get { return false; } - } - } - } -} diff --git a/src/Umbraco.Core/UdiEntityTypeHelper.cs b/src/Umbraco.Core/UdiEntityTypeHelper.cs index 6504ddc674..12e934b9ec 100644 --- a/src/Umbraco.Core/UdiEntityTypeHelper.cs +++ b/src/Umbraco.Core/UdiEntityTypeHelper.cs @@ -6,43 +6,7 @@ namespace Umbraco.Core { public static class UdiEntityTypeHelper { - internal static Dictionary GetTypes() => - new Dictionary - { - { Constants.UdiEntityType.Unknown, UdiType.Unknown }, - - { Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi }, - { Constants.UdiEntityType.Document, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi }, - { Constants.UdiEntityType.Media, UdiType.GuidUdi }, - { Constants.UdiEntityType.Member, UdiType.GuidUdi }, - { Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi }, - { Constants.UdiEntityType.Macro, UdiType.GuidUdi }, - { Constants.UdiEntityType.Template, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentType, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi }, - { Constants.UdiEntityType.MediaType, UdiType.GuidUdi }, - { Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, - { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, - { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, - { Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi }, - - { Constants.UdiEntityType.AnyString, UdiType.StringUdi }, - { Constants.UdiEntityType.Language, UdiType.StringUdi }, - { Constants.UdiEntityType.MacroScript, UdiType.StringUdi }, - { Constants.UdiEntityType.MediaFile, UdiType.StringUdi }, - { Constants.UdiEntityType.TemplateFile, UdiType.StringUdi }, - { Constants.UdiEntityType.Script, UdiType.StringUdi }, - { Constants.UdiEntityType.PartialView, UdiType.StringUdi }, - { Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi }, - { Constants.UdiEntityType.Stylesheet, UdiType.StringUdi } - }; + public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) { diff --git a/src/Umbraco.Core/UdiParserServiceConnectors.cs b/src/Umbraco.Core/UdiParserServiceConnectors.cs new file mode 100644 index 0000000000..6137b0bde9 --- /dev/null +++ b/src/Umbraco.Core/UdiParserServiceConnectors.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Deploy; +using Umbraco.Core.Composing; + +namespace Umbraco.Core +{ + public static class UdiParserServiceConnectors + { + // notes - see U4-10409 + // if this class is used during application pre-start it cannot scans the assemblies, + // this is addressed by lazily-scanning, with the following caveats: + // - parsing a root udi still requires a scan and therefore still breaks + // - parsing an invalid udi ("umb://should-be-guid/") corrupts KnowUdiTypes + + private static volatile bool _scanned; + private static readonly object ScanLocker = new object(); + + /// + /// Scan for deploy in assemblies for known UDI types. + /// + /// + public static void ScanDeployServiceConnectorsForUdiTypes(TypeLoader typeLoader) + { + if (typeLoader is null) + throw new ArgumentNullException(nameof(typeLoader)); + + if (_scanned) return; + + lock (ScanLocker) + { + // Scan for unknown UDI types + // there is no way we can get the "registered" service connectors, as registration + // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we + // just pick every service connectors - just making sure that not two of them + // would register the same entity type, with different udi types (would not make + // much sense anyways) + var connectors = typeLoader.GetTypes(); + var result = new Dictionary(); + foreach (var connector in connectors) + { + var attrs = connector.GetCustomAttributes(false); + foreach (var attr in attrs) + { + if (result.TryGetValue(attr.EntityType, out var udiType) && udiType != attr.UdiType) + throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); + result[attr.EntityType] = attr.UdiType; + } + } + + // merge these into the known list + foreach (var item in result) + UdiParser.RegisterUdiType(item.Key, item.Value); + + _scanned = true; + } + } + + /// + /// Registers a single to add it's UDI type. + /// + /// + public static void RegisterServiceConnector() + where T: IServiceConnector + { + var result = new Dictionary(); + var connector = typeof(T); + var attrs = connector.GetCustomAttributes(false); + foreach (var attr in attrs) + { + if (result.TryGetValue(attr.EntityType, out var udiType) && udiType != attr.UdiType) + throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); + result[attr.EntityType] = attr.UdiType; + } + + // merge these into the known list + foreach (var item in result) + UdiParser.RegisterUdiType(item.Key, item.Value); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5ca4efc67d..ab5198f677 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -527,7 +527,6 @@ - @@ -658,7 +657,6 @@ - @@ -1095,17 +1093,13 @@ - - - - - + diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index dc2265eac2..df5d5363e5 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -24,21 +24,7 @@ namespace Umbraco.Tests.CoreThings [SetUp] public void SetUp() { - // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! - var container = new Mock(); - var ioHelper = IOHelper.Default; - var typeFinder = new TypeFinder(Mock.Of()); - container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()))); - Current.Factory = container.Object; - - Udi.ResetUdiTypes(); - } - - [TearDown] - public void TearDown() - { - Current.Reset(); + UdiParser.ResetUdiTypes(); } [Test] @@ -53,7 +39,7 @@ namespace Umbraco.Tests.CoreThings [Test] public void StringUdiParseTest() { - var udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id"); + var udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id"); Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); Assert.IsInstanceOf(udi); var stringEntityId = udi as StringUdi; @@ -61,7 +47,7 @@ namespace Umbraco.Tests.CoreThings Assert.AreEqual("test-id", stringEntityId.Id); Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/test-id", udi.ToString()); - udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/DA845952BE474EE9BD6F6194272AC750"); + udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/DA845952BE474EE9BD6F6194272AC750"); Assert.IsInstanceOf(udi); } @@ -78,7 +64,7 @@ namespace Umbraco.Tests.CoreThings Assert.AreEqual("%2Fthis%20is%20a%20test", Uri.EscapeDataString("/this is a test")); Assert.AreEqual("/this%20is%20a%20test", Uri.EscapeUriString("/this is a test")); - var udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test"); + var udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test"); Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); Assert.IsInstanceOf(udi); var stringEntityId = udi as StringUdi; @@ -112,7 +98,7 @@ namespace Umbraco.Tests.CoreThings // with the proper fix in StringUdi this should work: var udi1 = new StringUdi("partial-view-macro", "path/to/View[1].cshtml"); Assert.AreEqual("umb://partial-view-macro/path/to/View%5B1%5D.cshtml", udi1.ToString()); - var udi2 = Udi.Parse("umb://partial-view-macro/path/to/View%5B1%5D.cshtml"); + var udi2 = UdiParser.Parse("umb://partial-view-macro/path/to/View%5B1%5D.cshtml"); Assert.AreEqual("path/to/View[1].cshtml", ((StringUdi) udi2).Id); } @@ -131,7 +117,7 @@ namespace Umbraco.Tests.CoreThings { var guid = Guid.NewGuid(); var s = "umb://" + Constants.UdiEntityType.AnyGuid + "/" + guid.ToString("N"); - var udi = Udi.Parse(s); + var udi = UdiParser.Parse(s); Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType); Assert.IsInstanceOf(udi); var gudi = udi as GuidUdi; @@ -198,15 +184,15 @@ namespace Umbraco.Tests.CoreThings Assert.IsTrue(guidUdi.IsRoot); Assert.AreEqual("umb://any-guid/00000000000000000000000000000000", guidUdi.ToString()); - var udi = Udi.Parse("umb://any-string/"); + var udi = UdiParser.Parse("umb://any-string/"); Assert.IsTrue(udi.IsRoot); Assert.IsInstanceOf(udi); - udi = Udi.Parse("umb://any-guid/00000000000000000000000000000000"); + udi = UdiParser.Parse("umb://any-guid/00000000000000000000000000000000"); Assert.IsTrue(udi.IsRoot); Assert.IsInstanceOf(udi); - udi = Udi.Parse("umb://any-guid/"); + udi = UdiParser.Parse("umb://any-guid/"); Assert.IsTrue(udi.IsRoot); Assert.IsInstanceOf(udi); } @@ -217,13 +203,13 @@ namespace Umbraco.Tests.CoreThings // can parse open string udi var stringUdiString = "umb://" + Constants.UdiEntityType.AnyString; Udi stringUdi; - Assert.IsTrue(Udi.TryParse(stringUdiString, out stringUdi)); + Assert.IsTrue(UdiParser.TryParse(stringUdiString, out stringUdi)); Assert.AreEqual(string.Empty, ((StringUdi)stringUdi).Id); // can parse open guid udi var guidUdiString = "umb://" + Constants.UdiEntityType.AnyGuid; Udi guidUdi; - Assert.IsTrue(Udi.TryParse(guidUdiString, out guidUdi)); + Assert.IsTrue(UdiParser.TryParse(guidUdiString, out guidUdi)); Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid); // can create a range @@ -264,7 +250,7 @@ namespace Umbraco.Tests.CoreThings [Test] public void ValidateUdiEntityType() { - var types = UdiEntityTypeHelper.GetTypes(); + var types = UdiParser.GetKnownUdiTypes(); foreach (var fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static)) { @@ -294,33 +280,34 @@ namespace Umbraco.Tests.CoreThings // cannot parse an unknown type, udi is null // this will scan - Assert.IsFalse(Udi.TryParse("umb://whatever/1234", out udi)); + Assert.IsFalse(UdiParser.TryParse("umb://whatever/1234", out udi)); Assert.IsNull(udi); - Udi.ResetUdiTypes(); - + UdiParser.ResetUdiTypes(); + // unless we want to know - Assert.IsFalse(Udi.TryParse("umb://whatever/1234", true, out udi)); + Assert.IsFalse(UdiParser.TryParse("umb://whatever/1234", true, out udi)); Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType); - Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName); - - Udi.ResetUdiTypes(); + Assert.AreEqual("Umbraco.Core.UnknownTypeUdi", udi.GetType().FullName); + UdiParser.ResetUdiTypes(); + // not known - Assert.IsFalse(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); + Assert.IsFalse(UdiParser.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType); - Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName); + Assert.AreEqual("Umbraco.Core.UnknownTypeUdi", udi.GetType().FullName); - // scanned - Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", out udi)); + // scanned + UdiParserServiceConnectors.RegisterServiceConnector(); // this is the equivalent of scanning but we'll just manually register this one + Assert.IsTrue(UdiParser.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", out udi)); Assert.IsInstanceOf(udi); // known - Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); + Assert.IsTrue(UdiParser.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); Assert.IsInstanceOf(udi); // can get method for Deploy compatibility - var method = typeof(Udi).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(bool) }, null); + var method = typeof(UdiParser).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(bool) }, null); Assert.IsNotNull(method); } diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index e3678a1db5..b13532b824 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,14 +1,10 @@ using System; -using System.IO; using System.Linq; using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -17,10 +13,7 @@ using Umbraco.Tests.Testing.Objects.Accessors; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; -using Umbraco.Web.Security; using Umbraco.Web.Templates; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; namespace Umbraco.Tests.Web { @@ -30,37 +23,9 @@ namespace Umbraco.Tests.Web [SetUp] public void SetUp() { - Current.Reset(); - - // FIXME: now UrlProvider depends on EntityService for GetUrl(guid) - this is bad - // should not depend on more than IdkMap maybe - fix this! - var entityService = new Mock(); - entityService.Setup(x => x.GetId(It.IsAny(), It.IsAny())).Returns(Attempt.Fail()); - var serviceContext = ServiceContext.CreatePartial(entityService: entityService.Object); - - // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! - var factory = new Mock(); - var typeFinder = new TypeFinder(Mock.Of()); - var ioHelper = IOHelper.Default; - factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()))); - factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); - - var settings = SettingsForTests.GetDefaultUmbracoSettings(); - factory.Setup(x => x.GetInstance(typeof(IUmbracoSettingsSection))).Returns(settings); - - Current.Factory = factory.Object; - - Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); - - Udi.ResetUdiTypes(); + UdiParser.ResetUdiTypes(); } - [TearDown] - public void TearDown() - { - Current.Reset(); - } [TestCase("", "")] [TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")] @@ -71,17 +36,7 @@ namespace Umbraco.Tests.Web [TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")] public void ParseLocalLinks(string input, string result) - { - var serviceCtxMock = new TestObjects(null).GetServiceContextMock(); - - //setup a mock entity service from the service context to return an integer for a GUID - var entityService = Mock.Get(serviceCtxMock.EntityService); - //entityService.Setup(x => x.GetId(It.IsAny(), It.IsAny())) - // .Returns((Guid id, UmbracoObjectTypes objType) => - // { - // return Attempt.Succeed(1234); - // }); - + { //setup a mock url provider which we'll use for testing var testUrlProvider = new Mock(); testUrlProvider @@ -106,8 +61,9 @@ namespace Umbraco.Tests.Web var mediaCache = Mock.Of(); Mock.Get(mediaCache).Setup(x => x.GetById(It.IsAny())).Returns(media); + var umbracoContextAccessor = new TestUmbracoContextAccessor(); var umbracoContextFactory = new UmbracoContextFactory( - Umbraco.Web.Composing.Current.UmbracoContextAccessor, + umbracoContextAccessor, snapshotService, new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index cdf17e12e9..425f24be41 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -176,7 +176,7 @@ namespace Umbraco.Tests.Web public static void Converting_Boxed_Udi_To_A_Udi_Returns_Original_Udi_Value() { // Arrange - Udi.ResetUdiTypes(); + UdiParser.ResetUdiTypes(); Udi sample = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.NewGuid()); // Act @@ -198,7 +198,7 @@ namespace Umbraco.Tests.Web { // Arrange SetUpDependencyContainer(); - Udi.ResetUdiTypes(); + UdiParser.ResetUdiTypes(); Udi sample = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.NewGuid()); // Act @@ -220,7 +220,7 @@ namespace Umbraco.Tests.Web { // Arrange SetUpDependencyContainer(); - Udi.ResetUdiTypes(); + UdiParser.ResetUdiTypes(); const string sample = "Hello"; // Act @@ -241,7 +241,7 @@ namespace Umbraco.Tests.Web public static void Converting_Unsupported_Object_To_A_Udi_Returns_False() { // Arrange - Udi.ResetUdiTypes(); + UdiParser.ResetUdiTypes(); var clearlyWillNotConvertToGuid = new StringBuilder(0); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index aa8f735cf2..2381867616 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -509,7 +509,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - if (Udi.TryParse(id, out _)) + if (UdiParser.TryParse(id, out _)) { //Not supported currently throw new HttpResponseException(HttpStatusCode.NotFound); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 2cab9f4ebc..315abaa8ce 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -793,10 +793,9 @@ namespace Umbraco.Web.Editors private int GetParentIdAsInt(string parentId, bool validatePermissions) { int intParentId; - GuidUdi parentUdi; // test for udi - if (GuidUdi.TryParse(parentId, out parentUdi)) + if (UdiParser.TryParse(parentId, out GuidUdi parentUdi)) { parentId = parentUdi.Guid.ToString(); } diff --git a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs index 4a3f6b43c3..9252899027 100644 --- a/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs +++ b/src/Umbraco.Web/Editors/RedirectUrlManagementController.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Editors public RedirectUrlSearchResult RedirectUrlsForContentItem(string contentUdi) { var redirectsResult = new RedirectUrlSearchResult(); - if (GuidUdi.TryParse(contentUdi, out var guidIdi)) + if (UdiParser.TryParse(contentUdi, out GuidUdi guidIdi)) { var redirectUrlService = Services.RedirectUrlService; var redirects = redirectUrlService.GetContentRedirectUrls(guidIdi.Guid); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index a56630d7c5..cf0e872c1a 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var nodeIds = source.ToString() .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(Udi.Parse) + .Select(UdiParser.Parse) .ToArray(); return nodeIds; } diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 47f8797295..46ebbd0abb 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var nodeIds = source.ToString() .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(Udi.Parse) + .Select(UdiParser.Parse) .ToArray(); return nodeIds; } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index b351cca972..c1844655a9 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -326,7 +326,7 @@ namespace Umbraco.Web.Search if (sb == null) throw new ArgumentNullException(nameof(sb)); if (entityService == null) throw new ArgumentNullException(nameof(entityService)); - Udi.TryParse(searchFrom, true, out var udi); + UdiParser.TryParse(searchFrom, true, out var udi); searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString(); var entityPath = int.TryParse(searchFrom, out var searchFromId) && searchFromId > 0 diff --git a/src/Umbraco.Web/Templates/TemplateUtilities.cs b/src/Umbraco.Web/Templates/TemplateUtilities.cs index 0f291a3c35..b98fedccbd 100644 --- a/src/Umbraco.Web/Templates/TemplateUtilities.cs +++ b/src/Umbraco.Web/Templates/TemplateUtilities.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Templates var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); //The id could be an int or a UDI - if (Udi.TryParse(id, out var udi)) + if (UdiParser.TryParse(id, out var udi)) { var guidUdi = udi as GuidUdi; if (guidUdi != null) @@ -166,7 +166,7 @@ namespace Umbraco.Web.Templates // - 4 = the data-udi attribute value // - 5 = anything after group 4 until the image tag is closed var udi = match.Groups[4].Value; - if(udi.IsNullOrWhiteSpace() || GuidUdi.TryParse(udi, out var guidUdi) == false) + if(udi.IsNullOrWhiteSpace() || UdiParser.TryParse(udi, out GuidUdi guidUdi) == false) { return match.Value; } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 4efbc2adc8..faa6f56d2b 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -491,7 +491,7 @@ namespace Umbraco.Web.Trees { return new Tuple(null, idInt); } - if (Udi.TryParse(id, out idUdi)) + if (UdiParser.TryParse(id, out idUdi)) { var guidUdi = idUdi as GuidUdi; if (guidUdi != null) @@ -523,7 +523,7 @@ namespace Umbraco.Web.Trees { entity = Services.EntityService.Get(idInt, UmbracoObjectType); } - else if (Udi.TryParse(s, out var idUdi)) + else if (UdiParser.TryParse(s, out var idUdi)) { var guidUdi = idUdi as GuidUdi; entity = guidUdi != null ? Services.EntityService.Get(guidUdi.Guid, UmbracoObjectType) : null; diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 705d72f3dd..070e57ca5c 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -621,7 +621,7 @@ namespace Umbraco.Web switch (id) { case string s: - return Udi.TryParse(s, out guidId); + return UdiParser.TryParse(s, out guidId); case Udi u: guidId = u; diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index efee045890..9f163f553a 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -87,7 +87,7 @@ namespace Umbraco.Web.WebApi.Filters { nodeId = parsedId; } - else if (Udi.TryParse(argument, true, out Udi udi)) + else if (UdiParser.TryParse(argument, true, out Udi udi)) { // TODO: inject? we can't because this is an attribute but we could provide ctors and empty ctors that pass in the required services nodeId = Current.Services.EntityService.GetId(udi).Result;