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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="XFEExtension.NetCore.AutoConfig" Version="4.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="XFEExtension.NetCore.AutoConfig" Version="4.1.0" />
                    
Directory.Packages.props
<PackageReference Include="XFEExtension.NetCore.AutoConfig" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add XFEExtension.NetCore.AutoConfig --version 4.1.0
                    
#r "nuget: XFEExtension.NetCore.AutoConfig, 4.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package XFEExtension.NetCore.AutoConfig@4.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=XFEExtension.NetCore.AutoConfig&version=4.1.0
                    
Install as a Cake Addin
#tool nuget:?package=XFEExtension.NetCore.AutoConfig&version=4.1.0
                    
Install as a Cake Tool

XFEExtension.NetCore.AutoConfig

NuGet NuGet Downloads License: MIT .NET

📖 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 initialize Current without reading the file; it can then be loaded explicitly with LoadProfile().

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
    }
}

ProfilePath and ProfileExtension are 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 full return statement yourself in the last get snippet.

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 XmlSerializer and has no complete Native AOT guarantee.
  • The default JSON and XFE dictionary operations use runtime Type overloads. They are annotated with RequiresDynamicCode / RequiresUnreferencedCode; for AOT, set JsonOptions.TypeInfoResolver to a source-generated JsonSerializerContext containing the profile and all property types.
  • For strict AOT deployments, prefer JSON with complete source-generated metadata or Custom mode with an AOT-safe serializer, and validate the actual application using dotnet 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 支持边界并标注反射序列化入口