From 44dffbe81b8dd194433b2f79d9ea5d69058ec61a Mon Sep 17 00:00:00 2001 From: bfren Date: Fri, 27 Feb 2026 09:26:16 +0000 Subject: [PATCH 01/18] Shifting parse to separate function - #61 --- src/All.Mvc/WrapModelBinder.cs | 135 +++++++++++++++++---------------- 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/src/All.Mvc/WrapModelBinder.cs b/src/All.Mvc/WrapModelBinder.cs index bef08a37..f6921d0f 100644 --- a/src/All.Mvc/WrapModelBinder.cs +++ b/src/All.Mvc/WrapModelBinder.cs @@ -82,97 +82,102 @@ string model try { - // Attempt to parse the value - var valueParsed = default(TValue) switch + // Attempt to parse value and wrap result + return Parse(value) switch { - bool => - M.ParseBool(value).Match(FNone, FSome), + TValue x => + (result, ModelBindingResult.Success(Wrap(x))), - byte => - M.ParseByte(value).Match(FNone, FSome), + _ when new MonadModelBinderProvider().GetBinder(typeof(TValue)) is IWrapModelBinder x => + x.GetValue(provider, model), - char => - M.ParseChar(value).Match(FNone, FSome), + _ when JsonSerializer.Deserialize($"\"{value}\"", WrapModelBinderHelpers.Options) is TValue x => + (result, ModelBindingResult.Success(Wrap(x))), - DateOnly => - M.ParseDateOnly(value).Match(FNone, FSome), + _ => + Nothing() + }; + } + catch + { + return Nothing(); + } + } - DateTime => - M.ParseDateTime(value).Match(FNone, FSome), + /// + /// Attempt to parse , returning null on failure. + /// + /// String value. + /// Parsed value or null. + internal object? Parse(string? value) => + default(TValue) switch + { + bool => + M.ParseBool(value).Match(FNone, FSome), - DateTimeOffset => - M.ParseDateTimeOffset(value).Match(FNone, FSome), + byte => + M.ParseByte(value).Match(FNone, FSome), - decimal => - M.ParseDecimal(value).Match(FNone, FSome), + char => + M.ParseChar(value).Match(FNone, FSome), - double => - M.ParseDouble(value).Match(FNone, FSome), + DateOnly => + M.ParseDateOnly(value).Match(FNone, FSome), - Guid => - M.ParseGuid(value).Match(FNone, FSome), + DateTime => + M.ParseDateTime(value).Match(FNone, FSome), - short => - M.ParseInt16(value).Match(FNone, FSome), + DateTimeOffset => + M.ParseDateTimeOffset(value).Match(FNone, FSome), - int => - M.ParseInt32(value).Match(FNone, FSome), + decimal => + M.ParseDecimal(value).Match(FNone, FSome), - long => - M.ParseInt64(value).Match(FNone, FSome), + double => + M.ParseDouble(value).Match(FNone, FSome), - Int128 => - M.ParseInt128(value).Match(FNone, FSome), + Guid => + M.ParseGuid(value).Match(FNone, FSome), - nint => - M.ParseIntPtr(value).Match(FNone, FSome), + short => + M.ParseInt16(value).Match(FNone, FSome), - float => - M.ParseSingle(value).Match(FNone, FSome), + int => + M.ParseInt32(value).Match(FNone, FSome), - TimeOnly => - M.ParseTimeOnly(value).Match(FNone, FSome), + long => + M.ParseInt64(value).Match(FNone, FSome), - ushort => - M.ParseUInt16(value).Match(FNone, FSome), + Int128 => + M.ParseInt128(value).Match(FNone, FSome), - uint => - M.ParseUInt32(value).Match(FNone, FSome), + nint => + M.ParseIntPtr(value).Match(FNone, FSome), - ulong => - M.ParseUInt64(value).Match(FNone, FSome), + float => + M.ParseSingle(value).Match(FNone, FSome), - UInt128 => - M.ParseUInt128(value).Match(FNone, FSome), + TimeOnly => + M.ParseTimeOnly(value).Match(FNone, FSome), - nuint => - M.ParseUIntPtr(value).Match(FNone, FSome), + ushort => + M.ParseUInt16(value).Match(FNone, FSome), - _ => - null - }; + uint => + M.ParseUInt32(value).Match(FNone, FSome), - // Wrap parsed value - return valueParsed switch - { - TValue x => - (result, ModelBindingResult.Success(Wrap(x))), + ulong => + M.ParseUInt64(value).Match(FNone, FSome), - _ when new MonadModelBinderProvider().GetBinder(typeof(TValue)) is IWrapModelBinder x => - x.GetValue(provider, model), + UInt128 => + M.ParseUInt128(value).Match(FNone, FSome), - _ when JsonSerializer.Deserialize($"\"{value}\"", WrapModelBinderHelpers.Options) is TValue x => - (result, ModelBindingResult.Success(Wrap(x))), + nuint => + M.ParseUIntPtr(value).Match(FNone, FSome), - _ => - Nothing() - }; - } - catch - { - return Nothing(); - } - } + _ => + null + }; /// /// Return a 'None' or null value. From 2552037ad4a48e680390b6ce52450e6f0daf222b Mon Sep 17 00:00:00 2001 From: bfren Date: Fri, 27 Feb 2026 09:26:40 +0000 Subject: [PATCH 02/18] Moving check to separate function - #61 --- src/Common/Functions/F.Format.cs | 93 ++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/src/Common/Functions/F.Format.cs b/src/Common/Functions/F.Format.cs index a0302195..65922853 100644 --- a/src/Common/Functions/F.Format.cs +++ b/src/Common/Functions/F.Format.cs @@ -30,53 +30,23 @@ public static string Format(string formatString, T source) => /// Formatted string. public static string Format(string formatString, T source, string? replaceIfNullOrEmpty) { - // Return if format string is null or empty - if (string.IsNullOrWhiteSpace(formatString)) + // Check arguments before proceeding + if (Check(formatString, source, replaceIfNullOrEmpty) is string earlyReturn) { - return replaceIfNullOrEmpty ?? string.Empty; + return earlyReturn; } - // Return if source is null or an empty array - if (source is null) - { - return replaceIfNullOrEmpty ?? formatString; - } - else if (source is Array arr) - { - if (arr.Length == 0) - { - return replaceIfNullOrEmpty ?? formatString; - } - - // Attempt to use string.Format - try - { - return string.Format(DefaultCulture, formatString, [.. arr]); - } - catch (Exception) - { - // do nothing - } - } - - // Thanks James Newton-King! + // Initialise variables before regex replace loop var regex = TemplateMatcherRegex(); - var values = new List(); var replaceIndex = 0; // keeps track of replace loop so we can match named template values with an array source var numberedTemplates = true; var flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance; var rewrittenFormat = regex.Replace(formatString, (m) => { - var startGroup = m.Groups["start"]; - var templateGroup = m.Groups["template"]; - var formatGroup = m.Groups["format"]; - var endGroup = m.Groups["end"]; - // This is the value inside the braces, e.g. "0" in "{0}" or "A" in "{A}" - // Remove any @ symbols from the start - used by Serilog to denote an object format - // but breaks the following - var template = templateGroup.Value.TrimStart('@'); + // Remove any @ symbols from the start - used by Serilog to denote an object format but breaks the following + var template = m.Groups["template"].Value.TrimStart('@'); var templateIsNumber = int.TryParse(template, out var templateNumber); numberedTemplates = numberedTemplates && templateIsNumber; @@ -107,16 +77,61 @@ public static string Format(string formatString, T source, string? replaceIfN values.Add(value); // Recreate format using zero-based string - return new string('{', startGroup.Captures.Count) + return new string('{', m.Groups["start"].Captures.Count) + (values.Count - 1) - + formatGroup.Value - + new string('}', endGroup.Captures.Count); + + m.Groups["format"].Value + + new string('}', m.Groups["end"].Captures.Count); }); // Format string with ordered values return string.Format(DefaultCulture, rewrittenFormat, [.. values]); } + /// + /// Check inputs before attempting to apply complex string format. + /// + /// Source type. + /// String to format. + /// Source object to use for template values. + /// Returned if or are null / empty. + /// Early return value if checks fail, or string.Format succeeds (much faster!). + internal static string? Check(string formatString, T source, string? replaceIfNullOrEmpty) + { + // Return if format string is null or empty + if (string.IsNullOrWhiteSpace(formatString)) + { + return replaceIfNullOrEmpty ?? string.Empty; + } + + // Return if source is null or an empty array + if (source is null) + { + return replaceIfNullOrEmpty ?? formatString; + } + else if (source is Array arr) + { + if (arr.Length == 0) + { + return replaceIfNullOrEmpty ?? formatString; + } + + // Attempt to use string.Format + try + { + return string.Format(DefaultCulture, formatString, [.. arr]); + } + catch (Exception) + { + // Do nothing - null return will cause Format function to continue + } + } + + return null; + } + + /// + /// Thanks James Newton-King! + /// [GeneratedRegex("(?\\{)+(?