DEV Community

cengmylmz
cengmylmz

Posted on

ASPNET CORE - Options Pattern

Options Pattern'nin aslında tek bir cümle ile açıkladığında ki karşılığı type-safe bir şekilde appsettings.json içerisindeki konfigurasyon değerlerinin okunmasını sağlamaktır.
Options Pattern kullanımının nasıl olduğunu incelemeden önce appsettings.json içerisiden değerleri okumak için neler kullandığımıza bakalım.
Proje olarak MVC uygulaması seçip bu proje üzerinden örneklerimizi inceleyeceğiz.İlk olarak appsettings.json dosyasına üyelik sistemi için kullanacağımız konfigürasyon değerlerini ekleyelim;

"IdentityConfiguration": {
    "UserConfiguration": {
      "RequiredPhoneNumber": true,
      "DefaultPassword" : "password"
    },
    "PasswordConfiguration": {
      "MinLength": 8,
      "RequireLowercase": true,
      "RequireUppercase": true
    }
  }
Enter fullscreen mode Exit fullscreen mode

Projeye Controllers klasörü altına AuthController adında bir controller ekleyelim. Constructor'da IConfiguration geçerek bunun üzerinden appsettings.json da yer alan değerleri nasıl okuyabildiğimize bakalım.
Örneğin sadece bir değerin lazım olduğunu düşünelim ve bunu nasıl okuyabileceğimize bakalım. appsettings.json içerisinde tanımlı olan minLength değerini okuyalım.

public class AuthController : Controller
{
   private readonly IConfiguration _configuration;

   public AuthController(IConfiguration configuration)
   {
       _configuration = configuration;
   }

   public IActionResult Index()
   {
       var passwordMinLength = _configuration.GetValue<int>("IdentityConfiguration:PasswordConfiguration:MinLength");

       return View();
   }
}
Enter fullscreen mode Exit fullscreen mode

Eğer ki bir appsettings.json içerisinde yer alan bir section lazımsa ve buna ait değerlere ihtiyacımız varsa önce ilgili sectiona ulaşıp onun üzerinden talep ettiğimiz değerleri alabiliriz.Ayrıca Configuration Hierarchy ile de alt ögelere de tek bir ifade ile ulaşmamıza olanak sağlar, bunun için ":" işareti kullanılır.

public class AuthController : Controller
{
     private readonly IConfiguration _configuration;

     public AuthController(IConfiguration configuration)
     {
         _configuration = configuration;
     }

     public IActionResult Index()
     {
         var userConfiguration = _configuration.GetSection("IdentityConfiguration:UserConfiguration");
         var defaultPassword = userConfiguration.GetValue<string>("DefaultPassword");

         return View();
     }
}
Enter fullscreen mode Exit fullscreen mode

Burada öncelikle ConfigurationManager tipinde userConfiguration değişkenine ilgili sectionu alıyoruz ve ardından GetValue<> methoduyla istemiş olduğumuz değeri çekiyoruz.


Bind Configuration to Object

Çok fazla string üzerinde çalışmak bizi hataya sürekleyebilmektedir, bu nedenle yazının başlığında geçen Options Pattern için bir adım daha atıyoruz ve appsettings.json içerisinde ki değerlerimizi bir objeye geçiyoruz. Bu şekilde nesneler üzerinde çalışmanın gücünden faydalanacağız. Configurations klasörü ekleyip içerisine aşağıdaki sınıfları ekleyelim.

PasswordConfiguration.cs

public class PasswordConfiguration
{
    public int MinLength { get; set; }
    public bool RequireLowercase { get; set; }
    public bool RequireUppercase { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

UserConfiguration.cs

public class PasswordConfiguration
{
    public bool RequiredPhoneNumber { get; set; }
    public string DefaultPassword { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Bu tanımlamış olduğumuz configuration sınıflarını okuduğumuz değerleri bind etmek için kullanacağız.

AuthController.cs

public class AuthController : Controller
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public IActionResult Index()
    {
        UserConfiguration userConfiguration = new();
        _configuration.Bind("IdentityConfiguration:UserConfiguration",userConfiguration);

        var requiredPhoneNumber = userConfiguration.RequiredPhoneNumber;

        return View();
    }
}

Enter fullscreen mode Exit fullscreen mode

IOptions < TOptions >

Başlıkta adı geçen Options Pattern için örneklendirmelerimize başlıyor ve string ifadelerden kurtulup type-safe olarak uygulamarımız geliştiriyor olacağız.

Değerleri okumak için Microsoft.Extensions.Configuration namespace i altında yer alan ConfigurationManager sınıfından faydalanacağız. Net 6 tarafında top level statements olarak gelen Program.cs dosyası içerisinde ConfigurationManager tipinde nesnemizi aşağıdaki gibi alalım.Bundan sonra Program.cs içerisinde configuration olarak kullanacağız.

var configuration = builder.Configuration;
Enter fullscreen mode Exit fullscreen mode

Configuration sınıflarımıza kapsayıcı IdentityConfiguration sınıfını da ekliyoruz;

IdentityConfiguration.cs

public class IdentityConfiguration
{
    public PasswordConfiguration PasswordConfiguration { get; set; }
    public UserConfiguration UserConfiguration { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

Ardından Program.cs tarafında service konfigurasyonumuzu yapmamız gerekiyor;

Program.cs

builder.Services.Configure<IdentityConfiguration>(configuration.GetSection(nameof(IdentityConfiguration)));

Enter fullscreen mode Exit fullscreen mode

Burada hiçbir magic string'e yer vermiyor oluşumuz olabilecek synthax hataları nedeniyle runtime da hata almanın önüne geçiyor, compile time da hatayı yakalayabiliyoruz.

ve son olarak da kullanmak istediğimiz yerde aşağıdaki kod bloğunu yazıyoruz;

AuthController.cs

public class AuthController : Controller
{
    private readonly IdentityConfiguration _identityConfiguration;

    public AuthController(IOptions<IdentityConfiguration> options)
    {
        _identityConfiguration = options.Value;
    }

    public IActionResult Index()
    {
        var passwordConfiguration = _identityConfiguration.PasswordConfiguration;
        var minLength = passwordConfiguration.MinLength;

        return View();
    }
}

Enter fullscreen mode Exit fullscreen mode

IOptions generic sınıfı için geçtiğimiz TOptions(bu örnek için IdentityConfiguration) sınıfı için bazı kısıtlar bulunmaktadır;

  • Abstract class olmamalıdır.
  • Public ve parametresiz constructor'ı olmalıdır.
  • Bind edilecek propertylere değer atanması ve okunması için public olarak tanımlanmalıdır.

IOptions için bize neler sağlıyor veya nasıl çalışıyor sorusuna cevap verecek olursak;

  • Options Reloading i desteklemez(Bir kere değerler okunur ve uygulama çalışırken değer değiştirmeniz uygulamaya yansımaz).
  • DI container a singleton olarak register olur.(Uygulama ayağa kalktıktan sonra hep aynı instance üzerinden çalışır.)
  • Her Service Lifetime'ına inject edilebilir.(Transient,Scoped,Singleton)
  • Named Options'ı desteklemez(aşağıda değiniyor olacağız.)

IOptionsSnapshot < TOptions >

IOptions<> ile yapamadığımız Options Reloading i yapabilmemize olanak sağlar.Bunu deneyimlemek için şöyle bir basit senaryo üzerinden yola çıkalım.Uygulamanın tema ayarlarını appsettings.json dan okuduğumuzu düşünün ve uygulama çalışıyorken sayfada herhangi bir div'in background color değerini değiştirelim.Önce IOptions ile deneyip olmadığını da gözlemleyelim.Bunun için appsettings.json dosyasına aşağıdaki değerleri ekleyerek başlayalım;

appsettings.json

"ThemaConfiguration": {
    "SuccesBackgroundColor": "#00ffff"
  }
Enter fullscreen mode Exit fullscreen mode

Daha sonra configuration sınıfını yazalım;

ThemaConfiguration.cs

public class ThemaConfiguration
{
    public string SuccesBackgroundColor { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

ardından Program.cs tarafında konfigürasyon tanımı yapalım;
Program.cs

builder.Services.Configure<ThemaConfiguration>(configuration.GetSection(nameof(ThemaConfiguration)));

Enter fullscreen mode Exit fullscreen mode

ve şimdi sıra geldi denemeye; Index sayfamız background color değeri değişecek bir div içeriyor sadece;

Index.cshtml

<style>
    .success {
        width: 100%;
        height: 300px;
        background-color:@ViewBag.SuccesBackgroundColor
    }
</style>
<div class="success">

    @ViewBag.SuccesBackgroundColor
</div>

Enter fullscreen mode Exit fullscreen mode

ilk olarak IOptions<> ile deniyor olacağız bunun için HomeController içerisine aşağıda ki kodları yazalım;

HomeController.cs

public class HomeController : Controller
{
    private readonly ThemaConfiguration _themaConfiguration;
    public HomeController(IOptions<ThemaConfiguration> options)
    {
        _themaConfiguration = options.Value;   
    }

    public IActionResult Index()
    {
        ViewBag.SuccesBackgroundColor = _themaConfiguration.SuccesBackgroundColor;
        return View();
    }
}
Enter fullscreen mode Exit fullscreen mode

Projeyi çalıştırın ve proje çalışırken appsettings.json dosyasında yer alan "SuccesBackgroundColor" değerini "#ff0000" ile değiştirin tarayıcı üzerinden sayfayı yenilediğinizde bir değişiklik olmadığını göreceksiniz. Çünkü IOptions ile değerler bir kere okunur ve uygulama ayakta olduğu sürece o değerler kullanılır.

HomeController'da küçük bir değişiklik yapıyoruz ve uygulamayı tekrar ayağa kaldırıyoruz.

HomeController.cs

public class HomeController : Controller
{
    private readonly ThemaConfiguration _themaConfiguration;
    public HomeController(IOptionsSnapshot<ThemaConfiguration> options)
    {
        _themaConfiguration = options.Value;   
    }

    public IActionResult Index()
    {
        ViewBag.SuccesBackgroundColor = _themaConfiguration.SuccesBackgroundColor;
        return View();
    }
}
Enter fullscreen mode Exit fullscreen mode

Biraz önceki işlemleri tekrar yapalım uygulama çalışıyorken "SuccesBackgroundColor" değerini "#0000ff" ile değiştirelim ve sayfayı yeniledikten sonra div'in arkaplan renginin mavi olduğunu görelim.İşte bu Options Reloading dediğimiz uygulama çalışıyor olsa da appsettings.json dosyasında ki değişiklikleri uygulamaya yansıtmaya yarar.

IOptionsSnapshot ile ilgili söylememiz gerekenler;

  • Reloading'i destekler.
  • DI container'a scoped olarak register olur.
  • Her requestte değerler yeniden yüklenir.
  • Singleton eklenen servisler ile kullanılamaz
  • Named Options destekler.

IOptionsMonitor < TOptions >

Yukarıda zaten iki çeşit options pattern ile ilgili çalışma yaptık ve bir üçüncüye neden ihtiyaç duyduğumuzu örnek üzerinden görelim. Yaptığımız bu inject işlemleri vs Controller sınıfında olmaması gerekir haliyle gelin bunları ThemaService adında tema ile ilgili işleri yapacağımız ayrı bir class a taşıyalım ve bunu da aynı isme sahip interface de n inherit edelim. Services klasörü ekledikten sonra içerisine aşağıda ki dosyaları ekleyelim

IThemaService.cs

public interface IThemaService
{
    string GetSuccessBackgroundColor();
}

Enter fullscreen mode Exit fullscreen mode

ThemaService.cs

public class ThemaService : IThemaService
{
    private readonly ThemaConfiguration themaConfiguration;

    public ThemaService(IOptionsSnapshot<ThemaConfiguration> options)
    {
        themaConfiguration = options.Value;
    }

    public string GetSuccessBackgroundColor()
    {
        return themaConfiguration.SuccesBackgroundColor;
    }
}

Enter fullscreen mode Exit fullscreen mode

Program.cs

builder.Services.AddSingleton<IThemaService, ThemaService>();
builder.Services.Configure<ThemaConfiguration>(configuration.GetSection(nameof(ThemaConfiguration)));

Enter fullscreen mode Exit fullscreen mode

Şimdi burada bir önceki senaryodan farklı olarak ne yaptık? ThemaService ekledik ve Options Pattern'in uygulanmasını buraya taşıdık.Bu şekilde çalıştırdığımızda "Can not consume scoped service" hatası ile karşılacağız. Aslında belirtmediğimiz buradaki fark ThemaService'in projeye singleton olarak eklenmesiydi.Haliyle burada hataya sebep olan singleton bir service den scoped service çağrımları yapmak istendiğinde DI validation'a takılmasıdır. Böyle bir durumda karşımıza çözüm olarak IOptionsMonitor<> tipi çıkmaktadır.ThemaService içerisinde küçük iki değişiklik yapıyoruz.

ThemaService.cs

public class ThemaService : IThemaService
{
    private readonly ThemaConfiguration themaConfiguration;

    public ThemaService(IOptionsMonitor<ThemaConfiguration> options)
    {
        themaConfiguration = options.CurrentValue;
    }

    public string GetSuccessBackgroundColor()
    {
        return themaConfiguration.SuccesBackgroundColor;
    }
}

Enter fullscreen mode Exit fullscreen mode

Bu değişiklikten sonra uygulamayı ayağa kaldırdığınızda appsettings.json üzerinde renk değişikliği yaptığınızda sayfayı yenileyerek değişikliği görebilirsiniz.

IOptionsMonitor<> için özet olarak aşağıdakileri söyleyebiliriz;

  • Reloading'i destekler.
  • Singleton olarak register edilir.
  • Her service lifetime için kullanılabilir.
  • Named Options desteği vardır.

Named Options

Aslında bu konuyu doğrudan anlayabilmek için en güzel yöntem her zaman olduğu gibi örneklemektir. Bir uygulama yazdığınızı ve bu uygulamaya Google, Facebook ile login olabilmeyi istediklerini düşünün.Burada her oauth için clientId ve clientSecret bilgisi olacaktır.Haliyle appsettings.json dosyası aşağıda ki gibi olacaktır;

appsettings.json

"OAuthClient": {
    "Google": {
      "ClientId": "123",
      "ClientSecret": "123"
    },
    "Facebook": {
      "ClientId": "qwe",
      "ClientSecret": "asd"
    }
  }
Enter fullscreen mode Exit fullscreen mode

Konfigurasyon değerlerini karşılayacak sınıfımızı yazalım;

OAuthConfiguration.cs

public class OAuthClientConfiguration
{
    public const string Google = "Google";
    public const string Facebook = "Facebook";

    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

Servis olarak ekleyelim;
Program.cs

builder.Services.
    Configure<OAuthClientConfiguration>(OAuthClientConfiguration.Google,
        configuration.GetSection($"{nameof(OAuthClientConfiguration)}:{OAuthClientConfiguration.Google}"));


builder.Services.
    Configure<OAuthClientConfiguration>(OAuthClientConfiguration.Facebook,
        configuration.GetSection($"{nameof(OAuthClientConfiguration)}:{OAuthClientConfiguration.Facebook}"));


Enter fullscreen mode Exit fullscreen mode

Magic string kullanmamaya özen gösterecek şekilde eklemelerimizi yaptık ve şimdi sıra kullanmaya geldi;

HomeController.cs

public class HomeController : Controller
{

    private readonly OAuthClientConfiguration _googleClientConfiguration;

    public HomeController(IOptionsMonitor<OAuthClientConfiguration> options)
    {
        _googleClientConfiguration = options.Get(OAuthClientConfiguration.Google);
    }

    public IActionResult Index()
    {
        var clientId = _googleClientConfiguration.ClientId;
        var clientSecret = _googleClientConfiguration.ClientSecret;

        return View();
    }
}
Enter fullscreen mode Exit fullscreen mode

Böylece yeni bir oauth talebi geldiğinde mevcut yapıya dokunmadan rahatlıkla ekleme yapabiliriz.

Özet olarak type safe olarak Options kullanmak birden fazla yöntem ile mümkün kılınmaktadır. Burada hangisini kullanacağınıza tamamen ihtiyaçlar doğrultusunda karar vermelisiniz.Aslında Type safe çalışmaya özen gösterdiğinizde çok daha az karmaşık işle uğraşıyor olacaksınız.

iyi çalışmalar;

Top comments (0)