ساختار موجودیت تنظیمات برنامه
تنظیمات برنامه با هر قالبی که تهیه شوند، دست آخر به صورت یک <Dictionary<string,string در برنامه پردازش شده و قابل دسترسی میشوند. بنابراین موجودیت معادل این Dictionary را به صورت زیر تعریف میکنیم:
namespace DbConfig.Web.DomainClasses { public class ConfigurationValue { public int Id { get; set; } public string Key { get; set; } public string Value { get; set; } } }
ساختار Context برنامه و مقدار دهی اولیهی آن
پس از تعریف موجودیت تنظیمات برنامه، آنرا به صورت زیر به Context برنامه معرفی میکنیم:
public class MyAppContext : DbContext, IUnitOfWork { public MyAppContext(DbContextOptions options) : base(options) { } public virtual DbSet<ConfigurationValue> Configurations { set; get; }
protected override void OnModelCreating(ModelBuilder builder) { // it should be placed here, otherwise it will rewrite the following settings! base.OnModelCreating(builder); // Custom application mappings builder.Entity<ConfigurationValue>(entity => { entity.Property(e => e.Key).HasMaxLength(450).IsRequired(); entity.HasIndex(e => e.Key).IsUnique(); entity.Property(e => e.Value).IsRequired(); entity.HasData(new ConfigurationValue { Id = 1, Key = "key-1", Value = "value_from_ef_1" }); entity.HasData(new ConfigurationValue { Id = 2, Key = "key-2", Value = "value_from_ef_2" }); }); }
انواع و اقسام تامین کنندههای تنظیمات برنامه در پروژههای ASP.NET Core، در حقیقت یک پیاده سازی سفارشی از اینترفیس IConfigurationSource هستند. به همین جهت در ادامه یک نمونهی مبتنی بر EF Core آن را تهیه میکنیم:
public class EFConfigurationSource : IConfigurationSource { private readonly IServiceProvider _serviceProvider; public EFConfigurationSource(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new EFConfigurationProvider(_serviceProvider); } }
public class EFConfigurationProvider : ConfigurationProvider { private readonly IServiceProvider _serviceProvider; public EFConfigurationProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; ensureDatabaseIsCreated(); } public override void Load() { using (var scope = _serviceProvider.CreateScope()) { var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>(); this.Data?.Clear(); this.Data = uow.Set<ConfigurationValue>() .AsNoTracking() .ToList() .ToDictionary(c => c.Key, c => c.Value); } } private void ensureDatabaseIsCreated() { using (var scope = _serviceProvider.CreateScope()) { var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>(); uow.Migrate(); } } }
در اینجا فراخوانی متد ensureDatabaseIsCreated را نیز مشاهده میکنید. کلاس EFConfigurationProvider در آغاز برنامه و پیش از هر عمل دیگری وهله سازی شده و سپس متد Load آن فراخوانی میشود. به همین جهت نیاز است یا پیشتر، بانک اطلاعاتی را توسط دستورات Migration ایجاد کرده باشید و یا متد ensureDatabaseIsCreated، اطلاعات Migration موجود را به بانک اطلاعاتی برنامه اعمال میکند.
معرفی EFConfigurationSource به برنامه
جهت معرفی سادهتر EFConfigurationSource تهیه شده، ابتدا یک متد الحاقی را بر اساس آن تهیه میکنیم:
public static class EFExtensions { public static IConfigurationBuilder AddEFConfig(this IConfigurationBuilder builder, IServiceProvider serviceProvider) { return builder.Add(new EFConfigurationSource(serviceProvider)); } }
namespace DbConfig.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUnitOfWork, MyAppContext>(); services.AddScoped<IConfigurationValuesService, ConfigurationValuesService>(); var connectionString = Configuration.GetConnectionString("SqlServerConnection") .Replace("|DataDirectory|", Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "app_data")); services.AddDbContext<MyAppContext>(options => { options.UseSqlServer( connectionString, dbOptions => { var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds; dbOptions.CommandTimeout(minutes); dbOptions.EnableRetryOnFailure(); }); }); var serviceProvider = services.BuildServiceProvider(); var configuration = new ConfigurationBuilder() .AddConfiguration(Configuration) // Adds all of the existing configurations .AddEFConfig(serviceProvider) .Build(); services.AddSingleton<IConfigurationRoot>(sp => configuration); // Replace services.AddSingleton<IConfiguration>(sp => configuration); // Replace
همچنین روش دسترسی به serviceProvider مورد نیاز AddEFConfig، توسط متد services.BuildServiceProvider نیز در کدهای فوق مشخص است. به همین جهت مجبور شدیم این تعریف را در اینجا قرار دهیم و گرنه میشد از کلاس Program و یا حتی سازندهی کلاس Startup نیز استفاده کرد. مشکل این دو مکان عدم دسترسی به سرویس IUnitOfWork و سایر تنظیمات برنامه است.
آزمایش برنامه
اگر به قسمت «ساختار Context برنامه و مقدار دهی اولیهی آن» مطلب جاری دقت کرده باشید، دو کلید پیشفرض در اینجا ثبت شدهاند. به همین جهت در ادامه با تزریق سرویس IConfiguration به سازندهی یک کنترلر، سعی در خواندن مقادیر آنها خواهیم کرد:
namespace DbConfig.Web.Controllers { public class HomeController : Controller { private readonly IConfiguration _configuration; public HomeController(IConfiguration configuration) { _configuration = configuration; } public IActionResult Index() { return Json( new { key1 = _configuration["key-1"], key2 = _configuration["key-2"] }); }
به روز رسانی بانک اطلاعاتی برنامه و بارگذاری مجدد اطلاعات IConfiguration
فرض کنید توسط سرویسی، اطلاعات جدول ConfigurationValue را تغییر دادهاید. نکتهی مهم اینجا است که اینکار سبب فراخوانی مجدد متد Load کلاس EFConfigurationProvider نخواهد شد و عملا این تغییرات در سراسر برنامه توسط تزریق اینترفیس IConfiguration قابل دسترسی نخواهند بود (مگر اینکه برنامه مجددا ریاستارت شود). نکتهی به روز رسانی این اطلاعات به صورت زیر است:
public class ConfigurationValuesService : IConfigurationValuesService { private readonly IConfiguration _configuration; public ConfigurationValuesService(IConfiguration configuration) { _configuration = configuration; } private void reloadEFConfigurationProvider() { ((IConfigurationRoot)_configuration).Reload(); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: EFCoreDbConfig.zip