From 3707db6c21786f32d2b2f3674eb7aecd83debe40 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 24 Aug 2016 19:30:33 +0200 Subject: [PATCH] Resvolution - ShortStringHelperResolver --- src/Umbraco.Core/CoreBootManager.cs | 4 +- .../DependencyInjection/Current.cs | 23 ++ src/Umbraco.Core/StringExtensions.cs | 63 ++-- .../Strings/DefaultShortStringHelper.cs | 302 ++---------------- .../Strings/DefaultShortStringHelperConfig.cs | 218 +++++++++++++ .../Strings/IShortStringHelper.cs | 6 - .../Strings/ShortStringHelperResolver.cs | 64 ---- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../DistributedCache/DistributedCacheTests.cs | 2 - .../PropertyEditorValueEditorTests.cs | 12 +- .../Routing/RenderRouteHandlerTests.cs | 6 +- .../Strings/DefaultShortStringHelperTests.cs | 152 ++++----- .../Strings/ShortStringHelperResolverTest.cs | 33 -- .../Strings/StringExtensionsTests.cs | 17 +- src/Umbraco.Tests/TryConvertToTests.cs | 9 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../UmbracoExamine/ExamineBaseTest.cs | 7 +- src/Umbraco.Web/Current.cs | 3 + .../WebServices/CoreStringsController.cs | 2 +- 19 files changed, 412 insertions(+), 514 deletions(-) create mode 100644 src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs delete mode 100644 src/Umbraco.Core/Strings/ShortStringHelperResolver.cs delete mode 100644 src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index d3e68ad0e1..13112eb63c 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -457,8 +457,8 @@ namespace Umbraco.Core .Append(PluginManager.ResolveTypes()); // use the new DefaultShortStringHelper - ShortStringHelperResolver.Current = new ShortStringHelperResolver(Container, - factory => new DefaultShortStringHelper(factory.GetInstance()).WithDefaultConfig()); + Container.RegisterSingleton(factory + => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance()))); UrlSegmentProviderCollectionBuilder.Register(Container) .Append(); diff --git a/src/Umbraco.Core/DependencyInjection/Current.cs b/src/Umbraco.Core/DependencyInjection/Current.cs index d8f1035775..90be5815ee 100644 --- a/src/Umbraco.Core/DependencyInjection/Current.cs +++ b/src/Umbraco.Core/DependencyInjection/Current.cs @@ -1,6 +1,8 @@ using System; using LightInject; using Umbraco.Core.Cache; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dictionary; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; @@ -38,6 +40,7 @@ namespace Umbraco.Core.DependencyInjection internal static void Reset() { _container = null; + _shortStringHelper = null; Resetted?.Invoke(null, EventArgs.Empty); } @@ -78,6 +81,26 @@ namespace Umbraco.Core.DependencyInjection public static ICultureDictionaryFactory CultureDictionaryFactory => Container.GetInstance(); + private static IShortStringHelper _shortStringHelper; + + public static IShortStringHelper ShortStringHelper + { + get + { + // fixme - refactor + // we don't want Umbraco to die because the resolver hasn't been initialized + // as the ShortStringHelper is too important, so as long as it's not there + // already, we use a default one. That should never happen, but... in can, in + // some tests - we should really cleanup our tests and get rid of this! + + if (_shortStringHelper != null) return _shortStringHelper; + var reg = HasContainer ? Container.GetAvailableService() : null; + return _shortStringHelper = reg == null + ? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(UmbracoConfig.For.UmbracoSettings())) + : Container.GetInstance(); + } + } + #endregion } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 94b2cb1981..ee34d442e7 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Configuration; using System.Web.Security; using Umbraco.Core.Strings; using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.IO; namespace Umbraco.Core @@ -998,32 +999,6 @@ namespace Umbraco.Core : input.Substring(0, 1).ToLowerInvariant() + input.Substring(1); } - /// - /// Gets the short string helper. - /// - /// This is so that unit tests that do not initialize the resolver do not - /// fail and fall back to defaults. When running the whole Umbraco, CoreBootManager - /// does initialise the resolver. - private static IShortStringHelper ShortStringHelper - { - get - { - if (ShortStringHelperResolver.HasCurrent) - return ShortStringHelperResolver.Current.Helper; - if (_helper != null) - return _helper; - - // we don't want Umbraco to die because the resolver hasn't been initialized - // as the ShortStringHelper is too important, so as long as it's not there - // already, we use a default one. That should never happen, but... - Logging.LogHelper.Warn("ShortStringHelperResolver.HasCurrent == false, fallback to default."); - _helper = new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig(); - _helper.Freeze(); - return _helper; - } - } - private static IShortStringHelper _helper; - /// /// Returns a new string in which all occurences of specified strings are replaced by other specified strings. /// @@ -1032,7 +1007,7 @@ namespace Umbraco.Core /// The filtered string. public static string ReplaceMany(this string text, IDictionary replacements) { - return ShortStringHelper.ReplaceMany(text, replacements); + return Current.ShortStringHelper.ReplaceMany(text, replacements); } /// @@ -1044,7 +1019,7 @@ namespace Umbraco.Core /// The filtered string. public static string ReplaceMany(this string text, char[] chars, char replacement) { - return ShortStringHelper.ReplaceMany(text, chars, replacement); + return Current.ShortStringHelper.ReplaceMany(text, chars, replacement); } // FORMAT STRINGS @@ -1056,7 +1031,7 @@ namespace Umbraco.Core /// The safe alias. public static string ToSafeAlias(this string alias) { - return ShortStringHelper.CleanStringForSafeAlias(alias); + return Current.ShortStringHelper.CleanStringForSafeAlias(alias); } /// @@ -1067,7 +1042,7 @@ namespace Umbraco.Core /// The safe alias. public static string ToSafeAlias(this string alias, bool camel) { - var a = ShortStringHelper.CleanStringForSafeAlias(alias); + var a = Current.ShortStringHelper.CleanStringForSafeAlias(alias); if (string.IsNullOrWhiteSpace(a) || camel == false) return a; return char.ToLowerInvariant(a[0]) + a.Substring(1); } @@ -1080,7 +1055,7 @@ namespace Umbraco.Core /// The safe alias. public static string ToSafeAlias(this string alias, CultureInfo culture) { - return ShortStringHelper.CleanStringForSafeAlias(alias, culture); + return Current.ShortStringHelper.CleanStringForSafeAlias(alias, culture); } /// @@ -1115,7 +1090,7 @@ namespace Umbraco.Core /// The safe url segment. public static string ToUrlSegment(this string text) { - return ShortStringHelper.CleanStringForUrlSegment(text); + return Current.ShortStringHelper.CleanStringForUrlSegment(text); } /// @@ -1126,7 +1101,7 @@ namespace Umbraco.Core /// The safe url segment. public static string ToUrlSegment(this string text, CultureInfo culture) { - return ShortStringHelper.CleanStringForUrlSegment(text, culture); + return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture); } // the new methods to clean a string (to alias, url segment...) @@ -1138,10 +1113,10 @@ namespace Umbraco.Core /// A flag indicating the target casing and encoding of the string. By default, /// strings are cleaned up to camelCase and Ascii. /// The clean string. - /// The string is cleaned in the context of the IShortStringHelper default culture. + /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. public static string ToCleanString(this string text, CleanStringType stringType) { - return ShortStringHelper.CleanString(text, stringType); + return Current.ShortStringHelper.CleanString(text, stringType); } /// @@ -1152,10 +1127,10 @@ namespace Umbraco.Core /// strings are cleaned up to camelCase and Ascii. /// The separator. /// The clean string. - /// The string is cleaned in the context of the IShortStringHelper default culture. + /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. public static string ToCleanString(this string text, CleanStringType stringType, char separator) { - return ShortStringHelper.CleanString(text, stringType, separator); + return Current.ShortStringHelper.CleanString(text, stringType, separator); } /// @@ -1168,7 +1143,7 @@ namespace Umbraco.Core /// The clean string. public static string ToCleanString(this string text, CleanStringType stringType, CultureInfo culture) { - return ShortStringHelper.CleanString(text, stringType, culture); + return Current.ShortStringHelper.CleanString(text, stringType, culture); } /// @@ -1182,11 +1157,11 @@ namespace Umbraco.Core /// The clean string. public static string ToCleanString(this string text, CleanStringType stringType, char separator, CultureInfo culture) { - return ShortStringHelper.CleanString(text, stringType, separator, culture); + return Current.ShortStringHelper.CleanString(text, stringType, separator, culture); } - // note: LegacyShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing. - // other helpers may not. DefaultShortStringHelper produces better, but non-compatible, results. + // note: LegacyCurrent.ShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing. + // other helpers may not. DefaultCurrent.ShortStringHelper produces better, but non-compatible, results. /// /// Splits a Pascal cased string into a phrase separated by spaces. @@ -1195,7 +1170,7 @@ namespace Umbraco.Core /// The splitted text. public static string SplitPascalCasing(this string phrase) { - return ShortStringHelper.SplitPascalCasing(phrase, ' '); + return Current.ShortStringHelper.SplitPascalCasing(phrase, ' '); } //NOTE: Not sure what this actually does but is used a few places, need to figure it out and then move to StringExtensions and obsolete. @@ -1216,7 +1191,7 @@ namespace Umbraco.Core /// The safe filename. public static string ToSafeFileName(this string text) { - return ShortStringHelper.CleanStringForSafeFileName(text); + return Current.ShortStringHelper.CleanStringForSafeFileName(text); } /// @@ -1228,7 +1203,7 @@ namespace Umbraco.Core /// The safe filename. public static string ToSafeFileName(this string text, CultureInfo culture) { - return ShortStringHelper.CleanStringForSafeFileName(text, culture); + return Current.ShortStringHelper.CleanStringForSafeFileName(text, culture); } /// diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 96b8187e22..71c9a76331 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Globalization; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Core.Strings @@ -19,30 +18,17 @@ namespace Umbraco.Core.Strings /// public class DefaultShortStringHelper : IShortStringHelper { - private readonly IUmbracoSettingsSection _umbracoSettings; - #region Ctor and vars - [Obsolete("Use the other ctor that specifies all dependencies")] - public DefaultShortStringHelper() + public DefaultShortStringHelper(IUmbracoSettingsSection settings) { - _umbracoSettings = _umbracoSettings; - InitializeLegacyUrlReplaceCharacters(); + _config = new DefaultShortStringHelperConfig().WithDefault(settings); } - public DefaultShortStringHelper(IUmbracoSettingsSection umbracoSettings) + // clones the config so it cannot be changed at runtime + public DefaultShortStringHelper(DefaultShortStringHelperConfig config) { - _umbracoSettings = umbracoSettings; - InitializeLegacyUrlReplaceCharacters(); - } - - /// - /// Freezes the helper so it can prevents its configuration from being modified. - /// - /// Will be called by ShortStringHelperResolver when resolution freezes. - public void Freeze() - { - _frozen = true; + _config = config.Clone(); } // see notes for CleanAsciiString @@ -50,9 +36,7 @@ namespace Umbraco.Core.Strings //const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //readonly static char[] ValidStringCharacters; - private CultureInfo _defaultCulture = CultureInfo.InvariantCulture; - private bool _frozen; - private readonly Dictionary> _configs = new Dictionary>(); + private readonly DefaultShortStringHelperConfig _config; // see notes for CleanAsciiString //static DefaultShortStringHelper() @@ -64,27 +48,6 @@ namespace Umbraco.Core.Strings #region Filters - private readonly Dictionary _urlReplaceCharacters = new Dictionary(); - - private void InitializeLegacyUrlReplaceCharacters() - { - foreach (var node in _umbracoSettings.RequestHandler.CharCollection) - { - if(string.IsNullOrEmpty(node.Char) == false) - _urlReplaceCharacters[node.Char] = node.Replacement; - } - } - - /// - /// Returns a new string in which characters have been replaced according to the Umbraco settings UrlReplaceCharacters. - /// - /// The string to filter. - /// The filtered string. - public string ApplyUrlReplaceCharacters(string s) - { - return s.ReplaceMany(_urlReplaceCharacters); - } - // ok to be static here because it's not configureable in any way private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars() @@ -97,194 +60,6 @@ namespace Umbraco.Core.Strings return InvalidFileNameChars.Contains(c) == false; } - public static string CutMaxLength(string text, int length) - { - return text.Length <= length ? text : text.Substring(0, length); - } - - #endregion - - #region Configuration - - private void EnsureNotFrozen() - { - if (_frozen) - throw new InvalidOperationException("Cannot configure the helper once it is frozen."); - } - - /// - /// Sets a default culture. - /// - /// The default culture. - /// The short string helper. - public DefaultShortStringHelper WithDefaultCulture(CultureInfo culture) - { - EnsureNotFrozen(); - _defaultCulture = culture; - return this; - } - - public DefaultShortStringHelper WithConfig(Config config) - { - return WithConfig(_defaultCulture, CleanStringType.RoleMask, config); - } - - public DefaultShortStringHelper WithConfig(CleanStringType stringRole, Config config) - { - return WithConfig(_defaultCulture, stringRole, config); - } - - public DefaultShortStringHelper WithConfig(CultureInfo culture, CleanStringType stringRole, Config config) - { - if (config == null) - throw new ArgumentNullException("config"); - - EnsureNotFrozen(); - if (_configs.ContainsKey(culture) == false) - _configs[culture] = new Dictionary(); - _configs[culture][stringRole] = config.Clone(); // clone so it can't be changed - return this; - } - - /// - /// Sets the default configuration. - /// - /// The short string helper. - public DefaultShortStringHelper WithDefaultConfig() - { - return WithConfig(CleanStringType.UrlSegment, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - PostFilter = x => CutMaxLength(x, 240), - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = (_umbracoSettings.RequestHandler.ConvertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase, - BreakTermsOnUpper = false, - Separator = '-' - }).WithConfig(CleanStringType.FileName, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, - BreakTermsOnUpper = false, - Separator = '-' - }).WithConfig(CleanStringType.Alias, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - IsTerm = (c, leading) => leading - ? char.IsLetter(c) // only letters - : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore - StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - BreakTermsOnUpper = false - }).WithConfig(CleanStringType.UnderscoreAlias, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - BreakTermsOnUpper = false - }).WithConfig(CleanStringType.ConvertCase, new Config - { - PreFilter = null, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Ascii, - BreakTermsOnUpper = true - }); - } - - public sealed class Config - { - public Config() - { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged; - PreFilter = null; - PostFilter = null; - IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c); - BreakTermsOnUpper = false; - CutAcronymOnNonUpper = false; - GreedyAcronyms = false; - Separator = Char.MinValue; - } - - public Config Clone() - { - return new Config - { - PreFilter = PreFilter, - PostFilter = PostFilter, - IsTerm = IsTerm, - StringType = StringType, - BreakTermsOnUpper = BreakTermsOnUpper, - CutAcronymOnNonUpper = CutAcronymOnNonUpper, - GreedyAcronyms = GreedyAcronyms, - Separator = Separator - }; - } - - public Func PreFilter { get; set; } - public Func PostFilter { get; set; } - public Func IsTerm { get; set; } - - public CleanStringType StringType { get; set; } - - // indicate whether an uppercase within a term eg "fooBar" is to break - // into a new term, or to be considered as part of the current term - public bool BreakTermsOnUpper { get; set; } - - // indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut - // the acronym (at "B" or "a" depending on GreedyAcronyms) or to give - // up the acronym and treat the term as a word - public bool CutAcronymOnNonUpper { get; set; } - - // indicates whether acronyms parsing is greedy ie whether "FOObar" is - // "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy) - public bool GreedyAcronyms { get; set; } - - // the separator char - // but then how can we tell we dont want any? - public char Separator { get; set; } - - // extends the config - public CleanStringType StringTypeExtend(CleanStringType stringType) - { - var st = StringType; - foreach (var mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask }) - { - var a = stringType & mask; - if (a == 0) continue; - - st = st & ~mask; // clear what we have - st = st | a; // set the new value - } - return st; - } - - internal static readonly Config NotConfigured = new Config(); - } - - private Config GetConfig(CleanStringType stringType, CultureInfo culture) - { - stringType = stringType & CleanStringType.RoleMask; - - Dictionary config; - if (_configs.ContainsKey(culture)) - { - config = _configs[culture]; - if (config.ContainsKey(stringType)) // have we got a config for _that_ role? - return config[stringType]; - if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? - return config[CleanStringType.RoleMask]; - } - else if (_configs.ContainsKey(_defaultCulture)) - { - config = _configs[_defaultCulture]; - if (config.ContainsKey(stringType)) // have we got a config for _that_ role? - return config[stringType]; - if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? - return config[CleanStringType.RoleMask]; - } - - return Config.NotConfigured; - } - #endregion #region JavaScript @@ -333,8 +108,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ /// public string GetShortStringServicesJavaScript(string controllerPath) { - return string.Format(SssjsFormat, - _umbracoSettings.Content.ForceSafeAliases ? "true" : "false", controllerPath); + return string.Format(SssjsFormat, _config.ForceSafeAliases ? "true" : "false", controllerPath); } #endregion @@ -352,7 +126,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ /// public virtual string CleanStringForSafeAlias(string text) { - return CleanStringForSafeAlias(text, _defaultCulture); + return CleanStringForSafeAlias(text, _config.DefaultCulture); } /// @@ -380,7 +154,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ /// public virtual string CleanStringForUrlSegment(string text) { - return CleanStringForUrlSegment(text, _defaultCulture); + return CleanStringForUrlSegment(text, _config.DefaultCulture); } /// @@ -406,7 +180,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. public virtual string CleanStringForSafeFileName(string text) { - return CleanStringForSafeFileName(text, _defaultCulture); + return CleanStringForSafeFileName(text, _config.DefaultCulture); } /// @@ -452,43 +226,43 @@ function validateSafeAlias(input, value, immediate, callback) {{ // - Leading digits are removed. // - Many consecutive separators are folded into one unique separator. - const byte StateBreak = 1; - const byte StateUp = 2; - const byte StateWord = 3; - const byte StateAcronym = 4; + private const byte StateBreak = 1; + private const byte StateUp = 2; + private const byte StateWord = 3; + private const byte StateAcronym = 4; /// /// Cleans a string. /// /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, + /// A flag indicating the target casing and encoding of the string. By default, /// strings are cleaned up to camelCase and Ascii. /// The clean string. /// The string is cleaned in the context of the default culture. public string CleanString(string text, CleanStringType stringType) { - return CleanString(text, stringType, _defaultCulture, null); + return CleanString(text, stringType, _config.DefaultCulture, null); } /// /// Cleans a string, using a specified separator. /// /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, + /// A flag indicating the target casing and encoding of the string. By default, /// strings are cleaned up to camelCase and Ascii. /// The separator. /// The clean string. /// The string is cleaned in the context of the default culture. public string CleanString(string text, CleanStringType stringType, char separator) { - return CleanString(text, stringType, _defaultCulture, separator); + return CleanString(text, stringType, _config.DefaultCulture, separator); } /// /// Cleans a string in the context of a specified culture. /// /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, + /// A flag indicating the target casing and encoding of the string. By default, /// strings are cleaned up to camelCase and Ascii. /// The culture. /// The clean string. @@ -501,7 +275,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ /// Cleans a string in the context of a specified culture, using a specified separator. /// /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, + /// A flag indicating the target casing and encoding of the string. By default, /// strings are cleaned up to camelCase and Ascii. /// The separator. /// The culture. @@ -514,13 +288,11 @@ function validateSafeAlias(input, value, immediate, callback) {{ protected virtual string CleanString(string text, CleanStringType stringType, CultureInfo culture, char? separator) { // be safe - if (text == null) - throw new ArgumentNullException("text"); - if (culture == null) - throw new ArgumentNullException("culture"); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (culture == null) throw new ArgumentNullException(nameof(culture)); // get config - var config = GetConfig(stringType, culture); + var config = _config.For(stringType, culture); stringType = config.StringTypeExtend(stringType); // apply defaults @@ -542,8 +314,8 @@ function validateSafeAlias(input, value, immediate, callback) {{ // recode var codeType = stringType & CleanStringType.CodeMask; - text = codeType == CleanStringType.Ascii - ? Utf8ToAsciiConverter.ToAsciiString(text) + text = codeType == CleanStringType.Ascii + ? Utf8ToAsciiConverter.ToAsciiString(text) : RemoveSurrogatePairs(text); // clean @@ -552,7 +324,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ // apply post-filter if (config.PostFilter != null) text = config.PostFilter(text); - + return text; } @@ -584,7 +356,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. // note: does NOT support surrogate pairs in text - internal string CleanCodeString(string text, CleanStringType caseType, char separator, CultureInfo culture, Config config) + internal string CleanCodeString(string text, CleanStringType caseType, char separator, CultureInfo culture, DefaultShortStringHelperConfig.Config config) { int opos = 0, ipos = 0; var state = StateBreak; @@ -822,12 +594,12 @@ function validateSafeAlias(input, value, immediate, callback) {{ { term = term.Substring(i); term.CopyTo(0, output, opos, term.Length); - opos += term.Length; + opos += term.Length; } break; default: - throw new ArgumentOutOfRangeException("caseType"); + throw new ArgumentOutOfRangeException(nameof(caseType)); } } @@ -847,7 +619,7 @@ function validateSafeAlias(input, value, immediate, callback) {{ { // be safe if (text == null) - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); var input = text.ToCharArray(); var output = new char[input.Length * 2]; @@ -898,12 +670,10 @@ function validateSafeAlias(input, value, immediate, callback) {{ public virtual string ReplaceMany(string text, IDictionary replacements) { // be safe - if (text == null) - throw new ArgumentNullException("text"); - if (replacements == null) - throw new ArgumentNullException("replacements"); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (replacements == null) throw new ArgumentNullException(nameof(replacements)); - // Have done various tests, implementing my own "super fast" state machine to handle + // Have done various tests, implementing my own "super fast" state machine to handle // replacement of many items, or via regexes, but on short strings and not too // many replacements (which prob. is going to be our case) nothing can beat this... // (at least with safe and checked code -- we don't want unsafe/unchecked here) @@ -924,10 +694,8 @@ function validateSafeAlias(input, value, immediate, callback) {{ public virtual string ReplaceMany(string text, char[] chars, char replacement) { // be safe - if (text == null) - throw new ArgumentNullException("text"); - if (chars == null) - throw new ArgumentNullException("chars"); + if (text == null) throw new ArgumentNullException(nameof(text)); + if (chars == null) throw new ArgumentNullException(nameof(chars)); // see note above diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs new file mode 100644 index 0000000000..589bf62628 --- /dev/null +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Strings +{ + public class DefaultShortStringHelperConfig + { + private readonly Dictionary> _configs = new Dictionary>(); + + public DefaultShortStringHelperConfig Clone() + { + var config = new DefaultShortStringHelperConfig(); + config.DefaultCulture = DefaultCulture; + config.ForceSafeAliases = ForceSafeAliases; + config.UrlReplaceCharacters = UrlReplaceCharacters; + + foreach (var kvp1 in _configs) + { + var c = config._configs[kvp1.Key] = new Dictionary(); + foreach (var kvp2 in _configs[kvp1.Key]) + c[kvp2.Key] = kvp2.Value.Clone(); + } + + return config; + } + + public CultureInfo DefaultCulture { get; set; } = CultureInfo.InvariantCulture; + + public Dictionary UrlReplaceCharacters { get; set; } + + public bool ForceSafeAliases { get; set; } + + public DefaultShortStringHelperConfig WithConfig(Config config) + { + return WithConfig(DefaultCulture, CleanStringType.RoleMask, config); + } + + public DefaultShortStringHelperConfig WithConfig(CleanStringType stringRole, Config config) + { + return WithConfig(DefaultCulture, stringRole, config); + } + + public DefaultShortStringHelperConfig WithConfig(CultureInfo culture, CleanStringType stringRole, Config config) + { + if (config == null) throw new ArgumentNullException(nameof(config)); + + if (_configs.ContainsKey(culture) == false) + _configs[culture] = new Dictionary(); + _configs[culture][stringRole] = config; + return this; + } + + /// + /// Sets the default configuration. + /// + /// The short string helper. + public DefaultShortStringHelperConfig WithDefault(IUmbracoSettingsSection umbracoSettings) + { + ForceSafeAliases = umbracoSettings.Content.ForceSafeAliases; + UrlReplaceCharacters = umbracoSettings.RequestHandler.CharCollection + .Where(x => string.IsNullOrEmpty(x.Char) == false) + .ToDictionary(x => x.Char, x => x.Replacement); + var convertUrlsToAscii = umbracoSettings.RequestHandler.ConvertUrlsToAscii; + + return WithConfig(CleanStringType.UrlSegment, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + PostFilter = x => CutMaxLength(x, 240), + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = (convertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase, + BreakTermsOnUpper = false, + Separator = '-' + }).WithConfig(CleanStringType.FileName, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, + BreakTermsOnUpper = false, + Separator = '-' + }).WithConfig(CleanStringType.Alias, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => leading + ? char.IsLetter(c) // only letters + : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore + StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, + BreakTermsOnUpper = false + }).WithConfig(CleanStringType.UnderscoreAlias, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, + BreakTermsOnUpper = false + }).WithConfig(CleanStringType.ConvertCase, new Config + { + PreFilter = null, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii, + BreakTermsOnUpper = true + }); + } + + // internal: we don't want ppl to retrieve a config and modify it + // (the helper uses a private clone to prevent modifications) + internal Config For(CleanStringType stringType, CultureInfo culture) + { + stringType = stringType & CleanStringType.RoleMask; + + Dictionary config; + if (_configs.ContainsKey(culture)) + { + config = _configs[culture]; + if (config.ContainsKey(stringType)) // have we got a config for _that_ role? + return config[stringType]; + if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? + return config[CleanStringType.RoleMask]; + } + else if (_configs.ContainsKey(DefaultCulture)) + { + config = _configs[DefaultCulture]; + if (config.ContainsKey(stringType)) // have we got a config for _that_ role? + return config[stringType]; + if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? + return config[CleanStringType.RoleMask]; + } + + return Config.NotConfigured; + } + + public sealed class Config + { + public Config() + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged; + PreFilter = null; + PostFilter = null; + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c); + BreakTermsOnUpper = false; + CutAcronymOnNonUpper = false; + GreedyAcronyms = false; + Separator = char.MinValue; + } + + public Config Clone() + { + return new Config + { + PreFilter = PreFilter, + PostFilter = PostFilter, + IsTerm = IsTerm, + StringType = StringType, + BreakTermsOnUpper = BreakTermsOnUpper, + CutAcronymOnNonUpper = CutAcronymOnNonUpper, + GreedyAcronyms = GreedyAcronyms, + Separator = Separator + }; + } + + public Func PreFilter { get; set; } + public Func PostFilter { get; set; } + public Func IsTerm { get; set; } + + public CleanStringType StringType { get; set; } + + // indicate whether an uppercase within a term eg "fooBar" is to break + // into a new term, or to be considered as part of the current term + public bool BreakTermsOnUpper { get; set; } + + // indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut + // the acronym (at "B" or "a" depending on GreedyAcronyms) or to give + // up the acronym and treat the term as a word + public bool CutAcronymOnNonUpper { get; set; } + + // indicates whether acronyms parsing is greedy ie whether "FOObar" is + // "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy) + public bool GreedyAcronyms { get; set; } + + // the separator char + // but then how can we tell we dont want any? + public char Separator { get; set; } + + // extends the config + public CleanStringType StringTypeExtend(CleanStringType stringType) + { + var st = StringType; + foreach (var mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask }) + { + var a = stringType & mask; + if (a == 0) continue; + + st = st & ~mask; // clear what we have + st = st | a; // set the new value + } + return st; + } + + internal static readonly Config NotConfigured = new Config(); + } + + /// + /// Returns a new string in which characters have been replaced according to the Umbraco settings UrlReplaceCharacters. + /// + /// The string to filter. + /// The filtered string. + public string ApplyUrlReplaceCharacters(string s) + { + return UrlReplaceCharacters == null ? s : s.ReplaceMany(UrlReplaceCharacters); + } + + public static string CutMaxLength(string text, int length) + { + return text.Length <= length ? text : text.Substring(0, length); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Strings/IShortStringHelper.cs b/src/Umbraco.Core/Strings/IShortStringHelper.cs index d415bdba4d..3108ef069f 100644 --- a/src/Umbraco.Core/Strings/IShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/IShortStringHelper.cs @@ -9,12 +9,6 @@ namespace Umbraco.Core.Strings /// Not necessarily optimized to work on large bodies of text. public interface IShortStringHelper { - /// - /// Freezes the helper so it can prevents its configuration from being modified. - /// - /// Will be called by ShortStringHelperResolver when resolution freezes. - void Freeze(); - /// /// Gets the JavaScript code defining client-side short string services. /// diff --git a/src/Umbraco.Core/Strings/ShortStringHelperResolver.cs b/src/Umbraco.Core/Strings/ShortStringHelperResolver.cs deleted file mode 100644 index 3548bb7f60..0000000000 --- a/src/Umbraco.Core/Strings/ShortStringHelperResolver.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Linq.Expressions; -using LightInject; -using Umbraco.Core.ObjectResolution; - -namespace Umbraco.Core.Strings -{ - /// - /// Resolves the IShortStringHelper object - /// - public sealed class ShortStringHelperResolver : ContainerSingleObjectResolver - { - /// - /// Initializes the resolver to use IoC - /// - /// - internal ShortStringHelperResolver(IServiceContainer container) - : base(container) - { - Resolution.Frozen += (sender, args) => Value.Freeze(); - } - - /// - /// Initializes the resolver to use IoC - /// - /// - /// - internal ShortStringHelperResolver(IServiceContainer container, Func implementationType) - : base(container, implementationType) - { - Resolution.Frozen += (sender, args) => Value.Freeze(); - } - - /// - /// Initializes a new instance of the class with an instance of a helper. - /// - /// A instance of a helper. - /// The resolver is created by the CoreBootManager and thus the constructor remains internal. - internal ShortStringHelperResolver(IShortStringHelper helper) - : base(helper) - { - Resolution.Frozen += (sender, args) => Value.Freeze(); - } - - /// - /// Sets the helper. - /// - /// The helper. - /// For developers, at application startup. - public void SetHelper(IShortStringHelper helper) - { - Value = helper; - } - - /// - /// Gets the helper. - /// - public IShortStringHelper Helper - { - get { return Value; } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f016c8eb3f..52fcca55e5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1128,6 +1128,7 @@ + @@ -1250,7 +1251,6 @@ - diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index 13f6351943..935bd50336 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -30,8 +30,6 @@ namespace Umbraco.Tests.Cache.DistributedCache CacheRefresherCollectionBuilder.Register(container) .Add(); - - Resolution.Freeze(); } [TearDown] diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 8fa332b108..973de54c22 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -1,8 +1,10 @@ using System; using System.Threading; +using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -20,15 +22,17 @@ namespace Umbraco.Tests.PropertyEditors { //normalize culture Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); - Resolution.Freeze(); + + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + container.Register(_ + => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()))); } [TearDown] public virtual void TestTearDown() { - ResolverCollection.ResetAll(); - Resolution.Reset(); + Current.Reset(); } [TestCase("{prop1: 'val1', prop2: 'val2'}", true)] diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 4b4f92217b..b25d6bfa55 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -12,10 +12,10 @@ using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Web.WebApi; -using umbraco.BusinessLogic; using Umbraco.Core.Plugins; -using Umbraco.Core.Profiling; using Umbraco.Core.Strings; +using Umbraco.Core.DependencyInjection; +using Current = Umbraco.Web.Current; namespace Umbraco.Tests.Routing { @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Routing var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(PluginManager.Current.ResolveUmbracoApiControllers()); Container.RegisterInstance(umbracoApiControllerTypes); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); + Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefault())); base.FreezeResolution(); } diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 7925ac0d1e..278efe682a 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -5,10 +5,12 @@ using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using LightInject; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; @@ -32,57 +34,57 @@ namespace Umbraco.Tests.Strings // NOTE pre-filters runs _before_ Recode takes place // so there still may be utf8 chars even though you want ascii - _helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.FileName, new DefaultShortStringHelper.Config + _helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.FileName, new DefaultShortStringHelperConfig.Config { //PreFilter = ClearFileChars, // done in IsTerm IsTerm = (c, leading) => (char.IsLetterOrDigit(c) || c == '_') && DefaultShortStringHelper.IsValidFileNameChar(c), StringType = CleanStringType.LowerCase | CleanStringType.Ascii, Separator = '-' }) - .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelperConfig.Config { PreFilter = StripQuotes, IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', StringType = CleanStringType.LowerCase | CleanStringType.Ascii, Separator = '-' }) - .WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + .WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelperConfig.Config { PreFilter = FilterFrenchElisions, IsTerm = (c, leading) => leading ? char.IsLetter(c) : (char.IsLetterOrDigit(c) || c == '_'), StringType = CleanStringType.LowerCase | CleanStringType.Ascii, Separator = '-' }) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { PreFilter = StripQuotes, IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii }) - .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelper.Config + .WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { PreFilter = WhiteQuotes, IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii }) - .WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelper.Config + .WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelperConfig.Config { PreFilter = null, IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = CleanStringType.Ascii, BreakTermsOnUpper = true - }); + })); - ShortStringHelperResolver.Reset(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(_helper); - Resolution.Freeze(); + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + container.Register(_ => _helper); } [TearDown] public void TearDown() { - ShortStringHelperResolver.Reset(); + Current.Reset(); } static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -114,17 +116,17 @@ namespace Umbraco.Tests.Strings const string input = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page"; - var helper = new DefaultShortStringHelper(settings).WithDefaultConfig(); // unicode + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(settings)); // unicode var output = helper.CleanStringForUrlSegment(input); Assert.AreEqual("æøå-and-æøå-and-中文测试-and-אודות-האתר-and-größer-ббдджж-page", output); - helper = new DefaultShortStringHelper(settings) - .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(settings) + .WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelperConfig.Config { IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', StringType = CleanStringType.LowerCase | CleanStringType.Ascii, // ascii Separator = '-' - }); + })); output = helper.CleanStringForUrlSegment(input); Assert.AreEqual("aeoa-and-aeoa-and-and-and-grosser-bbddzhzh-page", output); } @@ -132,103 +134,103 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringUnderscoreInTerm() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { // underscore is accepted within terms IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("foo_bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { // underscore is not accepted within terms IsTerm = (c, leading) => char.IsLetterOrDigit(c), StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("foo*bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias)); } [Test] public void CleanStringLeadingChars() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { // letters and digits are valid leading chars IsTerm = (c, leading) => char.IsLetterOrDigit(c), StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("0123foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { // only letters are valid leading chars IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c), StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123 foo_bar 543 nil 321", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()).WithDefaultConfig(); + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())); Assert.AreEqual("child2", helper.CleanStringForSafeAlias("1child2")); } [Test] public void CleanStringTermOnUpper() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, // uppercase letter means new term BreakTermsOnUpper = true, Separator = '*' - }); + })); Assert.AreEqual("foo*Bar", helper.CleanString("fooBar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, // uppercase letter is part of term BreakTermsOnUpper = false, Separator = '*' - }); + })); Assert.AreEqual("fooBar", helper.CleanString("fooBar", CleanStringType.Alias)); } [Test] public void CleanStringAcronymOnNonUpper() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, // non-uppercase letter means cut acronym CutAcronymOnNonUpper = true, Separator = '*' - }); + })); Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, // non-uppercase letter means word CutAcronymOnNonUpper = false, Separator = '*' - }); + })); Assert.AreEqual("foo*BARRnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); Assert.AreEqual("foo*BARnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); @@ -238,27 +240,27 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringGreedyAcronyms() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, CutAcronymOnNonUpper = true, GreedyAcronyms = true, Separator = '*' - }); + })); Assert.AreEqual("foo*BARR*nil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); Assert.AreEqual("foo*BAR*nil", helper.CleanString("foo BARnil", CleanStringType.Alias)); Assert.AreEqual("foo*BA*nil", helper.CleanString("foo BAnil", CleanStringType.Alias)); Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, CutAcronymOnNonUpper = true, GreedyAcronyms = false, Separator = '*' - }); + })); Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias)); Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias)); Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias)); @@ -268,12 +270,12 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringWhiteSpace() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("foo", helper.CleanString(" foo ", CleanStringType.Alias)); Assert.AreEqual("foo*bar", helper.CleanString(" foo bar ", CleanStringType.Alias)); } @@ -281,47 +283,47 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringSeparator() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("foo*bar", helper.CleanString("foo bar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = ' ' - }); + })); Assert.AreEqual("foo bar", helper.CleanString("foo bar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged - }); + })); Assert.AreEqual("foobar", helper.CleanString("foo bar", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '文' - }); + })); Assert.AreEqual("foo文bar", helper.CleanString("foo bar", CleanStringType.Alias)); } [Test] public void CleanStringSymbols() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias)); // FIXME but for a filename we want to keep them! @@ -370,21 +372,21 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringEncoding() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("中文测试", helper.CleanString("中文测试", CleanStringType.Alias)); Assert.AreEqual("léger*中文测试*ZÔRG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Ascii | CleanStringType.Unchanged, Separator = '*' - }); + })); Assert.AreEqual("", helper.CleanString("中文测试", CleanStringType.Alias)); Assert.AreEqual("leger*ZORG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); } @@ -397,7 +399,7 @@ namespace Umbraco.Tests.Strings contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty()); contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false); - var helper = new DefaultShortStringHelper(settings).WithDefaultConfig(); + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(settings)); const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x"; @@ -418,12 +420,12 @@ namespace Umbraco.Tests.Strings [Test] public void CleanStringCasing() { - var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()) - .WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config + var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()) + .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, Separator = ' ' - }); + })); // BBB is an acronym // E is a word (too short to be an acronym) diff --git a/src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs b/src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs deleted file mode 100644 index c40adbb9eb..0000000000 --- a/src/Umbraco.Tests/Strings/ShortStringHelperResolverTest.cs +++ /dev/null @@ -1,33 +0,0 @@ -using NUnit.Framework; -using Umbraco.Core.ObjectResolution; -using Umbraco.Core.Strings; - -namespace Umbraco.Tests.Strings -{ - [TestFixture] - public class ShortStringHelperResolverTest - { - [SetUp] - public void Setup() - { - ShortStringHelperResolver.Reset(); - } - - [TearDown] - public void TearDown() - { - ShortStringHelperResolver.Reset(); - } - - [Test] - public void FreezesHelperWhenResolutionFreezes() - { - var helper = new MockShortStringHelper(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(helper); - Assert.IsFalse(helper.IsFrozen); - Resolution.Freeze(); - Assert.AreSame(helper, ShortStringHelperResolver.Current.Helper); - Assert.IsTrue(helper.IsFrozen); - } - } -} diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 199bd9254d..5d21eacf1b 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -1,8 +1,10 @@ using System; using System.Diagnostics; using System.Globalization; +using LightInject; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Strings; @@ -14,15 +16,22 @@ namespace Umbraco.Tests.Strings [SetUp] public void Setup() { - ShortStringHelperResolver.Reset(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new MockShortStringHelper()); - Resolution.Freeze(); + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + container.RegisterSingleton(_ => new MockShortStringHelper()); } [TearDown] public void TearDown() { - ShortStringHelperResolver.Reset(); + Current.Reset(); + } + + [Test] + public void CurrentHelper() + { + var helper = Current.ShortStringHelper; + Assert.IsInstanceOf(helper); } [TestCase("hello", "world", false)] diff --git a/src/Umbraco.Tests/TryConvertToTests.cs b/src/Umbraco.Tests/TryConvertToTests.cs index 7116d98038..56af5d6d42 100644 --- a/src/Umbraco.Tests/TryConvertToTests.cs +++ b/src/Umbraco.Tests/TryConvertToTests.cs @@ -1,9 +1,11 @@ using System; +using LightInject; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests { @@ -14,14 +16,15 @@ namespace Umbraco.Tests public void SetUp() { var settings = SettingsForTests.GetDefault(); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(settings).WithDefaultConfig()); - Resolution.Freeze(); + var container = new ServiceContainer(); + container.ConfigureUmbracoCore(); + container.RegisterSingleton(_ => new DefaultShortStringHelper(settings)); } [TearDown] public void TearDown() { - ShortStringHelperResolver.Reset(); + Current.Reset(); } [Test] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 6bd785cca8..edefbadf79 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -406,7 +406,6 @@ - diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index 6346dd9f5a..90cec3c5bd 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -1,7 +1,9 @@ using Moq; using System.IO; +using LightInject; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence.Mappers; @@ -29,12 +31,9 @@ namespace Umbraco.Tests.UmbracoExamine /// protected override void FreezeResolution() { - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); + Container.RegisterSingleton(_ => new DefaultShortStringHelper(SettingsForTests.GetDefault())); base.FreezeResolution(); } - - - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Current.cs b/src/Umbraco.Web/Current.cs index 3576c5c7a3..b306a3a6e5 100644 --- a/src/Umbraco.Web/Current.cs +++ b/src/Umbraco.Web/Current.cs @@ -232,6 +232,9 @@ namespace Umbraco.Web public static ICultureDictionaryFactory CultureDictionaryFactory => Container.GetInstance(); + public static IShortStringHelper ShortStringHelper + => Container.GetInstance(); + #endregion } } diff --git a/src/Umbraco.Web/WebServices/CoreStringsController.cs b/src/Umbraco.Web/WebServices/CoreStringsController.cs index 43be19908c..b66aa1e582 100644 --- a/src/Umbraco.Web/WebServices/CoreStringsController.cs +++ b/src/Umbraco.Web/WebServices/CoreStringsController.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.WebServices public JavaScriptResult ServicesJavaScript() { var controllerPath = Url.GetCoreStringsControllerPath(); - var js = ShortStringHelperResolver.Current.Helper.GetShortStringServicesJavaScript(controllerPath); + var js = Current.ShortStringHelper.GetShortStringServicesJavaScript(controllerPath); return JavaScript(js); } }