ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: سیزده دقیقه

یکی دیگر از تغییرات ASP.NET Core با نگارش‌های قبلی آن، تغییرات اساسی در مورد نحوه‌ی کار با تنظیمات برنامه و فایل‌های مرتبط با آن‌ها است. در ASP.NET Core می‌توانید:
- تنظیمات برنامه را از چندین منبع مختلف خوانده و آن‌ها را یکی کنید.
- تنظیمات را بر اساس تنظیمات مختلف محیطی برنامه، بارگذاری کنید.
- امکان نگاشت اطلاعات خوانده شده‌ی از فایل‌های کانفیگ به کلاس‌ها پیش بینی شده‌است.
- امکان بارگذاری مجدد فایل‌های کانفیگ درصورت تغییر، بدون ری‌استارت کل برنامه وجود دارد.
- امکان تزریق وابستگی‌های تنظیمات برنامه، به قسمت‌های مختلف آن پیش بینی شده‌است.


نصب پیشنیاز خواندن تنظیمات برنامه از یک فایل JSON

برای شروع به کار با خواندن تنظیمات برنامه در ASP.NET Core، نیاز است ابتدا بسته‌ی نیوگت Microsoft.Extensions.Configuration.Json را نصب کنیم.
برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن Microsoft.Extensions.Configuration.Json را جستجو کرده و نصب نمائید:


البته همانطور که در تصویر مشاهده می‌کنید، اگر صرفا Microsoft.Extensions.Configuration را جستجو کنید (بدون ذکر JSON)، بسته‌های مرتبط با خواندن فایل‌های کانفیگ از نوع XML و یا حتی INI را هم خواهید یافت.
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{
    "dependencies": {
         //same as before  
         "Microsoft.Extensions.Configuration.Json": "1.0.0"
    },

 
افزودن یک فایل کانفیگ JSON دلخواه

بر روی پروژه کلیک راست کرده و از طریق منوی add->new item یک فایل خالی جدید را به نام appsettings.json ایجاد کنید (نام این فایل دلخواه است)؛ با این محتوا:
{
    "Key1": "Value1",
    "Auth": {
        "Users": [ "Test1", "Test2", "Test3" ]
    },
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    }
}
در نگارش‌های پیشین ASP.NET که از web.config برای تعریف تنظیمات برنامه استفاده می‌شد، حالت پیش فرض ذکر تنظیمات برنامه می‌توانست تنها یک سطحی و با ساختار ذیل باشد (البته امکان کدنویسی و نوشتن مداخل سفارشی هم وجود داشت؛ ولی حالت پیش فرض appSettings، تنها key/valueهای یک سطحی هستند):
<appSettings>
   <add key="Logging-IncludeScopes" value="false" />
   <add key="Logging-Level-Default" value="verbose" />
   <add key="Logging-Level-System" value="Information" />
   <add key="Logging-Level-Microsoft" value="Information" />
</appSettings>
اما اکنون یک فایل JSON را با هر تعداد سطح مورد نیاز می‌توان تعریف و استفاده کرد و برای اینکار نیازی به نوشتن کدهای سفارشی تعریف مداخل خاص، وجود ندارد.
در فایل JSON فوق، نمونه‌ای از key/valueها، آرایه‌ها و اطلاعات چندین سطحی را مشاهده می‌کنید.


خواندن فایل تنظیمات appsettings.json در برنامه

پس از نصب پیشنیاز خواندن فایل‌های کانفیگ از نوع JSON، به فایل آغازین برنامه مراجعه کرده و سازنده‌ی جدیدی را به آن اضافه کنید:
public class Startup
{
    public IConfigurationRoot Configuration { set; get; }
 
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
                            .SetBasePath(env.ContentRootPath)
                            .AddJsonFile("appsettings.json");
        Configuration = builder.Build();
    }
در اینجا نحوه‌ی خواندن فایل کانفیگ جدید appsettings.json را مشاهده می‌کنید. چند نکته در اینجا حائز اهمیت هستند:
الف) این خواندن، در سازنده‌ی کلاس آغازین برنامه و پیش از تمام تنظیمات دیگر باید انجام شود.
ب) جهت در معرض دید قرار دادن اطلاعات خوانده شده، آن‌را به یک خاصیت عمومی انتساب داده‌ایم.
ج) متد SetBasePath جهت مشخص کردن محل یافتن فایل appsettings.json ذکر شده‌است. این اطلاعات را می‌توان از سرویس توکار IHostingEnvironment و خاصیت ContentRootPath آن دریافت کرد. همانطور که ملاحظه می‌کنید، این تزریق وابستگی نیز به صورت خودکار توسط ASP.NET Core مدیریت می‌شود.


دسترسی به تنظیمات خوانده شده توسط اینترفیس IConfigurationRoot

تا اینجا موفق شدیم تا تنظیمات خوانده شده را به خاصیت عمومی Configuration از نوع IConfigurationRoot انتساب دهیم. اما ساختار ذخیره شده‌ی در این اینترفیس به چه صورتی است؟


همانطور که مشاهده می‌کنید، هر سطح از سطح قبلی آن با : جدا شده‌است. همچنین اعضای آرایه، دارای ایندکس‌های 0: و 1: و 2: هستند. بنابراین برای خواندن این اطلاعات می‌توان نوشت:
var key1 = Configuration["Key1"];
var user1 = Configuration["Auth:Users:0"];
var authUsers = Configuration.GetSection("Auth:Users").GetChildren().Select(x => x.Value).ToArray();
var loggingIncludeScopes = Configuration["Logging:IncludeScopes"];
var loggingLoggingLogLevelDefault = Configuration["Logging:LogLevel:Default"];
خاصیت Configuration نیز در نهایت بر اساس key/valueها کار می‌کند و این keyها اگر چند سطحی بودند، با : از هم جدا می‌شوند و اگر نیاز به دسترسی اعضای خاصی از آرایه‌ها وجود داشت می‌توان آن ایندکس خاص را در انتهای زنجیره ذکر کرد. همچنین در اینجا نحوه‌ی استخراج تمام اعضای یک آرایه را نیز مشاهده می‌کنید.

یک نکته: خاصیت Configuration، دارای متد GetValue نیز هست که توسط آن می‌توان نوع مقدار دریافتی و یا حتی مقدار پیش فرضی را در صورت عدم وجود این key، مشخص کرد:
 var val = Configuration.GetValue<int>("key-name", defaultValue: 10);
در متد GetValue، آرگومان جنریک آن، یک کلاس را نیز می‌پذیرد. یعنی می‌توان خواص تو در توی مشخص شده‌ی با : را به یک کلاس نیز نگاشت کرد. در اینجا مقدار کلید معرفی شده، اولین سطحی خواهد بود که باید این اطلاعات از آن استخراج و نگاشت شوند.


سرویس IConfigurationRoot قابل تزریق است

در قسمت قبل، سرویس‌ها و تزریق وابستگی‌ها را بررسی کردیم. نکته‌ی جالبی را که می‌توان به آن اضافه کرد، قابلیت تزریق خاصیت عمومی
public class Startup
{
    public IConfigurationRoot Configuration { set; get; }
به تمام قسمت‌های برنامه است. برای نمونه در همان مثال قسمت قبل، قصد داریم تنظیمات برنامه را در لایه سرویس آن خوانده و مورد استفاده قرار دهیم. برای اینکار باید مراحل ذیل طی شوند:
الف) اعلام موجودیت IConfigurationRoot به IoC Container
اگر از استراکچرمپ استفاده می‌کنید، باید مشخص کنید، زمانیکه IConfigurationRoot درخواست شد، آن‌را چگونه باید از خاصیت مرتبط با آن دریافت کند:
var container = new Container();
container.Configure(config =>
{
    config.For<IConfigurationRoot>().Singleton().Use(() => Configuration);
و یا اگر از همان IoC Container توکار ASP.NET Core استفاده می‌کنید، روش انجام این‌کار در متد ConfigureServices به صورت زیر است:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
طول عمر آن هم singleton مشخص شده‌است تا تنها یکبار وهله سازی و سپس کش شود (مناسب برای کار با تنظیمات سراسری برنامه).

ب) فایل project.json کتابخانه‌ی Core1RtmEmptyTest.Services را گشوده و وابستگی Microsoft.Extensions.Configuration.Abstractions را به آن اضافه کنید:
{ 
    "dependencies": {
        //same as before 
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0"
    }
این وابستگی امکان دسترسی به اینترفیس IConfigurationRoot را در اسمبلی‌های دیگر میسر می‌کند.

ج) سپس فایل MessagesService.cs را گشوده و این اینترفیس را به سازنده‌ی سرویس MessagesService تزریق می‌کنیم:
public interface IMessagesService
{
    string GetSiteName();
}
 
public class MessagesService : IMessagesService
{
    private readonly IConfigurationRoot _configurationRoot;
 
    public MessagesService(IConfigurationRoot configurationRoot)
    {
        _configurationRoot = configurationRoot;
    }
 
    public string GetSiteName()
    {
        var key1 = _configurationRoot["Key1"];
        return $"DNT {key1}";
    }
}
در ادامه، نحوه‌ی استفاده‌ی از آن، همانند نکاتی است که در قسمت «دسترسی به تنظیمات خوانده شده توسط اینترفیس IConfigurationRoot» عنوان شد.
اکنون اگر برنامه را اجرا کنید، با توجه به اینکه میان افزار Run از این سرویس سفارشی استفاده می‌کند:
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
{ 
    app.Run(async context =>
    {
        var siteName = messagesService.GetSiteName();
        await context.Response.WriteAsync($"Hello {siteName}");
    });
}
چنین خروجی را خواهیم داشت:



خواندن تنظیمات از حافظه

الزاما نیازی به استفاده از فایل‌های JSON و یا XML در اینجا وجود ندارد. ابتدایی‌ترین حالت کار با بسته‌ی Microsoft.Extensions.Configuration، متد AddInMemoryCollection آن است که در اینجا می‌توان لیستی از key/value‌ها را ذکر کرد:
var builder = new ConfigurationBuilder()
                    .AddInMemoryCollection(new[]
                                {
                                    new KeyValuePair<string,string>("the-key", "the-value"),
                                });
 و نحوه‌ی کار با آن نیز همانند قبل است:
 var theValue = Configuration["the-key"];


امکان بازنویسی تنظیمات انجام شده، بسته به شرایط محیطی

در اینجا محدود به یک فایل JSON و یک فایل تنظیمات برنامه، نیستیم. برای کار با ConfigurationBuilder می‌توان از Fluent interface آن استفاده کرد و به هر تعدادی که نیاز بود، متدهای خواندن از فایل‌های کانفیگ دیگر را اضافه کرد:
public class Startup
{
    public IConfigurationRoot Configuration { set; get; }
 
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
                            .SetBasePath(env.ContentRootPath)
                            .AddInMemoryCollection(new[]
                                {
                                    new KeyValuePair<string,string>("the-key", "the-value"),
                                })
                            .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false)
                            .AddJsonFile($"appsettings.{env}.json", optional: true);
        Configuration = builder.Build();
    }
و نکته‌ی مهم اینجا است که تنظیمات فایل دوم، تنظیمات مشابه فایل اول را بازنویسی می‌کند.
برای مثال در اینجا آخرین AddJsonFile تعریف شده، بنابر متغیر محیطی فعلی به appsettings.development.json تفسیر شده و در صورت وجود این فایل (با توجه به optional بودن آن) اطلاعات آن دریافت گردیده و اطلاعات مشابه فایل appsettings.json قبلی را بازنویسی می‌کند.


امکان دسترسی به متغیرهای محیطی سیستم عامل

در انتهای زنجیره‌ی ConfigurationBuilder می‌توان متد AddEnvironmentVariables را نیز ذکر کرد:
 var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
این متد سبب می‌شود تا تمام اطلاعات قسمت Environment سیستم عامل، به مجموعه‌ی تنظیمات جاری اضافه شوند (در صورت نیاز) که نمونه‌ای از آن‌را در تصویر ذیل مشاهده می‌کنید:



امکان نگاشت تنظیمات برنامه به کلاس‌‌های متناظر

کار کردن با key/valueهای رشته‌ای، هرچند روش پایه‌ای استفاده‌ی از تنظیمات برنامه است، اما آنچنان refactoring friendly نیست. در ASP.NET Core امکان تعریف تنظیمات strongly typed نیز پیش بینی شده‌است. برای این منظور باید مراحل زیر طی شوند:
به عنوان نمونه تنظیمات فرضی smtp ذیل را به انتهای فایل appsettings.json اضافه کنید:
{
    "Key1": "Value1",
    "Auth": {
        "Users": [ "Test1", "Test2", "Test3" ]
    },
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "Smtp": {
        "Server": "0.0.0.1",
        "User": "user@company.com",
        "Pass": "123456789",
        "Port": "25"
    }
}
مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شده‌است، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشه‌ی src کلیک راست کرده و گزینه‌ی Add new project را انتخاب کنید و سپس یک پروژه‌ی جدید از نوع NET Core -> Class library. را به آن با نام Core1RtmEmptyTest.ViewModels اضافه کنید (تصویر ذیل).


در این کتابخانه‌ی جدید که محل نگهداری ViewModelهای برنامه خواهد بود، کلاس معادل قسمت smtp فایل config فوق را اضافه کنید:
namespace Core1RtmEmptyTest.ViewModels
{
    public class SmtpConfig
    {
        public string Server { get; set; }
        public string User { get; set; }
        public string Pass { get; set; }
        public int Port { get; set; }
    }
}
از این جهت این کلاس را در یک library جداگانه قرار داده‌ایم تا بتوان از آن در لایه‌ی سرویس و همچنین خود برنامه استفاده کرد. اگر این کلاس را در برنامه‌ی اصلی قرار می‌دادیم، امکان دسترسی به آن در لایه‌ی سرویس میسر نمی‌شد.
سپس به پروژه‌ی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.ViewModels را انتخاب کنید، تا اسمبلی آن‌را بتوان در پروژه‌ی جاری استفاده کرد.
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{
    "dependencies": {
        // same as before        
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*"
    },
اکنون با فرض وجود تنظیمات خواندن فایل appsettings.json در سازنده‌ی کلاس آغازین برنامه، نیاز است بسته‌ی نیوگت Microsoft.Extensions.Configuration.Binder را نصب کنید:


و سپس در کلاس آغازین برنامه و متد ConfigureServices آن، نحوه‌ی نگاشت قسمت Smtp فایل کانفیگ را مشخص کنید:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
   services.Configure<SmtpConfig>(options => Configuration.GetSection("Smtp").Bind(options));
در اینجا مشخص شده‌است که کار وهله سازی کلاس SmtpConfig بر اساس اطلاعات قسمت smtp فایل کانفیگ تامین می‌شود. متغیر Configuration ایی که در اینجا استفاده شده‌است همان خاصیت عمومی public IConfigurationRoot Configuration کلاس آغازین برنامه است.

سپس برای استفاده از این تنظیمات strongly typed (برای نمونه در لایه سرویس برنامه)، ابتدا ارجاعی را به پروژه‌ی Core1RtmEmptyTest.ViewModels به لایه‌ی سرویس برنامه اضافه می‌کنیم (بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.ViewModels را انتخاب کنید).
در ادامه نیاز است بسته‌ی نیوگت جدیدی را به نام Microsoft.Extensions.Options به لایه‌ی سرویس برنامه اضافه کنیم. به این ترتیب قسمت وابستگی‌های فایل project.json این لایه چنین شکلی را پیدا می‌کند:
    "dependencies": {
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
        "Microsoft.Extensions.Options": "1.0.0",
        "NETStandard.Library": "1.6.0"
    }
پس از ذخیره سازی این کلاس و بازیابی خودکار وابستگی‌های آن، اکنون برای دسترسی به این تنظیم باید از اینترفیس ویژه‌ی IOptions استفاده کرد (به همین جهت بسته‌ی جدید نیوگت Microsoft.Extensions.Options را نصب کردیم):
public interface IMessagesService
{
    string GetSiteName();
}
 
public class MessagesService : IMessagesService
{
    private readonly IConfigurationRoot _configurationRoot;
    private readonly IOptions<SmtpConfig> _settings;
 
    public MessagesService(IConfigurationRoot configurationRoot, IOptions<SmtpConfig> settings)
    {
        _configurationRoot = configurationRoot;
        _settings = settings;
    }
 
    public string GetSiteName()
    {
        var key1 = _configurationRoot["Key1"];
        var server = _settings.Value.Server;
        return $"DNT {key1} - {server}";
    }
}
همانطور که ملاحظه می‌کنید <IOptions<SmtpConfig به سازنده‌ی کلاس تزریق شده‌است و سپس از طریق خاصیت Value آن می‌توان به تمام اطلاعات کلاس SmtpConfig به شکل strongly typed دسترسی یافت.

اکنون اگر برنامه را جرا کنید، این خروجی را می‌توان مشاهده کرد (که در آن آدرس Server دریافت شده‌ی از فایل کانفیگ نیز مشخص است):


البته همانطور که در قسمت قبل نیز عنوان شد، این تزریق وابستگی‌ها در تمام قسمت‌های برنامه کار می‌کند. برای مثال در کنترلرها هم می‌توان <IOptions<SmtpConfig را به همین نحو تزریق کرد.


نحوه‌ی واکنش به تغییرات فایل‌های کانفیگ

در نگارش‌های قبلی ASP.NET، هر تغییری در فایل web.config، سبب ری‌استارت شدن کل برنامه می‌شد که این مساله نیز خود سبب بروز مشکلات زیادی مانند از دست رفتن سشن تمام کاربران می‌شد.
در ASP.NET Core، برنامه‌ی وب ما دیگر متکی به فایل web.config نبوده و همچنین می‌توان چندین و چند نوع فایل config داشت. به علاوه در اینجا متدهای مرتبط معرفی فایل‌های کانفیگ دارای پارامتر مخصوص reloadOnChange نیز هستند:
 .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
این پارامتر در صورت true بودن، به صورت خودکار سبب بارگذاری مجدد اطلاعات فایل کانفیگ می‌شود (بدون ری‌استارت کل برنامه).
  • #
    ‫۷ سال و ۱۱ ماه قبل، چهارشنبه ۱۴ مهر ۱۳۹۵، ساعت ۱۳:۰۷
    یک نکته: طراحی کانفیگ برای یک کتابخانه
    فرض کنید قصد دارید برای کتابخانه‌ای که در حال طراحی هستید، تنظیماتی را از استفاده کننده دریافت کنید؛ مثلا از طریق کلاسی شبیه به تعریف SmtpConfig مطلب فوق.
    برای اینکار، ابتدا یک متد الحاقی را تعریف می‌کنیم که IServiceCollection را به همراه SmtpConfig دریافت کند:
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.DependencyInjection.Extensions;
    using Microsoft.Extensions.Options;
     
    namespace SampleLib
    {
      public static class MyServiceCollectionExtensions
      {
       public static void AddMySampleLib(this IServiceCollection services, SmtpConfig configureOptions)
       {
        services.TryAddSingleton(Options.Create(configureOptions));
       }
      }
    }
    سپس کار معرفی این کلاس تنظیمات به سیستم IOptions ، از طریق ثبت سرویس آن توسط متد Options.Create صورت می‌گیرد.
    استفاده کنند، تنها نیاز دارد تا این تنظیمات را به صورت ذیل در کلاس آغازین برنامه وارد کند:
    public void ConfigureServices(IServiceCollection services)
    {
       services.AddMySampleLib(new SmtpConfig { /*...*/ });
    و در آخر در کدهای کتابخانه‌ی در حال طراحی، با تزریق <IOptions<SmtpConfig به سازنده‌ی کلاس‌ها، می‌توان به تنظیمات ثبت شده، دسترسی پیدا کرد.
  • #
    ‫۷ سال و ۸ ماه قبل، سه‌شنبه ۵ بهمن ۱۳۹۵، ساعت ۲۲:۳۰
    یک نکته: بارگذاری مجدد اطلاعات فایل config در ASP.NET Core 1.1
    تنظیم reloadOnChange به true ایی که در متن عنوان شد، فقط با IConfigurationRoot و دیکشنری آن کار می‌کند. اگر از تنظیمات strongly typed استفاده کنید، این گزینه بر روی آن تاثیری نخواهد داشت. در نگارش 1.1، اینترفیس جدیدی را به نام IOptionsSnapshot معرفی کرده‌اند که به تغییرات فایل کانفیگ واکنش نشان می‌دهد. بنابراین تنها کاری را که باید انجام دهید، جایگزین کردن <>IOptions با <>IOptionsSnapshot است. سایر تنظیمات صورت گرفته یکی است و تفاوتی نمی‌کند.
  • #
    ‫۷ سال و ۶ ماه قبل، یکشنبه ۶ فروردین ۱۳۹۶، ساعت ۲۳:۵۷
    به روز رسانی
    با حذف فایل project.json در VS 2017، اکنون با کلیک راست بر روی گروه نام پروژه (فایل csproj)، گزینه‌ی Edit آن ظاهر شده و مداخل ذکر شده‌ی در مطلب فوق، چنین تعاریفی را پیدا می‌کنند: 
    - برای پروژه اصلی:
    <Project Sdk="Microsoft.NET.Sdk.Web">
      <ItemGroup>
        <ProjectReference Include="..\Core1RtmEmptyTest.ViewModels\Core1RtmEmptyTest.ViewModels.csproj" />
      </ItemGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
      </ItemGroup>
    </Project>

    - برای پروژه‌ی لایه‌ی سرویس:
    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>netstandard1.6</TargetFramework>
        <PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
      </PropertyGroup>
    
      <ItemGroup>
        <ProjectReference Include="..\Core1RtmEmptyTest.ViewModels\Core1RtmEmptyTest.ViewModels.csproj" />
      </ItemGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.1" />
        <PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" />
      </ItemGroup>
    </Project>
    • #
      ‫۴ سال و ۸ ماه قبل، پنجشنبه ۱۹ دی ۱۳۹۸، ساعت ۲۰:۵۶
      با سلام و تشکر; من یک پروژه mvc core 2.2 با کامند dotnet new mvc درست کردم. در فایل csproj پروژه فقط این دو رفرنس وجود دارد در حالیکه DI و تزریق IConfiguration به درستی در فایل StartUp و کنترلر‌های mvc انجام میشود و میتوان از محتویات فایل appsettings.json در همه جا استفاده کرد بدون اضافه کردن رفرنس دیگری.
          <PackageReference Include="Microsoft.AspNetCore.App"/>
          <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All"/>

  • #
    ‫۷ سال و ۱ ماه قبل، چهارشنبه ۲۵ مرداد ۱۳۹۶، ساعت ۱۷:۴۲
    یک نکته‌ی تکمیلی:  ارتقاء به ASP.NET Core 2.0 

    بر اساس مستندات دات نت Core 2.0،  در فایل Startup.cs، الزاما نیازی به تنظیمات ذیل
    public Startup(IHostingEnvironment env)
            {
                var builder = new ConfigurationBuilder()
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                    .AddEnvironmentVariables();
                Configuration = builder.Build();
            }
    نیست و با جایگزین کردن این قطعه کد با
    public IConfiguration Configuration { get; }
    
    public Startup(IConfiguration configuration)
    {
         Configuration = configuration;
    }
    و سپس انجام این تغییرات
    public static void Main(string[] args)
            {
                BuildWebHost(args).Run();
            }
    
            public static IWebHost BuildWebHost(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>()
                    .Build();
    در فایل Program.cs موجب فراخوانی کلیه تنظیمات پیش فرض خواهد شد.
    در واقع متد CreateDefaultBuilder این کار را انجام می‌دهد. البته همچنان امکان تنظیمات سفارشی نیز موجود است.
    public static IWebHost BuildWebHost(string[] args)
    {
      return WebHost.CreateDefaultBuilder()
        .ConfigureAppConfiguration((ctx, cfg) =>
        {
          cfg.SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("config.json", true) // require the json file!
            .AddEnvironmentVariables();
        })
        .ConfigureLogging((ctx, logging) => { }) // No logging
        .UseStartup<Startup>()
        .Build();
    }

    لازم به ذکر است سرویس IConfiguration از ابتدا در سیستم ثبت شده است و جهت دسترسی به تنظیمات میتوان در قسمت‌های مختلف برنامه آن را تزریق نمود.
    • #
      ‫۷ سال و ۱ ماه قبل، پنجشنبه ۲۶ مرداد ۱۳۹۶، ساعت ۱۵:۱۷
      یک نکته‌ی تکمیلی:  ارتقاء به ASP.NET Core 2.0   

      به صورت خلاصه چند سطر ذیل در ASP.NET Core 2.0 معادل این‌کارها را در ASP.NET Core 1.x انجام می‌دهند (و استفاده‌ی از آن اختیاری است):
       var host = WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .Build()
      host.Run();
      - انتخاب Kestrel  به عنوان وب سرور
      - تنظیم مسیر content root
      - بارگذاری خودکار تنظیمات از فایل‌های appsettings.json  و  appsettings.[EnvironmentName].json
      - بارگذاری تنظیمات از متغیرهای محیطی
      - تنظیم کردن ILoggerFactory جهت نمایش خروجی logهای سیستم در console و پنجره‌ی debug
      - فعالسازی یکپارچه سازی با IIS
      - نمایش صفحه‌ی استثناءها برای توسعه دهنده‌ها در حالت تنظیم متغیرهای محیطی به Development
      • #
        ‫۶ سال و ۶ ماه قبل، سه‌شنبه ۱۵ اسفند ۱۳۹۶، ساعت ۱۱:۵۰
        ارتقاء به ASP.NET Core 2.1

        قابلیتی به نام end-to-end in-memory test به ASP.NET Core 2.1 اضافه شده‌است که برای اجرای آن نیاز به وجود یک چنین امضایی در فایل Program.cs هست:
        public static IWebHostBuilder CreateWebHostBuilder(string [] args)
        بنابراین برای ارتقاء برنامه‌های ASP.NET Core 2.0 به 2.1 این مراحل باید طی شوند:
        الف) فایل Program.cs را باز کنید.
        ب) متد BuildWebHost قبلی را به CreateWebHostBuilder تغییر نام دهید. همچنین در این حالت نوع خروجی آن‌را به IWebHostBuilder تنظیم کرده و فراخوانی ()Build. را از انتهای آن حذف کنید.
        ج) اکنون بدنه‌ی متد Main آن چنین شکلی را پیدا می‌کند:
        CreateWebHostBuilder(args).Build().Run();
        و یا به صورت خلاصه فایل ابتدایی یک قالب ASP.NET Core 2.1 به این صورت است:
            public class Program
            {
                public static void Main(string[] args)
                {
                    CreateWebHostBuilder(args).Build().Run();
                }
        
                public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                    WebHost.CreateDefaultBuilder(args)
                        .UseStartup<Startup>();
            }
  • #
    ‫۶ سال و ۳ ماه قبل، دوشنبه ۲۱ خرداد ۱۳۹۷، ساعت ۱۵:۴۴
    روش دوم تعریف «امکان نگاشت تنظیمات برنامه به کلاس‌‌های متناظر» در ASP.NET Core 2.1 
    در نگارش 2.1، می‌توان تعریف تنظیمات strongly typed برنامه را در متد ConfigureServices، توسط متد جدید AddOptions نیز انجام داد:
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddOptions<MyOptions>()
       .Configure<IHostingEnvironment>((o,env) =>
       {
        o.Path = env.WebRootPath;
       });
    }
    • #
      ‫۳ سال و ۶ ماه قبل، جمعه ۱ اسفند ۱۳۹۹، ساعت ۲۰:۰۲
      نکته تکمیلی
      از Net 3. به بعد به جای واسط IHostingEnvironment از IHostEnvironment استفاده شود.
      اطلاعات بیشتر ( + )
      IHostingEnvironment  is one of the most annoying interfaces in .NET Core 2.x, because it exists in two different namespaces,  Microsoft.AspNetCore.Hosting  and  Microsoft.Extensions.Hosting . These are  slightly  different and are incompatible - one does not inherit from the other.
      • #
        ‫۳ سال و ۶ ماه قبل، جمعه ۱ اسفند ۱۳۹۹، ساعت ۲۳:۲۱
        البته در عمل از IWebHostEnvironment استفاده می‌شود که WebRootPath را هم دارد.
  • #
    ‫۶ سال و ۳ ماه قبل، سه‌شنبه ۲۲ خرداد ۱۳۹۷، ساعت ۱۷:۲۱
    نکته‌ای در مورد نحوه‌ی تعریف خواص تنظیمات strongly typed

    - خواص تعریف شده در کلاس‌های تنظیمات که قرار است به مقادیر و مداخل فایل appsetting.json نگاشت شوند، نیاز است get و set دار باشند.
    public class MySettings  
    {
       public string StringSetting { get; set; }
       public int IntSetting { get; set; }
    البته این مورد فقط جهت خواص ساده صدق می‌کند؛ چون در این حالت در صورت خالی بودن این مقادیر در فایل json، نیاز است نال یا صفر (مقدار پیش‌فرض) را دریافت کرد.

    - اما در مورد خواص پیچیده، اینطور نیست:
     public List<string> ListValues { get; } = new List<string>();
    در اینجا می‌توان یک خاصیت پیچیده را صرفا get دار تعریف کرد، با این شرط که در زمان binding دارای مقدار باشد؛ مانند new List فوق.

    - در حین تعریف خواص پیچیده از اینترفیس‌ها و یا کلاس‌های abstarct نمی‌توان استفاده کرد:
     public ISet<string> Values { get; set; }
    از این جهت که binder در پشت صحنه از Activator.CreateInstance(type) برای وهله سازی این خواص استفاده می‌کند و چون برای مثال ISet و یا IList و امثال آن صرفا اینترفیس هستند، قابلیت وهله سازی ندارند. این مورد را به صورت زیر می‌توان مدیریت کرد:
     public ISet<string> Values { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
    چون این خاصیت پیچیده‌است، صرفا get دار تعریف شده‌است به همراه یک آغاز کننده. همین مقدار برای بایند مقادیر به آن کافی است.
    البته باید دقت داشت که آغاز کننده‌ی تعریف شده باید دارای متد Add باشد (مانند IList و List)؛ در غیر اینصورت اطلاعاتی به این لیست اضافه نخواهد شد. برای نمونه نمی‌توان از ReadOnlyCollection در اینجا استفاده کرد؛ چون متد Add ندارد.
  • #
    ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۲:۴۸
    سلام؛ در سطر‌های اولیه نوشتید:«امکان بارگذاری مجدد فایل‌های کانفیگ درصورت تغییر، بدون ری‌استارت کل برنامه وجود دارد.» ولی در متن آموزشی در مورد تغییر در فایل کانفیگ و ذخیره تغییرات آورده نشده. آیا این امکان وجود دارد که برای تغییرات فایل Config  رابط کاربری ایجاد کرد؟
    • #
      ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۳:۵۵
      منظور این بود که اگر فایل متنی JSON مرتبط را در نوت‌پد (و امثال آن و یا با برنامه نویسی) باز کردید و تغییر دادید (ذخیره کردید، تاریخ Last modified آن را تغییر دادید)، بلافاصله تغییرات آن در برنامه منعکس می‌شوند؛ چون یک file watcher برای تغییرات آن فایل در پشت صحنه فعال است. مانند file watcher فایل معروف web.config در برنامه‌های ASP.NET که آن هم یک کانفیگ است البته از نوع XML و اگر تغییری در آن داده شود، کل برنامه را ری‌استارت می‌کند. اما در اینجا برنامه با تغییرات فایل JSON کانفیگ آن ری‌استارت نخواهد شد. فقط «یک نکته: بارگذاری مجدد اطلاعات فایل config در ASP.NET Core 1.1» قابل استفاده خواهد بود.
      ایجاد رابط کاربری هم برای آن مساله‌ای است شخصی که در نهایت شامل deserialization و تبدیل آن به شیء و تغییر آن در برنامه و در آخر serialization این شیء و بازگشت به حالت JSON آن است. عموما از کتابخانه‌ی JSON.NET برای انجام اینکار استفاده می‌کنند.
      • #
        ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۴:۱۷
        آیا امکان هست که در runtime فایل‌های config را ویرایش کرد (از لحاظ permission عرض می‌کنم)؟ این کار با StreamWriter ممکن هست (یا روش دیگری لازم هست)؟
          • #
            ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۶:۰۴
            من با این صورت فایل رو آپدیت می‌کنم
                    public void UpdateSettings(SiteSettings siteSettings)
                    {
                        var path = System.IO.Path.Combine(_hostingEnvironment.WebRootPath, "appsettings.json");
                        System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(path, false);
                        streamWriter.Write(Newtonsoft.Json.JsonConvert.SerializeObject(siteSettings));
                        streamWriter.Close();
                    }
            ولی نه در فایل تغییری ایجاد می‌شه و نه در مقادیر :
                    private readonly IOptionsSnapshot<SiteSettings> _siteOptions;
            برنامه خطای دسترسی هم نمی‌دهد!
            • #
              ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۰۶:۱۲
              از متد File.WriteAllText برای به روز رسانی یک فایل متنی استفاده کنید.
              • #
                ‫۶ سال و ۲ ماه قبل، پنجشنبه ۲۱ تیر ۱۳۹۷، ساعت ۱۶:۵۴
                طبق نظر شما با کد زیر درست شد.
                        public void UpdateSettings(SiteSettings siteSettings)
                        {
                            var path = System.IO.Path.Combine(_hostingEnvironment.ContentRootPath, "appsettings.json");
                            System.IO.File.WriteAllText(path, Newtonsoft.Json.JsonConvert.SerializeObject(siteSettings,Newtonsoft.Json.Formatting.Indented));
                        }
                البته در اینجا  (بازنویسی بهتر) به صورت دیگر نوشته شده است.
              • #
                ‫۶ سال و ۲ ماه قبل، جمعه ۲۲ تیر ۱۳۹۷، ساعت ۱۶:۰۲
                راهکار دیگری که در اینجا  بیان شده است، باعث می‌شود تنظیماتی که از فایل appsettings.json خوانده شود رونویسی شود، و استفاده از دو راهکار با هم مناسب نیست
  • #
    ‫۵ سال و ۶ ماه قبل، دوشنبه ۲۹ بهمن ۱۳۹۷، ساعت ۱۲:۳۲
    یک نکته‌ی تکمیلی: امکان اعتبارسنجی تنظیمات برنامه در ASP.NET Core 2.2

    فرض کنید، چنین تنظیماتی را تدارک دیده‌اید:
    "CustomConfig": {
      "Setting1": "Hello",
      "Setting2": "Hello" 
    }
    در ASP.NET Core 2.2 می‌توان با استفاده از data annotations، برای کلاس معادل آن‌ها اعتبارسنجی نیز درنظر گرفت:
    public class CustomConfig
    {
            [Required(ErrorMessage = "Custom Error Message")]
            public string Setting1 { get; set; }
    
            public string Setting2 { get; set; }
    
            public string Setting3 { get; set; }
    }
    سپس برای فعالسازی آن می‌توان به صورت زیر عمل کرد:
    namespace MvcTest
    {
        public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddOptions<CustomConfig>()
                        .Bind(Configuration.GetSection("CustomConfig"))
                        .ValidateDataAnnotations();
            }
    کار اعتبارسنجی خاصیت Setting1 زمانی رخ می‌دهد که برنامه برای اولین بار می‌خواهد از مقدار آن استفاده کند؛ نه زمانیکه برنامه برای اولین بار اجرا می‌شود. قابلیت eager validation احتمالا به نگارش‌های بعدی اضافه خواهد شد. اگر اعتبارسنجی تنظیمات با شکست مواجه شود، استثنای OptionsValidationException را صادر می‌کند. این استثناء به همراه لیستی از خطاهای ممکن، توسط خاصیت ex.Failures آن قابل بررسی است.

    همچنین اگر نیاز به تعریف اعتبارسنجی سفارشی نیز وجود داشت می‌توان به صورت زیر عمل کرد:
    namespace MvcHealthCheckTest
    {
        public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddOptions<CustomConfig>()
                        .Bind(Configuration.GetSection("CustomConfig"))
                        .ValidateDataAnnotations()
                        .Validate(customConfig =>
                        {
                            if (customConfig.Setting2 != default)
                            {
                                return customConfig.Setting3 != default;
                            }
                            return true;
                        }, "Setting 3 is required when Setting2 is present");
            }
    در اینجا متد Validate، شیء customConfig را از تنظیمات برنامه خوانده و مقدار دهی می‌کند. سپس آن‌را به صورت یک <Func<CustomConfig, bool جهت اعتبارسنجی سفارشی در اختیار مصرف کننده قرار می‌دهد. خروجی false آن به معنای شکست اعتبارسنجی است. در این حالت می‌توان توسط پارامتر دوم متد Validate، یک پیام خطا را نیز نمایش داد.
  • #
    ‫۵ سال و ۳ ماه قبل، شنبه ۱۸ خرداد ۱۳۹۸، ساعت ۱۴:۴۰
    یک نکته‌ی تکمیلی: تعریف متد AddConfig و عدم نیاز به استفاده‌ی از IOptions برای کار با آن
    کدهای متد الحاقی AddConfig زیر:
    services.AddConfig<MySettings>(Configuration.GetSection("MySettings"));
    به این صورت تعریف شده‌است:
    public static class ServiceCollectionExtensions
    {
        public static TSettings AddConfig<TSettings>(this IServiceCollection services, IConfiguration configuration)
            where TSettings : class, new()
        {
            return services.AddConfig<TSettings>(configuration, options => { });
        }
    
        public static TSettings AddConfig<TSettings>(this IServiceCollection services, IConfiguration configuration, Action<BinderOptions> configureOptions)
            where TSettings : class, new()
        {
            if (services == null) { throw new ArgumentNullException(nameof(services)); }
            if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); }
            TSettings setting = configuration.Get<TSettings>(configureOptions);
            services.TryAddSingleton(setting);
            return setting;
        }
    }
    در اینجا وهله‌ای از کلاس تنظیم خوانده شده را به صورت Singleton در سیستم ثبت می‌کند. بنابراین برای دریافت آن در برنامه، الزامی به استفاده‌ی از اینترفیس IOptions نبوده و می‌توان مستقیما خود کلاس تنظیم را به سازنده‌ی کلاس استفاده کننده‌ی از آن تزریق کرد:
    private readonly MySettings _settings;
    
    public MyViewComponent(MySettings settings)
    {
        _settings = settings;
    }
    مزیت آن حذف وابستگی مرتبط با IOptions در قسمت‌های مختلف برنامه است.
    • #
      ‫۴ سال و ۹ ماه قبل، یکشنبه ۳ آذر ۱۳۹۸، ساعت ۱۵:۳۰
      برای اینترفیس IOptionsSnapshot قابل استفاده نیست
      • #
        ‫۳ سال و ۲ ماه قبل، پنجشنبه ۳ تیر ۱۴۰۰، ساعت ۲۲:۱۹
        به جای TryAddSinglton از TryAddScoped باید استفاده کرد :
        //services.TryAddSingleton(setting);
        services.TryAddScoped(cfg => cfg.GetService<IOptionsSnapshot<TSettings>>().Value);
  • #
    ‫۳ سال و ۳ ماه قبل، سه‌شنبه ۲۱ اردیبهشت ۱۴۰۰، ساعت ۲۳:۵۱
    نکته تکمیلی: نحوه بایند کردن دو Section مختلف در asppsettings.json به یک کلاس
    مثلا دو Object متفاوت در appsettings.json داریم و میخواهیم آن را به یک کلاس بایند کنیم:
    "SiteSettingsA": {
        "DefaultUserPicture": "http://site.com/users/defaultavatar.png"
    },
    "SiteSettingsB": {
        "DefaultUserPicture": "http://site.com/users/defaultavatar2.png"
    }
    و این کلاس برای بایند کردن تنظیمات به آن:
    public class SiteSettings
    {
        public string DefaultUserPicture { get; set; }
    }
    اگر به روش انجام شده در این مطلب فراخوانی کنیم Object ـی که بالاتر است به کلاس بایند میشود که در این اینجا SiteSettingsA میباشد. برای حل این مشکل به این صورت عمل میکنیم:
    services.Configure<SiteSettings>("FirstSettings", Configuration.GetSection("SiteSettingsA"));
    services.Configure<SiteSettings>("SecondSettings", Configuration.GetSection("SiteSettingsB"));
    و فراخوانی آن در یک کنترلر:
    private readonly SiteSettings _firstSiteSettings;
    private readonly SiteSettings _secondSiteSettings;
    
    public HomeController(IOptionsSnapshot<SiteSettings> siteSettings)
    {
        _firstSiteSettings = siteSettings.Get("FirstSettings");
        _secondSiteSettings = siteSettings.Get("SecondSettings");
    }

  • #
    ‫۲ سال و ۶ ماه قبل، دوشنبه ۹ اسفند ۱۴۰۰، ساعت ۱۳:۳۴
    یک نکته‌ی تکمیلی: امکان اجباری کردن وجود قسمتی در تنظیمات برنامه در دات نت 6

    در مطلب فوق، نمونه‌ی استفاده از متد Configuration.GetSection را مشاهده می‌کنید؛ مانند: Configuration.GetSection("Auth:Users"). در دات نت 6 متد جدیدی به نام GetRequiredSection نیز بجای GetSection معرفی شده‌است که در صورت وجود نداشتن چنین قسمتی در فایل تنظیمات برنامه، یک استثناء را در ابتدای کار و شروع آن، صادر می‌کند.
  • #
    ‫۲ سال قبل، پنجشنبه ۲۰ مرداد ۱۴۰۱، ساعت ۱۵:۵۶
    سلام و وقت بخیر
    من یک پروژه Worker ایجاد کردم و توی program.cs کد زیر رو استفاده کردیم
    IHost host = Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, configuration) =>
        {
            configuration.Sources.Clear();
            IHostEnvironment env = hostingContext.HostingEnvironment;
            configuration
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
                //.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);
            IConfigurationRoot configurationRoot = configuration.Build();
        })
        .ConfigureServices((hostContext, services) =>
        {
            services.Configure<SiteSettings>(options => hostContext.Configuration.Bind(options));
    ولی مشکلی که دارم SiteSettings توی کلاسهایی که ازش استفاده کردم تقریبا خالی هست و اطلاعات appsetting رو نداره. ممنون میشم راهنمایی بفرمایید.
    • #
      ‫۲ سال قبل، پنجشنبه ۲۰ مرداد ۱۴۰۱، ساعت ۱۶:۱۳
      - قسمت ConfigureAppConfiguration را حذف کنید، چون جزو کدهای توکار CreateDefaultBuilder ابتدایی هست.
      - همچنین در قسمت services.Configure دقیقا باید مشخص کنید که SiteSettings از کدام Section باید دریافت شود. مثال‌های آن در این مطلب و نظرات مرتبط با آن زیاد هستند. اگر هم Section را مشخص نکنید، یعنی نگاشت کل فایل تنظیمات، مدنظر شما است و این محتوا با ساختار SiteSettings تطابق دارد.
  • #
    ‫۱ سال و ۲ ماه قبل، پنجشنبه ۱۸ خرداد ۱۴۰۲، ساعت ۱۷:۵۱
    یک نکته‌ی تکمیلی: روش تنظیم TimeSpan در فایل‌های config
    قالب ذکر TimeSpan در فایل‌های config به صورت d.hh:mm:ss.ff است (d در اینجا، تعداد روز است و ff، کسری از ثانیه)؛ مانند:
    "WaitForExit": "0.00:02:00.0000"
    که به معنای 2 دقیقه است.
    یک مثال دیگر:
    "1.02:03:04" is 1 day, 2 hours, 3 mins, 4 seconds