Skip to content

.NET8 exception on M1 arm setup (Exception has occurred: CLR/System.Reflection.TargetInvocationException) #96061

@jonasbetareader

Description

@jonasbetareader

Description

I get the below error on my Mac M1 arm setup when creating an EnumDictionary Json serializer:

An exception of type 'System.Reflection.TargetInvocationException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Exception has been thrown by the target of an invocation.'
 Inner exceptions found, see $exception in variables window for more details.
 Innermost exception 	 System.IO.FileLoadException : Undefined resource string ID:0x80131621
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)

The code in question works fine on my windows 11 (parallels) setup on the same computer, but fails with the above error after upgrading from .net6 to .net8

This is the code (taken from the example on https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0#sample-factory-pattern-converter):


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

/** Mostly taken from: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0#sample-factory-pattern-converter (12 jan 2022)*/
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        if (!typeToConvert.IsGenericType) {
            return false;
        }

        if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>)) {
            return false;
        }

        return typeToConvert.GetGenericArguments()[0].IsEnum;
    }

    public override JsonConverter CreateConverter(
        Type type,
        JsonSerializerOptions options)
    {
        Type keyType = type.GetGenericArguments()[0];
        Type valueType = type.GetGenericArguments()[1];

        JsonConverter converter = (JsonConverter)Activator.CreateInstance(
            typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
                new Type[] { keyType, valueType }),
            BindingFlags.Instance | BindingFlags.Public,
            binder: null,
            args: new object[] { options },
            culture: null);

        return converter;
    }

    private class DictionaryEnumConverterInner<TKey, TValue> :
        JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
    {
        private readonly JsonConverter<TValue> _valueConverter;
        private readonly Type _keyType;
        private readonly Type _valueType;

        public DictionaryEnumConverterInner(JsonSerializerOptions options)
        {
            // For performance, use the existing converter if available.
            _valueConverter = (JsonConverter<TValue>)options
                .GetConverter(typeof(TValue));

            // Cache the key and value types.
            _keyType = typeof(TKey);
            _valueType = typeof(TValue);
        }

        public override Dictionary<TKey, TValue> Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject) {
                throw new JsonException();
            }

            var dictionary = new Dictionary<TKey, TValue>();

            while (reader.Read()) {
                if (reader.TokenType == JsonTokenType.EndObject) {
                    return dictionary;
                }

                // Get the key.
                if (reader.TokenType != JsonTokenType.PropertyName) {
                    throw new JsonException();
                }

                string propertyName = reader.GetString();

                // For performance, parse with ignoreCase:false first.
                if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
                    !Enum.TryParse(propertyName, ignoreCase: true, out key)) {
                    throw new JsonException(
                        $"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
                }

                // Get the value.
                TValue value;
                if (_valueConverter != null) {
                    reader.Read();
                    value = _valueConverter.Read(ref reader, _valueType, options);
                }
                else {
                    value = JsonSerializer.Deserialize<TValue>(ref reader, options);
                }

                // Add to dictionary.
                dictionary.Add(key, value);
            }

            throw new JsonException();
        }

        public override void Write(
            Utf8JsonWriter writer,
            Dictionary<TKey, TValue> dictionary,
            JsonSerializerOptions options)
        {
            writer.WriteStartObject();

            foreach ((TKey key, TValue value) in dictionary) {
                var propertyName = Convert.ChangeType(key, key.GetTypeCode()).ToString();

                writer.WritePropertyName
                    (options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);

                if (_valueConverter != null) {
                    _valueConverter.Write(writer, value, options);
                }
                else {
                    JsonSerializer.Serialize(writer, value, options);
                }
            }

            writer.WriteEndObject();
        }
    }
}

The part that fails is when the code hits an EnumDictionary and runs

            typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
                new Type[] { keyType, valueType }),
            BindingFlags.Instance | BindingFlags.Public,
            binder: null,
            args: new object[] { options },
            culture: null);

Reproduction Steps

Create a basic .net core server:

using Business.Common;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Azure;
using Microsoft.IdentityModel.Tokens;
using NJsonSchema;
using NJsonSchema.Generation.TypeMappers;
using System.Diagnostics;
using System.Globalization;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
Console.WriteLine($"Using database: {builder.Configuration.GetConnectionString("DefaultConnection")}");

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<DB>((sp, options) =>
{
    var interceptor = sp.GetRequiredService<ISaveChangesInterceptor>();
    options.AddInterceptors(interceptor);
    options.UseSqlServer(connectionString);
});

builder.Services.AddIdentity<AppUser, AppRole>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddDefaultTokenProviders()
    .AddEntityFrameworkStores<DB>();

// Add service configurations;
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

// Add controllers
builder.Services.AddControllers()
    .AddJsonOptions(j =>
        {
            j.JsonSerializerOptions.Converters.Add(new DictionaryTKeyEnumTValueConverter());
        })
    .AddXmlDataContractSerializerFormatters();


builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "CorsPolicy",
        builder =>
        {
            builder
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader();
        });
});

builder.Host.UseDefaultServiceProvider(o =>
{
    o.ValidateOnBuild = true;
    o.ValidateScopes = true;
});

var app = builder.Build();

app.UseWhen(x => pathsToLog.Contains(x.Request.RouteValues["Controller"]?.ToString()),
        app => app.UseRequestBodyLogging()
        );

app.UseHttpsRedirection();
var supportedCultures = new[]
{
    new CultureInfo("sv-SE") // Set to the desired culture, e.g., "sv-SE" for Sweden
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("sv-SE"), // Set to the same culture as above
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures
});

app.UseRouting();

app.UseCors("CorsPolicy");

if (app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/error-development");
}
else
{
    app.UseExceptionHandler("/error");
}

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");

app.UseStaticFiles();
app.MapFallbackToFile("index.html"); ;

// ExcelPackage init
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

app.Run();

// Used for testing
public partial class Program
{
    public static bool IsRunningInUnitTest { get; set; } = false;
}

with a custom EnumDictionary serializer:


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

/** Mostly taken from: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0#sample-factory-pattern-converter (12 jan 2022)*/
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        if (!typeToConvert.IsGenericType) {
            return false;
        }

        if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>)) {
            return false;
        }

        return typeToConvert.GetGenericArguments()[0].IsEnum;
    }

    public override JsonConverter CreateConverter(
        Type type,
        JsonSerializerOptions options)
    {
        Type keyType = type.GetGenericArguments()[0];
        Type valueType = type.GetGenericArguments()[1];

        JsonConverter converter = (JsonConverter)Activator.CreateInstance(
            typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
                new Type[] { keyType, valueType }),
            BindingFlags.Instance | BindingFlags.Public,
            binder: null,
            args: new object[] { options },
            culture: null);

        return converter;
    }

    private class DictionaryEnumConverterInner<TKey, TValue> :
        JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
    {
        private readonly JsonConverter<TValue> _valueConverter;
        private readonly Type _keyType;
        private readonly Type _valueType;

        public DictionaryEnumConverterInner(JsonSerializerOptions options)
        {
            // For performance, use the existing converter if available.
            _valueConverter = (JsonConverter<TValue>)options
                .GetConverter(typeof(TValue));

            // Cache the key and value types.
            _keyType = typeof(TKey);
            _valueType = typeof(TValue);
        }

        public override Dictionary<TKey, TValue> Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject) {
                throw new JsonException();
            }

            var dictionary = new Dictionary<TKey, TValue>();

            while (reader.Read()) {
                if (reader.TokenType == JsonTokenType.EndObject) {
                    return dictionary;
                }

                // Get the key.
                if (reader.TokenType != JsonTokenType.PropertyName) {
                    throw new JsonException();
                }

                string propertyName = reader.GetString();

                // For performance, parse with ignoreCase:false first.
                if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
                    !Enum.TryParse(propertyName, ignoreCase: true, out key)) {
                    throw new JsonException(
                        $"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
                }

                // Get the value.
                TValue value;
                if (_valueConverter != null) {
                    reader.Read();
                    value = _valueConverter.Read(ref reader, _valueType, options);
                }
                else {
                    value = JsonSerializer.Deserialize<TValue>(ref reader, options);
                }

                // Add to dictionary.
                dictionary.Add(key, value);
            }

            throw new JsonException();
        }

        public override void Write(
            Utf8JsonWriter writer,
            Dictionary<TKey, TValue> dictionary,
            JsonSerializerOptions options)
        {
            writer.WriteStartObject();

            foreach ((TKey key, TValue value) in dictionary) {
                var propertyName = Convert.ChangeType(key, key.GetTypeCode()).ToString();

                writer.WritePropertyName
                    (options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName);

                if (_valueConverter != null) {
                    _valueConverter.Write(writer, value, options);
                }
                else {
                    JsonSerializer.Serialize(writer, value, options);
                }
            }

            writer.WriteEndObject();
        }
    }
}```

### Expected behavior

No crash

### Actual behavior

Crash:
```Exception has occurred: CLR/System.Reflection.TargetInvocationException
An exception of type 'System.Reflection.TargetInvocationException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Exception has been thrown by the target of an invocation.'
 Inner exceptions found, see $exception in variables window for more details.
 Innermost exception 	 System.IO.FileLoadException : Undefined resource string ID:0x80131621
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)

Regression?

This worked fine in .NET6, and still works fine in .NET8 on my windows 11 installation (via parallels desktop). Only breaks on arm macs.

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.Text.Jsonneeds-author-actionAn issue or pull request that requires more info or actions from the author.untriagedNew issue has not been triaged by the area owner

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions