عنوان:

‫مدیریت فایل‌های تنظیمات در برنامه‌های NET.: راهکاری برای کار تیمی و محیط‌های مختلف


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۸/۱۱ ۱۰:۳۵
آدرس: www.dntips.ir
فایل‌های تنظیمات مانند appsettings.json نقش کلیدی را در مدیریت رفتار برنامه در محیط‌های مختلف ایفا می‌کنند. این فایل‌ها به ما اجازه می‌دهند تا پیکربندی‌هایی مانند رشته‌های اتصال به پایگاه داده یا تنظیمات سرور را بدون تغییر کد اصلی، سفارشی کنیم. اما وقتی پای کار تیمی به میان می‌آید، چالش‌هایی مانند هماهنگی تنظیمات محلی هر توسعه‌دهنده یا شبیه‌سازی محیط‌های staging و production در حین توسعه پیش می‌آید. در این مقاله، به بررسی این مسائل می‌پردازیم و راهکارهایی عملی ارائه می‌دهیم تا کار شما روان‌تر شود. هدف این است که بتوانید بدون دردسر، برنامه‌تان را در محیط‌های مختلف تست کنید و در تیم، از تداخل تنظیمات جلوگیری کنید.

یک نکته‌: علاوه بر فایل‌های JSON استاندارد، .NET از منابع دیگری مانند متغیرهای محیطی (Environment Variables) یا سرویس‌های خارجی مانند Azure App Configuration پشتیبانی می‌کند. این منابع می‌توانند لایه‌های امنیتی بیشتری اضافه کنند، به‌خصوص برای اطلاعات حساس مانند کلیدهای API، که نباید در منبع کنترل نسخه (مانند Git) ذخیره شوند. در ادامه، بر روی فایل‌های محلی تمرکز می‌کنیم، اما پیشنهاد می‌کنم برای محیط‌های تولید، از User Secrets یا متغیرهای محیطی استفاده کنید تا امنیت حفظ شود.

توضیح مسئله
یکی از مشکلات رایج در توسعه برنامه‌های .NET، مدیریت تنظیمات در محیط‌های مختلف است. به‌طور پیش‌فرض، وقتی برنامه را با dotnet run اجرا می‌کنید، محیط به Development تنظیم می‌شود. اما اغلب نیاز دارید رفتار برنامه را در محیط‌های staging (برای تست) یا production (برای تولید) شبیه‌سازی کنید، بدون اینکه برنامه را مستقر کنید. این کار برای بررسی تنظیمات خاص هر محیط ضروری است، مانند رنگ‌های رابط کاربری یا اتصالات متفاوت.
در کار تیمی، مسئله پیچیده‌تر می‌شود. فرض کنید یک API ساده دارید که به پایگاه داده و Redis متصل می‌شود. هر توسعه‌دهنده ممکن است از منابع محلی متفاوتی استفاده کند؛ مثلاً یکی از SQL Server محلی و دیگری از Docker. اگر همه تنظیمات را در appsettings.Development.json ذخیره کنید، هر بار که کسی تغییری می‌دهد، این فایل در Git به‌روزرسانی می‌شود و توسعه‌دهندگان دیگر مجبور به اصلاح مداوم آن می‌شوند. این چرخه “گربه و موش” نه‌تنها وقت‌گیر است، بلکه خطر فراموش کردن تغییرات در زمان استقرار را افزایش می‌دهد؛ مثلاً استفاده از تنظیمات توسعه در تولید، که می‌تواند به مشکلات امنیتی یا عملکردی منجر شود. فراموش کردن به‌روزرسانی تنظیمات می‌تواند عواقب جدی داشته باشد، مانند اتصال به پایگاه داده تولید از محیط توسعه، که داده‌های واقعی را در معرض خطر قرار می‌دهد. همچنین، در تیم‌های بزرگ، عدم نسخه‌بندی تنظیمات محلی می‌تواند به عدم هماهنگی منجر شود، جایی که تنظیمات یک نفر با دیگری تداخل دارد.

راه‌حل قدیمی
در گذشته، راهکار اصلی استفاده از فایل appsettings.json به‌عنوان پایه و فایل‌های محیطی مانند appsettings.Development.json بود. .NET به‌طور خودکار فایل مناسب را بر اساس محیط بارگذاری می‌کند و تنظیمات محیطی بر تنظیمات پایه اولویت دارند.
برای مثال، در یک پروژه API، کلاس ConnectionStrings را تعریف می‌کنید:
public class ConnectionStrings
{
    public string DatabaseConnectionString { get; set; }
    public string RedisConnectionString { get; set; }
}
سپس در appsettings.json تنظیمات پایه (معمولاً برای تولید) را قرار می‌دهید:
{
  "ConnectionStrings": {
    "DatabaseConnectionString": "data source=SMEAGOL",
    "RedisConnectionString": "ARAGORN"
  }
}
و در appsettings.Development.json تنظیمات محلی را override می‌کنید:
{
  "ConnectionStrings": {
    "DatabaseConnectionString": "data source=FRODO",
    "RedisConnectionString": "ARWEN"
  }
}
در کد startup، تنظیمات را به DI متصل می‌کنید:
builder.Services.AddOptions<ConnectionStrings>()
    .Bind(builder.Configuration.GetSection("ConnectionStrings"));
این روش ساده است، اما در تیم مشکل‌ساز می‌شود. برای حل، برخی appsettings.Development.json را به .gitignore اضافه می‌کنند تا محلی بماند. اما این کار مزایای منبع کنترل را از دست می‌دهد؛ نمی‌توانید تنظیمات همکاران را ببینید یا تغییرات را ردیابی کنید.

راه‌حل جدید
برای غلبه بر مشکلات، از قابلیت‌های پیشرفته .NET استفاده کنید. ابتدا برای شبیه‌سازی محیط‌ها در توسعه، از پارامتر خط فرمان --Environment استفاده کنید (توجه: این پارامتر حساس به حروف بزرگ و کوچک است و مستندات فعلی ممکن است اشتباه --environment را پیشنهاد کنند).

مثال: اگر فایل appsettings.Staging.json دارید:
{
  "SystemSettings": {
    "ForegroundColour": "Cyan",
    "BackgroundColour": "FireRed"
  }
}
با دستور dotnet run --Environment Staging اجرا کنید. .NET فایل مربوطه را بارگذاری می‌کند.

برای جلوگیری از تداخل تنظیمات توسعه در محیط‌های دیگر، کد را شرطی کنید:
if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddJsonFile($"appsettings.{Environment.MachineName}.json", optional: true);
}

حالا برای کار تیمی: هر توسعه‌دهنده فایل appsettings.{MachineName}.json ایجاد کند (مثلاً appsettings.Ciara.json اگر نام ماشین Ciara باشد):
{
  "ConnectionStrings": {
    "DatabaseConnectionString": "data source=BOMBADIL",
    "RedisConnectionString": "MERIADOC"
  }
}
در کد startup، آن را شرطی بارگذاری کنید (با optional: false برای اجبار ایجاد فایل):
if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddJsonFile($"appsettings.{Environment.MachineName}.json", optional: false);
}
اگر فایل پیدا نشود، خطا می‌دهد تا توسعه‌دهنده مجبور به ایجاد آن شود.

کد کامل startup:
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddJsonFile($"appsettings.{Environment.MachineName}.json", optional: false);
}

builder.Services.AddOptions<ConnectionStrings>()
    .Bind(builder.Configuration.GetSection("ConnectionStrings"));

var app = builder.Build();

app.MapGet("/", (IOptions<ConnectionStrings> options) =>
{
    var settings = options.Value;
    return new
    {
        Database = settings.DatabaseConnectionString,
        Redis = settings.RedisConnectionString
    };
});

app.Run();
نکته اضافی: برای امنیت بیشتر، اطلاعات حساس را در secrets.json (از طریق User Secrets) ذخیره کنید، که به‌طور خودکار به .gitignore اضافه می‌شود. این روش با راهکار ما سازگار است و لایه حفاظتی اضافه می‌کند.

نتیجه‌گیری
با این رویکردها، می‌توانید محیط‌های مختلف را به‌راحتی شبیه‌سازی کنید و در تیم، بدون تداخل کار کنید. مزایا شامل نسخه‌بندی تنظیمات، دسترسی به پیکربندی همکاران، و کاهش خطاهای انسانی است.