Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions uSync.Core/Extensions/XElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,37 @@ public static string ValueOrDefault([AllowNull] this XElement? node, string defa
public static TObject ValueOrDefault<TObject>([AllowNull] this XElement? node, TObject defaultValue)
{
var value = node.ValueOrDefault(string.Empty);
if (value == string.Empty) return defaultValue;
if (value.Length == 0) return defaultValue;

return value.ConvertOrDefault(defaultValue);
}

/// <summary>
/// Convert a non-empty string value to the requested type.
/// </summary>
/// <remarks>
/// These getters are called for (almost) every attribute of every node during a
/// report/import. The handful of value types actually used have direct, allocation
/// free parsers that are much cheaper than routing through Umbraco's reflection based
/// TryConvertTo. The typeof(TObject) == typeof(...) comparisons are folded to
/// constants by the JIT per generic instantiation, so the branches have no runtime
/// cost. Anything not matched falls through to TryGetValueAs.
/// </remarks>
private static TObject ConvertOrDefault<TObject>(this string value, TObject defaultValue)
{
if (typeof(TObject) == typeof(int))
return int.TryParse(value, out var i) ? (TObject)(object)i : defaultValue;

if (typeof(TObject) == typeof(Guid))
return Guid.TryParse(value, out var g) ? (TObject)(object)g : defaultValue;

if (typeof(TObject) == typeof(bool))
return bool.TryParse(value, out var b) ? (TObject)(object)b : defaultValue;

if (typeof(TObject).IsEnum)
return Enum.TryParse(typeof(TObject), value, true, out var e) && e is TObject enumValue
? enumValue
: defaultValue;

return value.TryGetValueAs<TObject>(out var result) ? result : defaultValue;
}
Expand Down Expand Up @@ -284,9 +314,9 @@ public static string ValueOrDefault([AllowNull] this XAttribute? attribute, stri
public static TObject ValueOrDefault<TObject>([AllowNull] this XAttribute attribute, TObject defaultValue)
{
var value = attribute.ValueOrDefault(string.Empty);
if (value == string.Empty) return defaultValue;
if (value.Length == 0) return defaultValue;

return value.TryGetValueAs<TObject>(out var result) ? result : defaultValue;
return value.ConvertOrDefault(defaultValue);
}
#endregion

Expand All @@ -307,17 +337,17 @@ public static TObject ValueOrDefault<TObject>([AllowNull] this XAttribute attrib
/// </remarks>
public static async Task<string> MakePlatformSafeHashAsync(this XElement node)
{
using (MemoryStream stream = new MemoryStream())
{
await node.SaveAsync(stream, SaveOptions.None, CancellationToken.None);
stream.Seek(0, SeekOrigin.Begin);
using HashAlgorithm hashAlgorithm = CryptoConfig.AllowOnlyFipsAlgorithms ? SHA1.Create() : MD5.Create();

using (HashAlgorithm hashAlgorithm = CryptoConfig.AllowOnlyFipsAlgorithms ? SHA1.Create() : MD5.Create())
{
var hash = await hashAlgorithm.ComputeHashAsync(stream);
return Convert.ToHexStringLower(hash);
}
// stream the xml straight into the hash instead of buffering the whole
// serialized document into a MemoryStream first. CryptoStream feeds each
// written block to the algorithm as it arrives, so nothing is held in memory.
using (var cryptoStream = new CryptoStream(Stream.Null, hashAlgorithm, CryptoStreamMode.Write))
{
await node.SaveAsync(cryptoStream, SaveOptions.None, CancellationToken.None);
}

return Convert.ToHexStringLower(hashAlgorithm.Hash!);
}

}
Loading