19

I have a .NET Framework 4.8 / AngularJS app that I'm trying to upgrade to .NET 6 so that I can start experimenting with newer front-end frameworks (like Blazor). The upgrade is going okay, but I consistently see this error when calling one of my API methods from the Angular JS code:

System.NotSupportedException: Serialization and deserialization of 'System.IntPtr' instances are not supported. Path: $.TargetSite.MethodHandle.Value.
 ---> System.NotSupportedException: Serialization and deserialization of 'System.IntPtr' instances are not supported.
   at System.Text.Json.Serialization.Converters.UnsupportedTypeConverter`1.Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(WriteStack& state, NotSupportedException ex)
   at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.WriteCoreAsObject(Utf8JsonWriter writer, Object value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteCore[TValue](JsonConverter jsonConverter, Utf8JsonWriter writer, TValue& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
   at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
   at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Authorization: Negotiate TlRMTVNTUAADAAAAGAAYAHgAAABgAWABkAAAAAAAAABYAAAADgAOAFgAAAASABIAZgAAABAAEADwAQAAFYKI4goAYUoAAAAPV7Hpy762wOgD8P0Vp4p25WsAawBvAHoAbABlAHIASwBLAE8AWgBMAEUAUgAtAEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyKkYMRt+BEmGGiFVayCimwEBAAAAAAAAsRKCqZ8i2AHdnAn8ozInsAAAAAACABAAUABSAE8ATQBPAEQARQBMAAEAEgBLAEsATwBaAEwARQBSAC0ATAAEACIAYwBvAHIAcAAuAHAAcgBvAG0AbwBkAGUAbAAuAGMAbwBtAAMANgBLAEsATwBaAEwARQBSAC0ATAAuAGMAbwByAHAALgBwAHIAbwBtAG8AZABlAGwALgBjAG8AbQAFACIAYwBvAHIAcAAuAHAAcgBvAG0AbwBkAGUAbAAuAGMAbwBtAAcACACxEoKpnyLYAQYABAACAAAACAAwADAAAAAAAAAAAQAAAAAgAABZ/aEtx5BWovcqYmfEANzjrij27KQ+UdanKJzMjBJr2QoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwBsAG8AYwBhAGwAaABvAHMAdAAAAAAAAAAAAKxoJoGhKPec1JQ2hdsVSsQ=
Connection: keep-alive
Content-Length: 346
Content-Type: application/json;charset=UTF-8
Cookie: ai_user=r+bRv|2022-01-12T04:50:58.222Z
Host: localhost:42721
Referer: http://localhost:42721/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Origin: http://localhost:42721
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty

I'm mystified by this error. The API method looks like this:

[HttpPost]
public IActionResult Validate([FromBody] ValidationDto dto)
{
    try
    {
        ...
    }
    catch (Exception ex)
    {
        return StatusCode((int)HttpStatusCode.InternalServerError, ex.InnermostException());
    }
}

The ValidationDto class looks like this:

public class ValidationDto
{
    public Guid GuidProperty { get; set; }
    public DateTime? NullableDateTimeProperty { get; set; }
    public int IntegerProperty { get; set; }
    public ValidationType EnumProperty { get; set; }
    public bool BooleanProperty { get; set; }
}

The ValidationType enum looks like this:

public enum ValidationType
{
    FirstType,
    SecondType,
    ThirdType,
}

The only JSON options I've specified are as follows:

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    });

Where is System.Text.Json finding an IntPtr that it can't deserialize?

Keegan Kozler
  • 361
  • 1
  • 3
  • 9
  • What is your ValidationType is it enum? can you post it too pls – Serge Feb 15 '22 at 19:45
  • Added example snippet, thanks @Serge – Keegan Kozler Feb 15 '22 at 19:49
  • 1
    The traceback is in serialization not deserialization. Can you share what you are serializing? – dbc Feb 15 '22 at 19:49
  • 3
    Somewhere your code must be throwing an exception **that you are catching and trying to serialize with System.Text.Json**. If I try to serialize an `Exception` with System.Text.Json I get the *`System.NotSupportedException: Serialization and deserialization of 'System.IntPtr' instances are not supported. Path: $.TargetSite.MethodHandle.Value.`* error traceback shown in your question, see https://dotnetfiddle.net/RrlPQB – dbc Feb 15 '22 at 19:57
  • @dbc Thanks for pointing that out, I had missed it at first. I expanded the API snippet to show my exception handling. Looks like in .NET Framework, I was just passing the innermost Exception object back to the client; something in the exception must be the unsupported type that .NET's trying to serialize. – Keegan Kozler Feb 15 '22 at 19:58
  • @dbc You beat me to it. This is really helpful, thanks! – Keegan Kozler Feb 15 '22 at 19:59
  • 2
    System.Text.Json doesn't support serializing exceptions, see https://github.com/dotnet/runtime/issues/43026. You could write a converter, but it seems like a nuisance and I can't really recommend it. But what are you trying to accomplish here? Maybe [Returning exceptions as JSON messages](https://stackoverflow.com/q/38664207/3744182) is what you want? – dbc Feb 15 '22 at 20:08
  • 1
    I think that for my purposes, just returning the exception message instead of the exception itself should be fine. Thanks again for your help. – Keegan Kozler Feb 15 '22 at 20:12

3 Answers3

21

Your code is failing during serialization not deserialization because you are catching some inner exception and trying to serialize it with System.Text.Json by returning it here:

return StatusCode((int)HttpStatusCode.InternalServerError, ex.InnermostException());

Unfortunately, System.Text.Json does not support serialization of exceptions, see System.Text.Json - can't serialize exception #43026 and also the demo fiddle https://dotnetfiddle.net/RrlPQB. It fails specifically trying to serialize TargetSite.MethodHandle.Value which is, in fact, an IntPtr.

If you really need to return all the exception data, you could write a custom JsonConverter<Exception>. However, it will be easier just to return the necessary information as a string or DTO like so:

return StatusCode((int)HttpStatusCode.InternalServerError, ex.InnermostException().Message);

Or

return StatusCode((int)HttpStatusCode.InternalServerError, ex.InnermostException().ToString());

Or even an anonymous type DTO like so:

return StatusCode((int)HttpStatusCode.InternalServerError, new { ex.InnermostException().Message, StackTrace = ex.InnermostException().StackTrace?.ToString()} );

Returning exceptions as JSON messages has additional options for customizing how exceptions are returned.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

I was trying to return the entire search response:

public async Task<IActionResult> Search()
{
    var response = await _elasticClient.SearchAsync<User>();
    return Ok(response);
}

I should have returned just documents instead:

public async Task<IActionResult> Search()
{
    var response = await _elasticClient.SearchAsync<User>();
    return Ok(response.Documents);
}
Aleksei Mialkin
  • 2,257
  • 1
  • 28
  • 26
0

accepted answer helped but I still had to spend time to craft the final solution.

public class SystemTextJsonExceptionConverter : JsonConverter<Exception>
{
    public override Exception Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, Exception value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString(nameof(Exception.Message), value.Message);

#if DEBUG
        if (value.InnerException is not null)
        {
            writer.WriteStartObject(nameof(Exception.InnerException));
            Write(writer, value.InnerException, options);
            writer.WriteEndObject();
        }

        if (value.TargetSite is not null)
        {
            writer.WriteStartObject(nameof(Exception.TargetSite));
            writer.WriteString(nameof(Exception.TargetSite.Name), value.TargetSite?.Name);
            writer.WriteString(nameof(Exception.TargetSite.DeclaringType), value.TargetSite?.DeclaringType?.FullName);
            writer.WriteEndObject();
        }

        if (value.StackTrace is not null)
        {
            writer.WriteString(nameof(Exception.StackTrace), value.StackTrace);
        }
#endif

        writer.WriteString(nameof(Type), value.GetType().ToString());
        writer.WriteEndObject();
    }
}

and use it like :

static void Main(string[] args)
{
    try
    {
        throw new Exception("top ex", new InvalidCastException("nested ex", new Exception("super nested ex")));
    }
    catch (Exception ex)
    {
        var settings = new JsonSerializerOptions
        {
            Converters = { new SystemTextJsonExceptionConverter() },
            WriteIndented = true
        };

        var str = JsonSerializer.Serialize(ex, options: settings);
        Console.WriteLine(str);
    }
}

for Asp.Net Core

services.AddControllers()
            .AddJsonOptions(o =>
            {
              //add this
                o.JsonSerializerOptions.Converters.Add(new SystemTextJsonExceptionConverter());
                o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
            });
Uttam Ughareja
  • 842
  • 2
  • 12
  • 21