Url
URL, URLSearchParams and URLPattern from C#: parse and edit URLs exactly as the browser does, and match routes with named parameters - which .NET has no equivalent for at all.
@inject Bit.Butil.Url urlMDN reference
.. may climb, and on which characters get percent-encoded. And come here
for URLPattern, which has no .NET counterpart.
Parse splits a URL into the components the browser sees. CanParse asks the same question without the exception a constructor would throw. Resolve applies a relative reference to a base, including the scheme-relative and over-climbing cases.
@inject Bit.Butil.Url url
if (await url.CanParse("https://example.com:8080/docs/a") is false) return;
var parts = await url.Parse("https://example.com:8080/docs/a?x=1#top");
// parts.Origin, parts.Pathname, parts.Search, ...
var absolute = await url.Resolve("../b", "https://example.com/docs/a");
// "https://example.com/b"Query strings
ParseQuery / BuildQuery / GetQueryValues / SetQuery / AppendQuery / RemoveQuery / SortQueryParseQuery returns a flat list rather than a map, because a query string may repeat a name - ?tag=a&tag=b is two parameters, and a map would drop one. The whole-URL edits save re-assembling the parts by hand; SortQuery is the one to reach for when two URLs that differ only in parameter order should produce the same cache key.
var parameters = await url.ParseQuery("?tag=a&tag=b&page=2");
var values = await url.GetQueryValues("?tag=a&tag=b", "tag"); // ["a", "b"]
var query = await url.BuildQuery([new("q", "a b&c"), new("tag", "x")]);
var replaced = await url.SetQuery(current, [new("page", "1")]);
var next = await url.AppendQuery(current, [new("page", "3")]);
var clean = await url.RemoveQuery(current, "utm_source", "utm_medium");
var key = await url.SortQuery(current);URLPattern is a route matcher: :name captures a segment, * is a wildcard, and {} groups can be made optional with ?. TestPattern answers yes or no; MatchPattern also hands back what each parameter captured. A path-only pattern needs a base URL to resolve against.
var match = await url.MatchPattern("/books/:id", "https://example.com/books/42",
baseUrl: "https://example.com");
var id = match?.Groups["id"]; // "42"API reference
ValueTask<bool> IsSupported()ValueTask<bool> CanParse(string url, string? baseUrl = null)ValueTask<UrlParts?> Parse(string url, string? baseUrl = null)ValueTask<string?> Resolve(string url, string baseUrl)ValueTask<UrlQueryParameter[]> ParseQuery(string query)ValueTask<string> BuildQuery(UrlQueryParameter[] parameters)ValueTask<string[]> GetQueryValues(string query, string key)ValueTask<string?> SetQuery(string url, UrlQueryParameter[] parameters)ValueTask<string?> AppendQuery(string url, UrlQueryParameter[] parameters)ValueTask<string?> RemoveQuery(string url, params string[] keys)ValueTask<string?> SortQuery(string url)ValueTask<bool> IsPatternSupported()ValueTask<bool> IsPatternValid(string pattern, string? baseUrl = null)ValueTask<bool> TestPattern(string pattern, string url, string? baseUrl = null)ValueTask<UrlPatternMatch?> MatchPattern(string pattern, string url, string? baseUrl = null)