Configuration Basics

קבצי הקונפיגורציה

מהו מודל הקונפיגורציה שיש לנו בעולם ב-asp.net?

יש לנו את קובץ appsettings.json שהוא קובץ הקונפוגורציה של המערכת.

כדי לגשת לקובץ הקונפיגורציה היינו כותבים ConfigurationManager.Appsettings וניגשים למשתנה שאנחנו צריכים. הבעיה היא שהפנייה הזאת היא לא אבסטרקטית, היא מובילה אותנו לקובץ קונפיגורציה מסויים.

ניגש לקובץ קונפיגורציה ונוסיף לו ערך:

קובץ appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "courseName": "asp.net-core"
}

כאשר רוצים להציג את הערך הזה מהקונפיגורציה:

קובץ program.cs

app.Run(async (context) => {
    var valueFromConfig = app.Configuration["courseName"];
    await context.Response.WriteAsync(valueFromConfig);
});

אם נריץ את התוכנית נראה שהערך מודפס.

יש לנו עוד קובץ קונפיגורציה, נוסיף גם בו את הערך courseName.

קובץ appsettings.development.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "courseName":  "asp.net-core - dev file"
}

אם נריץ עכשיו נראה שאנחנו מקבלים את הערך של קובץ ה-dev.

אם נשים ערך אחר, כמו PATH ונריץ נראה שנקבל תוכן, למרות שהערך הזה לא נמצא באץ אחד מקבצי הקונפוגורציה.

app.Run(async (context) => {
    var valueFromConfig = app.Configuration["PATH"];
    await context.Response.WriteAsync(valueFromConfig);
});

מה זה אומר שמודל הקונפיגורציה זה לא קבצי הקונפוגורציה, שהם רק חלק מהמודל הזה. יש כל מיני services שמזינים ערכים למודל הזה.

מודל הקונפיגורציה

ראינו שיש כל מיני מקורות לקונפיגורציות, למשל PATH מגיע מתוך environment variables שהם גם מקור לקונפיגורציה.

מודל הקונפיגורציה נבנה ותוך כדי מייצר את הקבצים שראינו, ואת ה-environment variables וניתן לגשת אליו לקרוא ולשנות ערכים, כמו שעשינו ב-middleware.

קריאת קונפיגורציה מתוך ה-middleware

ראינו אפשרות להסיר headers עם middleware.

public Task Invoke(HttpContext httpContext) {
    var headersToRemove = new string[] { "Powered-by", "hosting-server" };
    foreach (var headerkey in headersToRemove) {
        httpContext.Response.Headers.Remove(headerkey);
    }
    return _next(httpContext);
}

נניח שאנחנו רוצים לקבל את הערכים של ה-header מתוך הקונפיגורציה ולא לרשום אותם ישירות.

כדי להשתמש ב-configuration נעביר אותו לפונקציה:

public Task Invoke(HttpContext httpContext, IConfiguration config) {
...
}

בקובץ הקונפיגורציה נכניס את המפתח והערכים שאנחנו צריכים.

קובץ appsettings.json

{
  ...
  "HeadersToRemove": "Powered-by,hosting-server",
  "AllowedHosts": "*",
  "courseName": "asp.net-core"
}

ואז נשלוף את הערכים מתוך משתנה ה-config.

קובץ ה-middleware.

public Task Invoke(HttpContext httpContext, IConfiguration config) {
    var headersToRemove = config["HeadersToRemove"].Split(',');
    foreach (var headerkey in headersToRemove) {
        httpContext.Response.Headers.Remove(headerkey);
    }
    return _next(httpContext);
}

בצורה הזאת אנחנו מקבלים את הערכים, אבל מתעסקים עם הפיצול של המחרוזת. נגדיר מחדש את האובייקט בקובץ הקונפיגורציה.

קובץ appsettings.json

{
  ...
  "HeadersToRemove": {
    "Enabled": true,
    "HeaderKeys": [
      "Powered-by",
      "hosting-server"
    ]
  },
  "AllowedHosts": "*",
  "courseName": "asp.net-core"
}

עכשיו אנחנו צריכים לקרוא את מודל הקונפיגורציה. נייצר מודל שיחזיר את האובייקט שאנחנו רוצים להציג.

יצירת Configurate Model

ניצור בתיקיית models תיקייה חדשה. בתוך התיקייה ניצור class חדש שנקרא לו HeadersRemoveConfig

קובץ HeadersRemoveConfig.cs

namespace FirstWebApp.Models.Config;

public class HeadersRemoveConfig
{
    public bool Enabled { get; set; }
    public string[] HeaderKeys { get; set; } = new string[0];

}

עכשיו צריך ליצור instance של ה-class הזה ולהכניס לתוכו את הנתונים שאנחנו צריכים.

בשביל זה אנחנו נחזור לקובץ ה-program שבו החלק העליון הוא קונפיגורציה עד החלק של ה-build. שם נבנה את האובייקט ונשאב את הערכים שלו מקובץ הקונפיגורציה.

קובץ program.cs

using FirstWebApp.Models.Config;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.Configure<HeadersRemoveConfig>(builder.Configuration.GetSection("HeadersToRemove"));

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();

עכשיו כשרוצים להשתמש בערך שנוצר במופע הזה אנחנו צריכים לבקש את מודל הנתונים הזה בתוך ה-middleware. נקבל את הערך בתוך ארגומנט options של הפונקציה, ואז תהיה לנו גישה למשתנים של ערך הקונפיגורציה הזה.

קובץ ה-middleware

public Task Invoke(HttpContext httpContext, IOptions<HeadersRemoveConfig> options)
{
    var headerConfig = options.Value;
    if (headerConfig.Enabled) {
        foreach (var headerkey in headerConfig.HeaderKeys) {
            httpContext.Response.Headers.Remove(headerkey);
        }
    }          
    return _next(httpContext);
}

לסיכום: כאשר אנחנו רוצים לקבל ערכים מה-config והם לא מחרוזת פשוטה, אלא הם מהווים מודל נתונים. למשל מתחסרים לשרת שצריך ערכים לחיבור שמוגדרים בקונפיגורציה, אנחנו מייצרים מודל שיתמוך בנתונים האלה, לטעון את המודל ב-program ואז אפשר להשתמש בו.

IOptions vs. IOptionsSnapshot

יש בעיה שאם שיניתי את ערכי הקונפוגורציה ב-runtime הערכים לא מתעדכנים. זה קורה כי אנחנו משתמשים ב-IOptions, שהוא לוקח את ערכי הקונפיגורציה בזמן עליית המערכת. כדי שהערכים יתעדכנו, במקום להשתמש ב-IOptions נשתמש ב-IOptionsSnapshot. לא צריך שינוי אחר.