C#のUriクラス完全ガイド|URL操作・相対パス・エンコードの悩みを解決

はじめに

C#でWeb APIやファイルパス、リンク先URLを扱うとき、単純にstring型でURLを連結していると、思わぬ不具合が起きることがあります。

たとえば、次のようなケースです。

C#
var url = "https://example.com/api" + "/users";

一見問題なさそうですが、ベースURLの末尾に/があるかどうか、相対パスが/から始まるかどうか、クエリ文字列に日本語やスペースが含まれるかどうかによって、期待と違うURLになることがあります。

このようなURL操作を安全に行うために用意されているのが、C#のUriクラスです。

Uriクラスを使うと、URLの解析、絶対URIと相対URIの判定、パスやクエリの取得、URL結合、エンコード処理などを扱いやすくなります。

この記事では、C#のUriクラスについて、基本的な使い方から実践的なURL操作、よくあるエラー、ベストプラクティスまで詳しく解説します。

1. C#のUriクラスとは?URL操作でできること

1-1. Uriクラスの基本的な役割

Uriクラスは、URLやURIを表現するための.NET標準クラスです。

URIとは、リソースの場所や名前を表す文字列のことです。WebサイトのURLだけでなく、ローカルファイルを表すfile://形式のパスなどもURIとして扱えます。

たとえば、次のような文字列はURIとして扱えます。

C#
https://example.com/products?id=10#detail
file:///C:/temp/sample.txt
/api/users

Uriクラスを使うと、これらの文字列からスキーム、ホスト名、パス、クエリ文字列、フラグメントなどを簡単に取得できます。

C#
var uri = new Uri("https://example.com/products?id=10#detail");

Console.WriteLine(uri.Scheme); // https
Console.WriteLine(uri.Host); // example.com
Console.WriteLine(uri.AbsolutePath); // /products
Console.WriteLine(uri.Query); // ?id=10
Console.WriteLine(uri.Fragment); // #detail

1-2. string型でURLを扱う場合との違い

URLは文字列なので、string型だけでも扱うことはできます。

C#
string url = "https://example.com/products?id=10";

しかし、string型はあくまで文字列です。URLとして正しい形式かどうか、ホスト名がどこか、クエリ文字列がどこから始まるかといった構造を理解しているわけではありません。

そのため、次のような処理をすべて自分で実装する必要があります。

C#
var url = "https://example.com/products?id=10";

var index = url.IndexOf("?");
var path = index >= 0 ? url.Substring(0, index) : url;

一方、Uriクラスを使えばURLの構造をプロパティとして取得できます。

C#
var uri = new Uri("https://example.com/products?id=10");

Console.WriteLine(uri.AbsolutePath); // /products
Console.WriteLine(uri.Query); // ?id=10

URLを単なる文字列ではなく、意味のある構造として扱える点がUriクラスの大きな特徴です。

1-3. Uriクラスを使うメリット

C#でUriクラスを使う主なメリットは次のとおりです。

C#
var uri = new Uri("https://example.com/search?q=c%23+uri");

このようにUriクラスを使うと、URL全体を安全に扱いやすくなります。

特に便利なのは、URLの各要素を分解して取得できることです。

C#
Console.WriteLine(uri.Scheme); // https
Console.WriteLine(uri.Host); // example.com
Console.WriteLine(uri.Query); // ?q=c%23+uri

また、ベースURLと相対パスを結合する処理でもUriクラスは役立ちます。

C#
var baseUri = new Uri("https://example.com/app/");
var result = new Uri(baseUri, "users/1");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/app/users/1

文字列連結ではミスが起きやすいURL操作も、Uriクラスを使うことで可読性と安全性を高められます。

1-4. Uriクラスがよく使われる場面

Uriクラスは、C#のさまざまな場面で使われます。

代表的なのは、HttpClientでAPIリクエストを送信するときです。

C#
using var client = new HttpClient();

var uri = new Uri("https://api.example.com/users");
var response = await client.GetAsync(uri);

HttpClient.BaseAddressにもUriを指定します。

C#
using var client = new HttpClient
{
BaseAddress = new Uri("https://api.example.com/")
};

var response = await client.GetAsync("users");

そのほか、ユーザーが入力したURLの検証、ローカルファイルパスのURI変換、クエリパラメータの組み立て、リダイレクト先URLの処理などでもUriクラスはよく利用されます。

2. Uriクラスの基本的な使い方

2-1. Uriインスタンスの作成方法

Uriインスタンスは、URL文字列をコンストラクタに渡して作成します。

C#
var uri = new Uri("https://example.com/");

絶対URLを扱う場合は、UriKind.Absoluteを指定すると意図が明確になります。

C#
var uri = new Uri("https://example.com/", UriKind.Absolute);

相対パスを扱う場合は、UriKind.Relativeを指定します。

C#
var uri = new Uri("/products/1", UriKind.Relative);

絶対URIか相対URIかがどちらでもよい場合は、UriKind.RelativeOrAbsoluteを使います。

C#
var uri = new Uri("products/1", UriKind.RelativeOrAbsolute);

ただし、相対URIの場合はAbsoluteUriHostなど、絶対URIでなければ取得できないプロパティを参照すると例外が発生することがあります。

2-2. 絶対URIと相対URIの違い

絶対URIとは、スキームやホスト名を含む完全なURIです。

C#
var absoluteUri = new Uri("https://example.com/products/1", UriKind.Absolute);

一方、相対URIは基準となるURLからの相対的な位置を表すURIです。

C#
var relativeUri = new Uri("products/1", UriKind.Relative);

絶対URIには、次のようにスキームやホストがあります。

C#
Console.WriteLine(absoluteUri.Scheme); // https
Console.WriteLine(absoluteUri.Host); // example.com

相対URIにはスキームやホストがありません。

C#
var relativeUri = new Uri("products/1", UriKind.Relative);

// relativeUri.Host などは使用できない

相対URIを絶対URLとして扱いたい場合は、ベースURIと結合します。

C#
var baseUri = new Uri("https://example.com/");
var relativeUri = new Uri("products/1", UriKind.Relative);

var result = new Uri(baseUri, relativeUri);

Console.WriteLine(result.AbsoluteUri);
// https://example.com/products/1

2-3. UriKind.Absolute・Relative・RelativeOrAbsoluteの使い分け

UriKindは、渡された文字列をどの種類のURIとして扱うかを指定する列挙型です。

絶対URLだけを許可したい場合は、UriKind.Absoluteを使います。

C#
var uri = new Uri("https://example.com/", UriKind.Absolute);

相対パスだけを許可したい場合は、UriKind.Relativeを使います。

C#
var uri = new Uri("images/logo.png", UriKind.Relative);

どちらの可能性もある入力を扱う場合は、UriKind.RelativeOrAbsoluteを使います。

C#
var uri = new Uri("https://example.com/", UriKind.RelativeOrAbsolute);

実務では、外部入力や設定値からURLを受け取る場合、どの種類のURIを想定しているかを明確にすることが重要です。

APIのエンドポイントやリダイレクト先など、完全なURLが必要な場面ではUriKind.Absoluteを使うべきです。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
Console.WriteLine(uri.AbsoluteUri);
}

アプリ内の相対パスを扱う場面ではUriKind.Relativeを使うと、意図しない絶対URLの混入を防ぎやすくなります。

2-4. 不正なURLで例外が発生するケース

Uriのコンストラクタに不正なURLを渡すと、UriFormatExceptionが発生することがあります。

C#
var uri = new Uri("http://[invalid-url]");

ユーザー入力をそのままnew Uri()に渡すと、入力内容によってはアプリケーションが例外で停止してしまう可能性があります。

C#
try
{
var uri = new Uri("http://[invalid-url]");
}
catch (UriFormatException ex)
{
Console.WriteLine(ex.Message);
}

特に、次のような入力には注意が必要です。

C#
""
" "
"http://"
"http://[test"
"example.com"

example.comのような文字列は、人間にとってはURLに見えても、スキームがないため絶対URIとしては扱えません。

C#
var uri = new Uri("example.com", UriKind.Absolute);
// 例外が発生する可能性がある

このようなケースでは、TryCreateを使って安全に処理するのが基本です。

2-5. TryCreateを使って安全にUriを生成する方法

Uri.TryCreateを使うと、例外を発生させずにURIを生成できます。

C#
string input = "https://example.com/";

if (Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
Console.WriteLine(uri.AbsoluteUri);
}
else
{
Console.WriteLine("URLの形式が正しくありません。");
}

ユーザー入力や外部システムから受け取った文字列を扱う場合は、new Uri()よりもTryCreateを使う方が安全です。

C#
public static bool TryGetUri(string input, out Uri? uri)
{
return Uri.TryCreate(input, UriKind.Absolute, out uri);
}

さらに、HTTPまたはHTTPSだけを許可したい場合は、スキームも確認します。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri)
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
{
Console.WriteLine("有効なHTTP/HTTPS URLです。");
}
else
{
Console.WriteLine("無効なURLです。");
}

3. Uriクラスの主要プロパティ一覧

3-1. AbsoluteUriで完全なURLを取得する

AbsoluteUriは、URI全体を文字列として取得するプロパティです。

C#
var uri = new Uri("https://example.com/products?id=10#detail");

Console.WriteLine(uri.AbsoluteUri);
// https://example.com/products?id=10#detail

AbsoluteUriは絶対URIでのみ使用できます。相対URIに対して参照すると例外が発生します。

C#
var relativeUri = new Uri("products/1", UriKind.Relative);

// Console.WriteLine(relativeUri.AbsoluteUri);
// InvalidOperationException

相対URIの文字列表現を取得したい場合は、ToString()を使います。

C#
Console.WriteLine(relativeUri.ToString());
// products/1

絶対URIかどうかを確認してからAbsoluteUriを使うと安全です。

C#
if (uri.IsAbsoluteUri)
{
Console.WriteLine(uri.AbsoluteUri);
}

3-2. Scheme・Host・Portを取得する

Schemeは、URIのスキームを取得します。

C#
var uri = new Uri("https://example.com:8443/products");

Console.WriteLine(uri.Scheme);
// https

Hostはホスト名を取得します。

C#
Console.WriteLine(uri.Host);
// example.com

Portはポート番号を取得します。

C#
Console.WriteLine(uri.Port);
// 8443

ポート番号がURLに明示されていない場合、スキームに応じた既定のポートが返されます。

C#
var uri = new Uri("https://example.com/");

Console.WriteLine(uri.Port);
// 443

ホスト名だけを取り出したい場合や、特定ドメインのみ許可したい場合にHostはよく使われます。

C#
if (uri.Host == "example.com")
{
Console.WriteLine("許可されたドメインです。");
}

3-3. PathAndQuery・AbsolutePathの違い

AbsolutePathは、URLのパス部分だけを取得します。

C#
var uri = new Uri("https://example.com/products/list?id=10");

Console.WriteLine(uri.AbsolutePath);
// /products/list

一方、PathAndQueryはパスとクエリ文字列をまとめて取得します。

C#
Console.WriteLine(uri.PathAndQuery);
// /products/list?id=10

APIリクエストやログ出力で、ホスト名を除いたリクエスト先を確認したい場合にはPathAndQueryが便利です。

C#
Console.WriteLine($"Request: {uri.PathAndQuery}");

パスだけが必要な場合はAbsolutePath、クエリ文字列も含めたい場合はPathAndQueryを使い分けます。

3-4. Queryでクエリ文字列を取得する

Queryは、URLのクエリ文字列を取得するプロパティです。

C#
var uri = new Uri("https://example.com/search?q=csharp&page=2");

Console.WriteLine(uri.Query);
// ?q=csharp&page=2

Queryには先頭の?が含まれます。

そのため、クエリ文字列を解析するときは、必要に応じて先頭の?を取り除きます。

C#
var query = uri.Query.TrimStart('?');

Console.WriteLine(query);
// q=csharp&page=2

簡単な解析であれば、&=で分割できます。

C#
var parameters = uri.Query.TrimStart('?')
.Split('&', StringSplitOptions.RemoveEmptyEntries);

foreach (var parameter in parameters)
{
var pair = parameter.Split('=', 2);
Console.WriteLine($"{pair[0]} = {pair.ElementAtOrDefault(1)}");
}

ただし、本格的にクエリパラメータを扱う場合は、エンコードや重複キーなども考慮する必要があります。

ASP.NET環境ではQueryHelpers.ParseQueryHttpUtility.ParseQueryStringなどの利用も検討できます。

3-5. Fragmentでハッシュ部分を取得する

Fragmentは、URLの#以降の部分を取得するプロパティです。

C#
var uri = new Uri("https://example.com/docs/page#section1");

Console.WriteLine(uri.Fragment);
// #section1

Fragmentにも先頭の#が含まれます。

C#
var fragment = uri.Fragment.TrimStart('#');

Console.WriteLine(fragment);
// section1

フラグメントは、ページ内リンクやSPAのルーティングなどで使われることがあります。

C#
if (!string.IsNullOrEmpty(uri.Fragment))
{
Console.WriteLine("フラグメントがあります。");
}

3-6. Segmentsでパスを分解する

Segmentsを使うと、パスをセグメントごとに分解できます。

C#
var uri = new Uri("https://example.com/products/category/10");

foreach (var segment in uri.Segments)
{
Console.WriteLine(segment);
}

出力例は次のようになります。

/
products/
category/
10

パスの階層ごとに処理したい場合に便利です。

C#
var lastSegment = uri.Segments.Last();

Console.WriteLine(lastSegment);
// 10

ただし、セグメントには末尾の/が含まれる場合があります。必要に応じてTrim('/')などで整形します。

C#
var parts = uri.Segments
.Select(s => s.Trim('/'))
.Where(s => !string.IsNullOrEmpty(s));

foreach (var part in parts)
{
Console.WriteLine(part);
}

4. C#でURLを結合する方法

4-1. ベースURLと相対パスを結合する基本構文

C#でURLを結合する場合は、文字列連結ではなくUriコンストラクタを使います。

C#
var baseUri = new Uri("https://example.com/app/");
var relativeUri = new Uri("users/1", UriKind.Relative);

var result = new Uri(baseUri, relativeUri);

Console.WriteLine(result.AbsoluteUri);
// https://example.com/app/users/1

相対URIを文字列で直接渡すこともできます。

C#
var baseUri = new Uri("https://example.com/app/");
var result = new Uri(baseUri, "users/1");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/app/users/1

この方法を使うと、/の扱いや相対パスの解決をUriクラスに任せられます。

4-2. 末尾スラッシュの有無で結果が変わる理由

URL結合で特に注意したいのが、ベースURLの末尾スラッシュです。

C#
var baseUri = new Uri("https://example.com/app/");
var result = new Uri(baseUri, "users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/app/users

末尾に/がある場合、app/はディレクトリとして扱われます。

一方、末尾に/がない場合は結果が変わります。

C#
var baseUri = new Uri("https://example.com/app");
var result = new Uri(baseUri, "users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/users

この場合、appはファイル名のように扱われ、usersによって置き換えられます。

APIのベースURLとして使う場合は、末尾に/を付けるのが基本です。

C#
var baseUri = new Uri("https://api.example.com/v1/");

4-3. ../ や ./ を含む相対パスの扱い

Uriクラスは、.././を含む相対パスも解決できます。

C#
var baseUri = new Uri("https://example.com/app/users/");
var result = new Uri(baseUri, "../products");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/app/products

./は現在の階層を表します。

C#
var baseUri = new Uri("https://example.com/app/");
var result = new Uri(baseUri, "./users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/app/users

文字列連結でこのようなパスを扱うと、../が残ったままになったり、意図しないURLになることがあります。

C#
var bad = "https://example.com/app/users/" + "../products";

Console.WriteLine(bad);
// https://example.com/app/users/../products

正規化されたURLが必要な場合は、Uriによる結合を使うと安全です。

4-4. HttpClientのBaseAddressとUriの関係

HttpClientBaseAddressにはUriを指定します。

C#
using var client = new HttpClient
{
BaseAddress = new Uri("https://api.example.com/v1/")
};

この状態で相対パスを指定すると、BaseAddressと結合されたURLにリクエストが送信されます。

C#
var response = await client.GetAsync("users/1");

実際のリクエスト先は次のようになります。

https://api.example.com/v1/users/1

ここでも、BaseAddressの末尾スラッシュは重要です。

C#
BaseAddress = new Uri("https://api.example.com/v1")

このように末尾スラッシュがない場合、相対URLとの結合結果が想定と異なることがあります。

HttpClientでAPIのベースURLを設定するときは、原則として末尾に/を付けましょう。

C#
BaseAddress = new Uri("https://api.example.com/v1/")

また、GetAsyncに渡す相対パスを/usersのようにスラッシュから始めると、ベースURLのパス部分がリセットされる点にも注意が必要です。

C#
var response = await client.GetAsync("/users/1");

この場合、v1/が消えて次のURLになる可能性があります。

https://api.example.com/users/1

4-5. URL結合でよくある失敗例と対策

URL結合でよくある失敗は、文字列連結によって/が重複したり不足したりすることです。

C#
var baseUrl = "https://example.com/api/";
var path = "/users";

var url = baseUrl + path;

Console.WriteLine(url);
// https://example.com/api//users

逆に、スラッシュが不足するケースもあります。

C#
var baseUrl = "https://example.com/api";
var path = "users";

var url = baseUrl + path;

Console.WriteLine(url);
// https://example.com/apiusers

このような問題を避けるには、Uriを使って結合します。

C#
var baseUri = new Uri("https://example.com/api/");
var result = new Uri(baseUri, "users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/api/users

ただし、相対パスを/usersのようにスラッシュから始めると、ルートからのパスとして扱われます。

C#
var baseUri = new Uri("https://example.com/api/");
var result = new Uri(baseUri, "/users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/users

ベースURLのパスを維持したい場合は、相対パスの先頭に/を付けないようにします。

5. UriBuilderを使ったURLの組み立て

5-1. UriBuilderとは

UriBuilderは、URIを構成する要素を個別に設定しながらURLを組み立てるためのクラスです。

Uriクラスは作成後に内容を変更できないイミュータブルなオブジェクトですが、UriBuilderSchemeHostPathQueryなどを変更しながらURLを作成できます。

C#
var builder = new UriBuilder();

builder.Scheme = "https";
builder.Host = "example.com";
builder.Path = "products";

var uri = builder.Uri;

Console.WriteLine(uri.AbsoluteUri);
// https://example.com/products

動的にURLを組み立てたい場合は、UriBuilderを使うと見通しがよくなります。

5-2. Scheme・Host・Path・Queryを個別に設定する方法

UriBuilderでは、URLの各要素をプロパティとして設定できます。

C#
var builder = new UriBuilder
{
Scheme = "https",
Host = "example.com",
Path = "search",
Query = "q=csharp&page=1"
};

var uri = builder.Uri;

Console.WriteLine(uri.AbsoluteUri);
// https://example.com/search?q=csharp&page=1

ポート番号を指定することもできます。

C#
var builder = new UriBuilder
{
Scheme = "https",
Host = "example.com",
Port = 8443,
Path = "api/users"
};

Console.WriteLine(builder.Uri.AbsoluteUri);
// https://example.com:8443/api/users

既定ポートを使いたい場合は、ポートを明示しないか、必要に応じて-1を設定します。

C#
builder.Port = -1;

5-3. クエリパラメータを含むURLを作成する方法

UriBuilder.Queryには、クエリ文字列を設定できます。

C#
var builder = new UriBuilder("https://example.com/search")
{
Query = "q=csharp&page=1"
};

Console.WriteLine(builder.Uri.AbsoluteUri);
// https://example.com/search?q=csharp&page=1

ただし、クエリパラメータの値に日本語、スペース、#&などが含まれる場合は、必ずエンコードしましょう。

C#
var keyword = "c# uri 入門";

var builder = new UriBuilder("https://example.com/search")
{
Query = "q=" + Uri.EscapeDataString(keyword)
};

Console.WriteLine(builder.Uri.AbsoluteUri);
// https://example.com/search?q=c%23%20uri%20%E5%85%A5%E9%96%80

複数のクエリパラメータを扱う場合は、値ごとにエンコードしてから結合します。

C#
var keyword = "c# uri";
var page = "1";

var query = $"q={Uri.EscapeDataString(keyword)}&page={Uri.EscapeDataString(page)}";

var builder = new UriBuilder("https://example.com/search")
{
Query = query
};

Console.WriteLine(builder.Uri.AbsoluteUri);

ASP.NETを使っている場合は、QueryHelpers.AddQueryStringを使う方法も便利です。

C#
using Microsoft.AspNetCore.WebUtilities;

var url = QueryHelpers.AddQueryString(
"https://example.com/search",
new Dictionary<string, string?>
{
["q"] = "c# uri",
["page"] = "1"
});

5-4. UriBuilderとUriクラスの使い分け

Uriクラスは、すでに存在するURLを解析したり、ベースURLと相対パスを結合したりする場合に向いています。

C#
var uri = new Uri("https://example.com/products?id=10");

Console.WriteLine(uri.Host);
Console.WriteLine(uri.Query);

一方、UriBuilderはURLを部品から組み立てる場合に向いています。

C#
var builder = new UriBuilder
{
Scheme = "https",
Host = "api.example.com",
Path = "v1/users",
Query = "page=1"
};

使い分けの目安は次のとおりです。

既存のURLを読み取るならUri、URLを動的に作るならUriBuilderを使うと考えるとわかりやすいです。

C#
// 解析
var uri = new Uri("https://example.com/path?x=1");

// 組み立て
var builder = new UriBuilder("https://example.com")
{
Path = "path",
Query = "x=1"
};

5-5. 動的にURLを生成する際の注意点

動的にURLを生成するときは、ユーザー入力をそのままURLに埋め込まないようにしましょう。

C#
var keyword = "c# uri";
var url = "https://example.com/search?q=" + keyword;

このようにすると、#やスペースなどが含まれた場合にURLとして正しく扱えない可能性があります。

安全に組み立てるには、値をエンコードします。

C#
var keyword = "c# uri";
var encodedKeyword = Uri.EscapeDataString(keyword);

var url = "https://example.com/search?q=" + encodedKeyword;

パス部分にユーザー入力を入れる場合も注意が必要です。

C#
var userName = "山田 太郎";
var path = "users/" + Uri.EscapeDataString(userName);

var builder = new UriBuilder("https://example.com")
{
Path = path
};

Console.WriteLine(builder.Uri.AbsoluteUri);

URL全体を作るときは、パス、クエリ、フラグメントのどこに値を入れるのかを明確にし、それぞれに適したエンコードを行うことが重要です。

6. C#でURLエンコード・デコードする方法

6-1. URLエンコードが必要になる場面

URLには、そのまま使うと意味が変わってしまう文字があります。

たとえば、#はフラグメントの開始、?はクエリ文字列の開始、&はクエリパラメータの区切りとして扱われます。

C#
var keyword = "c# uri";
var url = "https://example.com/search?q=" + keyword;

Console.WriteLine(url);
// https://example.com/search?q=c# uri

このURLでは、#以降がフラグメントとして解釈される可能性があります。

そのため、クエリパラメータの値としてc# uriを渡したい場合は、値をURLエンコードする必要があります。

C#
var keyword = "c# uri";
var encoded = Uri.EscapeDataString(keyword);

Console.WriteLine(encoded);
// c%23%20uri

6-2. Uri.EscapeDataStringの使い方

Uri.EscapeDataStringは、URLの一部として使うデータ文字列をエンコードするメソッドです。

C#
var value = "c# uri 入門";
var encoded = Uri.EscapeDataString(value);

Console.WriteLine(encoded);
// c%23%20uri%20%E5%85%A5%E9%96%80

クエリパラメータの値をエンコードする場合によく使います。

C#
var keyword = "c# uri 入門";

var url = "https://example.com/search?q=" + Uri.EscapeDataString(keyword);

Console.WriteLine(url);

複数の値を扱う場合も、値ごとにEscapeDataStringを適用します。

C#
var keyword = "c# uri";
var category = "C# 入門";

var query =
"q=" + Uri.EscapeDataString(keyword) +
"&category=" + Uri.EscapeDataString(category);

var url = "https://example.com/search?" + query;

6-3. Uri.UnescapeDataStringの使い方

Uri.UnescapeDataStringは、エンコードされた文字列をデコードするメソッドです。

C#
var encoded = "c%23%20uri%20%E5%85%A5%E9%96%80";
var decoded = Uri.UnescapeDataString(encoded);

Console.WriteLine(decoded);
// c# uri 入門

クエリ文字列から取得した値を人間が読める形に戻したい場合に使えます。

C#
var uri = new Uri("https://example.com/search?q=c%23%20uri");

var query = uri.Query.TrimStart('?');
var value = query.Split('=')[1];

Console.WriteLine(Uri.UnescapeDataString(value));
// c# uri

ただし、実際のクエリ解析では、+をスペースとして扱うケースや、複数の同名パラメータがあるケースも考慮する必要があります。

単純なSplitだけで処理すると不十分な場合があるため、必要に応じて専用のクエリ解析機能を使いましょう。

6-4. EscapeUriStringとの違いと注意点

Uri.EscapeUriStringは、URI全体をエスケープするためのメソッドとして使われてきました。

しかし、クエリパラメータの値をエンコードする目的では、EscapeDataStringを使うのが基本です。

C#
var value = "c# uri";

Console.WriteLine(Uri.EscapeDataString(value));
// c%23%20uri

Console.WriteLine(Uri.EscapeUriString(value));
// c#%20uri

EscapeUriStringでは、URIの区切り文字として意味を持つ文字が残ることがあります。

クエリ値として#を含めたい場合、#がそのまま残るとフラグメントとして解釈される可能性があります。

C#
var keyword = "c# uri";
var badUrl = "https://example.com/search?q=" + Uri.EscapeUriString(keyword);

Console.WriteLine(badUrl);
// https://example.com/search?q=c#%20uri

この場合、#以降がクエリ値ではなくフラグメントとして扱われるおそれがあります。

クエリパラメータやパスセグメントなど、URLの一部に埋め込む値はEscapeDataStringでエンコードしましょう。

6-5. クエリパラメータを安全にエンコードする方法

クエリパラメータを安全に作るには、キーと値をそれぞれエンコードします。

C#
var parameters = new Dictionary<string, string>
{
["q"] = "c# uri",
["category"] = "C# 入門"
};

var query = string.Join("&", parameters.Select(p =>
$"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));

var url = "https://example.com/search?" + query;

Console.WriteLine(url);

出力例は次のようになります。

https://example.com/search?q=c%23%20uri&category=C%23%20%E5%85%A5%E9%96%80

クエリ文字列を手作業で作る場合は、次の点に注意しましょう。

C#
// 悪い例
var url = $"https://example.com/search?q={keyword}";

// 良い例
var url = $"https://example.com/search?q={Uri.EscapeDataString(keyword)}";

ユーザー入力をURLに含める場合、エンコードは必須と考えるべきです。

6-6. 日本語URLやスペースを扱うときの注意点

日本語やスペースを含むURLを扱う場合も、Uriクラスは有効です。

C#
var keyword = "C# Uri 入門";
var encoded = Uri.EscapeDataString(keyword);

Console.WriteLine(encoded);
// C%23%20Uri%20%E5%85%A5%E9%96%80

スペースは%20としてエンコードされます。

C#
var text = "hello world";
Console.WriteLine(Uri.EscapeDataString(text));
// hello%20world

フォーム送信や一部のクエリ文字列では、スペースが+として表現されることもあります。

そのため、外部サービスの仕様によっては%20+の扱いを確認する必要があります。

日本語を含むパスを作る場合も、セグメント単位でエンコードするのが安全です。

C#
var category = "プログラミング";
var path = "category/" + Uri.EscapeDataString(category);

var uri = new Uri("https://example.com/" + path);

Console.WriteLine(uri.AbsoluteUri);

7. ファイルパスとUriの扱い方

7-1. ローカルファイルパスをUriに変換する方法

ローカルファイルパスをURIとして扱いたい場合は、Uriクラスを使ってfileスキームのURIに変換できます。

C#
var path = @"C:\temp\sample.txt";
var uri = new Uri(path);

Console.WriteLine(uri.AbsoluteUri);
// file:///C:/temp/sample.txt

Windowsのパスはバックスラッシュ\を使いますが、URIではスラッシュ/が使われます。

C#
Console.WriteLine(uri.LocalPath);
// C:\temp\sample.txt

ファイルURIからローカルパスを取り出す場合は、LocalPathを使います。

C#
var fileUri = new Uri("file:///C:/temp/sample.txt");

Console.WriteLine(fileUri.LocalPath);
// C:\temp\sample.txt

7-2. fileスキームのUriを扱う方法

fileスキームは、ローカルファイルやネットワーク上のファイルを表すURIで使われます。

C#
var uri = new Uri("file:///C:/temp/sample.txt");

Console.WriteLine(uri.Scheme);
// file

Console.WriteLine(uri.IsFile);
// True

IsFileを使うと、そのURIがファイルを表しているかどうかを判定できます。

C#
if (uri.IsFile)
{
Console.WriteLine(uri.LocalPath);
}

ファイルパスとして扱う場合は、AbsoluteUriではなくLocalPathを使うのが自然です。

C#
var path = uri.LocalPath;

AbsoluteUriはURI形式の文字列を返すため、Windows APIやファイル操作にそのまま渡すと期待どおりに動かないことがあります。

C#
File.ReadAllText(uri.LocalPath);

7-3. WindowsパスとURLパスの違い

WindowsパスとURLパスは似ていますが、区切り文字やルールが異なります。

Windowsパスでは、区切りに\を使います。

C:\temp\sample.txt

URLパスでは、区切りに/を使います。

file:///C:/temp/sample.txt

C#の文字列リテラルでは、\はエスケープ文字として扱われるため、Windowsパスを書くときは注意が必要です。

C#
var path1 = "C:\\temp\\sample.txt";
var path2 = @"C:\temp\sample.txt";

URIとして扱う場合は、Uriクラスに変換します。

C#
var uri = new Uri(@"C:\temp\sample.txt");

ファイルシステム上のパス操作にはPathクラス、URL操作にはUriクラスを使うのが基本です。

7-4. Path.CombineとUri結合を混同しないための注意点

Path.Combineはファイルパスを結合するためのメソッドです。

C#
var path = Path.Combine(@"C:\temp", "sample.txt");

Console.WriteLine(path);
// C:\temp\sample.txt

URLの結合にPath.Combineを使うのは避けましょう。

C#
var url = Path.Combine("https://example.com/api", "users");

Console.WriteLine(url);
// 環境によっては https:\example.com\api\users のようになる可能性がある

URLを結合する場合はUriを使います。

C#
var baseUri = new Uri("https://example.com/api/");
var uri = new Uri(baseUri, "users");

Console.WriteLine(uri.AbsoluteUri);
// https://example.com/api/users

逆に、ローカルファイルパスを結合する場合はUriではなくPath.Combineを使います。

C#
var filePath = Path.Combine(@"C:\temp", "sample.txt");

URLとファイルパスは似ていても別物として考えることが重要です。

7-5. ファイルパス変換でよくあるエラー

ファイルパスをURIに変換するときによくあるのが、相対パスの扱いです。

C#
var uri = new Uri("data/sample.txt", UriKind.Relative);

このURIは相対URIなので、AbsoluteUriを参照できません。

C#
// Console.WriteLine(uri.AbsoluteUri);
// InvalidOperationException

絶対ファイルURIが必要な場合は、フルパスに変換してからUriを作成します。

C#
var fullPath = Path.GetFullPath("data/sample.txt");
var uri = new Uri(fullPath);

Console.WriteLine(uri.AbsoluteUri);

ファイルURIをファイルパスとして使う場合は、LocalPathを使います。

C#
File.ReadAllText(uri.LocalPath);

file:///C:/temp/sample.txtをそのままFile.ReadAllTextに渡すのではなく、ローカルパスに変換してから渡すと安全です。

8. UriクラスでURLを検証する方法

8-1. IsWellFormedUriStringの使い方

Uri.IsWellFormedUriStringを使うと、指定した文字列がURIとして整形式かどうかを判定できます。

C#
var input = "https://example.com/";

var isValid = Uri.IsWellFormedUriString(input, UriKind.Absolute);

Console.WriteLine(isValid);
// True

相対URIとして検証することもできます。

C#
var input = "products/1";

var isValid = Uri.IsWellFormedUriString(input, UriKind.Relative);

Console.WriteLine(isValid);
// True

ただし、IsWellFormedUriStringだけでアプリケーションにとって安全なURLかどうかを完全に判断できるわけではありません。

たとえば、形式としては正しくても、許可していないスキームやドメインである可能性があります。

C#
var input = "ftp://example.com/file.txt";

HTTP/HTTPSだけを許可したい場合は、スキームの確認も必要です。

8-2. 絶対URLかどうかを判定する方法

絶対URLかどうかを判定するには、Uri.TryCreateIsAbsoluteUriを使います。

C#
var input = "https://example.com/";

if (Uri.TryCreate(input, UriKind.Absolute, out var uri) && uri.IsAbsoluteUri)
{
Console.WriteLine("絶対URLです。");
}

UriKind.Absoluteを指定しているため、相対URIは許可されません。

C#
var input = "/products/1";

if (!Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
Console.WriteLine("絶対URLではありません。");
}

URL入力フォームや外部リンクの検証では、相対パスではなく絶対URLを求めるケースが多いため、UriKind.Absoluteを指定するとよいでしょう。

8-3. http・httpsのみ許可する方法

WebページやAPIのURLとして扱う場合、httphttpsだけを許可したいことが多いです。

C#
public static bool IsHttpOrHttpsUrl(string? input)
{
return Uri.TryCreate(input, UriKind.Absolute, out var uri)
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
}

使用例は次のとおりです。

C#
Console.WriteLine(IsHttpOrHttpsUrl("https://example.com/")); // True
Console.WriteLine(IsHttpOrHttpsUrl("http://example.com/")); // True
Console.WriteLine(IsHttpOrHttpsUrl("ftp://example.com/")); // False
Console.WriteLine(IsHttpOrHttpsUrl("/products/1")); // False

セキュリティ上、ユーザー入力をリダイレクト先や外部リンクとして使う場合は、スキームの確認が重要です。

必要に応じて、許可するホスト名もチェックします。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri)
&& uri.Scheme == Uri.UriSchemeHttps
&& uri.Host == "example.com")
{
Console.WriteLine("許可されたURLです。");
}

8-4. null・空文字・不正形式を安全に処理する方法

ユーザー入力では、nullや空文字が渡される可能性があります。

C#
public static bool TryValidateUrl(string? input, out Uri? uri)
{
uri = null;

if (string.IsNullOrWhiteSpace(input))
{
return false;
}

return Uri.TryCreate(input, UriKind.Absolute, out uri)
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
}

このように、最初にnullや空白を除外してからTryCreateを使うと安全です。

C#
if (TryValidateUrl(userInput, out var uri))
{
Console.WriteLine(uri.AbsoluteUri);
}
else
{
Console.WriteLine("URLが正しくありません。");
}

例外処理に頼るよりも、通常の入力検証ではTryCreateを使った実装の方が扱いやすいです。

8-5. 入力されたURLを検証するときの実装例

実際の入力URL検証では、形式、スキーム、ホスト名などを確認します。

C#
public static bool IsAllowedUrl(string? input)
{
if (string.IsNullOrWhiteSpace(input))
{
return false;
}

if (!Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
return false;
}

if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
{
return false;
}

if (uri.Host != "example.com")
{
return false;
}

return true;
}

使用例は次のとおりです。

C#
Console.WriteLine(IsAllowedUrl("https://example.com/products")); // True
Console.WriteLine(IsAllowedUrl("https://evil.example.net/")); // False
Console.WriteLine(IsAllowedUrl("javascript:alert(1)")); // False
Console.WriteLine(IsAllowedUrl("")); // False

特にリダイレクトURLや外部リンクを扱う場合は、単にURL形式かどうかを見るだけでは不十分です。

アプリケーションとして許可するURLの条件を明確にし、Uriクラスで構造的に検証しましょう。

9. Uriクラスでよくあるエラーと原因

9-1. Invalid URI: The format of the URI could not be determined

このエラーは、URIの形式を判断できない場合に発生します。

たとえば、絶対URIを期待している場面でスキームのない文字列を渡すと発生することがあります。

C#
var uri = new Uri("example.com", UriKind.Absolute);

example.comはURLのように見えますが、https://がないため絶対URIではありません。

正しくは次のようにします。

C#
var uri = new Uri("https://example.com", UriKind.Absolute);

ユーザー入力でこのエラーを避けたい場合は、TryCreateを使います。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
Console.WriteLine(uri.AbsoluteUri);
}
else
{
Console.WriteLine("URLの形式が正しくありません。");
}

9-2. 相対URIでAbsoluteUriを参照してしまうエラー

相対URIに対してAbsoluteUriを参照すると、例外が発生します。

C#
var uri = new Uri("products/1", UriKind.Relative);

Console.WriteLine(uri.AbsoluteUri);

相対URIは完全なURLではないため、AbsoluteUriを取得できません。

対策として、IsAbsoluteUriを確認します。

C#
if (uri.IsAbsoluteUri)
{
Console.WriteLine(uri.AbsoluteUri);
}
else
{
Console.WriteLine(uri.ToString());
}

相対URIを絶対URLにしたい場合は、ベースURIと結合します。

C#
var baseUri = new Uri("https://example.com/");
var relativeUri = new Uri("products/1", UriKind.Relative);

var absoluteUri = new Uri(baseUri, relativeUri);

Console.WriteLine(absoluteUri.AbsoluteUri);

9-3. URL結合結果が想定と違う原因

URL結合結果が想定と違う場合、多くはベースURLの末尾スラッシュや相対パスの先頭スラッシュが原因です。

C#
var baseUri = new Uri("https://example.com/api");
var result = new Uri(baseUri, "users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/users

apiをディレクトリとして扱いたい場合は、末尾に/を付けます。

C#
var baseUri = new Uri("https://example.com/api/");
var result = new Uri(baseUri, "users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/api/users

また、相対パスが/から始まると、ルートからのパスとして扱われます。

C#
var baseUri = new Uri("https://example.com/api/");
var result = new Uri(baseUri, "/users");

Console.WriteLine(result.AbsoluteUri);
// https://example.com/users

ベースURLのパスを維持したい場合は、相対パスをusersのように指定します。

9-4. エンコード済み文字列を二重エンコードしてしまう問題

URLエンコードでよくある失敗が、すでにエンコード済みの文字列をもう一度エンコードしてしまうことです。

C#
var encoded = "c%23%20uri";
var doubleEncoded = Uri.EscapeDataString(encoded);

Console.WriteLine(doubleEncoded);
// c%2523%2520uri

%%25に変換されるため、結果が変わってしまいます。

二重エンコードを避けるには、値が未エンコードの状態なのか、すでにエンコード済みなのかを明確にすることが重要です。

C#
// 未エンコードの値
var rawValue = "c# uri";

// URLに入れる直前でエンコード
var encodedValue = Uri.EscapeDataString(rawValue);

アプリケーション内では、できるだけ未エンコードの値を保持し、URLに組み込む直前でエンコードする設計にするとミスを減らせます。

9-5. 日本語・空白・特殊文字を含むURLで失敗するケース

日本語、空白、#&?などを含む文字列をURLに直接埋め込むと、意図しない解釈をされることがあります。

C#
var keyword = "C# Uri 入門";
var url = "https://example.com/search?q=" + keyword;

Console.WriteLine(url);
// https://example.com/search?q=C# Uri 入門

この場合、#がフラグメントとして扱われる可能性があります。

正しくは、クエリ値をエンコードします。

C#
var keyword = "C# Uri 入門";
var url = "https://example.com/search?q=" + Uri.EscapeDataString(keyword);

Console.WriteLine(url);

パスに日本語を含める場合も、セグメント単位でエンコードします。

C#
var title = "C# Uri 入門";
var path = "articles/" + Uri.EscapeDataString(title);

var uri = new Uri("https://example.com/" + path);

Console.WriteLine(uri.AbsoluteUri);

特殊文字を含む値をURLに埋め込むときは、必ず「URLのどの部分に入れる値なのか」を意識しましょう。

10. 実践サンプルで学ぶUriクラスの使い方

10-1. URLからドメイン名を取得する

URLからドメイン名を取得するには、Hostプロパティを使います。

C#
var uri = new Uri("https://www.example.com/products?id=10");

Console.WriteLine(uri.Host);
// www.example.com

入力値が不正な可能性がある場合は、TryCreateを使います。

C#
public static string? GetHost(string input)
{
if (Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
return uri.Host;
}

return null;
}

HTTP/HTTPSだけを対象にする場合は、スキームも確認します。

C#
public static string? GetHttpHost(string input)
{
if (!Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
return null;
}

if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
{
return null;
}

return uri.Host;
}

10-2. URLからパスだけを取得する

URLからパスだけを取得するには、AbsolutePathを使います。

C#
var uri = new Uri("https://example.com/products/list?id=10");

Console.WriteLine(uri.AbsolutePath);
// /products/list

クエリ文字列も含めたい場合は、PathAndQueryを使います。

C#
Console.WriteLine(uri.PathAndQuery);
// /products/list?id=10

ルーティングやログ出力では、どちらを使うべきかを明確にしましょう。

C#
var path = uri.AbsolutePath;
var pathAndQuery = uri.PathAndQuery;

10-3. クエリ文字列を取得・解析する

Uri.Queryでクエリ文字列を取得できます。

C#
var uri = new Uri("https://example.com/search?q=c%23%20uri&page=2");

Console.WriteLine(uri.Query);
// ?q=c%23%20uri&page=2

簡易的に解析する例は次のとおりです。

C#
public static Dictionary<string, string> ParseQuery(Uri uri)
{
var result = new Dictionary<string, string>();

var query = uri.Query.TrimStart('?');

foreach (var part in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
{
var pair = part.Split('=', 2);

var key = Uri.UnescapeDataString(pair[0]);
var value = pair.Length > 1 ? Uri.UnescapeDataString(pair[1]) : "";

result[key] = value;
}

return result;
}

使用例です。

C#
var uri = new Uri("https://example.com/search?q=c%23%20uri&page=2");
var query = ParseQuery(uri);

Console.WriteLine(query["q"]);
// c# uri

ただし、実務では+の扱いや重複パラメータなどもあるため、必要に応じてライブラリやフレームワークの機能を使うのがおすすめです。

10-4. 相対パスを絶対URLに変換する

相対パスを絶対URLに変換するには、ベースURIと相対URIを結合します。

C#
var baseUri = new Uri("https://example.com/app/");
var relativePath = "products/1";

var absoluteUri = new Uri(baseUri, relativePath);

Console.WriteLine(absoluteUri.AbsoluteUri);
// https://example.com/app/products/1

../を含む相対パスも解決できます。

C#
var baseUri = new Uri("https://example.com/app/users/");
var absoluteUri = new Uri(baseUri, "../products/1");

Console.WriteLine(absoluteUri.AbsoluteUri);
// https://example.com/app/products/1

WebスクレイピングやHTML内リンクの解決、APIパスの組み立てなどでよく使うパターンです。

10-5. APIリクエスト用のURLを組み立てる

APIリクエスト用のURLを組み立てる場合は、ベースURL、パス、クエリパラメータを分けて考えると安全です。

C#
var baseUri = new Uri("https://api.example.com/v1/");
var endpoint = "users";

var uri = new Uri(baseUri, endpoint);

Console.WriteLine(uri.AbsoluteUri);
// https://api.example.com/v1/users

クエリパラメータを付ける場合は、UriBuilderを使います。

C#
var baseUri = new Uri("https://api.example.com/v1/");
var endpointUri = new Uri(baseUri, "users");

var builder = new UriBuilder(endpointUri)
{
Query = "page=1&keyword=" + Uri.EscapeDataString("c# uri")
};

var requestUri = builder.Uri;

Console.WriteLine(requestUri.AbsoluteUri);

HttpClientと組み合わせる場合は、次のように書けます。

C#
using var client = new HttpClient
{
BaseAddress = new Uri("https://api.example.com/v1/")
};

var keyword = Uri.EscapeDataString("c# uri");
var response = await client.GetAsync($"users?keyword={keyword}");

BaseAddressを使う場合も、末尾スラッシュと相対パスの先頭スラッシュには注意しましょう。

10-6. 入力URLを検証して安全に処理する

ユーザーが入力したURLを安全に処理するサンプルです。

C#
public static Uri? NormalizeHttpUrl(string? input)
{
if (string.IsNullOrWhiteSpace(input))
{
return null;
}

if (!Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
return null;
}

if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
{
return null;
}

return uri;
}

使用例です。

C#
var input = "https://example.com/products?id=10";

var uri = NormalizeHttpUrl(input);

if (uri != null)
{
Console.WriteLine(uri.AbsoluteUri);
}
else
{
Console.WriteLine("有効なURLではありません。");
}

必要に応じて、許可するドメインも制限できます。

C#
public static bool IsTrustedUrl(string? input)
{
var uri = NormalizeHttpUrl(input);

if (uri == null)
{
return false;
}

return uri.Host == "example.com" || uri.Host.EndsWith(".example.com");
}

URL検証では、形式だけでなく、アプリケーションとして許可してよいURLかどうかを確認することが重要です。

11. Uriクラスを使うときのベストプラクティス

11-1. 文字列連結でURLを作らない

C#でURLを扱うときは、できるだけ文字列連結でURLを作らないようにしましょう。

C#
// 避けたい例
var url = baseUrl + "/" + path + "?q=" + keyword;

この方法では、スラッシュの重複や不足、クエリ値の未エンコードなどが起きやすくなります。

UriUriBuilderを使うと、URLの構造を意識した実装にできます。

C#
var baseUri = new Uri("https://example.com/api/");
var uri = new Uri(baseUri, "users");

クエリ値を含める場合は、必ずエンコードします。

C#
var builder = new UriBuilder(uri)
{
Query = "q=" + Uri.EscapeDataString(keyword)
};

11-2. 相対URIと絶対URIを明確に分ける

Uriクラスを使うときは、絶対URIと相対URIを明確に分けることが重要です。

C#
var absoluteUri = new Uri("https://example.com/", UriKind.Absolute);
var relativeUri = new Uri("products/1", UriKind.Relative);

絶対URLが必要な場所では、UriKind.Absoluteを指定します。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
Console.WriteLine(uri.AbsoluteUri);
}

相対パスを受け付けたい場合は、相対URIとして扱い、必要に応じてベースURIと結合します。

C#
var baseUri = new Uri("https://example.com/");
var relativeUri = new Uri("products/1", UriKind.Relative);

var result = new Uri(baseUri, relativeUri);

相対URIに対してAbsoluteUriHostを参照しないように、IsAbsoluteUriで確認する習慣を持ちましょう。

11-3. クエリ値は必ずエンコードする

クエリパラメータに値を入れるときは、必ずUri.EscapeDataStringでエンコードしましょう。

C#
var keyword = "c# uri";
var url = "https://example.com/search?q=" + Uri.EscapeDataString(keyword);

特に、ユーザー入力をそのままURLに含めるのは避けるべきです。

C#
// 避けたい例
var url = "https://example.com/search?q=" + userInput;

値に&が含まれていると、別のパラメータとして解釈される可能性があります。

C#
var input = "c# uri & url";
var encoded = Uri.EscapeDataString(input);

URL全体ではなく、パラメータのキーや値など、URLに埋め込むデータ単位でエンコードするのがポイントです。

11-4. URL検証では例外処理とTryCreateを活用する

ユーザー入力や外部データからURLを作る場合は、new Uri()で直接生成するよりもTryCreateを使うのがおすすめです。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri))
{
Console.WriteLine(uri.AbsoluteUri);
}
else
{
Console.WriteLine("無効なURLです。");
}

TryCreateを使えば、不正な入力に対して例外を発生させずに処理できます。

さらに、スキームやホスト名も確認しましょう。

C#
if (Uri.TryCreate(input, UriKind.Absolute, out var uri)
&& uri.Scheme == Uri.UriSchemeHttps
&& uri.Host == "example.com")
{
Console.WriteLine("許可されたURLです。");
}

URL検証では、「URIとして正しいか」と「アプリケーションとして許可してよいか」を分けて考えることが大切です。

11-5. 用途に応じてUri・UriBuilder・HttpUtilityを使い分ける

C#でURLを扱う場合、すべてをUriだけで処理しようとする必要はありません。

既存のURLを解析するならUriが便利です。

C#
var uri = new Uri("https://example.com/search?q=csharp");

Console.WriteLine(uri.Host);
Console.WriteLine(uri.Query);

URLを組み立てるならUriBuilderが向いています。

C#
var builder = new UriBuilder
{
Scheme = "https",
Host = "example.com",
Path = "search",
Query = "q=csharp"
};

ASP.NETでクエリ文字列を扱う場合は、HttpUtility.ParseQueryStringQueryHelpersなども選択肢になります。

C#
using System.Web;

var query = HttpUtility.ParseQueryString("");
query["q"] = "c# uri";
query["page"] = "1";

var builder = new UriBuilder("https://example.com/search")
{
Query = query.ToString()
};

環境や用途によって、適切なクラスやメソッドを選ぶことが、保守しやすいURL処理につながります。

まとめ

C#のUriクラスは、URLやURIを安全に扱うための基本クラスです。

string型だけでURLを操作すると、スラッシュの扱い、相対パスの解決、クエリ文字列のエンコード、日本語や特殊文字の処理などでミスが起きやすくなります。

Uriクラスを使えば、URLを構造として扱えるため、スキーム、ホスト、パス、クエリ、フラグメントなどを簡単に取得できます。

C#
var uri = new Uri("https://example.com/products?id=10#detail");

Console.WriteLine(uri.Scheme);
Console.WriteLine(uri.Host);
Console.WriteLine(uri.AbsolutePath);
Console.WriteLine(uri.Query);
Console.WriteLine(uri.Fragment);

ベースURLと相対パスを結合する場合も、文字列連結ではなくUriを使うのが基本です。

C#
var baseUri = new Uri("https://example.com/api/");
var result = new Uri(baseUri, "users");

Console.WriteLine(result.AbsoluteUri);

動的にURLを組み立てる場合はUriBuilder、クエリパラメータを扱う場合はUri.EscapeDataStringや専用のクエリ解析機能を活用しましょう。

また、ユーザー入力をURLとして扱う場合は、Uri.TryCreateで安全に検証し、必要に応じてhttphttpsだけを許可するなど、アプリケーション側の条件も確認することが重要です。

C#でURL操作を行うなら、UriUriBuilder、エンコード処理の役割を理解して使い分けることで、バグが少なく保守しやすい実装になります。