# Uri

> Parse a URL into its parts with the C#-faithful `new Uri(url)` handle. Construct it from a URL string, then read `.Scheme`, `.Host`, `.Port`, `.AbsolutePath`, `.PathAndQuery`, `.Query`, and `.Fragment`; resolve a relative reference with `new Uri(baseUri, "…")`; test a text without an exception with `Uri.TryCreate` or `Uri.IsWellFormedUriString`; cut a URL down to its origin with `u.GetLeftPart(UriPartial.Authority)`. Pure — no capability needed — and it runs in the browser too, answering what the server answers. Pairs with the HTTP surface (parse a webhook or redirect URL).

<!-- id: stdlib-uri · area: stdlib · stability: stable · html: https://osysharp.com/reference/stdlib/uri/ -->

## Summary        {#summary}
**`Uri`** is the parsed-URL handle, spelled exactly as in C#. Construct it from a URL string and read its parts:

```osy syntax
var u = new Uri("https://api.example.com:8443/v1/users?active=true#top");
u.Scheme;         // "https"
u.Host;           // "api.example.com"
u.Port;           // 8443
u.AbsolutePath;   // "/v1/users"
u.PathAndQuery;   // "/v1/users?active=true"
u.Query;          // "?active=true"
u.Fragment;       // "#top"
```

It is **pure** — no capability, no `using` required. `Uri` is also where the URL-escaping helpers live
([Encoding — Base64, hex, text encodings, URL, HTML](https://osysharp.com/reference/stdlib/encoding/) — `Uri.EscapeDataString`), exactly as C#'s `System.Uri` carries both.

## Signature      {#signature}
```osy syntax
Uri     new Uri(string url)      // parse an ABSOLUTE URL
Uri     new Uri(string url, UriKind.Absolute)   // the same, with the kind said
Uri     new Uri(Uri baseUri, string relative)   // resolve a relative reference ("../b?x=1") against a base
bool    Uri.TryCreate(string? text, UriKind.Absolute, out Uri? u)   // false, never a throw, for a text that is not a URL
bool    Uri.TryCreate(Uri? baseUri, string? relative, out Uri? u)   // resolve against a base; false where C# answers false
bool    Uri.IsWellFormedUriString(string? text, UriKind kind)       // Absolute · Relative · RelativeOrAbsolute
string  uri.GetLeftPart(UriPartial part)  // Scheme "https://" · Authority "https://host:8443" · Path · Query

string  uri.Scheme               // "https"
string  uri.Host                 // "api.example.com"
int     uri.Port                 // 8443 (or the scheme default: 443 for https, 80 for http)
string  uri.AbsolutePath         // "/v1/users"
string  uri.PathAndQuery         // "/v1/users?active=true"
string  uri.Query                // "?active=true" (empty when there is none)
string  uri.Fragment             // "#top" (empty when there is none)
string  uri.AbsoluteUri          // the whole URL, canonical and escaped
string  uri.ToString()           // the canonical form, readable escapes undone ("…/a b") — also $"{uri}"
string  uri.OriginalString       // exactly the text it was made from
string  uri.Authority            // "api.example.com:8443" — the port only when it is not the default
string  uri.LocalPath            // the path, unescaped
string  uri.UserInfo             // "user:password" before the host, or empty
string  uri.IdnHost              // the host in ASCII (punycode)
string  uri.DnsSafeHost          // the host without IPv6 brackets
string[] uri.Segments            // ["/", "v1/", "users"]
bool    uri.IsDefaultPort  ·  uri.IsFile  ·  uri.IsLoopback  ·  uri.IsAbsoluteUri
bool    a == b  ·  a != b  ·  a.Equals(b)     // the same address, as C# compares two Uris

string  Uri.UriSchemeHttps       // "https"
string  Uri.UriSchemeHttp        // "http"
string  Uri.UriSchemeFtp         // "ftp"
string  Uri.UriSchemeFile        // "file"
string  Uri.UriSchemeMailto      // "mailto"
string  Uri.UriSchemeWs          // "ws"
string  Uri.UriSchemeWss         // "wss"
string  Uri.SchemeDelimiter      // "://"
```

## Description    {#description}
`new Uri(url)` parses an **absolute** URL once and exposes its components as instance members — the same shape as C#'s
`System.Uri`. When the URL omits the port, `.Port` is the scheme's default (443 for `https`, 80 for `http`). `.Query`
and `.Fragment` include their leading `?` / `#`, and are empty strings when absent.

An **invalid** or **relative** URL throws C#'s `UriFormatException` (a `FormatException`), with C#'s own message —
`catch (UriFormatException e)` takes it. `new Uri` parses an absolute URI, faithful to C#; to parse a relative one,
resolve it against a base: `new Uri(baseUri, "orders/42")` follows RFC 3986 exactly as `System.Uri` does (`../`
climbs, a leading `/` replaces the path, `//host` replaces the authority, `?x` keeps the path).

**To test a text without an exception**, `Uri.TryCreate(text, UriKind.Absolute, out var u)` answers `false` (and
`u` is null) where `new Uri(text)` would throw, and `Uri.TryCreate(baseUri, relative, out var u)` does the same for a
reference resolved against a base — a null base resolves only an absolute reference, as in C#.
`Uri.IsWellFormedUriString(text, kind)` is C#'s stricter question: whether the text is already written as a URL should
be — escaped, no stray spaces or backslashes — so `"http://a b"` is not well-formed although `new Uri` accepts it.
`UriKind.Relative` asks whether it is a well-formed relative reference (`"orders/42?x=1"`).

`u.GetLeftPart(part)` is the URL up to and including a part: `UriPartial.Scheme` is `"https://"`, `Authority` the
origin (`"https://api.example.com:8443"`), `Path` adds the path and `Query` the query — never the fragment.

`UriKind.Absolute` is accepted and means what `new Uri(url)` already means. In `new Uri(text, kind)` and
`Uri.TryCreate(text, kind, out var u)`, `UriKind.Relative` and `UriKind.RelativeOrAbsolute` are refused by name: a relative `Uri` has no scheme or host to read, so the refusal points
at `new Uri(baseUri, "…")`, which is what such a value is usually for.

One difference from .NET, on purpose: every member is read when the `Uri` is MADE, so where `System.Uri` accepts a host
and only throws later from `IdnHost` (a name IDN refuses), Osy# throws the same `UriFormatException` from `new Uri`,
and `Uri.TryCreate` answers `false` for it.

Every member answers what C#'s `System.Uri` answers, because the server parses with it: dot segments are removed
(`/a/../c` is `/c`), the scheme and host are lower-cased, a default port is dropped from `AbsoluteUri`, and
`ToString()` is the readable form where `AbsoluteUri` is the escaped one (`/a b` against `/a%20b`).

**Two `Uri`s are equal when they name the same address**, which is C#'s rule and not "the same text": the scheme
and host ignore case, a default port is the same as none, and the fragment is not compared. `==`, `!=`, `Equals`,
`Distinct()` and a `Dictionary<Uri, …>` key all answer by it.

**A `Uri` runs in the browser too, and answers what the server answers.** The client carries a port of .NET's URI
rules — which characters it escapes, unescapes and keeps, IPv4 in every numeric form, IPv6, IDN hosts, user info, the
per-scheme rules (`http`, `ftp`, `ldap`, `net.tcp`, …), `mailto:`, `file:` with Windows and UNC paths — held to
`System.Uri` over well over a thousand texts on both sides. So a page action can parse, compare and resolve a URL
without a round trip, and a `Uri` used as a `Dictionary` key or `HashSet` element compares by address there as it
does on the server.

## Examples       {#examples}

Route on the host of a webhook URL, parsing an [Http.*](https://osysharp.com/reference/http/facade/) target:

```osy title="check the host before trusting a webhook URL" test app=stdlib-uri
bool IsTrustedHost(string webhookUrl) {
  var u = new Uri(webhookUrl);
  return u.Scheme == "https" && Text.EndsWith(u.Host, ".example.com");
}
```

Build a URL with an escaped query value ([Encoding — Base64, hex, text encodings, URL, HTML](https://osysharp.com/reference/stdlib/encoding/)) and read it back:

```osy title="build a URL with an escaped query value" test app=stdlib-uri
Uri Search(string term) {
  return new Uri("https://api.example.com/search?q=" + Uri.EscapeDataString(term));
}
```

Normalize a URL the way C# does before storing it:

```osy title="the canonical and the readable forms" test app=stdlib-uri-forms
/// "https://example.com/c?x=1" — scheme and host lower-cased, `..` resolved, the default port dropped.
string Canonical(string url) => new Uri(url).AbsoluteUri;

/// "/a b" — the path with its escapes undone, where AbsolutePath keeps "/a%20b".
string Readable(string url) => new Uri(url).LocalPath;

bool IsSecure(string url) => new Uri(url).Scheme == Uri.UriSchemeHttps;
```

Compare two addresses, however each was written:

```osy title="the same address, written two ways" test app=stdlib-uri-equality
/// true for "HTTPS://Example.com:443/docs#intro" and "https://example.com/docs" — one address.
bool SameAddress(string a, string b) => new Uri(a) == new Uri(b);

/// How many different addresses a list of links holds.
int DistinctAddresses(List<string> links) => links.Select(l => new Uri(l)).Distinct().Count();
```

Resolve a link found in a page against the page's own address, and refuse what is not a URL:

```osy title="resolve a relative link, refusing what is not a URL" test app=stdlib-uri-resolve
/// "https://example.com/docs/guide/next" for ("https://example.com/docs/guide/intro", "next"),
/// "https://example.com/about" for "/about", and "" for a base that is not a URL at all.
string Resolve(string pageUrl, string href) {
  try {
    return new Uri(new Uri(pageUrl), href).AbsoluteUri;
  } catch (UriFormatException) {
    return "";
  }
}
```

Take a link from user input only when it is a web address, and keep only its origin:

```osy title="accept a web address and keep its origin" test app=stdlib-uri-try
/// "https://shop.example.com" for "https://shop.example.com/cart?id=7", and "" for "not a url" or "ftp://x".
string Origin(string text) {
  if (Uri.TryCreate(text, UriKind.Absolute, out var u) && (u.Scheme == Uri.UriSchemeHttps || u.Scheme == Uri.UriSchemeHttp))
    return u.GetLeftPart(UriPartial.Authority);
  return "";
}

/// Whether a stored link is written as a URL should be — escaped, absolute or relative.
bool IsTidyLink(string link) => Uri.IsWellFormedUriString(link, UriKind.RelativeOrAbsolute);

/// The page a link points at, resolved against the page it was found on, or null.
Uri? Target(Uri page, string href) => Uri.TryCreate(page, href, out var t) ? t : null;
```

Every scheme name C# gives a constant for, and the resolving and testing forms, pinned:

```osy title="the scheme constants and the two-argument forms, pinned" run app=stdlib-uri-schemes
[Test]
void Uri_constants_and_forms_answer_what_csharp_answers() {
  Assert.Equal("http", Uri.UriSchemeHttp);
  Assert.Equal("https", Uri.UriSchemeHttps);
  Assert.Equal("ftp", Uri.UriSchemeFtp);
  Assert.Equal("file", Uri.UriSchemeFile);
  Assert.Equal("mailto", Uri.UriSchemeMailto);
  Assert.Equal("ws", Uri.UriSchemeWs);
  Assert.Equal("wss", Uri.UriSchemeWss);
  Assert.Equal("://", Uri.SchemeDelimiter);

  var page = new Uri("https://example.com/docs/guide/intro");
  Assert.Equal("https://example.com/docs/guide/next", new Uri(page, "next").AbsoluteUri);
  Assert.True(Uri.TryCreate(page, "../about", out var about));
  Assert.Equal("https://example.com/docs/about", about.AbsoluteUri);
  Assert.False(Uri.TryCreate("not a url", UriKind.Absolute, out var nothing));
  Assert.Null(nothing);
  Assert.Equal("https://example.com", page.GetLeftPart(UriPartial.Authority));
}
```

## See also       {#see-also}
- [Comparing classes](https://osysharp.com/reference/class/equality/) — how `==` compares a class, a record and a struct
- [Encoding — Base64, hex, text encodings, URL, HTML](https://osysharp.com/reference/stdlib/encoding/) — `Uri.EscapeDataString` / `UnescapeDataString` for building the URL you parse
- [Http.*](https://osysharp.com/reference/http/facade/) — the outbound HTTP surface whose URLs this parses
- [Regex](https://osysharp.com/reference/stdlib/regex/) — validate a URL string before parsing untrusted input
