DecoderFallbackException:无法将索引 75 处的字节 [BF] 从指定的代码页转换为 Unicode

Txa*_*ran 3 c# character-encoding asp.net-core-webapi

在我正在开发的 .NET Core 2.1 Web API 上,我有一个 POST 方法,用于接收仅具有一个属性的 JSON 对象,如下所示:

\n\n
{\n    "longURL": "http://foo.example.com/path/path/path/path/path/pfad"\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

以及该方法的标头:

\n\n
public async Task<ActionResult<ShortenerOutputDto>> CreateAsync([FromBody] \nShortenerInputDto input)\n
Run Code Online (Sandbox Code Playgroud)\n\n

但如果输入 JSON 包含一些特殊字符,则会出现异常,如下所示:

\n\n
{\n    "longURL": "http://foo.example.com/path/path/path/path/path/pfad\xc2\xbf"\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,最后一个 (\xc2\xbf) 是有问题的字符。\n我得到的异常是:

\n\n
System.Text.DecoderFallbackException: Unable to translate bytes [BF] at index 75 from specified code page to Unicode.\n   at System.Text.DecoderExceptionFallbackBuffer.Throw(Byte[] bytesUnknown, Int32 index)\n   at System.Text.DecoderExceptionFallbackBuffer.Fallback(Byte[] bytesUnknown, Int32 index)\n   at System.Text.DecoderFallbackBuffer.InternalFallback(Byte[] bytes, Byte* pBytes, Char*& chars)\n   at System.Text.UTF8Encoding.GetChars(Byte* bytes, Int32 byteCount, Char* chars, Int32 charCount, DecoderNLS baseDecoder)\n   at System.Text.DecoderNLS.GetChars(Byte[] bytes, Int32 byteIndex, Int32 byteCount, Char[] chars, Int32 charIndex, Boolean flush)\n   at System.Text.DecoderNLS.GetChars(Byte[] bytes, Int32 byteIndex, Int32 byteCount, Char[] chars, Int32 charIndex)\n   at Microsoft.AspNetCore.WebUtilities.HttpRequestStreamReader.ReadIntoBuffer()\n   at Microsoft.AspNetCore.WebUtilities.HttpRequestStreamReader.Read(Char[] buffer, Int32 index, Int32 count)\n   at Newtonsoft.Json.JsonTextReader.ReadData(Boolean append, Int32 charsRequired)\n   at Newtonsoft.Json.JsonTextReader.ParseValue()\n   at Newtonsoft.Json.JsonTextReader.Read()\n   at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)\n   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)\n
Run Code Online (Sandbox Code Playgroud)\n\n

问题是在进入控制器方法之前抛出异常。

\n\n

那么,有没有办法避免这种情况呢?我想发送一个 BadRequest,以防输入无法解码。

\n\n

更新

\n\n

根据@jdweng\'s 的回答,我添加了以下转换器类:

\n\n
public class HtmlEncodingConverter : JsonConverter\n{\n        public override bool CanConvert(Type objectType)\n        {\n            return objectType == typeof(String);\n        }\n\n        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)\n        {\n            return System.Web.HttpUtility.HtmlDecode((string)reader.Value);\n        }\n\n        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)\n        {\n\nwriter.WriteRawValue(System.Web.HttpUtility.HtmlEncode((string)value));\n        }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

并注册为 JSONOptions:

\n\n
services.AddMvc()\n    .AddJsonOptions (opt => opt.SerializerSettings.Converters.Add(new HtmlEncodingConverter()));\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是如果请求中有一些特殊字符,ReadJson 和 WriteJson 都不会被命中。

\n\n

因此,似乎解码或解码发生在 Core 尝试转换输入之前。真奇怪。

\n\n

更新二

\n\n

原始 HTTP 请求消息:

\n\n
POST /create HTTP/1.0\nHost: localhost:5000\nContent-Length: 80\nContent-Type: application/json\n\n{\n    "longURL" : "http://foo.example.com/path/path/path/path/path/pfad\xc2\xbf"\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Txa*_*ran 6

最后我最终添加了一个异常过滤器,如下所示:

public class DecoderFallbackExceptionFilter : IExceptionFilter
{
        public void OnException(ExceptionContext context)
        {
            if (context.Exception.GetType() == typeof(DecoderFallbackException))
                context.Result = new BadRequestObjectResult(ShortURLResponse.InvalidURL());
        }
}
Run Code Online (Sandbox Code Playgroud)

...并在startup.cs中注册:

services.AddMvc(opt =>
        {
            opt.Filters.Add(new DecoderFallbackExceptionFilter());
        });
Run Code Online (Sandbox Code Playgroud)

也许这不是我最初期望的解决方案,但它有效并且允许我控制针对每种情况应该采取的任何行动。

此外,我可以再次向该方法添加输入参数,并重新启用单元测试。 https://github.com/aspnet/AspNetCore/issues/8676

顺便说一句,这种行为似乎将在 ASP.NET Core 3.0 中得到增强。 https://github.com/aspnet/AspNetCore/issues/3959