مفاهیم پایه در پیکربندی ASP.NET Core
شناسه پست: 2602
بازدید: 1914

در این مقاله مقدماتی، ما می خواهیم نحوه کار پیکربندی ASP.NET Core، مفاهیم اصلی پیکربندی و چند روش مختلف را که می توانیم برای پیکربندی برنامه خود استفاده کنیم را یاد بگیریم. اگرچه به طور پیشفرض، مکانیسم configuration در NET. خیلی قدرتمند است، اما با این حال، مکانیسمهای پیشرفته زیادی وجود دارد که آن را قدرتمندتر نیز میسازد.

در حالیکه داریم مفاهیم پایه را در طول این مقاله بررسی میکنیم، تعدادی از مفاهیم پیشرفته را نیز مورد بررسی قرار خواهیم داد. اما در حال حاضر ببینیم زمانیکه یک پروژه ASP.NET Core را ایجاد میکنیم، چه امکانات و ویژگیهایی به طور پیشفرض در اختیار ما قرار میگیرد.

اگر میخواهید در طول مسیر این مقاله، ما را دنبال کنید، از نقطه شروع پروژه استفاده کنید. برای بررسی پروژه تکمیل شده این مقاله، از پروژه نهایی این مقاله استفاده کنید.

این مقاله، به قسمتهای زیر تقسیم شده است:

بریم تا وارد عمل شویم.

Configuration چیست؟

Application configuration یک شیوه برای ارائه تنظیمات اولیه اپلیکیشنمان در startup آن میباشد. ما از configuration استفاده می کنیم تا برنامه خود را به راحتی به روش های مختلف که معمولاً بستگی به محیطی دارد که در آن مستقر شده است setup کنیم. پیکربندی ممکن است از چندین منبع مختلف و از طیف گسترده ای از اشکال مانند فایلها، متغیرهای محیطی، یا انواع محل ذخیره سازی مانند Azure Key Vault یا حتی از in-memory storage یا به عنوان آرگومان های خط فرمان گرفته شود.

هرکدام از این شیوه ها را که برای پیکربندی انتخاب کنیم، در کل مکانیسم پیکربندی وجود دارد تا به ما کمک کند تا بدون نیاز به recompile شدن سورس کدمان، اپلیکیشنهای انعطاف پذیری ایجاد کنیم. با استفاده از داده های پیکربندی، ما رفتار اپلیکیشنمان را در runtime (زمان اجرا) تعیین میکنیم.

پیکربندی در NET Core. بسیار قدرتمند است و میتواند به راحتی نصب شده و مورد استفاده قرار گیرد. پکیج موجود در NuGet package که شامل Configuration است، Microsoft.Extensions.Configuration میباشد و شما به راحتی میتوانید با نوشتن دستور زیر، آن را در هر پروژه NET Core. نصب کنید:

دستور PM> Install-Package Microsoft.Extensions.Configuration در Package Manager Console در ویژوال استودیو و اگر میخواهید از dotnet CLI استفاده کنید میتوانید از دستور dotnet add package Microsoft.Extensions.Configuration استفاده کنید.

اگر یک اپلیکیشن ASP.NET Core ایجاد کنید، نیازی نیست در مورد اضافه کردن آن نگران باشید، چرا که پکیج مورد نظر به طور پیشفرض در پروژه reference داده میشود.

Configuration در NET Core. با استفاده از sections, configuration providers و Options pattern، حتی خیلی قدرتمند تر نیز میشود. جلوتر در مورد تمام این مفاهیم صحبت خواهیم کرد.

چطور داده های پیکربندی را تعریف کنیم؟

داده های پیکربندی، به عنوان یک مجموعه ای از زوجهای key-value تعریف میشوند.

Value ها میتوانند:

  • integer باشند – هر عدد integer میتواند باشد و زمانی که یک مقدار عددی مانند maximum تعداد item ها یا به فرض، اگر به یک درجه حرارت پیشفرض نیاز داشته باشیم مورد استفاده قرار میگیرد. به عنوان مثال: DefaultRoomTemperature = 21.
  • boolean باشند – میتواند true یا false باشد. اغلب برای تعیین اینکه آیا یک رفتار داخل اپلیکیشن باید trigger شود و یا خیر، مورد استفاده قرار میگیرد: TurnOnDetailedReports = true
  • string باشند – در صورتی که به یک مقدار رشته ای ویژه نیاز داشته باشیم. به عنوان نمونه، connection string ها و URL ها و یا هرچیز دیگری: sqlConnection = “server=.; database=AccountOwnerDatabase; Integrated Security=true”

اینها فقط مثال هستند و هر کلید، علاوه بر یک value، حاوی اطلاعاتی در مورد سطح سلسله مراتب مربوط به خودش نیز است.

ببینیم این به چه معناست.

سازمان داده های سلسله مراتبی و مسطح سازی داده ها

Configuration API، با مسطح سازی ساختار با استفاده از جداکننده ها، داده های سلسله مراتبی را می خواند.

این به این معنی است که ما میتوانیم همچین چیزی را در فایل configuration مان بنویسیم:

و جلوتر، در کدمان، با استفاده از جداکننده semicolon “:”، به آن دسترسی پیدا میکنیم.

مثالها:

"Logging:LogLevel:Default" – میتوانیم Default logging level را get کنیم.

"AllowedHosts" – میتوانیم “*” (هر host) را در این مثال get کنیم.

این به این معنی است که میتوانیم چند کلید به نام “Default” را اینجا داشته باشیم. زیرا نوع سازماندهی اینجا سلسله مراتبی میباشد.

مثالمان را توسعه میدهیم:

اکنون می توانیم در یک سلسله مراتب متفاوت، به همان کلید دسترسی داشته باشیم:

"Logging:LogLevel:Default" – که “Information” را به عنوان مقدار برمیگرداند.

"OtherLoggingProvider:LogLevel:Default" – که “Debug” را به عنوان مقدار برمیگرداند.

اگر مقداری وجود نداشته باشد، در اینصورت null را به عنوان یک نتیجه دریافت میکنیم.

به جای دسترسی مستقیم به مقادیر با استفاده از جداکننده ها، میتوانیم از options pattern و متدهای GetSection() و GetChildren() برای گرفتن section ها و فرزند یک section استفاده کنیم. این مکانیسمها، configuration را جهت استفاده، خیلی راحت تر میسازد. جلوتر خواهیم دید که چطور میتوانیم از آنها استفاده کنیم.

ببینیم زمانیکه یک اپلیکیشن ASP.NET Core را میسازیم، چه امکانات و ویژگیهایی به طور پیشفرض در اختیار ما قرار میگیرد.

پیکربندی پیشفرض ASP.NET Core

زمانیکه یک اپلیکیشن جدید از نوع ASP.NET Core را میسازیم، فایل Program.cs ما به این صورت میباشد:

اگرچه ممکن است این کد، جذاب به نظر نرسد ، اما چند کار را برای ما انجام می دهد. به خصوص، متد ()CreateDefaultBuilder برای ما متد جالبی است.

این متد، یک نمونه از یک HostBuilder که IHostBuilder را پیاده سازی میکند را ایجاد میکند که این HostBuilder، یک ابزار سودمند برای initialize کردن برنامه ما است. علاوه بر این، متد ()HostBuilder ،CreateDefaultBuilder را با تعدادی مقادیر پیشفرض ست میکند که برخی از این مقادیر، از منابع مختلف گردآوری میشوند.

متد ()CreateDefaultBuilder، مقادیر پیشفرض پیکربندی را با این ترتیب خاص، به اپلیکیشن اضافه میکند:

  • ChainedConfigurationProvider – که host IConfiguration را به app IConfiguration اضافه میکند.
  • فایل appsettings.json – زمانیکه یک ASP.NET Core template را ایجاد میکنیم، این فایل پیشفرض برای ما ایجاد میشود.
  • فایلهای appsettings.{EnvironmentName}.json – برای override شدن فایل appsettings با توجه به نوع environment استفاده میشود.
  • App Secrets زمانیکه متغیر EnvironmentName به  “Development” ست میشود.
  • متغیرهای Environment
  • آرگومانهای خط فرمان

این زوجهای key-value، داخل IConfiguration ذخیره میشوند و جلوتر میبنیم که این IConfiguration، نقش بزرگی را در اپلیکیشن ما ایفا میکند.

حالا که میدانیم پیکربندی پیشفرض در اپلیکیشنمان، چطور تکمیل میشود، ببینیم چطور میتوانیم آن را در runtime بخوانیم.

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

حالا بعد از کمی تئوری، کمی کدنویسی کنیم.

اول از همه، ما به راهی برای دسترسی به مقادیرمان نیاز داریم.

از آنجا که تزریق وابستگی به طور پیش فرض در برنامه ما فعال میشود، می توانیم به سادگی این کار را با تزریق IConfiguration interface خود در کنترلر (یا هرجای دیگر) انجام دهیم. بنابراین، این کار را دقیقا انجام میدهیم. IConfiguration را به داخل کلاس HomeController تزریق میکنیم.

وقتی HomeController را باز میکنیم، بلافاصله میبینیم که ILogger هم اکنون به داخل سازنده، تزریق شده است:

ما می توانیم IConfiguration را دقیقاً به همین روش inject کنیم:

حالا میتوانیم از متغیر configuration_ برای دسترسی به مقادیرمان در کل کنترلر استفاده کنیم. اول یک dummy model را برای پرکردن آن با داده هایمان ایجاد میکنیم. به پوشه Model میرویم و یک کلاس HomeModel را ایجاد میکنیم. این یک کلاس ساده با ویژگی DefaultLogLevel میباشد:

حال برگردیم به کنترلرمان و configuration خود را بخوانیم و داده ها را به Home Index view بفرستیم:

و Home Index.html  را کمی تغییر میدهیم:

مطمئناً، برنامه ما اکنون مقدار configuration  ما را نشان می دهد:

داده های configuration در asp.net core

همانطور که میبینید، ما از یک متد  strongly type به نام ()GetValue با استفاده از همان اصول قبلی برای پیمایش در سلسله مراتب برای خواندن default logging level استفاده کردیم.

به همین راحتی.

Configuration Section ها

متد ()GetSection با جداسازی section ها و subsection های configuration، به ما کمک بیشتری میکند. برای اینکه این موضوع را بهتر درک کنیم، اجازه دهید تا یکبار دیگر، نگاهی به مثالمان بیندازیم:

در این مورد، “Logging” یک section و “LogLevel” یک subsection میباشد. از آنجایی که فقط به داده های LogLevel نیاز داریم، میتوانیم آن را فقط با برگرداندن LogLevel subsection در اکشن HomeController Index جداسازی کنیم:

نتیجه همانند قبل میباشد.

اگرچه این یک مثال کوچک میباشد، تنظیمات پیچیده تر موجود در یک فایل که هر روز در یک پروژه واقعی میبینیم را در نظر بگیرید. آن دارای section های زیاد برای قسمتهای مختلف اپلیکیشن است. دریافت section ها و سازماندهی آنها به طور منطقی یک کار اساسی و سخت است از جهت خوانا تر برنامه و اینکه وابستگی کمتری به رشته های hard code شده داشته باشد.

یک استفاده رایج از متد ()GetSection را میتوانیم در یک extension method به نام ()GetConnectionString ببینیم.

آن را از load ،assembly میکنیم:

میبینیم که این یک پیاده سازی از متد ()GetSection میباشد. این متد، نام connection string را گرفته و سعی میکند آن را داخل “ConnectionStrings” section پیدا کند.

به این دلیل است که connection string های ما داخل این section درون فایل appsettings.json قرار گرفته است:

اکنون می توانیم رشته مورد نظر را به همین راحتی دریافت کنیم:

عالی!

توصیه میکنیم که از یک الگوی مشابه با همین الگو، برای گرفتن دیگر section های configuration تان استفاده کنید. اگر با extension method ها آشنا نیستید، مقاله ما را برای یادگیری extension method ها بررسی کنید.

Bind کردن Configuration

دیدیم که چطور میتوانیم داده های configuration را با استفاده از IConfiguration استخراج کنیم. اما آن معایب خودش را دارد.

تایپ کردن نام section ها و کلیدها برای گرفتن مقادیر، میتواند واقعا یک کار تکراری و همچنین مستعد خطا باشد. ما اینجا، احتمال خطر ایجاد کردن خطا در کد خود را داریم، و این نوع خطاها ممکن است از لحاظ زمانی برای ما هزینه بر باشد تا زمانی که آنها را پیدا کنیم، زیرا شخص دیگری می تواند آنها را ایجاد کند و از آنجایی که در صورت عدم وجود مقادیر، یک نتیجه از نوع null بازگردانده شود، آن را متوجه نخواهیم شد.

برای رفع این مسئله، میتوانیم داده های configuration را به داده های Bind ،strongly typed کنیم. برای این منظور، میتوانیم از متد ()Bind استفاده کنیم.

میتوانیم سریع، یک کلاس container ساده برای configuration به نام LoggingLevelConfiguration را داخل پوشه Model ایجاد کنیم:

و حالا کمی تغییر در متد ()Index در HomeController میدهیم:

و بار دیگر میتوانیم پروژه را اجرا کنیم و مطمئن باشیم که همان نتیجه قبلی را میگیریم.

همانطور که میبینید، به جای استفاده از متدهای ()GetValue یا ()GetSection، داده های configuration خود را به طور مستقیم به کلاس LoggingLevelConfiguration متصل کرده ایم و با صدا زدن ویژگی Default این کلاس، به داده های configuration دسترسی پیدا میکنیم.

بسیار واضح و تر و تمیز. درسته؟

اگرچه باید اینجا به دو مورد توجه داشته باشید. اول اینکه، نام کلیدهای داده های configuration و خصوصیات داخل کلاس باید با هم مطابقت داشته باشند و دیگر اینکه اگر شما  configuration تان را توسعه میدهید، باید کلاستان را نیز توسعه دهید که میتواند کمی مایه زحمت باشد، اما زحمتش نسبت به روش گرفتن مقادیر با تایپ کردن رشته ها می ارزد.

ایجاد یک Configuration مختص Environment

هر اپلیکیشنی که قصد تولید آن را داشته باشید، دارای حداقل دو environment میباشد – development و production. علاوه بر اینها، میتوانیم environment های دیگر نیز داشته باشیم، مانند staging که یک environment است که در آن میتوانیم قبل از اینکه اپلییکشن را در مرحله production مستقر کنیم بررسی کنیم که آیا اپلیکیشن به درستی کار میکند یا خیر.

همانطور که ممکن است متوجه شده باشید، template پروژه ما، دو فایل appsetting دارد. یکی، فایل پیشفرض appsettings.json است و دیگری appsettings.Development.json. در این مورد دومی، ما میتوانیم هر value از فایل appsettings.json را override کنیم و زمانیکه در development environment در حال کار کردن هستیم، این مورد override شده، مورد استفاده قرار میگیرد.

یک مثال عملی، استفاده از رشته های مختلف configuration برای production و development است، زیرا ما نمی خواهیم پایگاه داده production را در هنگام development بهم بریزیم.

برای مثال، این یک connection string برای development است:

و حالا میتوانیم یک فایل appsettings.Production.json را ایجاد کنیم که در آن، connection string را به چیز دیگری تغییر میدهیم:

تمام. زمانیکه اپلیکیشن، در production environment اجرا میشود (با متغیر ASPNETCORE_ENVIRONMENT environment مشخص شده است)، رشته production از فایل apsettings.Production.json، به جای رشته پیشفرض مورد استفاده قرار میگیرد.

نتیجه گیری

در این مقاله، ما تعدادی از مفاهیم پایه پیکربندی اپلیکیشن  ASP.NET Core را مورد بررسی قرار دادیم. یاد گرفتیم که چطور تنظیمات configuration را برای اپلیکیشنمان تغییر دهیم، چطور از section ها برای group کردن منطقی داده های configuration خود استفاده کنیم و اینکه چطور ساختارهای سلسله مراتبی configuration را بخوانیم.

ما هنوز به options pattern یا configuration provider ها، که موضوعات بعدی ما در این مجموعه آموزشی هستند، اشاره ای نکرده ایم.

نویسنده

امید عباسی
من امید عباسی هستم. سالهاست که در زمینه برنامه نویسی با تکنولوژی دات نت فعالیت میکنم و عاشق این هستم که تجربیات و دانش خودم را در این زمینه با دیگران به اشتراک بزارم. خیلی دوست دارم که نظر و انتقاد خودتون رو در مورد این نوشته برای من بنویسید تا بتونم در آینده، مطالب بهتر و ارزشمندتری را برای شما فراهم کنم. در صورت داشتن هرگونه سوال هم در قسمت دیدگاه ها میتونید با بنده در ارتباط باشید
  1. با سلام من یه پروژه دات نت کور داریم می نویسم و به این خطا برمی خوردم زمانیکه درون ادرس
    account/register می شوم .
    An unhandled exception occurred while processing the request.
    InvalidOperationException: Unable to resolve service for type ‘Microsoft.AspNetCore.Identity.UserManager`1[BE.UserApp]’ while attempting to activate ‘StoreFile.Controllers.Admin.AccountController’.
    Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ThrowHelperUnableToResolveService(Type type, Type requiredBy)

    1. سلام
      احتمالا جایی در کدتان اشتباه عمل کردید. این خطا معمولا به دلیل استفاده از کلاس IdentityUser به جای استفاده از مدل داده کاربر که در SignInManager، UserManager استفاده میشود رخ میدهد. در ConfigureServices خود در کلاس startup.cs، به جای استفاده از کلاس IdentityUser، از مدل داده خود که معمولا به نام ApplicationUser و یا Account است استفاده کنید. یعنی کد زیر را:

      با کد زیر جایگزین کنید: