عنوان:

‫مدیریت هوشمند تنظیمات در NET 11.: آشنایی با ConfigurationIgnoreAttribute


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۳/۲۰ ۰۸:۰۵
آدرس: www.dntips.ir
در توسعه برنامه‌های مدرن با استفاده از اکوسیستم .NET، مدیریت تنظیمات (Configuration Management) یکی از ارکان اصلی طراحی ساختاریافته و ماژولار است. پکیج محبوب Microsoft.Extensions.Configuration سال‌هاست که وظیفه نگاشت (Binding) فایل‌های تنظیمات (مانند appsettings.json) را به کلاس‌های گزینه‌ها (Options Classes) بر عهده دارد. با این حال، تا پیش از انتشار .NET 11، یک خلاء ملموس در این سیستم وجود داشت: عدم وجود یک راهکار رسمی و استاندارد برای نادیده گرفتن یک Property خاص در فرآیند Bind شدن.
اگرچه مایکروسافت در .NET 6 ویژگی ConfigurationKeyNameAttribute را برای تغییر نام کلیدهای نگاشت معرفی کرد، اما امکان مسدود کردن کامل فرآیند Binding برای یک ویژگی مشخص وجود نداشت. خوشبختانه در .NET 11، با معرفی ویژگی جدید ConfigurationIgnoreAttribute، این شکاف به‌طور کامل و به شکلی بومی برطرف شده است. در این مقاله، به بررسی ریشه‌ای این چالش، راهکارهای موقت گذشته، و نحوه پیاده‌سازی این قابلیت جدید می‌پردازیم.

صورت مسئله: چرا به نادیده گرفتن تنظیمات نیاز داریم؟
در بسیاری از سناریوهای معماری نرم‌افزار، کلاس تنظیمات شما صرفاً یک نگاشت یک‌به‌یک (1:1) ساده از فایل JSON نیست. گاهی اوقات شما نیاز دارید برخی از ویژگی‌ها را بر اساس داده‌های خام اولیه، پردازش یا محاسبه (Compute) کنید.
به عنوان مثال، کلاس گزینه‌های زیر را برای مدیریت ورودی یک فایل CSV در نظر بگیرید:
public class CsvIngestionOptions
{
    // این ویژگی حاصل پردازش ثانویه است و نباید مستقیماً از فایل تنظیمات خوانده شود
    public CsvFormatOptions DefaultFormat { get; set; }

    // بخش خامی که از فایل تنظیمات خوانده شده و خودمان آن را پردازش می‌کنیم
    public IConfigurationSection? DefaultFormatSection { get; set; }

    internal void OnConfigurationLoaded()
    {
        DefaultFormat = new CsvFormatOptions();
        DefaultFormatSection?.Bind(DefaultFormat);
        DefaultFormat.EnsureEncodingDefined();
    }
}
در این سناریو، ویژگی DefaultFormat قرار است پس از بارگذاری اولیه تنظیمات، مقداردهی و پردازش شود. اما از آنجایی که این ویژگی دارای دسترسی‌های عمومی (public get; set;) است، سیستم Configuration مکانیزم .NET به صورت خودکار و کورکورانه تلاش می‌کند تا در صورت وجود کلیدی هم‌نام در فایل JSON، آن را مقداردهی کند. این رفتار می‌تواند منجر به بازنویسی (Overwrite) ناخواسته داده‌ها، بروز رفتارهای پیش‌بینی‌نشده یا کاهش کارایی برنامه شود.

راهکارهای موقت در نسخه‌های قدیمی (The Hacks)
توسعه‌دهندگان قبل از .NET 11 برای فرار از این مشکل، مجبور به استفاده از ترفندهای ناثوابی بودند. یکی از رایج‌ترین روش‌ها، سوءاستفاده از ConfigurationKeyNameAttribute و ارجاع آن به یک کلید ساختگی و نامعتبر بود:
// یک راهکار غیر استاندارد و آسیب‌پذیر
[ConfigurationKeyName("__ignored_" + nameof(DefaultFormat))]
public CsvFormatOptions DefaultFormat { get; set; }
این روش نه تنها خوانایی کد را کاهش می‌داد، بلکه معماری تنظیمات برنامه را شکننده می‌کرد و باعث ایجاد ورودی‌های اضافه و سردرگم‌کننده در ساختار (Schema) تنظیمات می‌شد. راهکار دیگر، استفاده از ویژگی‌های private یا internal بود که آن هم انعطاف‌پذیری کد را برای دسترسی در سایر بخش‌های برنامه محدود می‌کرد.

راهکار بومی NET 11.: ویژگی ConfigurationIgnoreAttribute
در NET 11.، مایکروسافت یک ویژگی اختصاصی و مهروموم شده (sealed) را در فضای نام Microsoft.Extensions.Configuration معرفی کرده است:
namespace Microsoft.Extensions.Configuration;

[AttributeUsage(AttributeTargets.Property)]
public sealed class ConfigurationIgnoreAttribute : Attribute { }
استفاده از آن بسیار ساده است. کافی است این اتریبیوت را بالای ویژگی مورد نظر قرار دهید تا مکانیزم ساختار به طور کامل آن را نادیده بگیرد:
using Microsoft.Extensions.Configuration;

public class CsvIngestionOptions
{
    [ConfigurationIgnore]
    public CsvFormatOptions DefaultFormat { get; set; }

    [ConfigurationKeyName(nameof(DefaultFormat))]
    public IConfigurationSection? DefaultFormatSection { get; set; }

    internal void OnConfigurationLoaded()
    {
        DefaultFormat = new CsvFormatOptions();
        DefaultFormatSection?.Bind(DefaultFormat);
        DefaultFormat.EnsureEncodingDefined();
    }
}

یک سناریوی کاربردی دیگر: فیلدهای محاسباتی (Parsed Fields)
به‌جز بحث پردازش ثانویه، مدیریت پرچم‌های ویژگی (Feature Flags) یا رشته‌های متصل (Connection Strings) متداول‌ترین کاربرد این قابلیت است. فرض کنید می‌خواهید یک رشته متنی خام از پرچم‌ها را دریافت کرده و آن را به یک دیکشنری بهینه‌تر برای دسترسی سریع تبدیل کنید:
public class FeatureFlags
{
    // مقداری که مستقیماً از appsettings.json خوانده می‌شود
    public string RawFlags { get; set; } = string.Empty;

    // خروجی پردازش شده که باید از دید فرآیند Binding پنهان بماند
    [ConfigurationIgnore]
    public IReadOnlyDictionary<string, bool> ParsedFlags { get; set; } 
        = new Dictionary<string, bool>();
}

یک اشتباه رایج: چرا نمی‌توان از [JsonIgnore] استفاده کرد؟
بسیاری از توسعه‌دهندگان به دلیل شباهت ظاهری فایل‌های appsettings.json با ساختارهای سریال‌سازی JSON، گمان می‌کنند که استفاده از اتریبیوت [JsonIgnore] متعلق به اکوسیستم System.Text.Json می‌تواند این مشکل را حل کند.

نکته کلیدی علمی: سیستم پیکربندی NET (Microsoft.Extensions.Configuration). یک لایه انتزاعی (Abstraction) مستقل است و از هسته سریال‌سازی JSON مجزا عمل می‌کند. این سیستم داده‌ها را از منابع مختلفی مانند کدهای دستوری (CLI Arguments)، متغیرهای محیطی (Environment Variables)، دیتابیس‌ها و فایل‌های XML/JSON جمع‌آوری کرده و به یک ساختار کلید-مقدار (Key-Value) تخت تبدیل می‌کند. بنابراین، Binder داخلی تنظیمات هیچ شناختی از اتریبیوت‌های اختصاصی System.Text.Json یا Newtonsoft.Json ندارد و آن‌ها را کاملاً نادیده می‌گیرد.

رفتار Source Generatorها در NET 11.
نکته حائز اهمیتی که باید به آن اشاره کرد، هماهنگی کامل این قابلیت با Configuration Binding Source Generator است که در نسخه‌های اخیر .NET برای افزایش کارایی و سازگاری با AOT (Ahead-Of-Time compilation) معرفی شده است.
در .NET 11، سورس‌ژنراتورها به گونه‌ای به‌روزرسانی شده‌اند که در زمان کامپایل، ویژگی‌های مزین به [ConfigurationIgnore] را شناسایی کرده و هیچ کدی برای نگاشت آن‌ها تولید نمی‌کنند. این امر باعث می‌شود که برنامه شما نه تنها از نظر طراحی تمیزتر باشد، بلکه از نظر مصرف حافظه و سرعت اجرا (Performance) در زمان بالا آمدن اپلیکیشن (Startup) نیز بهینه‌تر عمل کند.

نتیجه‌گیری
معرفی ConfigurationIgnoreAttribute در .NET 11، شاید در نگاه اول یک تغییر کوچک به نظر برسد، اما یک نقص طراحی قدیمی را برطرف کرده و به توسعه‌دهندگان اجازه می‌دهد کدهایی تمیزتر، امن‌تر و با قابلیت نگهداری بالاتر بنویسند. با این قابلیت، جداسازی میان «داده‌های خام ورودی تنظیمات» و «وضعیت‌های محاسباتی درون برنامه» به بهترین شکل ممکن پیاده‌سازی می‌شود. اگر در حال مهاجرت یا شروع پروژه‌ای با .NET 11 هستید، پیشنهاد می‌شود تمامی راهکارهای موقت قبلی را با این اتریبیوت بومی جایگزین کنید.