URL encoding in C#
.NET has had multiple URL encoding APIs over the years, and the framework still ships them all. Some are RFC 3986 compliant, some aren’t. Some live in System.Web, some in core. Picking the right one for new code matters because the wrong choice produces URLs that work with .NET endpoints but break with cross-platform APIs.
This article covers what each API does, which to use today, and a few common patterns.
For new code: Uri.EscapeDataString
The modern, RFC 3986 compliant approach:
using System;
string encoded = Uri.EscapeDataString("Hello, World!");
// "Hello%2C%20World%21"
string decoded = Uri.UnescapeDataString("Hello%2C%20World%21");
// "Hello, World!"
EscapeDataString encodes everything except unreserved characters (A-Z a-z 0-9 - _ . ~). Space becomes %20. This is what you want for query values, path segments, fragments — anywhere you’re inserting data into a URL.
The legacy HttpUtility.UrlEncode
From the .NET Framework days (and still working in .NET Core/.NET 5+):
using System.Web;
string encoded = HttpUtility.UrlEncode("Hello, World!");
// "Hello%2c+World!"
Three things to notice:
- Space became
+(form-encoded convention) - The hex digit is lowercase (
%2cinstead of%2C) - The exclamation mark wasn’t encoded (RFC 3986 says encode it)
HttpUtility.UrlEncode is the legacy ASP.NET URL encoding function. Use it only when you specifically need the form-encoded format (for matching legacy ASP.NET endpoints, or building application/x-www-form-urlencoded POST bodies).
WebUtility.UrlEncode (a middle ground)
From System.Net (available without referencing System.Web):
using System.Net;
string encoded = WebUtility.UrlEncode("Hello, World!");
// "Hello%2C+World%21"
Like HttpUtility.UrlEncode, this produces form-encoded output (+ for space), but with uppercase hex and RFC-3986-compliant character coverage. It’s the modern replacement for the System.Web version.
The three options side-by-side
| API | Space | Hex case | Style |
|---|---|---|---|
Uri.EscapeDataString | %20 | UPPER | RFC 3986 |
WebUtility.UrlEncode | + | UPPER | Form-encoded |
HttpUtility.UrlEncode | + | lower | Legacy form |
Building query strings
For modern code, manually compose with EscapeDataString or use QueryHelpers from Microsoft.AspNetCore.WebUtilities:
// Manual approach
string url = $"https://api.example.com/search" +
$"?q={Uri.EscapeDataString(query)}" +
$"&lang={Uri.EscapeDataString(lang)}";
// ASP.NET Core approach
using Microsoft.AspNetCore.WebUtilities;
string url = QueryHelpers.AddQueryString(
"https://api.example.com/search",
new Dictionary<string, string> {
["q"] = query,
["lang"] = lang
});
QueryHelpers.AddQueryString is the modern idiomatic way in ASP.NET Core apps.
HttpClient automatic encoding
HttpClient does NOT automatically encode query parameters. You must encode values before composing the URI:
using var client = new HttpClient();
// Wrong — special characters break the URL
var response = await client.GetAsync($"https://api.example.com/search?q={userInput}");
// Right — explicitly encode
var response = await client.GetAsync(
$"https://api.example.com/search?q={Uri.EscapeDataString(userInput)}");
For POST with form data, FormUrlEncodedContent handles encoding for you:
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("name", "John Doe"),
new KeyValuePair<string, string>("message", "Hello, World!")
});
var response = await client.PostAsync(url, content);
// Body: name=John+Doe&message=Hello%2C+World%21
UriBuilder for building URLs from parts
var builder = new UriBuilder("https://api.example.com/search");
var query = HttpUtility.ParseQueryString(builder.Query);
query["q"] = "Hello, World!";
query["lang"] = "en";
builder.Query = query.ToString();
string url = builder.Uri.ToString();
// "https://api.example.com/search?q=Hello%2c+World%21&lang=en"
Note: HttpUtility.ParseQueryString returns a NameValueCollection that encodes back as form-encoded when stringified.
Common .NET URL encoding mistakes
Mistake 1: Using HttpUtility.UrlEncode for new code
The lowercase hex digits (%2c instead of %2C) violate RFC 3986 conventions and trip up strict parsers. Use Uri.EscapeDataString for new code, or at least WebUtility.UrlEncode.
Mistake 2: Forgetting to escape interpolated values
// Wrong
var url = $"https://api.example.com/users/{userName}";
// If userName is "alice/bob", you get /users/alice/bob which is two segments
// Right
var url = $"https://api.example.com/users/{Uri.EscapeDataString(userName)}";
Mistake 3: Confusing EscapeDataString and EscapeUriString
Uri.EscapeUriString is for whole URLs (preserves reserved characters). Uri.EscapeDataString is for individual values (encodes everything). For new code, EscapeUriString is deprecated — use EscapeDataString on individual parts and assemble manually.
Quick reference
| Goal | API |
|---|---|
| Modern RFC 3986 encoding | Uri.EscapeDataString() |
| Modern RFC 3986 decoding | Uri.UnescapeDataString() |
| Build form-encoded body | FormUrlEncodedContent |
| ASP.NET Core query helpers | QueryHelpers.AddQueryString |
| Without System.Web ref | WebUtility.UrlEncode |
| Legacy ASP.NET | HttpUtility.UrlEncode |
For new .NET code in 2026, the answer is Uri.EscapeDataString — and reach for QueryHelpers or FormUrlEncodedContent when you’re building structured URLs or form bodies.
Found this useful? Try the URL decoder, the URL encoder, or browse all tools.