Article · Apr 12, 2026 · 5 min read

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:

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%20UPPERRFC 3986
WebUtility.UrlEncode+UPPERForm-encoded
HttpUtility.UrlEncode+lowerLegacy 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 encodingUri.EscapeDataString()
Modern RFC 3986 decodingUri.UnescapeDataString()
Build form-encoded bodyFormUrlEncodedContent
ASP.NET Core query helpersQueryHelpers.AddQueryString
Without System.Web refWebUtility.UrlEncode
Legacy ASP.NETHttpUtility.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.

More reading

From the blog.