XFEExtension.NetCore.AutoConfig
4.1.0
dotnet add package XFEExtension.NetCore.AutoConfig --version 4.1.0
NuGet\Install-Package XFEExtension.NetCore.AutoConfig -Version 4.1.0
<PackageReference Include="XFEExtension.NetCore.AutoConfig" Version="4.1.0" />
<PackageVersion Include="XFEExtension.NetCore.AutoConfig" Version="4.1.0" />
<PackageReference Include="XFEExtension.NetCore.AutoConfig" />
paket add XFEExtension.NetCore.AutoConfig --version 4.1.0
#r "nuget: XFEExtension.NetCore.AutoConfig, 4.1.0"
#:package XFEExtension.NetCore.AutoConfig@4.1.0
#addin nuget:?package=XFEExtension.NetCore.AutoConfig&version=4.1.0
#tool nuget:?package=XFEExtension.NetCore.AutoConfig&version=4.1.0
XFEExtension.NetCore.AutoConfig
📖 English | 简体中文
Description
XFEExtension.NetCore.AutoConfig is a .NET library powered by Roslyn incremental source generators. It automatically generates static properties, load/save methods, and persistence logic for any partial class that inherits from XFEProfile, eliminating the need to write boilerplate configuration code.
Getting Started
Installation
dotnet add package XFEExtension.NetCore.AutoConfig
Basic Usage
Annotate fields with [ProfileProperty]. The source generator will create a corresponding static property that automatically saves whenever it is assigned:
// Define a profile class
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
}
// Use the profile
class Program
{
static void Main(string[] args)
{
SystemProfile.Name = "Test"; // Automatically saved on assignment
Console.WriteLine(SystemProfile.Name);
Console.WriteLine(SystemProfile.Age); // Restored from disk on next run
}
}
Note: Profiles load automatically by default. Use
[AutoLoadProfile(false)]to create and initializeCurrentwithout reading the file; it can then be loaded explicitly withLoadProfile().Automatic saves are coalesced over a 100 ms window by default. A burst of assignments therefore produces one complete file write instead of one write per property. Call
SaveProfile()when the current snapshot must be flushed immediately.
.NET 10 Partial Properties
Projects targeting .NET 10 or later with C# 14 can declare partial instance properties for the generator to implement. The generated implementation uses field for backing storage, synchronization, and collection binding. The existing static Name facade is still generated, so profile call sites do not change:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
public partial string InstanceName { get; set; } = string.Empty;
[ProfileProperty]
public partial int InstanceAge { get; set; }
}
SystemProfile.Name = "Test";
Console.WriteLine(SystemProfile.Age);
Partial profile properties must be named InstanceXxx; the persisted name and static facade remain Xxx. For example, [ProfileProperty("DisplayName")] maps to InstanceDisplayName, static DisplayName, and the XML element <DisplayName>.
The field syntax remains supported. In a .NET 10+ project using C# 14, analyzer diagnostic XFE0003 suggests eligible fields and offers the “Convert to .NET 10 partial profile property” code fix. The fix preserves the initializer, updates field references and get/set hook strings, and retargets other field attributes with field:. The equivalent manual migration is:
// Before
[ProfileProperty]
string name = "Guest";
// .NET 10 / C# 14
[ProfileProperty]
public partial string InstanceName { get; set; } = "Guest";
Detailed Usage
Automatic Save Performance and Concurrency
Generated properties, ProfileList<T>, and ProfileDictionary<TKey, TValue> are synchronized for concurrent access. Automatic save requests are handled by one writer per profile and coalesced using AutoSaveDelay. Files are committed through a temporary file and atomic replacement, so readers never observe a partially written configuration.
The coalescing window can be customized in the profile constructor:
public SystemProfile()
{
AutoSaveDelay = TimeSpan.FromMilliseconds(500);
}
Current.LastSaveException exposes the most recent background save failure and is cleared after a successful save. Explicit SaveProfile() calls remain synchronous and surface write failures directly. SaveProfileAsync(CancellationToken) forces an asynchronous save, while FlushAsync(CancellationToken) waits for changes that have already requested automatic saving. Call one of them during application shutdown to avoid losing a pending background save.
If an existing profile cannot be loaded, the original file is moved to a unique .corrupt-* backup and the initialized defaults remain usable. Inspect Current.LastLoadException or subscribe to the global XFEProfile.ProfileLoadFailed event for details.
Changing the Storage Format
Set DefaultProfileOperationMode inside the instance constructor to switch the storage format. The file extension is updated automatically:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
public SystemProfile()
{
DefaultProfileOperationMode = ProfileOperationMode.Xml; // Switch to XML; extension becomes .xml
// Available modes: XFEDictionary (default), Json, Xml, MessagePack, Custom
}
}
Schema Versions, Migrations, and Validation
Override ProfileSchemaVersion and register every N -> N + 1 step in ConfigureMigrations. Files without metadata are version 0. Built-in XFE dictionary, JSON, XML, and MessagePack modes persist version metadata automatically, and RenameProperty handles every built-in format. Use TransformMessagePack for custom MessagePack migrations; untouched properties remain binary and are not deserialized during migration.
XML element names use the profile property name (for example, <Value>) even though the generated C# instance property remains InstanceValue.
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty("DisplayName")]
string displayName = "Guest";
protected override int ProfileSchemaVersion => 2;
protected override void ConfigureMigrations(ProfileMigrationBuilder migrations) => migrations
.RenameProperty(0, "Name", "DisplayName")
.Transform(1, context => UpgradeStructure(context.Content));
protected override ProfileValidationResult ValidateProfile() =>
string.IsNullOrWhiteSpace(InstanceDisplayName)
? ProfileValidationResult.Failure("DisplayName is required")
: ProfileValidationResult.Success;
}
Loading is transactional: the library creates a candidate instance, migrates and validates it, and replaces Current only after all steps succeed. A validation or migration failure is available through LastLoadException and ProfileLoadFailed, while the original file and current instance are retained. Successfully migrated files request an automatic save in the current version. Custom storage can override ReadCustomProfileVersion and WriteCustomProfileVersion to define its metadata representation.
JSON Options and Converters
Each profile owns a JsonOptions instance used by JSON mode and by property values in XFE dictionary mode. Configure it in the constructor:
public SystemProfile()
{
DefaultProfileOperationMode = ProfileOperationMode.Json;
JsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
JsonOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
}
The same options support custom converters, number handling, case-insensitive reads, and a source-generated JsonTypeInfoResolver.
Large Object Storage
Use the MessagePack binary mode for large nested objects. Its default contractless resolver supports ordinary objects with public properties without requiring MessagePack attributes, and files use the .mpk extension:
public SystemProfile()
{
DefaultProfileOperationMode = ProfileOperationMode.MessagePack;
// Optional for large data with substantial repetition
MessagePackOptions = MessagePackOptions.WithCompression(
MessagePack.MessagePackCompression.Lz4BlockArray);
}
File save/load stays binary throughout. For in-memory transfer of a large profile, prefer ExportProfileBytes() and ImportProfileBytes(ReadOnlyMemory<byte>) to avoid Base64 conversion. Existing ExportProfile() and ImportProfile(string) remain available in MessagePack mode and use Base64 strings.
Multiple Instances and Tenants
The generated static API remains the simplest singleton API. Use ProfileStore<TProfile> when one configuration type needs independent files or tenants. Its path is a complete file path including the extension:
var tenantA = new ProfileStore<SystemProfile>("profiles/tenant-a.json");
var tenantB = new ProfileStore<SystemProfile>("profiles/tenant-b.json");
tenantA.Update(profile => profile.InstanceDisplayName = "Tenant A");
tenantB.Update(profile => profile.InstanceDisplayName = "Tenant B");
await Task.WhenAll(tenantA.SaveAsync(), tenantB.SaveAsync());
Load, Save, SaveAsync, FlushAsync, Delete, Export, ExportBytes, Import, and ImportBytes operate only on that store. Update and Read synchronize instance access, and collections are automatically bound to the correct owning instance.
Custom Storage Path and File Extension
Use the generated static properties ProfilePath and ProfileExtension to control where the file is stored:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
public SystemProfile()
{
ProfilePath = $"MyPath/MySubPath/{nameof(SystemProfile)}"; // Path without extension
ProfileExtension = ".ini"; // Custom file extension
}
}
ProfilePathandProfileExtensionare generated static properties and can also be set from outside the class:SystemProfile.ProfilePath = "custom/path/SystemProfile"; SystemProfile.ProfileExtension = ".cfg";
Using the [ProfilePath] Attribute
[AutoLoadProfile]
[ProfilePath("MyPath/MySubPath/SystemProfile")]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
}
Custom Load and Save Operations
Set DefaultProfileOperationMode to Custom and provide your own load/save delegates:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
public SystemProfile()
{
DefaultProfileOperationMode = ProfileOperationMode.Custom;
ProfilePath = $"MyPath/MySubPath/{nameof(SystemProfile)}";
ProfileExtension = ".ini";
LoadOperation = MyCustomLoadProfileOperation;
SaveOperation = MyCustomSaveProfileOperation;
}
// Custom load method
public static XFEProfile? MyCustomLoadProfileOperation(
XFEProfile profileInstance,
string profileString,
Dictionary<string, Type> propertyInfoDictionary,
Dictionary<string, SetValueDelegate> propertySetFuncDictionary)
{
// Implement custom load logic here
return null;
}
// Custom save method
public static string MyCustomSaveProfileOperation(
XFEProfile profileInstance,
Dictionary<string, Type> propertyInfoDictionary,
Dictionary<string, GetValueDelegate> propertyGetFuncDictionary)
{
// Implement custom save logic here
return string.Empty;
}
}
Storing Collections with ProfileList and ProfileDictionary
ProfileList<T> and ProfileDictionary<TKey, TValue> request a coalesced automatic save whenever the collection is modified (add, remove, clear, index assignment, etc.). Their public operations and enumeration snapshots are safe to use concurrently:
The generator binds these collection types to their owning profile during initialization, deserialization, and assignment. No accessor-injection attributes or manual CurrentProfile assignment are required.
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
ProfileList<string> nameList = [];
[ProfileProperty]
ProfileDictionary<string, long> nameIdDictionary = [];
}
class Program
{
static void Main(string[] args)
{
SystemProfile.NameList.Add("Alice"); // Auto-saved on add
SystemProfile.NameList.AddRange(["Bob", "Carol"]); // Batch add
SystemProfile.NameList.Remove("Bob"); // Auto-saved on remove
SystemProfile.NameIdDictionary.Add("Alice", 100L); // Dictionary works the same way
}
}
Injecting Code into get/set Accessors
Use [ProfilePropertyAddGet] and [ProfilePropertyAddSet] to insert code snippets directly into the generated property accessors:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
[ProfilePropertyAddGet(@"Console.WriteLine(""Getting Name"")")]
[ProfilePropertyAddGet("return Current.name")]
[ProfilePropertyAddSet(@"Console.WriteLine(""Setting Name"")")]
[ProfilePropertyAddSet("Current.name = value")]
string name = string.Empty;
[ProfileProperty]
[ProfilePropertyAddGet(@"Console.WriteLine(""Getting Age"")")]
[ProfilePropertyAddGet("return Current._age")]
[ProfilePropertyAddSet(@"Console.WriteLine(""Setting Age"")")]
[ProfilePropertyAddSet("Current._age = value")]
int _age;
}
Note: When using
[ProfilePropertyAddGet], you must handle the fullreturnstatement yourself in the lastgetsnippet.
Partial Method Hooks
The generator creates static partial void GetXxxProperty() and static partial void SetXxxProperty(ref T value) for each property. Implement them in your own partial class to intercept reads and writes:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
static partial void GetNameProperty()
{
Console.WriteLine("Name was read");
}
static partial void SetNameProperty(ref string value)
{
Console.WriteLine($"Name changing: {Name} -> {value}");
}
static partial void GetAgeProperty()
{
Console.WriteLine("Age was read");
}
static partial void SetAgeProperty(ref int value)
{
value = 1999; // Modify the value before it is stored
Console.WriteLine($"Age forced to 1999");
}
}
Default Field Values
Assign values directly at the field declaration site:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
[ProfileProperty]
string name = "John Wick";
[ProfileProperty]
int _age = 59;
}
XML Documentation Comments
XML doc comments placed on a field are automatically propagated to the generated static property:
[AutoLoadProfile]
partial class SystemProfile : XFEProfile
{
/// <summary>
/// The user's name. This comment is copied to the generated Name property.
/// </summary>
[ProfileProperty]
string name = string.Empty;
[ProfileProperty]
int _age;
}
Manual Load / Save / Delete / Export / Import
The following static methods are generated for every profile class:
SystemProfile.LoadProfile(); // Load from file
SystemProfile.SaveProfile(); // Save to file
SystemProfile.DeleteProfile(); // Delete the config file
string exported = SystemProfile.ExportProfile(); // Export config as a string
SystemProfile.ImportProfile(exported); // Import config from a string
Native AOT and Trimming Policy
This package currently does not claim general Native AOT or trimming compatibility (IsAotCompatible=false, IsTrimmable=false):
- XML mode uses reflection-based
XmlSerializerand has no complete Native AOT guarantee. - The default JSON and XFE dictionary operations use runtime
Typeoverloads. They are annotated withRequiresDynamicCode/RequiresUnreferencedCode; for AOT, setJsonOptions.TypeInfoResolverto a source-generatedJsonSerializerContextcontaining the profile and all property types. - For strict AOT deployments, prefer JSON with complete source-generated metadata or
Custommode with an AOT-safe serializer, and validate the actual application usingdotnet publish -p:PublishAot=true.
This explicit policy prevents the sample or package metadata from implying an AOT guarantee that the selected serializer cannot provide.
API Reference
Attributes
| Attribute | Target | Description |
|---|---|---|
[ProfileProperty] |
Field or partial property | Marks the member for code generation. Optionally specify a property name: [ProfileProperty("CustomName")] |
[ProfilePropertyAddGet(code)] |
Field or partial property | Appends a code line to the generated get accessor. Supports multiple attributes. |
[ProfilePropertyAddSet(code)] |
Field or partial property | Appends a code line to the generated set accessor. Supports multiple attributes. |
[AutoLoadProfile(false)] |
Class | Keeps Current initialized but disables automatic file loading. |
[ProfilePath(path)] |
Class | Sets the storage path for the config file. |
Storage Modes (ProfileOperationMode)
| Value | Extension | Description |
|---|---|---|
XFEDictionary (default) |
.xpf |
XFE dictionary format |
Json |
.json |
JSON serialization |
Xml |
.xml |
XML serialization |
MessagePack |
.mpk |
Binary serialization for large objects |
Custom |
custom | User-provided load/save delegates |
Auto-Generated Static Members
For every partial class that inherits XFEProfile and uses [ProfileProperty], the source generator produces:
| Member | Kind | Description |
|---|---|---|
Current |
static T |
The singleton profile instance |
ProfilePath |
static string |
Storage path (without extension) |
ProfileExtension |
static string |
File extension (auto-detected when empty) |
LoadProfile() |
static void |
Loads config from file |
SaveProfile() |
static void |
Immediately saves config to file and waits for completion |
SaveProfileAsync(CancellationToken) |
static Task |
Immediately saves config asynchronously |
FlushAsync(CancellationToken) |
static Task |
Flushes an already requested automatic save |
DeleteProfile() |
static void |
Deletes the config file |
ExportProfile() |
static string |
Exports config as a string |
ExportProfileBytes() |
static byte[] |
Exports raw config bytes; preferred for MessagePack |
ImportProfile(string) |
static void |
Imports config from a string |
ImportProfileBytes(ReadOnlyMemory<byte>) |
static void |
Imports config directly from bytes |
Xxx (per profile member) |
static T |
Auto-generated static property; saves on set |
InstanceXxx (per profile member) |
T (instance) |
Corresponding instance property; may be user-declared as a partial property on .NET 10 |
GetXxxProperty() |
static partial void |
Invoked when the property is read |
SetXxxProperty(ref T) |
static partial void |
Invoked when the property is written |
XFEProfile Base Class Members
| Member | Kind | Description |
|---|---|---|
DefaultProfileOperationMode |
ProfileOperationMode |
Load/save mode |
LoadOperation |
ProfileLoadOperation |
Custom load delegate |
SaveOperation |
ProfileSaveOperation |
Custom save delegate |
ProfilesDefaultPath |
static string |
Default root directory for all profile files |
AutoSaveDelay |
TimeSpan |
Coalescing window for automatic saves (100 ms by default) |
LastSaveException |
Exception? |
Most recent background save failure |
LastLoadException |
Exception? |
Most recent load failure for this profile |
ProfileLoadFailed |
static event |
Raised after a failed load and corrupt-file preservation attempt |
ProfileSchemaVersion |
protected int |
Current schema version written to built-in formats |
LoadedProfileVersion |
int |
Source version from the last successful load/import |
JsonOptions |
JsonSerializerOptions |
Per-instance JSON naming, converter, and metadata options |
MessagePackOptions |
MessagePackSerializerOptions |
Per-instance MessagePack resolver, security, and compression options |
ConfigureMigrations(...) |
virtual hook | Registers sequential schema migrations |
ValidateProfile() |
virtual hook | Validates a candidate before it replaces the current profile |
License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- MessagePack (>= 3.1.8)
- XFEExtension.NetCore (>= 5.2.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on XFEExtension.NetCore.AutoConfig:
| Package | Downloads |
|---|---|
|
XFEExtension.NetCore.ServerInteractive
Server interaction extension, including user identity verification and querying in conjunction with AutoConfig |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.1.0 | 30 | 8/17/2026 |
| 3.0.1 | 78 | 8/11/2026 |
| 3.0.0 | 204 | 5/11/2026 |
| 2.0.7 | 691 | 1/24/2026 |
| 2.0.6 | 398 | 9/27/2025 |
| 2.0.5 | 313 | 12/15/2024 |
| 2.0.4 | 210 | 12/11/2024 |
| 2.0.3 | 208 | 12/10/2024 |
| 2.0.2 | 197 | 12/9/2024 |
| 2.0.1 | 195 | 12/8/2024 |
| 2.0.0 | 207 | 12/8/2024 |
| 1.3.0 | 224 | 11/9/2024 |
| 1.2.0 | 208 | 11/9/2024 |
| 1.1.1 | 281 | 7/13/2024 |
| 1.1.0 | 228 | 7/13/2024 |
| 1.0.4 | 204 | 6/4/2024 |
| 1.0.3 | 200 | 6/4/2024 |
| 1.0.2 | 187 | 6/4/2024 |
| 1.0.1 | 220 | 6/4/2024 |
| 1.0.0 | 210 | 6/4/2024 |
## 新增
- 配置版本元数据、逐版本迁移、字段重命名和候选配置验证
- 每实例 JSON 序列化选项、命名策略和 converter
- ProfileStore<TProfile> 多实例/多租户存储
- ProfileList/ProfileDictionary 所属配置自动绑定
- .NET 10 / C# 14 部分配置属性及字段升级诊断与代码修复
## 调整
- 明确 Native AOT/Trim 支持边界并标注反射序列化入口