You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中FormUrlEncodedContent转空格为%20适配Discord OAuth认证

C# HttpClient对接Discord OAuth2的Scope编码问题

问题背景

对接Discord的OAuth2 Client Credentials Grant端点时,Discord要求Scope参数的分隔符必须是%20(URL编码后的空格),但默认使用FormUrlEncodedContent提交表单时,HttpClient会将空格转换为+,导致请求返回400 Bad Request,错误信息为:

{"error": "invalid_scope", "error_description": "The requested scope is invalid, unknown, or malformed."}

如果提前用Uri.EscapeDataString对Scope字符串编码,HttpClient会再次编码,把%20转为%2520,同样无法通过验证:

POST https://discord.com/api/v10/oauth2/token HTTP/1.1
Host: discord.com
Authorization: Basic VerySecret
Content-Type: application/x-www-form-urlencoded
Content-Length: 65

grant_type=client_credentials&scope=identify%2520guilds%2520email

原核心代码片段:

var scopeasStr = string.Join(" ", opts.Scopes);
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", opts.GrantType));
nvc.Add(new KeyValuePair<string, string>("scope", scopeasStr));

var content = new FormUrlEncodedContent(nvc);
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{apiUrl}/oauth2/token");
requestMessage.Content = content;

解决方案

方案1:自定义表单内容类,替换默认编码逻辑

创建自定义的CustomFormUrlEncodedContent类,使用Uri.EscapeDataString处理键值对,确保空格转为%20:

public class CustomFormUrlEncodedContent : ByteArrayContent
{
    public CustomFormUrlEncodedContent(IEnumerable<KeyValuePair<string, string>> nameValueCollection)
        : base(GetContentByteArray(nameValueCollection))
    {
        Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
    }

    private static byte[] GetContentByteArray(IEnumerable<KeyValuePair<string, string>> nameValueCollection)
    {
        if (nameValueCollection == null)
            throw new ArgumentNullException(nameof(nameValueCollection));

        var encodedPairs = nameValueCollection.Select(pair =>
            $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(pair.Value)}");

        var contentString = string.Join("&", encodedPairs);
        return Encoding.UTF8.GetBytes(contentString);
    }
}

替换原代码中的FormUrlEncodedContent:

// 替换原FormUrlEncodedContent实例化代码
var content = new CustomFormUrlEncodedContent(nvc);

方案2:手动构建表单内容字符串

直接拼接符合要求的表单内容,用StringContent发送:

// 直接用%20拼接Scope,无需额外编码
var scopeasStr = string.Join("%20", opts.Scopes);
// 手动拼接完整表单内容
var formContent = $"grant_type={Uri.EscapeDataString(opts.GrantType)}&scope={scopeasStr}";
var content = new StringContent(formContent, Encoding.UTF8, "application/x-www-form-urlencoded");

方案3:避免HttpClient二次编码的拼接方式

先将Scope用%20拼接,再手动构建表单字符串:

var scopeasStr = string.Join("%20", opts.Scopes);
var nvc = new List<KeyValuePair<string, string>>
{
    new KeyValuePair<string, string>("grant_type", opts.GrantType),
    new KeyValuePair<string, string>("scope", scopeasStr)
};
// 手动编码每个键值对并拼接
var formString = string.Join("&", nvc.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"));
var content = new StringContent(formString, Encoding.UTF8, "application/x-www-form-urlencoded");

原理说明

FormUrlEncodedContent遵循HTTP标准,将空格转为+,但Discord OAuth端点仅支持%20作为Scope分隔符。上述方案通过自定义编码逻辑或手动构建内容,绕过了HttpClient的默认编码,确保生成的Scope参数符合Discord的要求。

内容的提问来源于stack exchange,提问作者Reinard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 22:20:31