> ## Content Index
> Fetch the complete content index at: https://mennankose.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# .NET Core'da Custom Feature Flag Provider Geliştirme
- URL: https://mennankose.com/net-coreda-custom-feature-flag-provider-gelistirme/
- Published: 2021-05-14T18:37:33.000Z
- Updated: 2026-10-29T05:26:17.000Z
- Author: Mennan Köse
- Tags: .Net Core, ASP.NET Core, #Import 2026-11-06 07:43

Merhabalar 👋

Bu yazımda .NET Core'da feature'ları yönetebildiğimiz kütüphane olan [FeatureManagement](https://www.nuget.org/packages/Microsoft.FeatureManagement/?ref=mennankose.com) kütüphanesinden ve feature ayarlarımızı saklayabileceğimiz farklı alanlardan bahsedeceğim.

Bazen geliştirdiğimiz özellikleri canlı ortama almışken o özelliklerin kapalı gelmesini ve zamanı geldiği zaman açmak isteyebiliriz. Bu tip operasyonları yönetmek için .NET Core'da FeatureManagement kütüphanesinden yararlanıyoruz.

## FeatureManagement'ı Kullanarak Feature'ların Yönetimi

Bu yazıda örneğimizi bir console application üzerinde gerçekleştireceğiz. Örneğimize başlamak için aşağıdaki komutu kullanarak bir console application oluşturalım.

```bash
dotnet new console -n FeatureManagement.Console

```

Sonrasında ise projemize Microsoft.FeatureManagement pakedini referans olarak eklemek için aşağıdaki komutu yazalım.

```bash
dotnet add package Microsoft.FeatureManagement

```

Projeyi oluşturduktan sonra kullanmış olduğumuz IDE veya metin editörü yardımıyla projemizi açalım. `Program.cs` dosyamızın içeriğini aşağıdaki şekilde değiştirelim.

```csharp
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;

namespace FeatureManagement.Console
{
	class Program
	{
		static IConfiguration SetupConfiguration()
		{
			var builder = new ConfigurationBuilder()
					.SetBasePath(Directory.GetCurrentDirectory())
					.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

			return builder.Build();
		}

		static IServiceCollection ConfigureServices()
		{
			var services = new ServiceCollection();
			var config = SetupConfiguration();

			services.AddSingleton(config);
			services.AddFeatureManagement(config.GetSection("Features"));

			return services;
		}

		static async Task Main(string[] args)
		{
			var services = ConfigureServices();
			var serviceProvider = services.BuildServiceProvider();
			var featureManager = serviceProvider.GetRequiredService<IFeatureManager>();

			if (await featureManager.IsEnabledAsync("SendEmail"))
			{
				SendMail();
			}
		}

		static void SendMail()
		{
			System.Console.WriteLine("E-mail sent!");
		}
	}
}

```

Bu değişikliği yaptıktan sonra ise projemizin ana dizinine `appsettings.json` dosyasını ekleyelim ve içeriğini aşağıdaki şekilde düzenleyelim.

```json
{
  "Features": {
    "SendEmail": true
  }
}

```

Şimdi ise neler yaptığımızı inceleyelim. `SetupConfiguration` metodu ile **appsettings.json** dosyasının uygulamamız tarafından okunmasını sağladık. Ayar dosyasını okumamızın sebebi FeatureManagement'ın varsayılan olarak aktif/pasif ayarlarını appsettings.json üzerinde yönettiğinden dolayıdır.

`ConfigureServices` metodu ile dependency injection ayarlarını yaptık. Bu bölümde `services.AddFeatureManagement` extension method'u ile FeatureManagement'ı uygulamamız genelinde kullanılabilir hale getirdik. Parametre olarak verdiğimiz `config.GetSection("Features")` değeri ile de feature'ların appsettings.json dosyasında hangi bölümde bulunduğunu belirttik. Ayrıca bu extension method [IFeatureManager](https://docs.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.ifeaturemanager?ref=mennankose.com) interface'ini uygulama genelinde kullanılabilir hale getirmektedir.

> `services.AddFeatureManagement` metoduna herhangi bir parametre vermezseniz feature ayarlarını **appsettings.json** dosyasında `FeatureManagement` isminde bir alanda arayıp yönetecektir.

Artık uygulamamız üzerinde feature'ların aktif olup olmadığını `IFeatureManager.IsEnabledAsync` metodu ile kontrol edebiliriz. Örnek kodumunuzun **Main** metodunda `featureManager.IsEnabledAsync("SendMail")` çağrımı ile SendMail feature'ının aktif olup olmadığını anlayabiliriz. SendMail'in aktif olup olmadığının bilgisi **appsettings.json** dosyasında `Features.SendMail` bölümününde bulunmaktadır.

Şimdi `dotnet run` komutu ile uygulamamızı çalıştıralım ve çıktıyı kontrol edelim.

![Feature Management](https://cdn.synaps.media/mennan/content/images/2021/05/feature-management-1.png)

appsettings.json dosyasında SendEmail feature'ının değeri true olduğundan dolayı ekranda **Email sent!** yazmaktadır. Bu alanın değerini false olarak değiştirirsek ekranda hiçbir şey yazmayacaktır.

### Feature Filter Tanımlamak

Eğer featurelarımızın temel yönetimini true/false olarak değil de daha kapsamlı değerlerle yönetmek istersek Feature Filter'lardan yararlanabiliriz.

Örneğin kullanıcının girmiş olduğu mobil işletim sistemi versiyonuna göre push notification gönderim seçeneklerini düzenleyelim. Öncelikle projemize **appsettings.json** dosyamızı aşağıdaki şekilde düzenleyelim.

```json
{
  "Features": {
    "SendPush": {
      "EnabledFor": [
        {
          "Name": "OperatingSystem",
          "Parameters": {
            "Value": "14.5"
          }
        }
      ]
    }
  }
}

```

Bu dosyadaki tanımlamaya göre feature'ımızın ismi `SendPush` ve filter'ımızın ismi ise `OperatingSystem`'dır. `Parameteres`'ın altında ise filter'ın çalışması gereken koşulu belirtiyoruz.

Şimdi ise feature filter'ı tanımlayabiliriz. Bunun için projemize **PushNotificationFilter.cs** isminde bir dosua ekleyelim ve içeriğini aşağıdaki şekilde düzenleyelim.

```csharp
using System.Threading.Tasks;
using Microsoft.FeatureManagement;

namespace FeatureManagement.Console
{
	public class PushNotificationContext
	{
		public string OsVersion { get; set; }
	}

	[FilterAlias("OperatingSystem")]
	public class PushNotificationFilter : IContextualFeatureFilter<PushNotificationContext>
	{
		public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext, PushNotificationContext appContext)
		{
			return Task.FromResult(appContext.OsVersion == "14.5");
		}
	}
}

```

**PushNotificationFilter** sınıfımızın appsettings.json dosyasında hangi filter ile eşleşeceğini [FilterAlias](https://docs.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.filteraliasattribute?view=azure-dotnet-preview&ref=mennankose.com) attribute'ü ile belirtiyoruz. Feature filter dışarıdan veri alacağından dolayı sınıfı [IContextualFeatureFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.icontextualfeaturefilter-1?view=azure-dotnet-preview&ref=mennankose.com) interface'inden türetiyoruz. Eğer dışarıdan alınacak bir context'e ihtiyaç yoksa [IFeatureFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.ifeaturefilter?view=azure-dotnet-preview&ref=mennankose.com) interface'i kullanılabilir. `EvaluateAsync` metodu ile context üzerinden gelen verinin **14.5** değerine eşit olup olmadığına bakıyoruz. Eğer eşitse feature'ımız aktif olarak değerlendirilecektir.

Son aşamada ise yazdıklarımızı test edelim. **Program.cs** dosyasının içeriğini aşağıdaki şekilde düzenleyelim.

```csharp
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;

namespace FeatureManagement.Console
{
	class Program
	{
		static IConfiguration SetupConfiguration()
		{
			var builder = new ConfigurationBuilder()
					.SetBasePath(Directory.GetCurrentDirectory())
					.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

			return builder.Build();
		}

		static IServiceCollection ConfigureServices()
		{
			var services = new ServiceCollection();
			var config = SetupConfiguration();

			services.AddSingleton(config);
			services.AddFeatureManagement(config.GetSection("Features"))
					.AddFeatureFilter<PushNotificationFilter>();

			return services;
		}

		static async Task Main(string[] args)
		{
			var services = ConfigureServices();
			var serviceProvider = services.BuildServiceProvider();
			var featureManager = serviceProvider.GetRequiredService<IFeatureManager>();

			var supportedContext = new PushNotificationContext { OsVersion = "14.5" };
			var unsupportedContext = new PushNotificationContext { OsVersion = "11.4" };

			if (await featureManager.IsEnabledAsync("SendPush", supportedContext))
			{
				SendPush();
			}

			if (!await featureManager.IsEnabledAsync("SendPush", unsupportedContext))
			{
				System.Console.WriteLine("Unsupported operating system.");
			}
		}

		static void SendPush()
		{
			System.Console.WriteLine("Push sent!");
		}
	}
}

```

`ConfigureServices`'ın altında `AddFeatureFilter<PushNotificationFilter>()` extension method'unu kullanarak FeatureManagement'a **PushNotificationFilter** sınıfını kullanmasını ilettik.

> Birden fazla feature kullanılabilir. Bunun için zincir şeklinde `AddFeatureFilter<TFilter>()` metodunu çağırmanız yeterlidir.

Artık dışarıdan girdi olarak gelen değerleri `PushNotificationContext` sınıfının bir instance'ına atadık. `featureManager.IsEnabledAsync` metoduna ikinci parametre olarak context'i geçerek feature filter'ın dışarıdan gelen değeri almasını sağladık.

Uygulamamızı çalıştırdığımız zaman aşağıdaki gibi bir sonuç alacağız.

![Feature Management Filter](https://cdn.synaps.media/mennan/content/images/2021/05/feature-management-2.png)

Görüldüğü üzere ilk context'te **14.5** değeri tanımlı olduğundan dolayı ekranda **Push sent!** ifadesi yazmaktadır. İkinci context'te ise **11.4** değeri yazdığı için feature koşulu gerçekleşmedi ve bu sebepten dolayı ekranda **Unsupported operating system.** ifadesi yazılmıştır.

## Custom Feature Flag Provider Geliştirme

FeatureManagement kütüphanesi feature'ları varsayılan olarak appsettings.json dosyasından okuyup yönetmektedir. Ancak projelerimizin igtiyacı dahilinde feature'ların farklı ortamlarda yönetilmesi gerekebilir. Örneğin bu bilgiler **Redis**, **Couchbase**, **MongoDb**, **SQL Server** vb. ortamlarda saklanıp yönetilebilir. FeatureManagement kütüphanesi bu esnekliği bize [IFeatureDefinitionProvider](https://docs.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement.ifeaturedefinitionprovider?view=azure-dotnet-preview&ref=mennankose.com) interface'i üzerinden bize sağlamaktadır.

Bu örneğimizde feature'ları Redis üzerinde yöneteceğiz. Docker üzerinde bir Redis container'ı ayağa kaldırmak için aşağıdaki komutu kullanabiliriz.

```bash
docker run -p 6379:6379 --name redis redis

```

Redis üzerinde feature bilgilerini `Features` key'inin altında aşağıdakine benzer bir formatta tutacağız.

```json
{
 "SendEmail": true,
 "SendPush": false
}

```

Şimdi ise projemize custom feature flag provider'ını ekleyebiliriz. Bunun için projemize **RedisFeatureDefinitionProvider.cs** isminde bir dosya ekleyelim ve içeriğini aşağıdaki şekilde düzenleyelim.

```csharp
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.FeatureManagement;
using StackExchange.Redis;

namespace FeatureManagement.Console.CustomFeatureProvider
{
	public class RedisFeatureDefinitionProvider : IFeatureDefinitionProvider
	{
		public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
		{
			var features = await GetAllFeatures();

			foreach (var feature in features)
			{
				yield return GetFeatureDefinition(feature.Key, feature.Value);
			}
		}

		public async Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
		{
			var features = await GetAllFeatures();

			if (features.TryGetValue(featureName, out var isFeatureEnabled))
			{
				return GetFeatureDefinition(featureName, isFeatureEnabled);
			}

			return null;
		}

		private async Task<Dictionary<string, bool>> GetAllFeatures()
		{
			var redis = ConnectionMultiplexer.Connect("localhost");
			var db = redis.GetDatabase();
			var featuresString = await db.StringGetAsync("Features");
			var features = JsonSerializer.Deserialize<Dictionary<string, bool>>(featuresString);

			return features;
		}

		private FeatureDefinition GetFeatureDefinition(string featureName, bool isEnabled)
		{
			var featureDefinition = new FeatureDefinition
			{
				Name = featureName
			};

			if (isEnabled)
				featureDefinition.EnabledFor = new[]
				{
					new FeatureFilterConfiguration
					{
						Name = "AlwaysOn"
					}
				};

			return featureDefinition;
		}
	}
}

```

FeatureManagement, **IsEnabledAsync** metodunu çağırdığında eklemiş olduğunuz provider'daki `GetFeatureDefinitionAsync` metodu tetiklenecektir. Bu metot tetiklendiğinde Redis'ten feature bilgilerini alıyoruz ve bir Dictionary'nin içerisini dolduğuruyoruz. GetFeatureDefinitionAsync metoduna parametre olarak gelen feature ismiyle eşleşen bir kayıt varsa bu kaydın bilgilerini `GetFeatureDefinition` metoduna iletiyoruz. Eğer feature aktif ise FeatureDefinition'ın EnabledFor property'sinde **AlwaysOn** değerini dönerek FeatureManagement'a ilgili feature'ın aktif olduğunu bildirmiş oluyoruz.

Son olarak yaptığımız geliştirmeyi test edebiliriz. Bunun için **Program.cs** dosyasının içeriğini aşağıdaki şekilde düzenleyelim.

```csharp
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;

namespace FeatureManagement.Console.CustomFeatureProvider
{
	class Program
	{
		static IServiceCollection ConfigureServices()
		{
			var services = new ServiceCollection();

			services
				.AddScoped<IFeatureDefinitionProvider, RedisFeatureDefinitionProvider>()
				.AddFeatureManagement();

			return services;
		}

		static async Task Main(string[] args)
		{
			var services = ConfigureServices();
			var serviceProvider = services.BuildServiceProvider();
			var featureManager = serviceProvider.GetRequiredService<IFeatureManager>();

			if (await featureManager.IsEnabledAsync("SendEmail"))
			{
				System.Console.WriteLine("Email service active!");
			}
			else
			{
				System.Console.WriteLine("Email service passive!");
			}

			if (await featureManager.IsEnabledAsync("SendPush"))
			{
				System.Console.WriteLine("Push service active!");
			}
			else
			{
				System.Console.WriteLine("Push service passive!");
			}
		}
	}
}

```

Eklemiş olduğumuz provider'ı scoped olarak `AddScoped<IFeatureDefinitionProvider, RedisFeatureDefinitionProvider>()` kayıt ederek FeatureManagement'ın ilgili bilgilere erişirken artık bizim yeni eklediğimiz provider'ı kullanmasını sağladık.

Uygulamamızı çalıştırdığımız zaman aşağıdaki gibi bir sonuçla karşılaşacağız.

![Feature Management](https://cdn.synaps.media/mennan/content/images/2021/05/feature-management-3.png)

Yazıda kullanılan örnek projeye [https://github.com/mennan/feature-management-sample](https://github.com/mennan/feature-management-sample?ref=mennankose.com) adresinden erişebilirsiniz.

## Kaynaklar

- [https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core?tabs=core5x](https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core?tabs=core5x&ref=mennankose.com)
- [https://github.com/microsoft/FeatureManagement-Dotnet](https://github.com/microsoft/FeatureManagement-Dotnet?ref=mennankose.com)
- [https://andrewlock.net/series/adding-feature-flags-to-an-asp-net-core-app/](https://andrewlock.net/series/adding-feature-flags-to-an-asp-net-core-app/?ref=mennankose.com)