.Net’te Option Pattern ve Validation

4 min readFeb 3, 2025

Modern .NET uygulamalarında, yapılandırma ayarlarını yönetmek önemli bir gerekliliktir. Geleneksel olarak, bu ayarlar genellikle appsettings.json, ortam değişkenleri veya komut satırı argümanları gibi farklı kaynaklardan okunur ve doğrudan kullanılabilirdi. Ancak, bu yöntemler uygulama bakımını zorlaştırabilir ve yapılandırma hatalarına karşı savunmasız hale getirebilir.

Options Pattern, .NET Core ve .NET 5+ sürümlerinde, yapılandırma yönetimini daha düzenli ve güvenli hale getiren bir tasarım desenidir. Bu desen sayesinde yapılandırma ayarları, POCO (Plain Old CLR Object) sınıflarına bağlanarak güçlü bir tip güvenliği sağlanır. Böylece, uygulama başlatılırken yapılandırma hataları erken tespit edilebilir ve geliştirme süreci daha güvenilir hale gelir.

Neden Kullanılır?

  • Daha Modüler ve Yönetilebilir Kod: Options Pattern, yapılandırma ayarlarını merkezi ve modüler bir yapıya kavuşturarak kodun daha okunabilir ve sürdürülebilir olmasını sağlar.
  • Dependency Injection ile Entegrasyon: IOptions<T>, IOptionsSnapshot<T> ve IOptionsMonitor<T> gibi arayüzler sayesinde yapılandırma ayarları Dependency Injection ile yönetilebilir.
  • Runtime Güncellenebilirlik: IOptionsMonitor<T> ile çalışma zamanında yapılandırma ayarları dinamik olarak değiştirilebilir.
  • Tip Güvenliği: JSON veya başka bir kaynaktan alınan yapılandırmalar, tip dönüşüm hatalarından kaçınarak doğrudan nesnelere bağlanır.

Yapılandırma ayarlarının yönetimi kadar, bunların doğruluğunu sağlamak da kritik bir konudur. Yanlış veya eksik ayarlar uygulamanın beklenmedik şekilde çalışmasına veya hata vermesine neden olabilir. Bu nedenle, Options Pattern ile birlikte doğrulama (validation) mekanizmalarının kullanılması, hataları önceden yakalamak ve güvenilir bir sistem oluşturmak için önemlidir.

Yazı boyunca kullanacağım örnek ayarlar aşağıdaki gibi olacaktır.

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppSettings": {
"ApplicationName": "MyApp",
"MaxUsers": 500,
"Url": "https://github.com"
}
}

En basit haliyle ayarların doğruluğunu sağlamak için hızlıca kodunuza aşağıdaki gibi basit koşullar ekleyebilirsiniz.

var applicationName = builder.Configuration.GetValue<string>("AppSettings:ApplicationName");
if (string.IsNullOrEmpty(applicationName))
{
throw new InvalidOperationException("ApplicationName is required");
}

veya

var applicationName = builder.Configuration.GetValue<string>("AppSettings:ApplicationName");
ArgumentException.ThrowIfNullOrEmpty(applicationName);

Tek bir değerin doğruluğunu sağlamak için en basit yöntem elbette hızlıca bir if eklemek veya .Net sağladığı built-in kaynakları kullanmak olacaktır ancak gerçek dünya senaryolarında uygulamalarımız tek bir configuration object veya value içermez. Uygulamalarımız geliştikçe configuration dosyamız kullandığımız teknolojiler veya iletişim kurulan servislerin bilgileri ile dolup taşar. Yukarıdaki bu yöntem basittir ancak gerçek dünya senaryolarında kullanışlı değildir. Bunun yerine daha okunaklı ve yönetilebilir bir validation yöntemine ihtiyaç duyarız.

Data Annotations

Data Annotations kullanmak bunu yapmanın basit bir yoludur.
Doğrulanmasını istediğimiz özellikleri işaretlemek için öznitelik(attribute) kullanırız.

public class AppSettings
{
[Required(ErrorMessage = "Application Name is required")]
public string ApplicationName { get; set; }

[Range(1, 100, ErrorMessage = "Max Users must be between 1 and 100")]
public int MaxUsers { get; set; }

[Required(ErrorMessage = "Url is required"), Url(ErrorMessage = "The url format is incorrect")]
public string Url { get; set; }
}

Ayarlamalarınızı doğrulamak için artık tek yapmanız gereken ValidateDataAnnotation extension metodunu kullanmaktır.

builder.Services.AddOptions<AppSettings>()
.Bind(builder.Configuration.GetSection("AppSettings"))
.ValidateDataAnnotations()
.ValidateOnStart();

ValidateOnStart ile uygulama başladığında ilgili yapılandırma için validation uygulanır ve eğer eksik veya hatalı bir durum varsa hata verilir.

fail: Microsoft.Extensions.Hosting.Internal.Host[11]
Hosting failed to start
Microsoft.Extensions.Options.OptionsValidationException:
DataAnnotation validation failed for 'AppSettings' members: 'MaxUsers'
with the error: 'Max Users must be between 1 and 100'.

Fluent Validation

FluentValidation .Net ekosisteminde oldukça yaygın kullanılan bir kütüphanedir. Option Pattern Validation için FluentValidation kullanmak isteyebilirsiniz. (Ben istedim 🥳) FluentValidation kullanımını kolaylaştırmak için OptionValidationSharp isimli bir kütüphane oluşturdum.

Bu kütüphane FluentValidation ile birlikte çalışır ve yazdığınız validation kurallarını Option Validation için kullanmanızı sağlar.

Kütüphaneyi NuGet Package Manager üzerinden indirmek için:

dotnet add package OptionValidationSharp --version 1.0.0

Örneğin;

public class AppSettingsValidator : AbstractValidator<AppSettings>
{
public AppSettingsValidator()
{
RuleFor(r => r.ApplicationName).NotEmpty();
RuleFor(r => r.MaxUsers).InclusiveBetween(1,100);
RuleFor(r => r.Url).NotEmpty();
RuleFor(r => r.Url)
.Must(url => Uri.TryCreate(url, UriKind.Absolute, out _))
.When(r => !string.IsNullOrWhiteSpace(r.Url));
}
}

Artık her şey hazır. Tek yapmak gereken ValidateOptionSharp isimli methodu kullanmak.

builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);

builder.Services.AddOptions<AppSettings>()
.Bind(builder.Configuration.GetSection("AppSettings"))
.ValidateOptionSharp() // <- 🔨 Validation Option with Fluent Validation
.ValidateOnStart();

Option Validation Sharp

Bu kütüphaneden biraz daha bahsetmek istiyorum. Aslında oldukça küçük ancak kullanışlı bir kütüphane hazırladığımı düşünüyorum. Kütüphane FluentValidation bağımlılığı taşır. Option Validation için yazdığınız kuralları Start esnasında kullanabilmeniz için küçük bir extension method sağlar.

ValidateOptionSharp arkasında OptionValidate isimli bir sınıfı Singleton olarak ekler.

using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace OptionValidationSharp;

/// <summary>
/// Option Validate extension
/// </summary>
/// <typeparam name="TOptions">
/// The type of options being validated.
/// </typeparam>
public class OptionValidate<TOptions>(IServiceProvider serviceProvider, string? optionName) : IValidateOptions<TOptions>
where TOptions : class

{
/// <summary>
/// Validate the options.
/// </summary>
/// <param name="name">
/// The name of the options instance being validated, if any.
/// </param>
/// <param name="options">
/// The options instance to validate.
/// </param>
/// <returns>
/// The <see cref="ValidateOptionsResult" />.
/// </returns>
public ValidateOptionsResult Validate(string? name, TOptions options)
{
if (optionName is not null && optionName != name)
{
return ValidateOptionsResult.Skip;
}

ArgumentNullException.ThrowIfNull(options);

using var scope = serviceProvider.CreateScope();

var validator = scope.ServiceProvider.GetRequiredService<IValidator<TOptions>>();

var result = validator.Validate(options);

if (result.IsValid)
{
return ValidateOptionsResult.Success;
}

var type = options.GetType().Name;
var errors = string.Join(", ", result.Errors.Select(x => x.ErrorMessage));
var message = $"Options validation failed for type '{type}'. {errors}";
return ValidateOptionsResult.Fail(message);
}
}

FluentValidation ile yazdığınız kurallar IValidator<T> olarak çağırılıp kullanılabilir. OptionValidationSharp tam olarak bu özelliği kullanır. Oluşturduğunuz kurallı yukarıda belirtildiği gibi çeker ve objeniz üzerinde uygular. Eğer bir hata varsa bunu geri döndürür.

fail: Microsoft.Extensions.Hosting.Internal.Host[11]
Hosting failed to start
Microsoft.Extensions.Options.OptionsValidationException:
Options validation failed for type 'AppSettings'.
'Max Users' must be between 1 and 100. You entered 500.

Modern uygulamalarda Option Pattern oldukça önemli bir rol oynar. Bu kadar önem kazanan bir noktada validation da oldukça önemlidir.

Umarım faydalı olur.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Furkan Güngör
Furkan Güngör

Written by Furkan Güngör

Solution Developer — I want to change the world, give me the source code.

No responses yet

Write a response