Merge branch 'vx/feature/unicore' of https://github.com/umbraco/Umbraco-CMS into vx/feature/unicore

This commit is contained in:
Lars-Erik Aabech
2019-05-20 17:58:27 +02:00
40 changed files with 2066 additions and 2015 deletions

View File

@@ -5,7 +5,7 @@ namespace Umbraco.Core.Collections
/// <summary>
/// Represents a composite key of (Type, Type) for fast dictionaries.
/// </summary>
internal struct CompositeTypeTypeKey : IEquatable<CompositeTypeTypeKey>
public struct CompositeTypeTypeKey : IEquatable<CompositeTypeTypeKey>
{
/// <summary>
/// Initializes a new instance of the <see cref="CompositeTypeTypeKey"/> struct.

View File

@@ -7,7 +7,7 @@ namespace Umbraco.Core.Collections
/// and is customizable to keep the newest or oldest equatable item
/// </summary>
/// <typeparam name="T"></typeparam>
internal class OrderedHashSet<T> : KeyedCollection<T, T>
public class OrderedHashSet<T> : KeyedCollection<T, T>
{
private readonly bool _keepOldest;

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Core.Collections
/// </summary>
/// <remarks>Types in the list are, or derive from, or implement, the base type.</remarks>
/// <typeparam name="TBase">The base type.</typeparam>
internal class TypeList<TBase>
public class TypeList<TBase>
{
private readonly List<Type> _list = new List<Type>();
@@ -30,4 +30,4 @@ namespace Umbraco.Core.Collections
return _list.Contains(type);
}
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Umbraco.Core
/// <summary>
/// Allows for converting string representations of 0 and 1 to boolean
/// </summary>
internal class CustomBooleanTypeConverter : BooleanConverter
public class CustomBooleanTypeConverter : BooleanConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core
/// A set of helper methods for dealing with expressions
/// </summary>
/// <remarks></remarks>
internal static class ExpressionHelper
public static class ExpressionHelper
{
private static readonly ConcurrentDictionary<LambdaExpressionCacheKey, PropertyInfo> PropertyInfoCache = new ConcurrentDictionary<LambdaExpressionCacheKey, PropertyInfo>();
@@ -106,7 +106,7 @@ namespace Umbraco.Core
case ExpressionType.Call:
var callExpr = (MethodCallExpression) expr;
var method = callExpr.Method;
if (method.DeclaringType != typeof(NPocoSqlExtensions.Statics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr))
if (method.DeclaringType != typeof(SqlExtensionsStatics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr))
Throw();
expr = callExpr.Arguments[0];
alias = aliasExpr.Value.ToString();

View File

@@ -0,0 +1,761 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using Umbraco.Core.Collections;
namespace Umbraco.Core
{
/// <summary>
/// Provides object extension methods.
/// </summary>
public static class ObjectExtensions
{
private static readonly ConcurrentDictionary<Type, Type> NullableGenericCache = new ConcurrentDictionary<Type, Type>();
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter> InputTypeConverterCache = new ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter>();
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter> DestinationTypeConverterCache = new ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter>();
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, bool> AssignableTypeCache = new ConcurrentDictionary<CompositeTypeTypeKey, bool>();
private static readonly ConcurrentDictionary<Type, bool> BoolConvertCache = new ConcurrentDictionary<Type, bool>();
private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' };
private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter();
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<T> AsEnumerableOfOne<T>(this T input)
{
return Enumerable.Repeat(input, 1);
}
/// <summary>
///
/// </summary>
/// <param name="input"></param>
public static void DisposeIfDisposable(this object input)
{
if (input is IDisposable disposable)
disposable.Dispose();
}
/// <summary>
/// Provides a shortcut way of safely casting an input when you cannot guarantee the <typeparamref name="T"/> is
/// an instance type (i.e., when the C# AS keyword is not applicable).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input">The input.</param>
/// <returns></returns>
public static T SafeCast<T>(this object input)
{
if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default;
if (input is T variable) return variable;
return default;
}
/// <summary>
/// Attempts to convert the input object to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <typeparam name="T">The type to convert to</typeparam>
/// <param name="input">The input.</param>
/// <returns>The <see cref="Attempt{T}"/></returns>
public static Attempt<T> TryConvertTo<T>(this object input)
{
var result = TryConvertTo(input, typeof(T));
if (result.Success)
return Attempt<T>.Succeed((T)result.Result);
// just try to cast
try
{
return Attempt<T>.Succeed((T)input);
}
catch (Exception e)
{
return Attempt<T>.Fail(e);
}
}
/// <summary>
/// Attempts to convert the input object to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <param name="input">The input.</param>
/// <param name="target">The type to convert to</param>
/// <returns>The <see cref="Attempt{Object}"/></returns>
public static Attempt<object> TryConvertTo(this object input, Type target)
{
if (target == null)
{
return Attempt<object>.Fail();
}
try
{
if (input == null)
{
// Nullable is ok
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
{
return Attempt<object>.Succeed(null);
}
// Reference types are ok
return Attempt<object>.If(target.IsValueType == false, null);
}
var inputType = input.GetType();
// Easy
if (target == typeof(object) || inputType == target)
{
return Attempt.Succeed(input);
}
// Check for string so that overloaders of ToString() can take advantage of the conversion.
if (target == typeof(string))
{
return Attempt<object>.Succeed(input.ToString());
}
// If we've got a nullable of something, we try to convert directly to that thing.
// We cache the destination type and underlying nullable types
// Any other generic types need to fall through
if (target.IsGenericType)
{
var underlying = GetCachedGenericNullableType(target);
if (underlying != null)
{
// Special case for empty strings for bools/dates which should return null if an empty string.
if (input is string inputString)
{
// TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null?
if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool)))
{
return Attempt<object>.Succeed(null);
}
}
// Recursively call into this method with the inner (not-nullable) type and handle the outcome
var inner = input.TryConvertTo(underlying);
// And if successful, fall on through to rewrap in a nullable; if failed, pass on the exception
if (inner.Success)
{
input = inner.Result; // Now fall on through...
}
else
{
return Attempt<object>.Fail(inner.Exception);
}
}
}
else
{
// target is not a generic type
if (input is string inputString)
{
// Try convert from string, returns an Attempt if the string could be
// processed (either succeeded or failed), else null if we need to try
// other methods
var result = TryConvertToFromString(inputString, target);
if (result.HasValue)
{
return result.Value;
}
}
// TODO: Do a check for destination type being IEnumerable<T> and source type implementing IEnumerable<T> with
// the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it.
if (GetCachedCanAssign(input, inputType, target))
{
return Attempt.Succeed(Convert.ChangeType(input, target));
}
}
if (target == typeof(bool))
{
if (GetCachedCanConvertToBoolean(inputType))
{
return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input));
}
}
var inputConverter = GetCachedSourceTypeConverter(inputType, target);
if (inputConverter != null)
{
return Attempt.Succeed(inputConverter.ConvertTo(input, target));
}
var outputConverter = GetCachedTargetTypeConverter(inputType, target);
if (outputConverter != null)
{
return Attempt.Succeed(outputConverter.ConvertFrom(input));
}
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
{
// cannot Convert.ChangeType as that does not work with nullable
// input has already been converted to the underlying type - just
// return input, there's an implicit conversion from T to T? anyways
return Attempt.Succeed(input);
}
// Re-check convertibles since we altered the input through recursion
if (input is IConvertible convertible2)
{
return Attempt.Succeed(Convert.ChangeType(convertible2, target));
}
}
catch (Exception e)
{
return Attempt<object>.Fail(e);
}
return Attempt<object>.Fail();
}
/// <summary>
/// Attempts to convert the input string to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <param name="input">The input.</param>
/// <param name="target">The type to convert to</param>
/// <returns>The <see cref="Nullable{Attempt}"/></returns>
private static Attempt<object>? TryConvertToFromString(this string input, Type target)
{
// Easy
if (target == typeof(string))
{
return Attempt<object>.Succeed(input);
}
// Null, empty, whitespaces
if (string.IsNullOrWhiteSpace(input))
{
if (target == typeof(bool))
{
// null/empty = bool false
return Attempt<object>.Succeed(false);
}
if (target == typeof(DateTime))
{
// null/empty = min DateTime value
return Attempt<object>.Succeed(DateTime.MinValue);
}
// Cannot decide here,
// Any of the types below will fail parsing and will return a failed attempt
// but anything else will not be processed and will return null
// so even though the string is null/empty we have to proceed.
}
// Look for type conversions in the expected order of frequency of use.
//
// By using a mixture of ordered if statements and switches we can optimize both for
// fast conditional checking for most frequently used types and the branching
// that does not depend on previous values available to switch statements.
if (target.IsPrimitive)
{
if (target == typeof(int))
{
if (int.TryParse(input, out var value))
{
return Attempt<object>.Succeed(value);
}
// Because decimal 100.01m will happily convert to integer 100, it
// makes sense that string "100.01" *also* converts to integer 100.
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2));
}
if (target == typeof(long))
{
if (long.TryParse(input, out var value))
{
return Attempt<object>.Succeed(value);
}
// Same as int
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2));
}
// TODO: Should we do the decimal trick for short, byte, unsigned?
if (target == typeof(bool))
{
if (bool.TryParse(input, out var value))
{
return Attempt<object>.Succeed(value);
}
// Don't declare failure so the CustomBooleanTypeConverter can try
return null;
}
// Calling this method directly is faster than any attempt to cache it.
switch (Type.GetTypeCode(target))
{
case TypeCode.Int16:
return Attempt<object>.If(short.TryParse(input, out var value), value);
case TypeCode.Double:
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(double.TryParse(input2, out var valueD), valueD);
case TypeCode.Single:
var input3 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(float.TryParse(input3, out var valueF), valueF);
case TypeCode.Char:
return Attempt<object>.If(char.TryParse(input, out var valueC), valueC);
case TypeCode.Byte:
return Attempt<object>.If(byte.TryParse(input, out var valueB), valueB);
case TypeCode.SByte:
return Attempt<object>.If(sbyte.TryParse(input, out var valueSb), valueSb);
case TypeCode.UInt32:
return Attempt<object>.If(uint.TryParse(input, out var valueU), valueU);
case TypeCode.UInt16:
return Attempt<object>.If(ushort.TryParse(input, out var valueUs), valueUs);
case TypeCode.UInt64:
return Attempt<object>.If(ulong.TryParse(input, out var valueUl), valueUl);
}
}
else if (target == typeof(Guid))
{
return Attempt<object>.If(Guid.TryParse(input, out var value), value);
}
else if (target == typeof(DateTime))
{
if (DateTime.TryParse(input, out var value))
{
switch (value.Kind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
return Attempt<object>.Succeed(value);
case DateTimeKind.Local:
return Attempt<object>.Succeed(value.ToUniversalTime());
default:
throw new ArgumentOutOfRangeException();
}
}
return Attempt<object>.Fail();
}
else if (target == typeof(DateTimeOffset))
{
return Attempt<object>.If(DateTimeOffset.TryParse(input, out var value), value);
}
else if (target == typeof(TimeSpan))
{
return Attempt<object>.If(TimeSpan.TryParse(input, out var value), value);
}
else if (target == typeof(decimal))
{
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(decimal.TryParse(input2, out var value), value);
}
else if (input != null && target == typeof(Version))
{
return Attempt<object>.If(Version.TryParse(input, out var value), value);
}
// E_NOTIMPL IPAddress, BigInteger
return null; // we can't decide...
}
internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname)
{
// TODO: Localize this exception
if (isDisposed)
throw new ObjectDisposedException(objectname);
}
//public enum PropertyNamesCaseType
//{
// CamelCase,
// CaseInsensitive
//}
///// <summary>
///// Convert an object to a JSON string with camelCase formatting
///// </summary>
///// <param name="obj"></param>
///// <returns></returns>
//public static string ToJsonString(this object obj)
//{
// return obj.ToJsonString(PropertyNamesCaseType.CamelCase);
//}
///// <summary>
///// Convert an object to a JSON string with the specified formatting
///// </summary>
///// <param name="obj">The obj.</param>
///// <param name="propertyNamesCaseType">Type of the property names case.</param>
///// <returns></returns>
//public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType)
//{
// var type = obj.GetType();
// var dateTimeStyle = "yyyy-MM-dd HH:mm:ss";
// if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
// {
// return obj.ToString();
// }
// if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type))
// {
// return Convert.ToDateTime(obj).ToString(dateTimeStyle);
// }
// var serializer = new JsonSerializer();
// switch (propertyNamesCaseType)
// {
// case PropertyNamesCaseType.CamelCase:
// serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
// break;
// }
// var dateTimeConverter = new IsoDateTimeConverter
// {
// DateTimeStyles = System.Globalization.DateTimeStyles.None,
// DateTimeFormat = dateTimeStyle
// };
// if (typeof(IDictionary).IsAssignableFrom(type))
// {
// return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
// }
// if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type)))
// {
// return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
// }
// return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
//}
/// <summary>
/// Converts an object into a dictionary
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <typeparam name="TVal"> </typeparam>
/// <param name="o"></param>
/// <param name="ignoreProperties"></param>
/// <returns></returns>
public static IDictionary<string, TVal> ToDictionary<T, TProperty, TVal>(this T o, params Expression<Func<T, TProperty>>[] ignoreProperties)
{
return o.ToDictionary<TVal>(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray());
}
/// <summary>
/// Turns object into dictionary
/// </summary>
/// <param name="o"></param>
/// <param name="ignoreProperties">Properties to ignore</param>
/// <returns></returns>
public static IDictionary<string, TVal> ToDictionary<TVal>(this object o, params string[] ignoreProperties)
{
if (o != null)
{
var props = TypeDescriptor.GetProperties(o);
var d = new Dictionary<string, TVal>();
foreach (var prop in props.Cast<PropertyDescriptor>().Where(x => ignoreProperties.Contains(x.Name) == false))
{
var val = prop.GetValue(o);
if (val != null)
{
d.Add(prop.Name, (TVal)val);
}
}
return d;
}
return new Dictionary<string, TVal>();
}
internal static string ToDebugString(this object obj, int levels = 0)
{
if (obj == null) return "{null}";
try
{
if (obj is string)
{
return "\"{0}\"".InvariantFormat(obj);
}
if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || obj is int? || obj is short? || obj is long? || obj is float? || obj is double? || obj is bool?)
{
return "{0}".InvariantFormat(obj);
}
if (obj is Enum)
{
return "[{0}]".InvariantFormat(obj);
}
if (obj is IEnumerable)
{
var enumerable = (obj as IEnumerable);
var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList();
return items.Any()
? "{{ {0} }}".InvariantFormat(string.Join(", ", items))
: null;
}
var props = obj.GetType().GetProperties();
if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2)
{
try
{
var key = props[0].GetValue(obj, null) as string;
var value = props[1].GetValue(obj, null).ToDebugString(levels - 1);
return "{0}={1}".InvariantFormat(key, value);
}
catch (Exception)
{
return "[KeyValuePropertyException]";
}
}
if (levels > -1)
{
var items =
(from propertyInfo in props
let value = GetPropertyDebugString(propertyInfo, obj, levels)
where value != null
select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray();
return items.Any()
? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items))
: null;
}
}
catch (Exception ex)
{
return "[Exception:{0}]".InvariantFormat(ex.Message);
}
return null;
}
/// <summary>
/// Attempts to serialize the value to an XmlString using ToXmlString
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
internal static Attempt<string> TryConvertToXmlString(this object value, Type type)
{
try
{
var output = value.ToXmlString(type);
return Attempt.Succeed(output);
}
catch (NotSupportedException ex)
{
return Attempt<string>.Fail(ex);
}
}
/// <summary>
/// Returns an XmlSerialized safe string representation for the value
/// </summary>
/// <param name="value"></param>
/// <param name="type">The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown</param>
/// <returns></returns>
public static string ToXmlString(this object value, Type type)
{
if (value == null) return string.Empty;
if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString());
if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
if (type == typeof(byte)) return XmlConvert.ToString((byte)value);
if (type == typeof(char)) return XmlConvert.ToString((char)value);
if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified);
if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value);
if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value);
if (type == typeof(double)) return XmlConvert.ToString((double)value);
if (type == typeof(float)) return XmlConvert.ToString((float)value);
if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value);
if (type == typeof(int)) return XmlConvert.ToString((int)value);
if (type == typeof(long)) return XmlConvert.ToString((long)value);
if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value);
if (type == typeof(short)) return XmlConvert.ToString((short)value);
if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value);
if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
if (type == typeof(uint)) return XmlConvert.ToString((uint)value);
if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value);
if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value);
throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert");
}
/// <summary>
/// Returns an XmlSerialized safe string representation for the value and type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static string ToXmlString<T>(this object value)
{
return value.ToXmlString(typeof (T));
}
private static string GetEnumPropertyDebugString(object enumItem, int levels)
{
try
{
return enumItem.ToDebugString(levels - 1);
}
catch (Exception)
{
return "[GetEnumPartException]";
}
}
private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels)
{
try
{
return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1);
}
catch (Exception)
{
return "[GetPropertyValueException]";
}
}
public static Guid AsGuid(this object value)
{
return value is Guid guid ? guid : Guid.Empty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string NormalizeNumberDecimalSeparator(string s)
{
var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized);
}
// gets a converter for source, that can convert to target, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (InputTypeConverterCache.TryGetValue(key, out var typeConverter))
{
return typeConverter;
}
var converter = TypeDescriptor.GetConverter(source);
if (converter.CanConvertTo(target))
{
return InputTypeConverterCache[key] = converter;
}
return InputTypeConverterCache[key] = null;
}
// gets a converter for target, that can convert from source, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (DestinationTypeConverterCache.TryGetValue(key, out var typeConverter))
{
return typeConverter;
}
var converter = TypeDescriptor.GetConverter(target);
if (converter.CanConvertFrom(source))
{
return DestinationTypeConverterCache[key] = converter;
}
return DestinationTypeConverterCache[key] = null;
}
// gets the underlying type of a nullable type, or null if the type is not nullable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Type GetCachedGenericNullableType(Type type)
{
if (NullableGenericCache.TryGetValue(type, out var underlyingType))
{
return underlyingType;
}
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type underlying = Nullable.GetUnderlyingType(type);
return NullableGenericCache[type] = underlying;
}
return NullableGenericCache[type] = null;
}
// gets an IConvertible from source to target type, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedCanAssign(object input, Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (AssignableTypeCache.TryGetValue(key, out var canConvert))
{
return canConvert;
}
// "object is" is faster than "Type.IsAssignableFrom.
// We can use it to very quickly determine whether true/false
if (input is IConvertible && target.IsAssignableFrom(source))
{
return AssignableTypeCache[key] = true;
}
return AssignableTypeCache[key] = false;
}
// determines whether a type can be converted to boolean
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedCanConvertToBoolean(Type type)
{
if (BoolConvertCache.TryGetValue(type, out var result))
{
return result;
}
if (CustomBooleanTypeConverter.CanConvertFrom(type))
{
return BoolConvertCache[type] = true;
}
return BoolConvertCache[type] = false;
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// Provides a mean to express aliases in SELECT Sql statements.
/// </summary>
/// <remarks>
/// <para>First register with <c>using static Umbraco.Core.Persistence.NPocoSqlExtensions.Aliaser</c>,
/// then use eg <c>Sql{Foo}(x => Alias(x.Id, "id"))</c>.</para>
/// </remarks>
public static class SqlExtensionsStatics
{
/// <summary>
/// Aliases a field.
/// </summary>
/// <param name="field">The field to alias.</param>
/// <param name="alias">The alias.</param>
public static object Alias(object field, string alias) => field;
/// <summary>
/// Produces Sql text.
/// </summary>
/// <param name="field">The name of the field.</param>
/// <param name="expr">A function producing Sql text.</param>
public static T SqlText<T>(string field, Func<string, string> expr) => default;
/// <summary>
/// Produces Sql text.
/// </summary>
/// <param name="field1">The name of the first field.</param>
/// <param name="field2">The name of the second field.</param>
/// <param name="expr">A function producing Sql text.</param>
public static T SqlText<T>(string field1, string field2, Func<string, string, string> expr) => default;
/// <summary>
/// Produces Sql text.
/// </summary>
/// <param name="field1">The name of the first field.</param>
/// <param name="field2">The name of the second field.</param>
/// <param name="field3">The name of the third field.</param>
/// <param name="expr">A function producing Sql text.</param>
public static T SqlText<T>(string field1, string field2, string field3, Func<string, string, string, string> expr) => default;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ using System.Text.RegularExpressions;
namespace Umbraco.Core.Strings.Css
{
internal class StylesheetHelper
public class StylesheetHelper
{
private const string RuleRegexFormat = @"/\*\*\s*umb_name:\s*(?<Name>{0}?)\s*\*/\s*(?<Selector>[^,{{]*?)\s*{{\s*(?<Styles>.*?)\s*}}";

View File

@@ -4,7 +4,7 @@ using System.Text;
namespace Umbraco.Core.Strings.Css
{
internal class StylesheetRule
public class StylesheetRule
{
public string Name { get; set; }

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Core.Strings
/// <para>Removes all non-Utf8 (unicode) characters, so in fact it can sort-of "convert" Unicode to Ascii.</para>
/// <para>Replaces symbols with '?'.</para>
/// </remarks>
internal static class Utf8ToAsciiConverter
public static class Utf8ToAsciiConverter
{
/// <summary>
/// Converts an Utf8 string into an Ascii string.

View File

@@ -63,7 +63,7 @@ namespace Umbraco.Core.Configuration
public void Dispose()
{
ObjectExtensions.DisposeIfDisposable(_stringEnumerator);
_stringEnumerator.DisposeIfDisposable();
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Linq;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0
{

View File

@@ -1,504 +1,15 @@
using System;
using System.Collections;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using Newtonsoft.Json;
using Umbraco.Core.Collections;
namespace Umbraco.Core
{
/// <summary>
/// Provides object extension methods.
/// </summary>
public static class ObjectExtensions
{
private static readonly ConcurrentDictionary<Type, Dictionary<string, object>> ToObjectTypes = new ConcurrentDictionary<Type, Dictionary<string, object>>();
private static readonly ConcurrentDictionary<Type, Type> NullableGenericCache = new ConcurrentDictionary<Type, Type>();
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter> InputTypeConverterCache = new ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter>();
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter> DestinationTypeConverterCache = new ConcurrentDictionary<CompositeTypeTypeKey, TypeConverter>();
private static readonly ConcurrentDictionary<CompositeTypeTypeKey, bool> AssignableTypeCache = new ConcurrentDictionary<CompositeTypeTypeKey, bool>();
private static readonly ConcurrentDictionary<Type, bool> BoolConvertCache = new ConcurrentDictionary<Type, bool>();
private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' };
private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter();
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IEnumerable<T> AsEnumerableOfOne<T>(this T input)
{
return Enumerable.Repeat(input, 1);
}
/// <summary>
///
/// </summary>
/// <param name="input"></param>
public static void DisposeIfDisposable(this object input)
{
if (input is IDisposable disposable)
disposable.Dispose();
}
/// <summary>
/// Provides a shortcut way of safely casting an input when you cannot guarantee the <typeparamref name="T"/> is
/// an instance type (i.e., when the C# AS keyword is not applicable).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input">The input.</param>
/// <returns></returns>
internal static T SafeCast<T>(this object input)
{
if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default;
if (input is T variable) return variable;
return default;
}
/// <summary>
/// Attempts to convert the input object to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <typeparam name="T">The type to convert to</typeparam>
/// <param name="input">The input.</param>
/// <returns>The <see cref="Attempt{T}"/></returns>
public static Attempt<T> TryConvertTo<T>(this object input)
{
var result = TryConvertTo(input, typeof(T));
if (result.Success)
return Attempt<T>.Succeed((T)result.Result);
// just try to cast
try
{
return Attempt<T>.Succeed((T)input);
}
catch (Exception e)
{
return Attempt<T>.Fail(e);
}
}
/// <summary>
/// Attempts to convert the input object to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <param name="input">The input.</param>
/// <param name="target">The type to convert to</param>
/// <returns>The <see cref="Attempt{Object}"/></returns>
public static Attempt<object> TryConvertTo(this object input, Type target)
{
if (target == null)
{
return Attempt<object>.Fail();
}
try
{
if (input == null)
{
// Nullable is ok
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
{
return Attempt<object>.Succeed(null);
}
// Reference types are ok
return Attempt<object>.If(target.IsValueType == false, null);
}
var inputType = input.GetType();
// Easy
if (target == typeof(object) || inputType == target)
{
return Attempt.Succeed(input);
}
// Check for string so that overloaders of ToString() can take advantage of the conversion.
if (target == typeof(string))
{
return Attempt<object>.Succeed(input.ToString());
}
// If we've got a nullable of something, we try to convert directly to that thing.
// We cache the destination type and underlying nullable types
// Any other generic types need to fall through
if (target.IsGenericType)
{
var underlying = GetCachedGenericNullableType(target);
if (underlying != null)
{
// Special case for empty strings for bools/dates which should return null if an empty string.
if (input is string inputString)
{
// TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null?
if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool)))
{
return Attempt<object>.Succeed(null);
}
}
// Recursively call into this method with the inner (not-nullable) type and handle the outcome
var inner = input.TryConvertTo(underlying);
// And if successful, fall on through to rewrap in a nullable; if failed, pass on the exception
if (inner.Success)
{
input = inner.Result; // Now fall on through...
}
else
{
return Attempt<object>.Fail(inner.Exception);
}
}
}
else
{
// target is not a generic type
if (input is string inputString)
{
// Try convert from string, returns an Attempt if the string could be
// processed (either succeeded or failed), else null if we need to try
// other methods
var result = TryConvertToFromString(inputString, target);
if (result.HasValue)
{
return result.Value;
}
}
// TODO: Do a check for destination type being IEnumerable<T> and source type implementing IEnumerable<T> with
// the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it.
if (GetCachedCanAssign(input, inputType, target))
{
return Attempt.Succeed(Convert.ChangeType(input, target));
}
}
if (target == typeof(bool))
{
if (GetCachedCanConvertToBoolean(inputType))
{
return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input));
}
}
var inputConverter = GetCachedSourceTypeConverter(inputType, target);
if (inputConverter != null)
{
return Attempt.Succeed(inputConverter.ConvertTo(input, target));
}
var outputConverter = GetCachedTargetTypeConverter(inputType, target);
if (outputConverter != null)
{
return Attempt.Succeed(outputConverter.ConvertFrom(input));
}
if (target.IsGenericType && GetCachedGenericNullableType(target) != null)
{
// cannot Convert.ChangeType as that does not work with nullable
// input has already been converted to the underlying type - just
// return input, there's an implicit conversion from T to T? anyways
return Attempt.Succeed(input);
}
// Re-check convertibles since we altered the input through recursion
if (input is IConvertible convertible2)
{
return Attempt.Succeed(Convert.ChangeType(convertible2, target));
}
}
catch (Exception e)
{
return Attempt<object>.Fail(e);
}
return Attempt<object>.Fail();
}
/// <summary>
/// Attempts to convert the input string to the output type.
/// </summary>
/// <remarks>This code is an optimized version of the original Umbraco method</remarks>
/// <param name="input">The input.</param>
/// <param name="target">The type to convert to</param>
/// <returns>The <see cref="Nullable{Attempt}"/></returns>
private static Attempt<object>? TryConvertToFromString(this string input, Type target)
{
// Easy
if (target == typeof(string))
{
return Attempt<object>.Succeed(input);
}
// Null, empty, whitespaces
if (string.IsNullOrWhiteSpace(input))
{
if (target == typeof(bool))
{
// null/empty = bool false
return Attempt<object>.Succeed(false);
}
if (target == typeof(DateTime))
{
// null/empty = min DateTime value
return Attempt<object>.Succeed(DateTime.MinValue);
}
// Cannot decide here,
// Any of the types below will fail parsing and will return a failed attempt
// but anything else will not be processed and will return null
// so even though the string is null/empty we have to proceed.
}
// Look for type conversions in the expected order of frequency of use.
//
// By using a mixture of ordered if statements and switches we can optimize both for
// fast conditional checking for most frequently used types and the branching
// that does not depend on previous values available to switch statements.
if (target.IsPrimitive)
{
if (target == typeof(int))
{
if (int.TryParse(input, out var value))
{
return Attempt<object>.Succeed(value);
}
// Because decimal 100.01m will happily convert to integer 100, it
// makes sense that string "100.01" *also* converts to integer 100.
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2));
}
if (target == typeof(long))
{
if (long.TryParse(input, out var value))
{
return Attempt<object>.Succeed(value);
}
// Same as int
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2));
}
// TODO: Should we do the decimal trick for short, byte, unsigned?
if (target == typeof(bool))
{
if (bool.TryParse(input, out var value))
{
return Attempt<object>.Succeed(value);
}
// Don't declare failure so the CustomBooleanTypeConverter can try
return null;
}
// Calling this method directly is faster than any attempt to cache it.
switch (Type.GetTypeCode(target))
{
case TypeCode.Int16:
return Attempt<object>.If(short.TryParse(input, out var value), value);
case TypeCode.Double:
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(double.TryParse(input2, out var valueD), valueD);
case TypeCode.Single:
var input3 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(float.TryParse(input3, out var valueF), valueF);
case TypeCode.Char:
return Attempt<object>.If(char.TryParse(input, out var valueC), valueC);
case TypeCode.Byte:
return Attempt<object>.If(byte.TryParse(input, out var valueB), valueB);
case TypeCode.SByte:
return Attempt<object>.If(sbyte.TryParse(input, out var valueSb), valueSb);
case TypeCode.UInt32:
return Attempt<object>.If(uint.TryParse(input, out var valueU), valueU);
case TypeCode.UInt16:
return Attempt<object>.If(ushort.TryParse(input, out var valueUs), valueUs);
case TypeCode.UInt64:
return Attempt<object>.If(ulong.TryParse(input, out var valueUl), valueUl);
}
}
else if (target == typeof(Guid))
{
return Attempt<object>.If(Guid.TryParse(input, out var value), value);
}
else if (target == typeof(DateTime))
{
if (DateTime.TryParse(input, out var value))
{
switch (value.Kind)
{
case DateTimeKind.Unspecified:
case DateTimeKind.Utc:
return Attempt<object>.Succeed(value);
case DateTimeKind.Local:
return Attempt<object>.Succeed(value.ToUniversalTime());
default:
throw new ArgumentOutOfRangeException();
}
}
return Attempt<object>.Fail();
}
else if (target == typeof(DateTimeOffset))
{
return Attempt<object>.If(DateTimeOffset.TryParse(input, out var value), value);
}
else if (target == typeof(TimeSpan))
{
return Attempt<object>.If(TimeSpan.TryParse(input, out var value), value);
}
else if (target == typeof(decimal))
{
var input2 = NormalizeNumberDecimalSeparator(input);
return Attempt<object>.If(decimal.TryParse(input2, out var value), value);
}
else if (input != null && target == typeof(Version))
{
return Attempt<object>.If(Version.TryParse(input, out var value), value);
}
// E_NOTIMPL IPAddress, BigInteger
return null; // we can't decide...
}
internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname)
{
// TODO: Localize this exception
if (isDisposed)
throw new ObjectDisposedException(objectname);
}
//public enum PropertyNamesCaseType
//{
// CamelCase,
// CaseInsensitive
//}
///// <summary>
///// Convert an object to a JSON string with camelCase formatting
///// </summary>
///// <param name="obj"></param>
///// <returns></returns>
//public static string ToJsonString(this object obj)
//{
// return obj.ToJsonString(PropertyNamesCaseType.CamelCase);
//}
///// <summary>
///// Convert an object to a JSON string with the specified formatting
///// </summary>
///// <param name="obj">The obj.</param>
///// <param name="propertyNamesCaseType">Type of the property names case.</param>
///// <returns></returns>
//public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType)
//{
// var type = obj.GetType();
// var dateTimeStyle = "yyyy-MM-dd HH:mm:ss";
// if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
// {
// return obj.ToString();
// }
// if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type))
// {
// return Convert.ToDateTime(obj).ToString(dateTimeStyle);
// }
// var serializer = new JsonSerializer();
// switch (propertyNamesCaseType)
// {
// case PropertyNamesCaseType.CamelCase:
// serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
// break;
// }
// var dateTimeConverter = new IsoDateTimeConverter
// {
// DateTimeStyles = System.Globalization.DateTimeStyles.None,
// DateTimeFormat = dateTimeStyle
// };
// if (typeof(IDictionary).IsAssignableFrom(type))
// {
// return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
// }
// if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type)))
// {
// return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
// }
// return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter);
//}
/// <summary>
/// Converts an object into a dictionary
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <typeparam name="TVal"> </typeparam>
/// <param name="o"></param>
/// <param name="ignoreProperties"></param>
/// <returns></returns>
public static IDictionary<string, TVal> ToDictionary<T, TProperty, TVal>(this T o, params Expression<Func<T, TProperty>>[] ignoreProperties)
{
return o.ToDictionary<TVal>(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray());
}
/// <summary>
/// Turns object into dictionary
/// </summary>
/// <param name="o"></param>
/// <param name="ignoreProperties">Properties to ignore</param>
/// <returns></returns>
public static IDictionary<string, TVal> ToDictionary<TVal>(this object o, params string[] ignoreProperties)
{
if (o != null)
{
var props = TypeDescriptor.GetProperties(o);
var d = new Dictionary<string, TVal>();
foreach (var prop in props.Cast<PropertyDescriptor>().Where(x => ignoreProperties.Contains(x.Name) == false))
{
var val = prop.GetValue(o);
if (val != null)
{
d.Add(prop.Name, (TVal)val);
}
}
return d;
}
return new Dictionary<string, TVal>();
}
/// <summary>
/// Converts an object's properties into a dictionary.
@@ -506,10 +17,12 @@ namespace Umbraco.Core
/// <param name="obj">The object to convert.</param>
/// <param name="namer">A property namer function.</param>
/// <returns>A dictionary containing each properties.</returns>
public static Dictionary<string, object> ToObjectDictionary<T>(T obj, Func<PropertyInfo, string> namer = null)
public static Dictionary<string, object> ToObjectDictionary<T>(this T obj, Func<PropertyInfo, string> namer = null)
{
if (obj == null) return new Dictionary<string, object>();
//fixme: This has a hard reference to Newtonsoft
string DefaultNamer(PropertyInfo property)
{
var jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
@@ -530,264 +43,7 @@ namespace Umbraco.Core
ToObjectTypes[t] = properties;
}
return properties.ToDictionary(x => x.Key, x => ((Func<T, object>) x.Value)(obj));
return properties.ToDictionary(x => x.Key, x => ((Func<T, object>)x.Value)(obj));
}
internal static string ToDebugString(this object obj, int levels = 0)
{
if (obj == null) return "{null}";
try
{
if (obj is string)
{
return "\"{0}\"".InvariantFormat(obj);
}
if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?)
{
return "{0}".InvariantFormat(obj);
}
if (obj is Enum)
{
return "[{0}]".InvariantFormat(obj);
}
if (obj is IEnumerable)
{
var enumerable = (obj as IEnumerable);
var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList();
return items.Any()
? "{{ {0} }}".InvariantFormat(string.Join(", ", items))
: null;
}
var props = obj.GetType().GetProperties();
if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2)
{
try
{
var key = props[0].GetValue(obj, null) as string;
var value = props[1].GetValue(obj, null).ToDebugString(levels - 1);
return "{0}={1}".InvariantFormat(key, value);
}
catch (Exception)
{
return "[KeyValuePropertyException]";
}
}
if (levels > -1)
{
var items =
(from propertyInfo in props
let value = GetPropertyDebugString(propertyInfo, obj, levels)
where value != null
select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray();
return items.Any()
? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items))
: null;
}
}
catch (Exception ex)
{
return "[Exception:{0}]".InvariantFormat(ex.Message);
}
return null;
}
/// <summary>
/// Attempts to serialize the value to an XmlString using ToXmlString
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
internal static Attempt<string> TryConvertToXmlString(this object value, Type type)
{
try
{
var output = value.ToXmlString(type);
return Attempt.Succeed(output);
}
catch (NotSupportedException ex)
{
return Attempt<string>.Fail(ex);
}
}
/// <summary>
/// Returns an XmlSerialized safe string representation for the value
/// </summary>
/// <param name="value"></param>
/// <param name="type">The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown</param>
/// <returns></returns>
internal static string ToXmlString(this object value, Type type)
{
if (value == null) return string.Empty;
if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString());
if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
if (type == typeof(byte)) return XmlConvert.ToString((byte)value);
if (type == typeof(char)) return XmlConvert.ToString((char)value);
if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified);
if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value);
if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value);
if (type == typeof(double)) return XmlConvert.ToString((double)value);
if (type == typeof(float)) return XmlConvert.ToString((float)value);
if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value);
if (type == typeof(int)) return XmlConvert.ToString((int)value);
if (type == typeof(long)) return XmlConvert.ToString((long)value);
if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value);
if (type == typeof(short)) return XmlConvert.ToString((short)value);
if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value);
if (type == typeof(bool)) return XmlConvert.ToString((bool)value);
if (type == typeof(uint)) return XmlConvert.ToString((uint)value);
if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value);
if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value);
throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert");
}
/// <summary>
/// Returns an XmlSerialized safe string representation for the value and type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
internal static string ToXmlString<T>(this object value)
{
return value.ToXmlString(typeof (T));
}
private static string GetEnumPropertyDebugString(object enumItem, int levels)
{
try
{
return enumItem.ToDebugString(levels - 1);
}
catch (Exception)
{
return "[GetEnumPartException]";
}
}
private static string GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels)
{
try
{
return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1);
}
catch (Exception)
{
return "[GetPropertyValueException]";
}
}
internal static Guid AsGuid(this object value)
{
return value is Guid guid ? guid : Guid.Empty;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string NormalizeNumberDecimalSeparator(string s)
{
var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized);
}
// gets a converter for source, that can convert to target, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeConverter GetCachedSourceTypeConverter(Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (InputTypeConverterCache.TryGetValue(key, out var typeConverter))
{
return typeConverter;
}
var converter = TypeDescriptor.GetConverter(source);
if (converter.CanConvertTo(target))
{
return InputTypeConverterCache[key] = converter;
}
return InputTypeConverterCache[key] = null;
}
// gets a converter for target, that can convert from source, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeConverter GetCachedTargetTypeConverter(Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (DestinationTypeConverterCache.TryGetValue(key, out var typeConverter))
{
return typeConverter;
}
var converter = TypeDescriptor.GetConverter(target);
if (converter.CanConvertFrom(source))
{
return DestinationTypeConverterCache[key] = converter;
}
return DestinationTypeConverterCache[key] = null;
}
// gets the underlying type of a nullable type, or null if the type is not nullable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Type GetCachedGenericNullableType(Type type)
{
if (NullableGenericCache.TryGetValue(type, out var underlyingType))
{
return underlyingType;
}
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type underlying = Nullable.GetUnderlyingType(type);
return NullableGenericCache[type] = underlying;
}
return NullableGenericCache[type] = null;
}
// gets an IConvertible from source to target type, or null if none exists
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedCanAssign(object input, Type source, Type target)
{
var key = new CompositeTypeTypeKey(source, target);
if (AssignableTypeCache.TryGetValue(key, out var canConvert))
{
return canConvert;
}
// "object is" is faster than "Type.IsAssignableFrom.
// We can use it to very quickly determine whether true/false
if (input is IConvertible && target.IsAssignableFrom(source))
{
return AssignableTypeCache[key] = true;
}
return AssignableTypeCache[key] = false;
}
// determines whether a type can be converted to boolean
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetCachedCanConvertToBoolean(Type type)
{
if (BoolConvertCache.TryGetValue(type, out var result))
{
return result;
}
if (CustomBooleanTypeConverter.CanConvertFrom(type))
{
return BoolConvertCache[type] = true;
}
return BoolConvertCache[type] = false;
}
}
}

View File

@@ -11,54 +11,10 @@ using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence
{
public static class NPocoSqlExtensions
public static partial class NPocoSqlExtensions
{
// note: here we take benefit from the fact that NPoco methods that return a Sql, such as
// when doing "sql = sql.Where(...)" actually append to, and return, the original Sql, not
// a new one.
#region Special extensions
/// <summary>
/// Provides a mean to express aliases in SELECT Sql statements.
/// </summary>
/// <remarks>
/// <para>First register with <c>using static Umbraco.Core.Persistence.NPocoSqlExtensions.Aliaser</c>,
/// then use eg <c>Sql{Foo}(x => Alias(x.Id, "id"))</c>.</para>
/// </remarks>
public static class Statics
{
/// <summary>
/// Aliases a field.
/// </summary>
/// <param name="field">The field to alias.</param>
/// <param name="alias">The alias.</param>
public static object Alias(object field, string alias) => field;
/// <summary>
/// Produces Sql text.
/// </summary>
/// <param name="field">The name of the field.</param>
/// <param name="expr">A function producing Sql text.</param>
public static T SqlText<T>(string field, Func<string, string> expr) => default;
/// <summary>
/// Produces Sql text.
/// </summary>
/// <param name="field1">The name of the first field.</param>
/// <param name="field2">The name of the second field.</param>
/// <param name="expr">A function producing Sql text.</param>
public static T SqlText<T>(string field1, string field2, Func<string, string, string> expr) => default;
/// <summary>
/// Produces Sql text.
/// </summary>
/// <param name="field1">The name of the first field.</param>
/// <param name="field2">The name of the second field.</param>
/// <param name="field3">The name of the third field.</param>
/// <param name="expr">A function producing Sql text.</param>
public static T SqlText<T>(string field1, string field2, string field3, Func<string, string, string, string> expr) => default;
}
#region Special extensions
#endregion

View File

@@ -619,7 +619,7 @@ namespace Umbraco.Core.Persistence.Querying
// GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
case "SqlText":
if (m.Method.DeclaringType != typeof(NPocoSqlExtensions.Statics))
if (m.Method.DeclaringType != typeof(SqlExtensionsStatics))
goto default;
if (m.Arguments.Count == 2)
{

View File

@@ -10,14 +10,13 @@ using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{

View File

@@ -15,7 +15,7 @@ using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{

View File

@@ -4,11 +4,10 @@ using System.Linq;
using NPoco;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Services;

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -13,7 +12,7 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{

View File

@@ -11,7 +11,7 @@ using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{

View File

@@ -63,7 +63,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
ids = ids
.Select(x => StringExtensions.EnsureEndsWith(x, ".css"))
.Select(x => x.EnsureEndsWith(".css"))
.Distinct()
.ToArray();

View File

@@ -11,7 +11,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{

File diff suppressed because it is too large Load Diff

View File

@@ -116,6 +116,8 @@
</Compile>
-->
<Compile Include="Cache\AppPolicedCacheDictionary.cs" />
<Compile Include="Collections\DeepCloneableList.cs" />
<Compile Include="Collections\ListCloneBehavior.cs" />
<Compile Include="Compose\AuditEventsComponent.cs" />
<Compile Include="Cache\AppCaches.cs" />
<Compile Include="Cache\CacheKeys.cs" />
@@ -148,10 +150,12 @@
<Compile Include="Cache\SingleItemsOnlyRepositoryCachePolicy.cs" />
<Compile Include="Cache\DictionaryAppCache.cs" />
<Compile Include="Cache\TypedCacheRefresherBase.cs" />
<Compile Include="CodeAnnotations\FriendlyNameAttribute.cs" />
<Compile Include="CodeAnnotations\UmbracoObjectTypeAttribute.cs" />
<Compile Include="CodeAnnotations\UmbracoUdiTypeAttribute.cs" />
<Compile Include="Collections\CompositeNStringNStringKey.cs" />
<Compile Include="Collections\CompositeStringStringKey.cs" />
<Compile Include="Collections\CompositeIntStringKey.cs" />
<Compile Include="Collections\CompositeTypeTypeKey.cs" />
<Compile Include="Collections\ConcurrentHashSet.cs" />
<Compile Include="Collections\DeepCloneableList.cs" />
<Compile Include="Collections\ListCloneBehavior.cs" />
@@ -212,8 +216,10 @@
<Compile Include="Mapping\IMapDefinition.cs" />
<Compile Include="Mapping\UmbracoMapper.cs" />
<Compile Include="Migrations\Upgrade\V_8_1_0\ConvertTinyMceAndGridMediaUrlsToLocalLink.cs" />
<Compile Include="Models\ContentBaseExtensions.cs" />
<Compile Include="Models\CultureImpact.cs" />
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="ObjectExtensions.cs" />
<Compile Include="Persistence\Dtos\PropertyTypeCommonDto.cs" />
<Compile Include="Persistence\Repositories\Implement\ContentTypeCommonRepository.cs" />
<Compile Include="Persistence\Repositories\IContentTypeCommonRepository.cs" />
@@ -224,6 +230,12 @@
<Compile Include="Services\PropertyValidationService.cs" />
<Compile Include="Composing\TypeCollectionBuilderBase.cs" />
<Compile Include="StringExtensions.cs" />
<Compile Include="Strings\DefaultShortStringHelper.cs" />
<Compile Include="Strings\DefaultShortStringHelperConfig.cs" />
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
<Compile Include="Strings\IUrlSegmentProvider.cs" />
<Compile Include="Strings\UrlSegmentProviderCollection.cs" />
<Compile Include="Strings\UrlSegmentProviderCollectionBuilder.cs" />
<Compile Include="TypeLoaderExtensions.cs" />
<Compile Include="Composing\WeightAttribute.cs" />
<Compile Include="Composing\WeightedCollectionBuilderBase.cs" />
@@ -528,7 +540,6 @@
<Compile Include="RuntimeOptions.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
<Compile Include="Runtime\CoreInitialComponent.cs" />
<Compile Include="CustomBooleanTypeConverter.cs" />
<Compile Include="Migrations\Expressions\Common\ExecutableBuilder.cs" />
<Compile Include="Migrations\Expressions\Common\IExecutableBuilder.cs" />
<Compile Include="Migrations\Expressions\Create\KeysAndIndexes\CreateKeysAndIndexesBuilder.cs" />
@@ -608,7 +619,6 @@
<Compile Include="Events\UninstallPackageEventArgs.cs" />
<Compile Include="Composing\LightInject\LightInjectException.cs" />
<Compile Include="ExpressionExtensions.cs" />
<Compile Include="ExpressionHelper.cs" />
<Compile Include="FileResources\Files.Designer.cs" />
<Compile Include="GuidUdi.cs" />
<Compile Include="HashCodeHelper.cs" />
@@ -630,6 +640,8 @@
<Compile Include="IO\SystemDirectories.cs" />
<Compile Include="IO\SystemFiles.cs" />
<Compile Include="IO\ViewHelper.cs" />
<Compile Include="IRuntime.cs" />
<Compile Include="IRuntimeState.cs" />
<Compile Include="Logging\DebugDiagnosticsLogger.cs" />
<Compile Include="Logging\Serilog\SerilogLogger.cs" />
<Compile Include="Logging\LoggingTaskExtension.cs" />
@@ -862,8 +874,8 @@
<Compile Include="NamedUdiRange.cs" />
<Compile Include="NameValueCollectionExtensions.cs" />
<Compile Include="NetworkHelper.cs" />
<Compile Include="ObjectExtensions.cs" />
<Compile Include="Collections\OrderedHashSet.cs" />
<Compile Include="ObjectExtensions.cs" />
<Compile Include="Packaging\ConflictingPackageData.cs" />
<Compile Include="Models\Packaging\UninstallationSummary.cs" />
<Compile Include="Packaging\PackageExtraction.cs" />
@@ -1415,6 +1427,7 @@
<Compile Include="Compose\ManifestWatcherComponent.cs" />
<Compile Include="Compose\RelateOnCopyComponent.cs" />
<Compile Include="Compose\RelateOnTrashComponent.cs" />
<Compile Include="SimpleMainDom.cs" />
<Compile Include="Strings\CleanStringType.cs" />
<Compile Include="Strings\ContentBaseExtensions.cs" />
<Compile Include="Strings\Css\StylesheetHelper.cs" />
@@ -1443,7 +1456,6 @@
<Compile Include="Sync\RefreshMethodType.cs" />
<Compile Include="Sync\ServerMessengerBase.cs" />
<Compile Include="Sync\SingleServerRegistrar.cs" />
<Compile Include="Collections\TopoGraph.cs" />
<Compile Include="TypeExtensions.cs" />
<Compile Include="Udi.cs" />
<Compile Include="UdiDefinitionAttribute.cs" />
@@ -1452,6 +1464,8 @@
<Compile Include="UdiRange.cs" />
<Compile Include="UdiTypeConverter.cs" />
<Compile Include="UriExtensions.cs" />
<Compile Include="VersionExtensions.cs" />
<Compile Include="WriteLock.cs" />
<Compile Include="XmlExtensions.cs" />
<Compile Include="PackageActions\IPackageAction.cs" />
<Compile Include="PackageActions\PackageActionCollection.cs" />
@@ -1462,6 +1476,7 @@
<Compile Include="..\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="XmlExtensions.cs" />
<Compile Include="Xml\DynamicContext.cs" />
<Compile Include="Xml\UmbracoXPathPathSyntaxParser.cs" />
<Compile Include="Xml\XmlHelper.cs" />

View File

@@ -518,7 +518,7 @@ namespace Umbraco.Tests.Clr
// test object extensions
Console.WriteLine("Getting object5D values...");
var values5D = ObjectExtensions.ToObjectDictionary(object5);
var values5D = object5.ToObjectDictionary();
Console.WriteLine("Writing object5D values...");
foreach ((var name, var value) in values5)

View File

@@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Tests.TestHelpers;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Tests.Persistence.NPocoTests
{

View File

@@ -246,7 +246,7 @@ namespace Umbraco.Tests.Persistence.Querying
//Console.WriteLine(result2);
Expression<Func<UserDto, UserGroupDto, object>> predicate3 = (user, group) => NPocoSqlExtensions.Statics.SqlText<bool>(user.Login, group.Name, (n1, n2) => $"({n1} LIKE concat({n2}, ',%'))");
Expression<Func<UserDto, UserGroupDto, object>> predicate3 = (user, group) => SqlExtensionsStatics.SqlText<bool>(user.Login, group.Name, (n1, n2) => $"({n1} LIKE concat({n2}, ',%'))");
var modelToSqlExpressionHelper3 = new PocoToSqlExpressionVisitor<UserDto, UserGroupDto>(SqlContext, null, null);
var result3 = modelToSqlExpressionHelper3.Visit(predicate3);

View File

@@ -210,10 +210,10 @@ namespace Umbraco.Tests.Strings
{
var bytes = Encoding.UTF8.GetBytes(value);
Console.WriteLine("base64: " + Convert.ToBase64String(bytes));
var encoded = StringExtensions.UrlTokenEncode(bytes);
var encoded = bytes.UrlTokenEncode();
Assert.AreEqual(expected, encoded);
var backBytes = StringExtensions.UrlTokenDecode(encoded);
var backBytes = encoded.UrlTokenDecode();
var backString = Encoding.UTF8.GetString(backBytes);
Assert.AreEqual(value, backString);
}

View File

@@ -11,7 +11,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Scoping;
using Umbraco.Core.Serialization;
using Umbraco.Web.Composing;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
{