مقالات › ASP.NET Core

پیکربندی در ASP.NET Core؛ appsettings، محیط‌ها، Secretها و الگوی Options

پیکربندی ASP.NET Core را کامل یاد بگیرید: Providerها و اولویتشان، تنظیمات مخصوص هر محیط، User Secrets، متغیرهای محیطی، تفاوت IOptions و IOptionsSnapshot و IOptionsMonitor و اعتبارسنجی.

هر برنامه‌ای به تنظیمات نیاز دارد: Connection String، کلید API، Feature Flag و محدودیت‌ها. نوشتن مستقیم آن‌ها در کد انتشار را دردسرساز می‌کند و رمزها را وارد سورس‌کنترل می‌کند. ASP.NET Core یک سیستم پیکربندی انعطاف‌پذیر دارد که از منابع متعدد می‌خواند و مقادیر را به کلاس‌های Strongly-Typed متصل می‌کند. این راهنما نشان می‌دهد چطور کار می‌کند و چطور امن از آن استفاده کنید.

Providerهای پیکربندی و اولویت آن‌ها

WebApplication.CreateBuilder پیکربندی را از چند منبع و به ترتیب بارگذاری می‌کند؛ منابع بعدی مقادیر قبلی را بازنویسی می‌کنند:

  1. appsettings.json
  2. appsettings.{Environment}.json (مثلاً appsettings.Production.json)
  3. User Secrets (فقط در Development)
  4. متغیرهای محیطی (Environment Variables)
  5. آرگومان‌های خط فرمان

پس مقداری در appsettings.json را می‌توان روی سرور با یک متغیر محیطی بازنویسی کرد، بدون تغییر هیچ فایلی؛ دقیقاً همان چیزی که برای انتشار لازم دارید.

خواندن مقادیر

{
  "ConnectionStrings": {
    "Default": "Server=.;Database=Portfolio;Trusted_Connection=True;TrustServerCertificate=True"
  },
  "Jwt": {
    "Issuer": "PortfolioApi",
    "Audience": "PortfolioClient",
    "AccessTokenMinutes": 30
  },
  "Features": {
    "EnableComments": true
  }
}
var connection = builder.Configuration.GetConnectionString("Default");
var issuer = builder.Configuration["Jwt:Issuer"];                 // ":" بخش‌ها را جدا می‌کند
var minutes = builder.Configuration.GetValue<int>("Jwt:AccessTokenMinutes");

این کار در Program.cs مشکلی ندارد، اما پخش کردن رشته‌های IConfiguration["Some:Key"] در سرویس‌ها شکننده است: غلط املایی بی‌صدا در زمان اجرا شکست می‌خورد و هیچ چیز مستند نمی‌کند چه تنظیماتی وجود دارد. الگوی Options همین مشکل را حل می‌کند.

الگوی Options

کلاسی تعریف کنید که آینه‌ی یک بخش از تنظیمات باشد:

public class JwtOptions
{
    public const string SectionName = "Jwt";

    [Required] public string Issuer { get; init; } = "";
    [Required] public string Audience { get; init; } = "";
    [Required, MinLength(32)] public string Key { get; init; } = "";
    [Range(1, 1440)] public int AccessTokenMinutes { get; init; } = 30;
}

آن را ثبت و اعتبارسنجی کنید:

builder.Services.AddOptions<JwtOptions>()
    .BindConfiguration(JwtOptions.SectionName)
    .ValidateDataAnnotations()
    .Validate(o => o.Issuer != o.Audience, "Issuer and Audience must differ.")
    .ValidateOnStart();

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

هر جا لازم است تزریقش کنید:

public class TokenService(IOptions<JwtOptions> options)
{
    private readonly JwtOptions _jwt = options.Value;
    // _jwt.Issuer، _jwt.AccessTokenMinutes و...
}

تفاوت IOptions، IOptionsSnapshot و IOptionsMonitor

اینترفیس طول عمر بارگذاری مجدد با تغییر زمان استفاده
IOptions<T> Singleton ❌ تنظیمات در طول عمر برنامه ثابت‌اند (بیشتر موارد)
IOptionsSnapshot<T> Scoped ✅ در هر درخواست تنظیمات ممکن است تغییر کنند و در یک سرویس Scoped هستید
IOptionsMonitor<T> Singleton ✅ زنده، همراه با OnChange Singletonها و سرویس‌های پس‌زمینه که باید به تغییرات واکنش نشان دهند
public class FeatureGate(IOptionsMonitor<FeatureOptions> monitor)
{
    public bool CommentsEnabled => monitor.CurrentValue.EnableComments;
}

فایل‌های JSON با تغییر، خودکار دوباره بارگذاری می‌شوند (reloadOnChange: true به‌طور پیش‌فرض)؛ پس IOptionsMonitor اجازه می‌دهد قابلیت‌ها را بدون Restart روشن یا خاموش کنید.

محیط‌ها

ASP.NET Core محیط را از ASPNETCORE_ENVIRONMENT می‌خواند (Development، Staging، Production). از آن برای تغییر رفتار استفاده کنید:

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

و مقادیر مخصوص هر محیط را در appsettings.Development.json یا appsettings.Production.json قرار دهید. فایل پایه را به‌عنوان پیش‌فرض مشترک نگه دارید.

Secretها: هرگز در سورس‌کنترل

محیط توسعه: User Secrets

dotnet user-secrets init
dotnet user-secrets set "Jwt:Key" "a-long-random-development-key-of-32+-chars"

Secretها در پروفایل کاربری شما، بیرون از پوشه‌ی پروژه ذخیره می‌شوند و در Development خودکار بارگذاری می‌شوند.

Production: متغیر محیطی یا Vault

متغیرهای محیطی برای تودرتویی از دو زیرخط استفاده می‌کنند:

Jwt__Key=super-secret-production-key
ConnectionStrings__Default="Server=db;Database=Portfolio;User Id=app;Password=..."

در IIS می‌توانید آن‌ها را در web.config (بخش <environmentVariables>) یا Application Pool تنظیم کنید؛ در Docker با -e یا سازوکار Secret؛ و در فضای ابری از Azure Key Vault، AWS Secrets Manager یا Providerهای مشابه استفاده کنید.

هشدار: اگر یک Secret حتی یک بار Commit شده، آن را لورفته فرض کنید و عوضش کنید. حذف آن از آخرین Commit، آن را از تاریخچه‌ی Git پاک نمی‌کند.

منابع پیکربندی سفارشی

سیستم پیکربندی قابل گسترش است. می‌توانید فایل‌های JSON دیگر، مقادیر داخل حافظه برای تست یا حتی یک Provider مبتنی بر دیتابیس اضافه کنید:

builder.Configuration
    .AddJsonFile("features.json", optional: true, reloadOnChange: true)
    .AddInMemoryCollection(new Dictionary<string, string?> { ["Features:Beta"] = "false" });

تست با پیکربندی

کلاس‌های Options تست واحد را بسیار ساده می‌کنند؛ نیازی به سیستم پیکربندی نیست:

var options = Options.Create(new JwtOptions
{
    Issuer = "test", Audience = "tests", Key = new string('k', 32), AccessTokenMinutes = 5
});
var service = new TokenService(options);

در تست‌های یکپارچه، WebApplicationFactory اجازه می‌دهد تنظیمات را با ConfigureAppConfiguration بازنویسی کنید.

بهترین روش‌ها

  1. تنظیمات مرتبط را در بخش‌ها گروه‌بندی و به کلاس‌های Options متصل کنید.
  2. در شروع برنامه اعتبارسنجی کنید؛ با پیام روشن سریع شکست بخورید.
  3. Secretها را بیرون از appsettings.json ثبت‌شده در Git نگه دارید.
  4. برای مقادیر مخصوص هر سرور از متغیرهای محیطی استفاده کنید.
  5. مگر اینکه واقعاً به بارگذاری مجدد نیاز دارید، IOptions<T> را ترجیح دهید.
  6. IConfiguration را به سرویس‌های بیزینس تزریق نکنید؛ Options تزریق کنید.

جمع‌بندی

ASP.NET Core پیکربندی را به‌صورت لایه‌ای از فایل‌های JSON، فایل‌های مخصوص محیط، Secretها، متغیرهای محیطی و آرگومان‌های خط فرمان می‌سازد و منبع بعدی برنده است. بخش‌ها را به کلاس‌های Options Strongly-Typed متصل کنید، در شروع برنامه اعتبارسنجی کنید، بسته به اینکه مقادیر می‌توانند تغییر کنند یا نه بین IOptions، IOptionsSnapshot و IOptionsMonitor انتخاب کنید و Secretها را در محیط توسعه در User Secrets و در Production در متغیرهای محیطی یا یک Vault نگه دارید.

پرسش‌های متداول

چرا فایل مخصوص محیط من بارگذاری نمی‌شود؟

ASPNETCORE_ENVIRONMENT را بررسی کنید. نام فایل باید دقیقاً منطبق باشد؛ مثلاً برای Production فایل appsettings.Production.json.

مقادیر تودرتو را چطور با متغیر محیطی تنظیم کنم؟

از دو زیرخط استفاده کنید: Jwt__Key به Jwt:Key نگاشت می‌شود. در لینوکس : در نام متغیر مجاز نیست.

آیا می‌توانم تنظیمات را بدون Restart تغییر دهم؟

برای تنظیمات فایل JSON که از طریق IOptionsMonitor یا IOptionsSnapshot خوانده می‌شوند بله. تغییر متغیرهای محیطی به Restart نیاز دارد.