C# İçerisinde Custom Configuration Provider nasıl ekleyebiliriz eklememiz için neler gerekli olduğunu anlattığım yazıma hoş geldiniz.
Öncelikle ihtiyaç doğrultusunda ilerleyelim, Neye ihtiyacımız var çözüm olarak neyimiz var şeklinde.
C# projelerimiz içerisinde çoğu zoman uygulama üzerinde konfigürasyon ayarlarımızı appsettings.json veya environment olarak verdiğimizi biliyoruz, burada gereksinimimiz şuradan kaynaklanıyor appsettings.json dosyası içerisindeki bilgiler kalabalıklaştığında ayarlar iç içe geçebiliyor, bunun için farklı yöntemler mevcut örneğin konfigurasyon dosyalarını json şeklinde bölüp o şekilde configuration’a eklemek gibi. Mesela Eposta gönderimi ile ilgili ayarları appsettings.json içerisinde”EmailSettings”:{ “Port”:25} şeklinde tutmak yerine emailsettings.json içerisinde { “Port”:25} şeklinde tutmak gibi.
Fakat biz daha farklısını yapacağız, projemiz üzerindeki konfigurasyon ayarlarını veri tabanında(postgresql) içerisinde tutacağız böylelikle veri tabanı üzerinden konfigurasyon ayarlarımızı düzenleyebilir ve değiştirebilir hale gelmiş olacağız.
c# webapi içerisinde builder.Configuration adlı ConfigurationManager‘ı hepimiz bilmekteyiz. Bilmiyorsak ve tekrar hatırlamak istiyorsak aşağıdaki flooddan devam edebiliriz.
ConfigurationManager adlı sınıfa gittiğimizde tanımlamasında bunun bir IConfigurationManager,IConfigurationRoot olduğunu görüyoruz.
Buradaki IConfigurationManager interface’ine göz attığımızda ise bunun da bir IConfiguration,IConfigurationBuilder olduğunu görüyoruz.
IConfigurationBuilder‘a baktığımızda içerisinde bir Add adlı fonksiyon görüyoruz ve tanımlamasında ise
1 |
<em>Adds a new configuration source.</em> |
Yazmakta, yani yeni bir konfigurasyon kaynağı ekleyebiliyoruz, peki IConfigurationSource nedir?
Gördüğünüz gibi IConfigurationSource geriye bir IConfigurationProvder dönen Build adlı method’a sahip bir fonksiyen içeren bir interface’den başka bir şey değil. Ve bu Method içerisine sadece bir IConfigurationBuilder almakta
Özetle, builder.Configuration bir IConfigurationManager ve bu da içerisinde Add methodu ile içerisine bir IConfigurationSource interface’ini almakta ve IConfigurationSource’da IConfigurationProvider dönen bir Build methoduna sahip sadece bu kadar.
Bütün bu bildiklerimizi kullanarak ihtiyaç analizlerimizi yaptığımızda biz bir kaynağı Config altına ekleyebilmemiz için iki adet şeye ihtiyacımız var 1.Si IConfigurationProvider bir diğeri ise IConfigurationSource bunları oluşturmak adına ilk olarak IConfigurationProvider’ımızı üretmemiz gerekmekte.
Ben bağlantıyı postgre üzerinden yapacağım için adına PostgresSqlConfigurationProvider dedim siz istediğiniz ismi verebilirsiniz.
1 2 3 4 5 6 7 |
public class PostgreSqlConfigurationProvider: ConfigurationProvider { public override void Load() { base.Load(); } } |
Ek olarak, burada da görebileceğiniz üzere IConfigurationProvider üzerinden değil de ConfigurationProvider üzerinden kalıtım sağladım. Çünkü ConfigurationProvider zaten bir abstractclass ve içerisinde hazır methodları mevcut. örneğin ConfigurationReloadToken adlı bir sınıf gibi. Şuan için konumuz sadece konfiglerimizi başka bir kaynakta tutmak olduğu için burayı es geçiyorum. sadece interface değil de abstract bir class üzerinden kalıtım yaptığımızı dile getirmek istedim.
Konuya geçecek olursak, ConfigurationProvider üzerinde config dosyalarının yüklendiği, bir ade Load adlı senkronik bir method bulunmakta. Burada biz veri tabanına bağlanıp ilgili verileri okuyup çekmemiz gerekecek.
Öncelikle veri tabanına bağlanıp verileri çekeceğimiz veri tabanını ve tabloyu oluşturmak isterim.
Veritabanı isminin bir önemi olmadığı için belirtmiyorum. public şeması altında Configs isimli bir adet tablo oluşturdum içerisinde de iki adet Kolon mevcut bunlardan biri Key diğeri de Value isminde ve bunların ikisini de PrimaryKey olarak ayarladım. Yani Key:A ve Value:A değeri bir kere girilebilirr. Kolon tiplerinin ikisi de text veri türündedir.
Şimdi yazmış olduğumuz ConfigurationProvider içerisinde Load işlemi esnasında veri tabanına bağlanıp tablomuzdaki verileri çekmemiz gerekiyor.
Hangi verileri çekeceğiz diye soranlar için iki adet satır ekledim satırlar yan taraftadır.
Şimdi geldik verileri çekip ne yapacağız işlemine. ConfigurationProvider load methodu üzerinde verileri çekeceğimizi belirtmiştim, ConfigurationProvider abstract class’ında data adlı bir Dictionary bulunmakta ve bu arkadaşın tipi IDictionary<string,string?> yani içerisinde key ve value değerleri string o yüzden ekstra bir casting yapmamıza gerek yok.
1 |
public class PostgreSqlConfigurationProvider : ConfigurationProvider<br>{<br> public override void Load()<br> {<br> string connectionString = ""; // kendi postgresql veri tabanı bağlantınızı vermelisiniz.<br> string sql = "select * from \"Configs\"";<br> using var connection = new NpgsqlConnection(connectionString);<br> connection.Open();<br><br> using (var command = new NpgsqlCommand(sql, connection))<br> {<br> using (var reader = command.ExecuteReader())<br> {<br> while (reader.Read())<br> {<br> Console.WriteLine("Key: " + reader["Key"] + " | Value :" + reader["Value"]);<br> }<br> }<br> }<br><br> connection.Close();<br> base.Load();<br> }<br>} |
yukarıda bıraktığım method içerisinde connectionString adlı bir string değeri bulunmaktadır, buradaki değeri kendi postgresql veri tabanı bağlantınızı vermelisiniz, aynı zamanda sql içerisinde bulunan query’i de veri tabanı tablonuzun adı olacak şekilde güncelleyebilirsiniz. (Eğer Configs isimdeki tabloyu değil de başka bir tabloyu kullanmak isterseniz.)
burada yaptığım işlem ise key ve value değerlerini console’a yazdırmak oldu yukarıda da bahsettiğim gibi bu işlemleri bir dictonary oluşturmak ve postgreconfigurationProvider içerisinde bulunan Data adlı dictionary’mize set etmemiz lazım.
1 |
public class PostgreSqlConfigurationProvider : ConfigurationProvider<br>{<br> public override void Load()<br> {<br> string connectionString = "";<br> string sql = "select * from \"Configs\"";<br> using var connection = new NpgsqlConnection(connectionString);<br> connection.Open();<br> Data = new Dictionary<string, string?>();<br> using (var command = new NpgsqlCommand(sql, connection))<br> {<br> using (var reader = command.ExecuteReader())<br> {<br> while (reader.Read())<br> {<br> Data.Add(reader["Key"].ToString()!, reader["Value"].ToString());<br> }<br> }<br> }<br> }<br>} |
Son hali yukarıdaki şekildedir. Peki bunu nasıl test edeceğiz?? bildiğiniz gibi c# içerisinde configuration ayarlarını IOptions parametresi ile çekebilmektesiniz, yani buradaki smtp ayarlarını tutabilecek bir sınıf oluşturalım.
Yazmış olduğumuz ConfigurationProvider’ı kullanabilmek adına Program.cs dosyamızı aşağıdaki gibi değiştirdim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using DotnetConfiguration.CustomConfiguration; using DotnetConfiguration; var builder = WebApplication.CreateBuilder(args); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var configurationManager = builder.Configuration as IConfigurationManager; configurationManager.Add(new PostgreConfigurationSource()); builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection(nameof(SmtpOptions))); builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapControllers(); await app.RunAsync(); |
Ve TestController adlı bir controller oluşturdum böylelikle IOptions değerinin yüklendiğini görebileceğim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace DotnetConfiguration.Controllers; [ApiController] [Route("api/[controller]")] public sealed class TestController : ControllerBase { private readonly SmtpOptions _options; public TestController(IOptions<SmtpOptions> options) { _options = options.Value; } [HttpGet] public async Task<IActionResult> Get() { return Ok(_options); } } |
Burada istediğim SmtpOptions class’ı ise şu şekilde.
1 2 3 4 5 6 7 |
namespace DotnetConfiguration; public class SmtpOptions { public int SmtpPort { get; set; } public string SmtpHost { get; set; } } |
Şimdi test etmek için endpoint’e istek atalım.
İstek attığımda ise aşağıdaki şekilde bir response görüyoruz.
Burada gördüğünüz gibi bizim veri tabanımızda vermiş olduğumuz verilerimiz yerine default değerler verilmektedir int default değeri 0 string default değeri olarak da null verilmiş.
Peki neden?
Configuration Binding işlemi belirtmiş olduğumuz class’ın ismiyle başlar yani biz bir class içerisinde property yazdığımız takdirde, ClassIsmi:PropertyIsmi şeklinde devam edecektir.
Bu sebepten ötürü veri tabanımızdaki Key değerlerimizin başına ClassIsmi: vermek zorundayız.
Yukarıda verdiğim bilgi doğrultusunda key değerlerini yandaki gibi değiştirdim.
sonrasında istek attığımızda ise gördüğünüz gibi değerlerimiz gelmektedir.
Unutmayın değerleri burada elde edebilmek adına builder.Services.Configure diyerek IOptions için konfigurasyonu geçmemiz, ve yukarıda belirttiğim şekilde veri tabanı tablomuzu ve değerlerimizi girmemiz gerekmektedir.
Projenin github linkine ulaşmak için
Burada fark edebileceğiniz üzere configuration veri tabanınızın bağlantı dizesini vermeniz gerekiyor, bunu kodunuz üzerinde direkt olarak belirtmek yerine daha farklı yöntemler kullanarak(environment gibi) projenize ekleyip kullanabilirsiniz. İyi çalışamalr dilerim.