ASPNET CORE - Options Pattern
Tags: optionspattern, appsettings, aspnetcore
Published: 2022-06-03 12:39:30

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
    }
  }
```

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();
   }
}
```


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();
     }
}
```
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; }
}
```


`UserConfiguration.cs`

```
public class PasswordConfiguration
{
    public bool RequiredPhoneNumber { get; set; }
    public string DefaultPassword { get; set; }
}
```

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();
    }
}

```

---


## 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;
```

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; }
}

```

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

`Program.cs`

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

```
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();
    }
}

```



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"
  }
```
Daha sonra configuration sınıfını yazalım;

`ThemaConfiguration.cs`
```
public class ThemaConfiguration
{
    public string SuccesBackgroundColor { get; set; }
}

```

ardından Program.cs tarafında konfigürasyon tanımı yapalım;
`Program.cs`
```
builder.Services.Configure<ThemaConfiguration>(configuration.GetSection(nameof(ThemaConfiguration)));

```

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>

```

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();
    }
}
```
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();
    }
}
```

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();
}

```

`ThemaService.cs`

```
public class ThemaService : IThemaService
{
    private readonly ThemaConfiguration themaConfiguration;

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

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

```

`Program.cs`

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

```

Ş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;
    }
}

```

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"
    }
  }
```
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; }
}

```

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}"));


```

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();
    }
}
```

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;