diff --git a/src/Components/WebView/WebView/src/PathString.cs b/src/Components/WebView/WebView/src/PathString.cs
deleted file mode 100644
index b8bb145e5f6e..000000000000
--- a/src/Components/WebView/WebView/src/PathString.cs
+++ /dev/null
@@ -1,482 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-// NOTE: This file is copied from src/Http/Http.Abstractions/src/PathString.cs
-// and made internal with a namespace change.
-// It can't be referenced directly from the StaticFiles package because that would cause this package to require
-// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as
-// various platforms that .NET MAUI runs on, such as Android and iOS).
-
-using System;
-using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
-using System.Text;
-
-namespace Microsoft.AspNetCore.Components.WebView
-{
- ///
- /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string
- ///
- [TypeConverter(typeof(PathStringConverter))]
- internal readonly struct PathString : IEquatable
- {
- ///
- /// Represents the empty path. This field is read-only.
- ///
- public static readonly PathString Empty = new(string.Empty);
-
- ///
- /// Initialize the path string with a given value. This value must be in unescaped format. Use
- /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format.
- ///
- /// The unescaped path to be assigned to the Value property.
- public PathString(string? value)
- {
- if (!string.IsNullOrEmpty(value) && value[0] != '/')
- {
- throw new ArgumentException(Resources.FormatException_PathMustStartWithSlash(nameof(value)), nameof(value));
- }
- Value = value;
- }
-
- ///
- /// The unescaped path value
- ///
- public string? Value { get; }
-
- ///
- /// True if the path is not empty
- ///
- [MemberNotNullWhen(true, nameof(Value))]
- public bool HasValue
- {
- get { return !string.IsNullOrEmpty(Value); }
- }
-
- ///
- /// Provides the path string escaped in a way which is correct for combining into the URI representation.
- ///
- /// The escaped path value
- public override string ToString()
- {
- return ToUriComponent();
- }
-
- ///
- /// Provides the path string escaped in a way which is correct for combining into the URI representation.
- ///
- /// The escaped path value
- public string ToUriComponent()
- {
- if (!HasValue)
- {
- return string.Empty;
- }
-
- var value = Value;
- var i = 0;
- for (; i < value.Length; i++)
- {
- if (!PathStringHelper.IsValidPathChar(value[i]) || PathStringHelper.IsPercentEncodedChar(value, i))
- {
- break;
- }
- }
-
- if (i < value.Length)
- {
- return ToEscapedUriComponent(value, i);
- }
-
- return value;
- }
-
- private static string ToEscapedUriComponent(string value, int i)
- {
- StringBuilder? buffer = null;
-
- var start = 0;
- var count = i;
- var requiresEscaping = false;
-
- while (i < value.Length)
- {
- var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(value, i);
- if (PathStringHelper.IsValidPathChar(value[i]) || isPercentEncodedChar)
- {
- if (requiresEscaping)
- {
- // the current segment requires escape
- buffer ??= new StringBuilder(value.Length * 3);
- buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
-
- requiresEscaping = false;
- start = i;
- count = 0;
- }
-
- if (isPercentEncodedChar)
- {
- count += 3;
- i += 3;
- }
- else
- {
- count++;
- i++;
- }
- }
- else
- {
- if (!requiresEscaping)
- {
- // the current segment doesn't require escape
- buffer ??= new StringBuilder(value.Length * 3);
- buffer.Append(value, start, count);
-
- requiresEscaping = true;
- start = i;
- count = 0;
- }
-
- count++;
- i++;
- }
- }
-
- if (count == value.Length && !requiresEscaping)
- {
- return value;
- }
- else
- {
- if (count > 0)
- {
- buffer ??= new StringBuilder(value.Length * 3);
-
- if (requiresEscaping)
- {
- buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
- }
- else
- {
- buffer.Append(value, start, count);
- }
- }
-
- return buffer?.ToString() ?? string.Empty;
- }
- }
-
- ///
- /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any
- /// value that is not a path.
- ///
- /// The escaped path as it appears in the URI format.
- /// The resulting PathString
- public static PathString FromUriComponent(string uriComponent)
- {
- // REVIEW: what is the exactly correct thing to do?
- return new PathString(Uri.UnescapeDataString(uriComponent));
- }
-
- ///
- /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported.
- ///
- /// The Uri object
- /// The resulting PathString
- public static PathString FromUriComponent(Uri uri)
- {
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
-
- // REVIEW: what is the exactly correct thing to do?
- return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped));
- }
-
- ///
- /// Determines whether the beginning of this instance matches the specified .
- ///
- /// The to compare.
- /// true if value matches the beginning of this string; otherwise, false.
- public bool StartsWithSegments(PathString other)
- {
- return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase);
- }
-
- ///
- /// Determines whether the beginning of this instance matches the specified when compared
- /// using the specified comparison option.
- ///
- /// The to compare.
- /// One of the enumeration values that determines how this and value are compared.
- /// true if value matches the beginning of this string; otherwise, false.
- public bool StartsWithSegments(PathString other, StringComparison comparisonType)
- {
- var value1 = Value ?? string.Empty;
- var value2 = other.Value ?? string.Empty;
- if (value1.StartsWith(value2, comparisonType))
- {
- return value1.Length == value2.Length || value1[value2.Length] == '/';
- }
- return false;
- }
-
- ///
- /// Determines whether the beginning of this instance matches the specified and returns
- /// the remaining segments.
- ///
- /// The to compare.
- /// The remaining segments after the match.
- /// true if value matches the beginning of this string; otherwise, false.
- public bool StartsWithSegments(PathString other, out PathString remaining)
- {
- return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining);
- }
-
- ///
- /// Determines whether the beginning of this instance matches the specified when compared
- /// using the specified comparison option and returns the remaining segments.
- ///
- /// The to compare.
- /// One of the enumeration values that determines how this and value are compared.
- /// The remaining segments after the match.
- /// true if value matches the beginning of this string; otherwise, false.
- public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining)
- {
- var value1 = Value ?? string.Empty;
- var value2 = other.Value ?? string.Empty;
- if (value1.StartsWith(value2, comparisonType))
- {
- if (value1.Length == value2.Length || value1[value2.Length] == '/')
- {
- remaining = new PathString(value1[value2.Length..]);
- return true;
- }
- }
- remaining = Empty;
- return false;
- }
-
- ///
- /// Determines whether the beginning of this instance matches the specified and returns
- /// the matched and remaining segments.
- ///
- /// The to compare.
- /// The matched segments with the original casing in the source value.
- /// The remaining segments after the match.
- /// true if value matches the beginning of this string; otherwise, false.
- public bool StartsWithSegments(PathString other, out PathString matched, out PathString remaining)
- {
- return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out matched, out remaining);
- }
-
- ///
- /// Determines whether the beginning of this instance matches the specified when compared
- /// using the specified comparison option and returns the matched and remaining segments.
- ///
- /// The to compare.
- /// One of the enumeration values that determines how this and value are compared.
- /// The matched segments with the original casing in the source value.
- /// The remaining segments after the match.
- /// true if value matches the beginning of this string; otherwise, false.
- public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString matched, out PathString remaining)
- {
- var value1 = Value ?? string.Empty;
- var value2 = other.Value ?? string.Empty;
- if (value1.StartsWith(value2, comparisonType))
- {
- if (value1.Length == value2.Length || value1[value2.Length] == '/')
- {
- matched = new PathString(value1.Substring(0, value2.Length));
- remaining = new PathString(value1[value2.Length..]);
- return true;
- }
- }
- remaining = Empty;
- matched = Empty;
- return false;
- }
-
- ///
- /// Adds two PathString instances into a combined PathString value.
- ///
- /// The combined PathString value
- public PathString Add(PathString other)
- {
- if (HasValue &&
- other.HasValue &&
- Value[^1] == '/')
- {
- // If the path string has a trailing slash and the other string has a leading slash, we need
- // to trim one of them.
- var combined = string.Concat(Value.AsSpan(), other.Value.AsSpan(1));
- return new PathString(combined);
- }
-
- return new PathString(Value + other.Value);
- }
-
- ///
- /// Combines a PathString and QueryString into the joined URI formatted string value.
- ///
- /// The joined URI formatted string value
- public string Add(QueryString other)
- {
- return ToUriComponent() + other.ToUriComponent();
- }
-
- ///
- /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase.
- ///
- /// The second PathString for comparison.
- /// True if both PathString values are equal
- public bool Equals(PathString other)
- {
- return Equals(other, StringComparison.OrdinalIgnoreCase);
- }
-
- ///
- /// Compares this PathString value to another value using a specific StringComparison type
- ///
- /// The second PathString for comparison
- /// The StringComparison type to use
- /// True if both PathString values are equal
- public bool Equals(PathString other, StringComparison comparisonType)
- {
- if (!HasValue && !other.HasValue)
- {
- return true;
- }
- return string.Equals(Value, other.Value, comparisonType);
- }
-
- ///
- /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase.
- ///
- /// The second PathString for comparison.
- /// True if both PathString values are equal
- public override bool Equals(object? obj)
- {
- if (obj is null)
- {
- return !HasValue;
- }
- return obj is PathString pathString && Equals(pathString);
- }
-
- ///
- /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation.
- ///
- /// The hash code
- public override int GetHashCode()
- {
- return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(Value) : 0);
- }
-
- ///
- /// Operator call through to Equals
- ///
- /// The left parameter
- /// The right parameter
- /// True if both PathString values are equal
- public static bool operator ==(PathString left, PathString right)
- {
- return left.Equals(right);
- }
-
- ///
- /// Operator call through to Equals
- ///
- /// The left parameter
- /// The right parameter
- /// True if both PathString values are not equal
- public static bool operator !=(PathString left, PathString right)
- {
- return !left.Equals(right);
- }
-
- ///
- ///
- /// The left parameter
- /// The right parameter
- /// The ToString combination of both values
- public static string operator +(string left, PathString right)
- {
- // This overload exists to prevent the implicit string<->PathString converter from
- // trying to call the PathString+PathString operator for things that are not path strings.
- return string.Concat(left, right.ToString());
- }
-
- ///
- ///
- /// The left parameter
- /// The right parameter
- /// The ToString combination of both values
- public static string operator +(PathString left, string? right)
- {
- // This overload exists to prevent the implicit string<->PathString converter from
- // trying to call the PathString+PathString operator for things that are not path strings.
- return string.Concat(left.ToString(), right);
- }
-
- ///
- /// Operator call through to Add
- ///
- /// The left parameter
- /// The right parameter
- /// The PathString combination of both values
- public static PathString operator +(PathString left, PathString right)
- {
- return left.Add(right);
- }
-
- ///
- /// Operator call through to Add
- ///
- /// The left parameter
- /// The right parameter
- /// The PathString combination of both values
- public static string operator +(PathString left, QueryString right)
- {
- return left.Add(right);
- }
-
- ///
- /// Implicitly creates a new PathString from the given string.
- ///
- ///
- public static implicit operator PathString(string? s)
- => ConvertFromString(s);
-
- ///
- /// Implicitly calls ToString().
- ///
- ///
- public static implicit operator string(PathString path)
- => path.ToString();
-
- internal static PathString ConvertFromString(string? s)
- => string.IsNullOrEmpty(s) ? new PathString(s) : FromUriComponent(s);
- }
-
- internal sealed class PathStringConverter : TypeConverter
- {
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
-
- public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
- => value is string @string
- ? PathString.ConvertFromString(@string)
- : base.ConvertFrom(context, culture, value);
-
- public override object ConvertTo(ITypeDescriptorContext context,
- CultureInfo culture, object value, Type destinationType)
- => destinationType == typeof(string)
- ? value.ToString() ?? string.Empty
- : base.ConvertTo(context, culture, value, destinationType);
- }
-}
diff --git a/src/Components/WebView/WebView/src/PathStringHelper.cs b/src/Components/WebView/WebView/src/PathStringHelper.cs
deleted file mode 100644
index e132eeb13098..000000000000
--- a/src/Components/WebView/WebView/src/PathStringHelper.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-// NOTE: This file is copied from src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs
-// and made internal with a namespace change.
-// It can't be referenced directly from the StaticFiles package because that would cause this package to require
-// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as
-// various platforms that .NET MAUI runs on, such as Android and iOS).
-
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace Microsoft.AspNetCore.Components.WebView
-{
- internal static class PathStringHelper
- {
- // uint[] bits uses 1 cache line (Array info + 16 bytes)
- // bool[] would use 3 cache lines (Array info + 128 bytes)
- // So we use 128 bits rather than 128 bytes/bools
- private static readonly uint[] ValidPathChars = {
- 0b_0000_0000__0000_0000__0000_0000__0000_0000, // 0x00 - 0x1F
- 0b_0010_1111__1111_1111__1111_1111__1101_0010, // 0x20 - 0x3F
- 0b_1000_0111__1111_1111__1111_1111__1111_1111, // 0x40 - 0x5F
- 0b_0100_0111__1111_1111__1111_1111__1111_1110, // 0x60 - 0x7F
- };
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsValidPathChar(char c)
- {
- // Use local array and uint .Length compare to elide the bounds check on array access
- var validChars = ValidPathChars;
- var i = (int)c;
-
- // Array is in chunks of 32 bits, so get offset by dividing by 32
- var offset = i >> 5; // i / 32;
- // Significant bit position is the remainder of the above calc; i % 32 => i & 31
- var significantBit = 1u << (i & 31);
-
- // Check offset in bounds and check if significant bit set
- return (uint)offset < (uint)validChars.Length &&
- ((validChars[offset] & significantBit) != 0);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsPercentEncodedChar(string str, int index)
- {
- var len = (uint)str.Length;
- if (str[index] == '%' && index < len - 2)
- {
- return AreFollowingTwoCharsHex(str, index);
- }
-
- return false;
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static bool AreFollowingTwoCharsHex(string str, int index)
- {
- Debug.Assert(index < str.Length - 2);
-
- var c1 = str[index + 1];
- var c2 = str[index + 2];
- return IsHexadecimalChar(c1) && IsHexadecimalChar(c2);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsHexadecimalChar(char c)
- {
- // Between 0 - 9 or uppercased between A - F
- return (uint)(c - '0') <= 9 || (uint)((c & ~0x20) - 'A') <= ('F' - 'A');
- }
- }
-}
diff --git a/src/Components/WebView/WebView/src/QueryString.cs b/src/Components/WebView/WebView/src/QueryString.cs
deleted file mode 100644
index f2a23c6b2e88..000000000000
--- a/src/Components/WebView/WebView/src/QueryString.cs
+++ /dev/null
@@ -1,304 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-// NOTE: This file is copied from src/Http/Http.Abstractions/src/QueryString.cs
-// and made internal with a namespace change.
-// It can't be referenced directly from the StaticFiles package because that would cause this package to require
-// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as
-// various platforms that .NET MAUI runs on, such as Android and iOS).
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Text;
-using System.Text.Encodings.Web;
-using Microsoft.Extensions.Primitives;
-
-namespace Microsoft.AspNetCore.Components.WebView
-{
- ///
- /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string
- ///
- internal readonly struct QueryString : IEquatable
- {
- ///
- /// Represents the empty query string. This field is read-only.
- ///
- public static readonly QueryString Empty = new QueryString(string.Empty);
-
- ///
- /// Initialize the query string with a given value. This value must be in escaped and delimited format with
- /// a leading '?' character.
- ///
- /// The query string to be assigned to the Value property.
- public QueryString(string? value)
- {
- if (!string.IsNullOrEmpty(value) && value[0] != '?')
- {
- throw new ArgumentException("The leading '?' must be included for a non-empty query.", nameof(value));
- }
- Value = value;
- }
-
- ///
- /// The escaped query string with the leading '?' character
- ///
- public string? Value { get; }
-
- ///
- /// True if the query string is not empty
- ///
- public bool HasValue => !string.IsNullOrEmpty(Value);
-
- ///
- /// Provides the query string escaped in a way which is correct for combining into the URI representation.
- /// A leading '?' character will be included unless the Value is null or empty. Characters which are potentially
- /// dangerous are escaped.
- ///
- /// The query string value
- public override string ToString()
- {
- return ToUriComponent();
- }
-
- ///
- /// Provides the query string escaped in a way which is correct for combining into the URI representation.
- /// A leading '?' character will be included unless the Value is null or empty. Characters which are potentially
- /// dangerous are escaped.
- ///
- /// The query string value
- public string ToUriComponent()
- {
- // Escape things properly so System.Uri doesn't mis-interpret the data.
- return !string.IsNullOrEmpty(Value) ? Value!.Replace("#", "%23") : string.Empty;
- }
-
- ///
- /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any
- /// value that is not a query.
- ///
- /// The escaped query as it appears in the URI format.
- /// The resulting QueryString
- public static QueryString FromUriComponent(string uriComponent)
- {
- if (string.IsNullOrEmpty(uriComponent))
- {
- return new QueryString(string.Empty);
- }
- return new QueryString(uriComponent);
- }
-
- ///
- /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported.
- ///
- /// The Uri object
- /// The resulting QueryString
- public static QueryString FromUriComponent(Uri uri)
- {
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
-
- string queryValue = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped);
- if (!string.IsNullOrEmpty(queryValue))
- {
- queryValue = "?" + queryValue;
- }
- return new QueryString(queryValue);
- }
-
- ///
- /// Create a query string with a single given parameter name and value.
- ///
- /// The un-encoded parameter name
- /// The un-encoded parameter value
- /// The resulting QueryString
- public static QueryString Create(string name, string value)
- {
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (!string.IsNullOrEmpty(value))
- {
- value = UrlEncoder.Default.Encode(value);
- }
- return new QueryString($"?{UrlEncoder.Default.Encode(name)}={value}");
- }
-
- ///
- /// Creates a query string composed from the given name value pairs.
- ///
- ///
- /// The resulting QueryString
- public static QueryString Create(IEnumerable> parameters)
- {
- var builder = new StringBuilder();
- var first = true;
- foreach (var pair in parameters)
- {
- AppendKeyValuePair(builder, pair.Key, pair.Value, first);
- first = false;
- }
-
- return new QueryString(builder.ToString());
- }
-
- ///
- /// Creates a query string composed from the given name value pairs.
- ///
- ///
- /// The resulting QueryString
- public static QueryString Create(IEnumerable> parameters)
- {
- var builder = new StringBuilder();
- var first = true;
-
- foreach (var pair in parameters)
- {
- // If nothing in this pair.Values, append null value and continue
- if (StringValues.IsNullOrEmpty(pair.Value))
- {
- AppendKeyValuePair(builder, pair.Key, null, first);
- first = false;
- continue;
- }
- // Otherwise, loop through values in pair.Value
- foreach (var value in pair.Value)
- {
- AppendKeyValuePair(builder, pair.Key, value, first);
- first = false;
- }
- }
-
- return new QueryString(builder.ToString());
- }
-
- ///
- /// Concatenates to the current query string.
- ///
- /// The to concatenate.
- /// The concatenated .
- public QueryString Add(QueryString other)
- {
- if (!HasValue || Value!.Equals("?", StringComparison.Ordinal))
- {
- return other;
- }
- if (!other.HasValue || other.Value!.Equals("?", StringComparison.Ordinal))
- {
- return this;
- }
-
- // ?name1=value1 Add ?name2=value2 returns ?name1=value1&name2=value2
- return new QueryString(string.Concat(Value, "&", other.Value.AsSpan(1)));
- }
-
- ///
- /// Concatenates a query string with and
- /// to the current query string.
- ///
- /// The name of the query string to concatenate.
- /// The value of the query string to concatenate.
- /// The concatenated .
- public QueryString Add(string name, string value)
- {
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (!HasValue || Value!.Equals("?", StringComparison.Ordinal))
- {
- return Create(name, value);
- }
-
- var builder = new StringBuilder(Value);
- AppendKeyValuePair(builder, name, value, first: false);
- return new QueryString(builder.ToString());
- }
-
- ///
- /// Evalutes if the current query string is equal to .
- ///
- /// The to compare.
- /// if the ssquery strings are equal.
- public bool Equals(QueryString other)
- {
- if (!HasValue && !other.HasValue)
- {
- return true;
- }
- return string.Equals(Value, other.Value, StringComparison.Ordinal);
- }
-
- ///
- /// Evaluates if the current query string is equal to an object .
- ///
- /// An object to compare.
- /// if the query strings are equal.
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj))
- {
- return !HasValue;
- }
- return obj is QueryString && Equals((QueryString)obj);
- }
-
- ///
- /// Gets a hash code for the value.
- ///
- /// The hash code as an .
- public override int GetHashCode()
- {
- return (HasValue ? Value!.GetHashCode() : 0);
- }
-
- ///
- /// Evaluates if one query string is equal to another.
- ///
- /// A instance.
- /// A instance.
- /// if the query strings are equal.
- public static bool operator ==(QueryString left, QueryString right)
- {
- return left.Equals(right);
- }
-
- ///
- /// Evaluates if one query string is not equal to another.
- ///
- /// A instance.
- /// A instance.
- /// if the query strings are not equal.
- public static bool operator !=(QueryString left, QueryString right)
- {
- return !left.Equals(right);
- }
-
- ///
- /// Concatenates and into a single query string.
- ///
- /// A instance.
- /// A instance.
- /// The concatenated .
- public static QueryString operator +(QueryString left, QueryString right)
- {
- return left.Add(right);
- }
-
- private static void AppendKeyValuePair(StringBuilder builder, string key, string? value, bool first)
- {
- builder.Append(first ? '?' : '&');
- builder.Append(UrlEncoder.Default.Encode(key));
- builder.Append('=');
- if (!string.IsNullOrEmpty(value))
- {
- builder.Append(UrlEncoder.Default.Encode(value));
- }
- }
- }
-}