[csharp] fix: stop percent-encoding the User-Agent

Merged
WI
1mo ago
+54-25

csharp/Configuration.mustache assigns the user agent through WebUtility.UrlEncode:

UserAgent = WebUtility.UrlEncode("{{httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{packageVersion}}/csharp{{/httpUserAgent}}");

A User-Agent is not a URL component, and nothing downstream decodes it. So the "/" that
separates product from version — the one character nearly every user agent in the wild uses —
reaches the server as %2F, and because WebUtility.UrlEncode is form encoding, a space
reaches it as +:

httpUserAgentwhat the server sees
(unset — the default)OpenAPI-Generator%2F1.0.0%2Fcsharp
my-client/1.2.3my-client%2F1.2.3
my-client/1.2.3 (linux)my-client%2F1.2.3+(linux)

Found porting a real client: the header the API sees is how support answers "which SDK and
which release is this customer on", and csharp was the one language of ten that did not send
what it was configured with.

Verified on the wire

A raw TcpListener, so the header is read as the bytes that actually went out — RestSharp
112.0.0 (the version the samples pin):

User-Agent: OpenAPI-Generator%2F1.0.0%2Fcsharp     <- RestClientOptions.UserAgent = WebUtility.UrlEncode(...)
User-Agent: OpenAPI-Generator/1.0.0/csharp         <- RestClientOptions.UserAgent = "..."

The second line is the point: RestSharp takes the unencoded value without complaint, so the
encoding buys nothing and costs the header's meaning.

All three libraries that share this template pass the value straight through, so what
Configuration assigns is what goes out:

  • restsharp — RestClientOptions.UserAgent = configuration.UserAgent
  • httpclient — request.Headers.TryAddWithoutValidation("User-Agent", configuration.UserAgent)
  • unityWebRequest — request.SetRequestHeader("User-Agent", configuration.UserAgent)

Where it came from

The encoding arrived on 2022-07-20 in 2dcc319e13 (#12789, "[csharp-netcore] Update RestSharp
and add client_credentials auth support"), which upgraded RestSharp to v108. That commit flips
the line from a plain assignment to the encoded one; its message mentions "fix authenticator
bug, and fix user agent bug" and later "Switch HttpUtility to WebUtility for compatibility",
and neither the PR description nor any review comment records what the user agent bug was.
Whatever RestSharp v108 did with the header then, RestSharp 112 does not do now — see the
probe above.

Two things suggest it was collateral rather than intent:

  • the sibling generator disagrees — csharp-functions/Configuration.mustache has always
    assigned the same value with no encoding;
  • Configuration.mustache is shared by restsharp, httpclient and unityWebRequest, so a fix
    aimed at one RestSharp version landed on all three.

It stayed invisible because the result is still a valid header — no client errors, no server
rejects it — so the only symptom is server-side analytics grouping the SDK under a name nobody
recognizes.

The change

The WebUtility.UrlEncode call is removed from both restsharp Configuration templates — the
default one and the useIntForTimeout v7.9.0 fallback Configuration.v790.mustache.
using System.Net; stays: Proxy is a WebProxy.

generichost is untouched — it never sets UserAgent at all, which is a separate gap.

Tests

CSharpClientCodegenTest#testUserAgentIsNotUrlEncoded generates with
httpUserAgent=my-client/1.2.3 (linux) and asserts the literal reaches Configuration.cs and
that the WebUtility.UrlEncode( wrapper is gone, once per Configuration template
(useIntForTimeout false and true). It fails on master:

java.lang.AssertionError: File '.../Client/Configuration.cs' does not contain line
[UserAgent = "my-client/1.2.3 (linux)";] expected [true] but found [false]

13 tests pass in CSharpClientCodegenTest.

PR checklist

  • Read the contribution guidelines.
  • Built the project and updated samples (./bin/generate-samples.sh bin/configs/csharp-*).
    23 sample files changed, each by exactly this one line — 12 restsharp, 5 httpclient,
    3 unityWebRequest, plus the echo-api and complex-files samples. No option was added or
    changed, so docs/generators is unaffected.
  • Filed against master.
  • Technical committee: @jimschubert @Blackclaws @devhl-labs @Gronsak

Generated with Claude Code


Summary by cubic

Stops percent-encoding the C# generator's User-Agent so servers see it exactly as configured instead of OpenAPI-Generator%2F1.0.0%2Fcsharp (spaces became +). The encoding was added for a RestSharp v108 quirk, but RestSharp 112 and the other libraries pass the value through unchanged, so it only corrupted the header.

  • Removes the WebUtility.UrlEncode wrapper from both restsharp Configuration templates (default and useIntForTimeout v790 fallback); using System.Net; stays for WebProxy.
  • Affects restsharp, httpclient, and unityWebRequest, which all send the User-Agent as assigned.
  • Adds a test asserting the literal my-client/1.2.3 (linux) value reaches Configuration.cs for both templates.