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
Description
I get the below error on my Mac M1 arm setup when creating an EnumDictionary Json serializer:
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):
The part that fails is when the code hits an EnumDictionary and runs
Reproduction Steps
Create a basic .net core server:
with a custom EnumDictionary serializer:
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