بازم سپاسگزارم.
متغیرهای استاتیک و برنامههای ASP.NET
بازم سپاسگزارم.
"ApplicationName": "Dependency Injection Demo", "GreetingMessage": "Welcome to Dependency Injection Demo", "AllowedHosts": "*", "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } },
برای سادگی کار، با بخش Logging
کاری نداریم . اکنون فایل AppConfig.cs را به برنامه اضافه میکنیم:
namespace AspNetCoreDependencyInjection.Configs { public class AppConfig { public string ApplicationName { get; set; } public string GreetingMessage { get; set; } public string AllowedHosts { get; set; } } }
برای دسترسی بهتر میتوانیم سازندهی کلاس Startup را تغییر دهیم:
public IWebHostEnvironment Environment { get; } public IConfiguration Configuration { get; } public IServiceCollection Services { get; set; } public Startup(IWebHostEnvironment environment) { var builder = new ConfigurationBuilder() .SetBasePath(environment.ContentRootPath) .AddJsonFile("appsettings.json", optional: true) .AddEnvironmentVariables(); this.Environment = environment; this.Configuration = builder.Build(); }
var appSettingsFile = environment.IsProduction() ? "appsettings.json" : "appsettings_dev.json"; var builder = new ConfigurationBuilder() .SetBasePath(environment.ContentRootPath) .AddJsonFile( appSettingsFile , optional: true) .AddEnvironmentVariables();
services.AddSingleton(services => new AppConfig { ApplicationName = this.Configuration["ApplicationName"], GreetingMessage = this.Configuration["GreetingMessage"], AllowedHosts = this.Configuration["AllowedHosts"] });
در کد بالا در هنگام اجرای برنامه، یک نمونه از کلاس AppConfig را با طول حیات Singleton ثبت کردیم و Property های این شیء را به وسیلهی ایندکس Configuration[“FieldName”]، تک تک پر کردیم.
حالا میتوانیم سرویس AppConfig را
در هر کلاسی از برنامهی خودمان تزریق و از آن استفاده کنیم. برای مثل در اینجا یک
کنترلر به نام AppSettingsController ساختم و کلاس فوق را به آن تزریق کردم:
public class AppSettingsController : Controller { private readonly AppConfig _appConfig; public AppSettingsController(AppConfig appConfig) { _appConfig = appConfig; } // codes here … }
"UserOptionConfig": { "UsersAvatarsFolder": "avatars", "UserDefaultPhoto": "icon-user-default.png", "UserAvatarImageOptions": { "MaxWidth": 150, "MaxHeight": 150 } }, "LiteDbConfig": { "ConnectionString": "Filename=\\Data\\DependencyInjectionDemo.db;Connection=direct;Password=@123456;" }
برای LiteDbConfig
مانند AppConfig عمل میکنیم، ولی در هنگام ثبت آن، به روش زیر عمل میکنیم. تنها تفاوتی
که وجود دارد، نحوهی دستیابی به فیلدهای درونی فایل JSON به وسیلهی شیء Configuration
است:
services.AddSingleton(services => new LiteDbConfig { ConnectionString = this.Configuration["LiteDbConfig:ConnectionString"], });
اکنون برای استفادهی از مدخل UserOptionConfig،
کلاسهای زیر را میسازیم:
namespace AspNetCoreDependencyInjection.Configs { public class UserOptionConfig { public string UsersAvatarsFolder { get; set; } public string UserDefaultPhoto { get; set; } public UserAvatarImageOptions UserAvatarImageOptions { get; set; } } public class UserAvatarImageOptions { public int MaxHeight { get; set; } public int MaxWidth { get; set; } } }
جداسازی بخشهای مختلف تنظیمات پیکربندی باعث میشود تا بتوانیم دو اصل اساسی از طراحی نرم افزار را رعایت کنیم :
در اینجا نیاز به استفاده از پکیج Microsoft.Extensions.Options.ConfigurationExtensions را داریم که به صورت درونی در ASP.NET Core تعبیه شده است.
برای ثبت این تنظیمات درون DI Container، از نمونهی جنریک متد Configure در IServiceCollection به صورت زیر استفاده میکنیم:
services.Configure<UserOptionConfig>(this.Configuration.GetSection("UserOptionConfig"));
متد GetSection بر اساس نام بخش تنظیمات، خود آن تنظیم و تمامی تنظیمات درونی آن را به صورت یک IConfigurationSection بر میگرداند و متد Configure<TOption> یک IConfiguration را گرفته و به صورت خودکار به TOption اتصال میدهد و سپس این شیء را درون DI Container به عنوان یک IConfigurationOptions<TOption> و با طول حیات Singleton ثبت میکند.
برای دسترس به UserOptionConfig درون کلاس مورد نظر ما، اینترفیس <IOptionMonitor<TOption را به سازندهی کلاس مورد نظر تزریق میکنیم. کد زیر را که نسخهی تغییر یافتهی کلاس AppSettingsController است را مشاهده کنید:private readonly LiteDbConfig _liteDbConfig; private readonly AppConfig _appConfig; private readonly UserOptionConfig _userOptionConfig; public AppSettingsController(AppConfig appConfig , LiteDbConfig liteDbConfig , IOptionsMonitor<UserOptionConfig> userOptionConfig) { _appConfig = appConfig; _liteDbConfig = liteDbConfig; _userOptionConfig = userOptionConfig.CurrentValue; }
نکته ای که وجود دارد، کلاسهای تعریف شده برای استفادهی از این الگو باید شرایط زیر را داشته باشند ( مثل کلاس UserOptionConfig ) :
هر دو روش بالا که یکی به
صورت عادی تنظیمات را ثبت میکند و دیگری با استفاده از Option Pattern بخشهای مختلف را ثبت میکند،
مناسب هستند. البته گاهی اوقات فایلهای تنظیمات پروژهی شما در لایههای زیرین (یا درونیتر اگر از onion architecture استفاده میکنید) قرار دارند و شما نمیخواهید
در آن لایهها و لایههای درونیتر، وابستگی به پکیجهای ASP.NET Core ایجاد کنید. در این حالت با در
نظر گرفتن دو اصل ISP و Separation of Concerns ،
به ازای هر بخش مختلف از تنظیمات، فایلهای تنظیمات را در لایههای زیرین/درونی
تعریف کرده، بعد در لایههای بالاتر/بیرونیتر آنها را به درون سرویسها یا کلاسهای مورد نیاز، تزریق کنید. البته مثل همین مثال، ثبت این سرویسها درون برنامهی ASP.NET Core که
معمولا بالاترین/بیرونیترین لایه از پروژهی ما هست، انجام میشود.
return now.Second % 1 == 0 && now.Second == 1;
حال بیایید ببینیم موقعی که درخواست وارد پروسهی کارگر میشود چه اتفاقی میافتد؟
در پروسههای کارگر، یک درخواست از مراحل لیست شده ای به ترتیب عبور میکند. در هسته وب سرور، رویدادهایی را فراخوانی میکند که در هر رویداد چندین ماژول native برای کارهایی چون authentication یا events logs دارد و در صورتیکه درخواستی نیاز به یک ماژول مدیریت شده CLR داشته باشد، از ماژول native managedEngine کمک گرفته و یک app domain را ایجاد میکند تا ماژولهای مدیریت شده، عملیات لازم خودشان را انجام دهند. مثل authentication form و ...
موقعی هم که درخواست، از تمامی این رویدادها عبور کند، response برای http.sys ارسال میشود تا به کلاینت بازگشت داده شود. شکل زیر نحوه ورود یک درخواست به پروسه کارگر را نشان میدهد.
<TargetFramework>netcoreapp2.0</TargetFramework>
C:\Program Files\dotnet\store\x64\netcoreapp2.0
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> </ItemGroup>
و این تصویر، پوشهی نهایی قابل توزیع یک برنامهی ASP.NET Core 2.x را پس از publish نمایش میدهد:
در این حالت پوشهی نهایی نگارش 1x شامل 94 آیتم و پوشهی نهایی نگارش 2x شامل 13 آیتم است. یعنی حجم نهایی را که باید ارائه داد، به شدت کاهش یافتهاست.
<system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\MyApp.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true" /> </system.webServer>
Right click on the folder -> Properties -> Security tab -> Click at Edit button -> Enter `IIS AppPool\DefaultAppPool` user (IIS AppPool\<app_pool_name>) -> Click at Check names -> OK -> Then give it `read & execute` or other permissions.
"Console": { "LogLevel": { "Default": "Information" } }
Application ‘<IIS path>’ with physical root ‘<Application path>’ failed to start process with commandline ‘”dotnet” .\MyApp.dll’, ErrorCode = ‘0x80004005 : 80008083.
HTTP Error 500.19 - Internal Server Error The requested page cannot be accessed because the related configuration data for the page is invalid.
The Module DLL C:\WINDOWS\system32\inetsrv\aspnetcore.dll failed to load. The data is the error.