Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions AOT_SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# AOT (Ahead-of-Time) 编译支持

## 概述

从版本 2.0.8 开始,XFEExtension.NetCore.AutoConfig 支持 .NET Native AOT 编译。
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc claims AOT support is available “从版本 2.0.8 开始”, but the library project currently declares <Version>2.0.7</Version> in XFEExtension.NetCore.AutoConfig.csproj. Please align the documented version with the actual release/versioning plan (or bump the package version accordingly).

Suggested change
从版本 2.0.8 开始,XFEExtension.NetCore.AutoConfig 支持 .NET Native AOT 编译。
从版本 2.0.7 开始,XFEExtension.NetCore.AutoConfig 支持 .NET Native AOT 编译。

Copilot uses AI. Check for mistakes.

## 问题背景

在使用 Native AOT 编译时,默认情况下 .NET 会禁用基于反射的 JSON 序列化,这会导致以下错误:

```
System.InvalidOperationException: Reflection-based serialization has been disabled for this application.
Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.
```

## 解决方案

### 1. 创建 JsonSerializerContext

为您的应用程序创建一个 `JsonSerializerContext`,包含所有需要序列化的类型:

```csharp
using System.Text.Json.Serialization;
using XFEExtension.NetCore.AutoConfig;

namespace YourApp;

[JsonSerializable(typeof(YourDataType))]
[JsonSerializable(typeof(ProfileList<YourDataType>))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(DateTime))]
// 添加其他您需要序列化的类型
public partial class AppJsonContext : JsonSerializerContext
{
}
```

### 2. 配置 JsonSerializerOptions

在程序启动时配置 `XFEProfile.JsonOptions`:

```csharp
using System.Text.Json;
using XFEExtension.NetCore.AutoConfig;

// 在 Main 方法或启动代码中
XFEProfile.JsonOptions = new JsonSerializerOptions
{
TypeInfoResolver = AppJsonContext.Default
};
```

### 3. 项目配置

在您的 `.csproj` 文件中启用 AOT:

```xml
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
```

**注意**:不再需要 `<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>` 设置。

## 完整示例

参考 `AutoConfig.Analyzer.Test` 项目中的示例:

1. `ProfileJsonContext.cs` - JsonSerializerContext 定义
2. `Program.cs` - JsonOptions 配置示例

## 常见问题

### Q: 我需要为每个类型都添加 JsonSerializable 属性吗?

A: 是的,所有在配置文件中使用的类型都需要添加。包括:
- 配置文件中的数据类型
- 泛型类型(如 `ProfileList<T>`)
- 基础类型(string, int, bool, DateTime 等)

### Q: 如果我不使用 AOT 编译,还需要配置 JsonOptions 吗?

A: 不需要。如果不使用 AOT,库会使用默认的反射序列化,无需额外配置。

### Q: 我可以在运行时更改 JsonOptions 吗?

A: 可以,但建议在应用程序启动时配置一次,避免在使用过程中修改。

## 技术细节

- `XFEProfile.JsonOptions` 是一个静态属性,所有配置文件实例共享
- 所有 JSON 序列化操作(`JsonSerializer.Serialize` 和 `JsonSerializer.Deserialize`)都使用此选项
- 支持的序列化模式:XFEDictionary(默认)、JSON、XML
19 changes: 19 additions & 0 deletions AutoConfig.Analyzer.Test/ProfileJsonContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
using XFEExtension.NetCore.AutoConfig;

namespace XFEExtension.NetCore.XUnit.Test;

/// <summary>
/// JSON序列化上下文,用于AOT编译支持
/// </summary>
[JsonSerializable(typeof(UserInfo))]
[JsonSerializable(typeof(ProfileList<UserInfo>))]
[JsonSerializable(typeof(List<UserInfo>))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(DateTime))]
[JsonSerializable(typeof(Guid))]
public partial class ProfileJsonContext : JsonSerializerContext
{
}
12 changes: 9 additions & 3 deletions AutoConfig.Analyzer.Test/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using AutoConfig.Analyzer.Test;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using XFEExtension.NetCore.AutoConfig;
using XFEExtension.NetCore.StringExtension;
using XFEExtension.NetCore.StringExtension.Json;

Expand All @@ -11,6 +11,12 @@ public class Program
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(UserInfo))]
public static void Main(string[] args)
{
// 配置AOT兼容的JSON序列化选项
XFEProfile.JsonOptions = new JsonSerializerOptions
{
TypeInfoResolver = ProfileJsonContext.Default
};

//Console.WriteLine($"上一次读取的值是{SystemProfile.MyText}");
//var current = SystemProfile.Current;
//Console.Write("添加值:");
Expand Down Expand Up @@ -38,7 +44,7 @@ public static void Main(string[] args)
EndDateTime = DateTime.Now
};
Console.WriteLine(target.ShopID);
Console.WriteLine(JsonSerializer.Serialize(target));
Console.WriteLine(JsonSerializer.Serialize(target, ProfileJsonContext.Default.UserInfo));
Console.WriteLine("对象创建完成!准备进行分析...");
target.X();
Console.WriteLine(target.ToJson());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

<ItemGroup>
Expand Down
20 changes: 12 additions & 8 deletions XFEExtension.NetCore.AutoConfig/XFEProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public abstract class XFEProfile
{
private string id = Guid.NewGuid().ToString();
/// <summary>
/// JSON序列化选项,用于配置序列化行为(支持AOT)
/// </summary>
public static JsonSerializerOptions JsonOptions { get; set; } = new JsonSerializerOptions();
Comment on lines 15 to +18
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonOptions is publicly settable; if a consumer assigns null it will cause a NullReferenceException in every serialization call site. Consider guarding the setter (throw ArgumentNullException or coerce to a non-null default) using a backing field so JsonOptions is never null.

Suggested change
/// <summary>
/// JSON序列化选项,用于配置序列化行为(支持AOT)
/// </summary>
public static JsonSerializerOptions JsonOptions { get; set; } = new JsonSerializerOptions();
private static JsonSerializerOptions jsonOptions = new JsonSerializerOptions();
/// <summary>
/// JSON序列化选项,用于配置序列化行为(支持AOT)
/// </summary>
public static JsonSerializerOptions JsonOptions
{
get => jsonOptions;
set => jsonOptions = value ?? throw new ArgumentNullException(nameof(value));
}

Copilot uses AI. Check for mistakes.
/// <summary>
/// 配置文件所在的默认目录
/// </summary>
public static string ProfilesDefaultPath { get; set; } = $@"{AppDomain.CurrentDomain.BaseDirectory}Profiles";
Expand Down Expand Up @@ -61,7 +65,7 @@ public abstract class XFEProfile
XFEDictionary propertyFileContent = profileString;
foreach (var property in propertyFileContent)
if (propertySetFuncDictionary.TryGetValue(property.Header, out var setValueDelegate) && propertyInfoDictionary.TryGetValue(property.Header, out var type))
setValueDelegate(JsonSerializer.Deserialize(property.Content, type));
setValueDelegate(JsonSerializer.Deserialize(property.Content, type, JsonOptions));
return null;
}
/// <summary>
Expand All @@ -77,7 +81,7 @@ public static string XFEDictionarySaveProfileOperation(XFEProfile profileInstanc
return string.Empty;
var saveProfileDictionary = new XFEDictionary();
foreach (var property in propertyGetFuncDictionary)
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(property.Value()));
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(property.Value(), JsonOptions));
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XFEDictionarySaveProfileOperation currently serializes values via JsonSerializer.Serialize(property.Value(), JsonOptions). Because property.Value() is object?, this calls the Serialize<object> overload and uses metadata for object, which will not pick up source-generated metadata for the actual runtime type—breaking AOT even when JsonOptions.TypeInfoResolver is configured. Use the overload that supplies the declared type (e.g., look up propertyInfoDictionary[property.Key] and call Serialize(value, declaredType, JsonOptions)), with a safe fallback if the type is missing.

Suggested change
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(property.Value(), JsonOptions));
{
var value = property.Value();
if (propertyInfoDictionary.TryGetValue(property.Key, out var declaredType))
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(value, declaredType, JsonOptions));
else
saveProfileDictionary.Add(property.Key, JsonSerializer.Serialize(value, JsonOptions));
}

Copilot uses AI. Check for mistakes.
return saveProfileDictionary.ToString();
}
/// <summary>
Expand All @@ -88,7 +92,7 @@ public static string XFEDictionarySaveProfileOperation(XFEProfile profileInstanc
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertySetFuncDictionary">配置文件 “属性名称-属性设置方法” 字典</param>
/// <returns>配置文件实例</returns>
public static XFEProfile? JsonLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary) => JsonSerializer.Deserialize(profileString, profileInstance.GetType()) is XFEProfile xFEProfile ? xFEProfile : null;
public static XFEProfile? JsonLoadProfileOperation(XFEProfile profileInstance, string profileString, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, SetValueDelegate> propertySetFuncDictionary) => JsonSerializer.Deserialize(profileString, profileInstance.GetType(), JsonOptions) is XFEProfile xFEProfile ? xFEProfile : null;

/// <summary>
/// 通过Json保存配置文件方法
Expand All @@ -97,7 +101,7 @@ public static string XFEDictionarySaveProfileOperation(XFEProfile profileInstanc
/// <param name="propertyInfoDictionary">配置文件 “属性名称-属性类型” 字典</param>
/// <param name="propertyGetFuncDictionary">配置文件 “属性名称-属性值获取方法” 字典</param>
/// <returns>保存内容</returns>
public static string JsonSaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary) => profileInstance is null ? string.Empty : JsonSerializer.Serialize(profileInstance, profileInstance.GetType());
public static string JsonSaveProfileOperation(XFEProfile profileInstance, Dictionary<string, Type> propertyInfoDictionary, Dictionary<string, GetValueDelegate> propertyGetFuncDictionary) => profileInstance is null ? string.Empty : JsonSerializer.Serialize(profileInstance, profileInstance.GetType(), JsonOptions);
/// <summary>
/// 通过XML加载配置文件方法
/// </summary>
Expand Down Expand Up @@ -214,19 +218,19 @@ internal protected void SetProfileOperation()
private static Func<object?, ProfileEntryInfo, string> SaveProfilesFunc { get; set; } = (i, p) =>
{
if (p.MemberInfo is FieldInfo fieldInfo)
return JsonSerializer.Serialize(fieldInfo.GetValue(i));
return JsonSerializer.Serialize(fieldInfo.GetValue(i), JsonOptions);
else if (p.MemberInfo is PropertyInfo propertyInfo)
return JsonSerializer.Serialize(propertyInfo.GetValue(i));
return JsonSerializer.Serialize(propertyInfo.GetValue(i), JsonOptions);
else
Comment on lines 219 to 224
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The obsolete SaveProfilesFunc still serializes using JsonSerializer.Serialize(fieldInfo.GetValue(i), JsonOptions) / Serialize(propertyInfo.GetValue(i), JsonOptions), which again routes through the Serialize<object> overload and won’t use source-generated metadata for the member’s declared type in AOT. Prefer the overload that provides the member type (e.g., Serialize(value, fieldInfo.FieldType, JsonOptions) / Serialize(value, propertyInfo.PropertyType, JsonOptions)).

Copilot uses AI. Check for mistakes.
return string.Empty;
};
[Obsolete("SaveProfilesFunc属性现已过时,对于每个配置文件实例,请使用 XXXProfile.LoadOperation")]
private static Func<string, ProfileEntryInfo, object?> LoadProfilesFunc { get; set; } = (x, p) =>
{
if (p.MemberInfo is FieldInfo fieldInfo)
return JsonSerializer.Deserialize(x, fieldInfo.FieldType);
return JsonSerializer.Deserialize(x, fieldInfo.FieldType, JsonOptions);
else if (p.MemberInfo is PropertyInfo propertyInfo)
return JsonSerializer.Deserialize(x, propertyInfo.PropertyType);
return JsonSerializer.Deserialize(x, propertyInfo.PropertyType, JsonOptions);
else
return null;
};
Expand Down