ASP.NET Core Web API’da Swagger ile Dökümantasyon Oluşturma

Okuma Süresi: 4 dakika

ASP.NET Core ile geliştirmiş olduğumuz Web API’ları kullanarak uygulama geliştirecek olan kişiler için dökümantasyon oldukça önemlidir. API’ın hangi endpointlere sahip olduğu, hangi HTTP metoduyla iletişim kuracağı, endpoint’in almış olduğu parametreler ve body içeriği, API’dan dönecek olan cevabın içeriğinin nasıl olduğu gibi bilgileri vermek uygulama geliştiricilerin işlerini oldukça kolaylaştıracaktır.

ASP.NET Core’da geliştirilmiş olan API’ların dökümantasyonu için kullanabileceğimiz araçlardan birisi Swagger‘dır. Swagger, açık kaynak kodlu bir araçtır. ASP.NET Core özelinde bir araç değildir. 20’nin üzerinde programlama dili veya kütüphane için desteği bulunmaktadır. Swagger’ın kendine has bir JSON dosyası oluşturmaktadır. Oluşturulan bu JSON dosyasından ister Swagger UI‘ı kullanarak isterseniz de kendinizin geliştireceği bir tema ile dökümantasyonunuzu oluşturabilirsiniz.

O zaman örnek uygulamamızı geliştirmeye başlayalım. Öncelikle aşağıdaki terminal komutunu kullanarak yeni bir ASP.NET Core Web API projesi oluşturalım ve kullanmış olduğumuz IDE veya metin editörüyle projemizi açalım.

dotnet new webapi -o aspnetcore-swagger-sample -n AspNetCore.Swagger.Sample

Projemizi oluşturduktan sonra sıra geldi Swagger dosyamızı oluşturacak olan paketi projemize referans olarak eklemeye. Bu işlem için Swashbuckle.AspNetCore paketini kullanacağız. Aşağıdaki komut ile ilgili paketi projemize referans olarak ekleyelim.

dotnet add package Swashbuckle.AspNetCore

İlgili paketimiz ekledikten sonra sırada Swagger ayarlarının yapılması var. Swagger ayalarını yapmak için Startup.cs dosyamızın içeriğini aşağıdaki gibi değiştirelim.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;

namespace AspNetCore.Swagger.Sample
{
	public class Startup
	{
		public Startup(IConfiguration configuration)
		{
			Configuration = configuration;
		}

		public IConfiguration Configuration { get; }

		// This method gets called by the runtime. Use this method to add services to the container.
		public void ConfigureServices(IServiceCollection services)
		{
			services.AddMvc();

			services.AddSwaggerGen(c =>
			{
				c.SwaggerDoc("v1", new Info { Title = "Sample Web API", Version = "v1" });
			});
		}

		// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
		public void Configure(IApplicationBuilder app, IHostingEnvironment env)
		{
			if (env.IsDevelopment())
			{
				app.UseDeveloperExceptionPage();
			}

			app.UseSwagger();
			app.UseSwaggerUI(c =>
			{
				c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample Web API v1");
			});

			app.UseMvc();
		}
	}
}

AddSwaggerGen metodu ile Swagger JSON dosyamızın oluşturulmasını sağladık. Parametre olarak aldığı Info tipinden bir nesne ile API’ımıza ait çeşitli bilgileri tanımlıyoruz. API’ın adı, sürümü, geliştirici bilgileri v.b. gibi bilgileri burada tanımlıyoruz.

UseSwaggerUI metodu ile Swagger UI’ın dosyalarının oluşturulmasını ve tarayıcı üzerinden erişebilmek için ilgili route tanımlamalarının yapılması sağlanmaktadır.

Şimdi Swagger’ın çalışıp çalışmadığını test edebilmek için projemizi çalıştıralım ve tarayıcıdan http://localhost:5000/swagger adresine girelim. Eğer herhangi bir sorun yoksa aşağıdaki gibi bir ekranla karşılaşacağız.

Values endpoint’ine ait dökümantasyon oluştu.

Dökümantasyonun bir kısmı başarıyla oluştu ancak hala yetersiz bilgiler mevcut. Örneğin endpoint’ler ne işe yarıyor ve HTTP durum kodlarına göre endpoint’den gelecek olan cevabın modeli nedir gibi soruların cevabı bulunmamaktadır.

Şimdi endpoint açıklamalarının gözükmesini sağlayalım. Bu işlem için ValuesController.cs dosyamızı açalım ve aşağıdaki değişiklikleri yapalım.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCore.Swagger.Sample.Controllers
{
	[Route("api/[controller]")]
	public class ValuesController : Controller
	{
		/// <summary>
		/// Get all values
		/// </summary>
		[HttpGet]
		public IEnumerable<string> Get()
		{
			return new string[] { "value1", "value2" };
		}

		/// <summary>
		/// Get value by id
		/// </summary>
		/// <param name="id">Value id</param>
		[HttpGet("{id}")]
		public string Get(int id)
		{
			return "value";
		}

		/// <summary>
		/// Create a new value
		/// </summary>
		[HttpPost]
		public void Post([FromBody] string value)
		{
		}

		/// <summary>
		/// Update value by id
		/// </summary>
		/// <param name="id">Value id</param>
		/// <param name="value">Value informations</param>
		[HttpPut("{id}")]
		public void Put(int id, [FromBody] string value)
		{
		}

		/// <summary>
		/// Delete a value by id
		/// </summary>
		/// <param name="id">Value id</param>
		[HttpDelete("{id}")]
		public void Delete(int id)
		{
		}
	}
}

Endpoint açıklamalarını XML Documentation Comments kurallarına göre her action’ın başına ekledik. Bu sayede Swagger dosyası oluşurken ilgili açıklamaların eklenmesini sağladık. Eklemiş olduğumuz açıklamalara ait XML dosyalarının oluşabilmesi için AspNetCore.Swagger.Sample.csproj dosyasını aşağıdaki gibi düzenliyoruz.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" AllowExplicitVersion="true" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
  </ItemGroup>

</Project>

7. ve 9. satırlar arasındaki düzenlemeleri yaparak build işlemi esnasında XML dosyasının oluşmasını sağladık.

Son aşama olarak oluşan XML dosyasının yolunu Swagger’a bildirerek açıklamaların UI’da gözükmesini sağlamak. Bu iş için Startup.cs dosyasının içeriğini aşağıdaki gibi değiştirelim.

using System;
using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;

namespace AspNetCore.Swagger.Sample
{
	public class Startup
	{
		public Startup(IConfiguration configuration)
		{
			Configuration = configuration;
		}

		public IConfiguration Configuration { get; }

		// This method gets called by the runtime. Use this method to add services to the container.
		public void ConfigureServices(IServiceCollection services)
		{
			services.AddMvc();

			services.AddSwaggerGen(c =>
			{
				c.SwaggerDoc("v1", new Info { Title = "Sample Web API", Version = "v1" });

				var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
				var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
				c.IncludeXmlComments(xmlPath);
			});
		}

		// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
		public void Configure(IApplicationBuilder app, IHostingEnvironment env)
		{
			if (env.IsDevelopment())
			{
				app.UseDeveloperExceptionPage();
			}

			app.UseSwagger();
			app.UseSwaggerUI(c =>
			{
				c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample Web API v1");
			});

			app.UseMvc();
		}
	}
}

30. ve 32. satırlar arasında eklediğimiz kod sayesinde Swagger’ın JSON dosyası oluşurken XML dosyasının içeriğinin de yüklenmesini sağladık.

Bu işlemleri yaptıktan sonra projemizi çalıştıralım. Herhangi bir sorun yoksa aşağıdakine benzer bir ekranla karşılaşacağız.

Swagger UI’a erişirken http://localhost:5000/swagger/ adresi üzerinden ulaşıyoruz. Bu adresteki swagger ifadesini help olarak değiştirmek istediğimizde SwaggerUIOptions.RoutePrefix özelliğinin değerini değiştirmemiz yeterlidir. Biz http://localhost:5000/help/ adresi üzerinden ulaşmak istediğimizi varsayalım. Bunun için Startup.cs dosyasını aşağıdaki şekilde düzenleyelim.

using System;
using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.Swagger;

namespace AspNetCore.Swagger.Sample
{
	public class Startup
	{
		public Startup(IConfiguration configuration)
		{
			Configuration = configuration;
		}

		public IConfiguration Configuration { get; }

		// This method gets called by the runtime. Use this method to add services to the container.
		public void ConfigureServices(IServiceCollection services)
		{
			services.AddMvc();

			services.AddSwaggerGen(c =>
			{
				c.SwaggerDoc("v1", new Info { Title = "Sample Web API", Version = "v1" });

				var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
				var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
				c.IncludeXmlComments(xmlPath);
			});
		}

		// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
		public void Configure(IApplicationBuilder app, IHostingEnvironment env)
		{
			if (env.IsDevelopment())
			{
				app.UseDeveloperExceptionPage();
			}

			app.UseSwagger();
			app.UseSwaggerUI(c =>
			{
				c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample Web API v1");
				c.RoutePrefix = "help";
			});

			app.UseMvc();
		}
	}
}

48. satırdaki değişikliği yapıp uygulamamızı çalıştırdığımız zaman Swagger UI’a artık http://localhost:5000/help/ adresinden erişebiliriz.

Örnek projeye https://github.com/mennan/aspnetcore-swagger-sample adresinden ulaşabilirsiniz.

Visual Studio Code API’da Context Menu Alanına Menü Ekleme

Okuma Süresi: 1 dakika

Visual Studio Code eklentisi geliştirirken ekranın sol tarafında bulunan Explorer alanına menü eklemek isteyebiliriz. Bunu yapabilmek için eklenti projemizin ana dizininde bulunan package.json‘ın menus bölümüne ekleme yapmamız gerekmektedir.

Öncelikle aşağıdaki komutu kullanarak yeni bir Visual Studio Code projesi oluşturalım.

yo code
Ayarları yukarıda gibi seçebilirsiniz.

Oluşturmuş olduğumuz projeyi Visual Studio Code ile açalım ve package.json dosyasının içeriğini aşağıdaki şekilde değiştirelim.

{
  "name": "vscode-contextmenu-sample",
  "displayName": "vscode-contextmenu-sample",
  "description": "",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.36.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onCommand:extension.helloWorld"
  ],
  "main": "./extension.js",
  "contributes": {
    "commands": [
      {
        "command": "extension.helloWorld",
        "title": "Hello World"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "group": "navigation",
          "command": "extension.helloWorld"
        }
      ]
    }
  },
  "scripts": {
    "postinstall": "node ./node_modules/vscode/bin/install",
    "test": "node ./node_modules/vscode/bin/test"
  },
  "devDependencies": {
    "typescript": "^3.3.1",
    "vscode": "^1.1.28",
    "eslint": "^5.13.0",
    "@types/node": "^10.12.21",
    "@types/mocha": "^2.2.42"
  }
}

contributes node’unun altına menus isminde bir node daha ekledik. explorer/context ile menünün context menu alanında nereye yerleşeceğini belirledik. command alanında ise menüye tıklanıldığında eklentimize ait çalışacak olan komutu belirttik.

Aşağıda context menu’ye ait alanların adları ve yerleşim yerleri bulunmaktadır.

https://code.visualstudio.com/assets/api/references/contribution-points/groupSorting.png

Şimdi projemizi test edelim. F5 tuşuna basarak eklentimizin yüklenmesini sağlayalım. Ekrana gelen Extension Development Host penceresinde sol taraftaki Explorer bölümüne tıklayarak menümüzün yüklenip yüklenmediğini kontrol edelim.

Görüldüğü gibi 3. sıraya Hello World isminde menümüz eklendi.

Örnek uygulamaya https://github.com/mennan/vscode-contextmenu-sample adresinden erişebilirsiniz.

ASP.NET Core’da ile Farklı Configuration Provider Geliştirme

Okuma Süresi: 3 dakika

ASP.NET Core ile oluşturmuş olduğunuz bir uygulamaya ait belli başlı ayarları olabilir. ASP.NET Core’da varsayılan olarak File Configuration Provider (INI, JSON ve XML dosyaları) kullanılmaktadır. Ancak bazı durumlarda ayarlarımızı farklı ortamlarda saklayıp yüklememiz gerekebilir. Örneğin Redis’te, Vault’ta veya environment variable’da (ortam değişkenleri) saklayabiliriz. Bu gibi durumlarda kendi configuration provider’ımızı geliştirmemiz gerekmektedir.

Bugün yapacağımız örnekte uygulama ayarlarını Redis’te tutup uygulama başlatılırken ayarların okunmasını sağlayacağız.

Öncelikle aşağıdaki komutla yeni bir ASP.NET Core uygulaması oluşturup metin editörümüzle veya IDE’mizle açalım.

dotnet new mvc -n AspNetCoreCustomConfigSample -o aspnetcore-custom-config-sample

Uygulamamız üzerinden Redis’e bağlanabilmek için StackExchange.Redis paketini kullanacağız. Aşağıdaki komutu yazarak ilgili paketi projemize referans olarak ekliyoruz.

dotnet add package StackExchange.Redis

Ayarlarımızı Redis üzerinde Settings ismindeki anahtarda tutacağız. İçeriği aşağıdaki gibi JSON yapıda olacaktır.

{
  "AppName": "Redis Configuration Sample",
  "AppVersion": "1.0.0"
}

Geliştireceğimiz sınıflar aşağıdaki gibi olacaktır:

  • Configuration Provider
  • Configuration Source
  • Extension Methods

İlk önce Configuration Provider sınıfımızı geliştirmeye başlayacağız. RedisConfigurationProvider isminde bir sınıf oluşturalım ve içeriğini aşağıdaki gibi değiştirelim.

using aspnetcore_custom_config_sample.Model;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using StackExchange.Redis;

namespace aspnetcore_custom_config_sample.Configuration
{
	public class RedisConfigurationProvider : ConfigurationProvider
	{
		private readonly string _redisHost;

		public RedisConfigurationProvider(string redisHost)
		{
			_redisHost = redisHost;
		}

		public override void Load()
		{
			var options = ConfigurationOptions.Parse(_redisHost);
			var connectionMultiplexer = ConnectionMultiplexer.Connect(options);
			var redisSettings = connectionMultiplexer.GetDatabase().StringGet("Settings");
			var settings = JsonConvert.DeserializeObject<AppSettings>(redisSettings);

			Data.Add("AppName", settings.AppName);
			Data.Add("AppVersion", settings.AppVersion);
		}
	}
}

Yazmış olduğumuz Configuration Provider sınıfında base sınıftaki Load metodunu ezmekteyiz. Load metodunun içerisinde Redis’e bağlanarak ilgili ayarları okuyoruz ve yine base sınıftan gelen Data ismindeki Dictionary‘nin içeriğini dolduruyoruz.

İkinci adımda ise Configuration Source sınıfımızı geliştireceğiz. RedisConfigurationSource isminde bir sınıf ekliyoruz ve içeriğini aşağıdaki şekilde değiştiriyoruz.

using Microsoft.Extensions.Configuration;

namespace aspnetcore_custom_config_sample.Configuration
{
	public class RedisConfigurationSource : IConfigurationSource
	{
		private readonly string _redisHost;

		public RedisConfigurationSource(string redisHost)
		{
			_redisHost = redisHost;
		}

		public IConfigurationProvider Build(IConfigurationBuilder builder)
		{
			return new RedisConfigurationProvider(_redisHost);
		}
	}
}

Configuration Source sınıfımız IConfigurationSource sınıfından türemektedir. IConfigurationSource interface’i Build isminde bir metot bulundurmaktadır. Bu sınıf bir configuration provider’ı temsil etmek için bulunmaktadır. Bizim Configuration Source sınıfımız ise RedisConfigurationProvider’ı temsil etmektedir. Constructor metottan parametre olarak Redis sunucumuzun adresini almaktadır.

Şimdi sıra extension metotları geliştirmeye geldi. ConfigurationExtensions isminde bir sınıf oluşturarak içeriğini aşağıdaki şekilde değiştirelim.

using Microsoft.Extensions.Configuration;

namespace aspnetcore_custom_config_sample.Configuration
{
	public static class ConfigurationExtensions
	{
		public static IConfigurationBuilder AddRedisConfiguration(this IConfigurationBuilder configuration, string redisHost)
		{
			configuration.Add(new RedisConfigurationSource(redisHost));
			return configuration;
		}
	}
}

Bu sınıfımızın amacı ise IConfigurationBuilder tipindeki bir nesne örneğine Configuration Source sınıfımızın nesne örneğini eklemektedir. Bu sayede ASP.NET Core uygulamamız artık Redis’ten uygulama ayarlarımızı okuyabilecek hale gelecektir.

Son adım olarak extension metodumuzu kullanmak kaldı. Bunun için Program.cs dosyamızı aşağıdaki şekilde değiştiriyoruz.

using aspnetcore_custom_config_sample.Configuration;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;

namespace AspNetCoreCustomConfigSample
{
	public class Program
	{
		public static void Main(string[] args)
		{
			BuildWebHost(args).Run();
		}

		public static IWebHost BuildWebHost(string[] args) =>
			WebHost.CreateDefaultBuilder(args)
				.ConfigureAppConfiguration(AddRedisConfiguration)
				.UseUrls("http://localhost:5000")
				.UseStartup<Startup>()
				.Build();

		private static void AddRedisConfiguration(WebHostBuilderContext context, IConfigurationBuilder builder)
		{
			var configuration = builder.Build();
			builder.AddRedisConfiguration("localhost");
		}
	}
}

ConfigureAppConfiguration metodu parametre olarak bir delegate almaktadır. Delegate ettiğimiz metot ile AddRedisConfiguration extension metodunu kullanarak ASP.NET Core’un uygulamamız yüklenirken ayarlarımızı Redis’ten okumasını sağladık.

Şimdi ayarlarımızı okumaya geldi. Ayarlarımızı örnek olması açısından HomeController üzerinden okuyacağız. HomeController’ın içeriğini aşağıdaki şekilde değiştiriyoruz.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using AspNetCoreCustomConfigSample.Models;
using aspnetcore_custom_config_sample.Model;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration;

namespace AspNetCoreCustomConfigSample.Controllers
{
	public class HomeController : Controller
	{
		private readonly IConfiguration _appSettings;

		public HomeController(IConfiguration config)
		{
			_appSettings = config;
		}

		public IActionResult Index()
		{
			ViewBag.AppName = _appSettings.GetSection("AppName").Value;
			ViewBag.AppVersion = _appSettings.GetSection("AppVersion").Value;

			return View();
		}
	}
}

Dependency Injection yardımıyla IConfiguration tipinde bir nesne örneği alıyoruz. Index action’ında ise AppName ve AppVersion değerlerini okuyarak ViewBag‘lere atıyoruz. AppName ve AppVersion isimleri ise Configuration Provider sınıfımızda Data Dictionary‘sine eklemiş olduğumuz anahtar alanlardır.

Son olarak Home/Index.cshtml dosyamızı aşağıdaki şekilde düzenliyoruz.

@{
    ViewData["Title"] = "Home Page";
}

<h1>@ViewBag.AppName</h1>
<h2>@ViewBag.AppVersion</h2>

Uygulamızı tarayıcımızla ile açtığımızda aşağıdaki ekran görüntüsünü görmekteyiz.

Örnek proje dosyalarına https://github.com/mennan/aspnetcore-custom-config-sample adresinden ulaşabilirsiniz.

Docker Image’larının HTTP API Kullanılarak Silinmesi

Okuma Süresi: 1 dakika

Merhabalar.

Bu yazımda private container registry’de bulunan Docker image’larının Docker’ın HTTP API’ını kullanarak nasıl silineceğinden bahsedeceğim.

Image’ların silinmesi 2 adımdan oluşmaktadır. İlk adım image ismi ve tag değerinin digest değerini almak, ikinci adım ise digest değerini API’a göndererek Docker üzerinden image’ın silinmesi ile sağlamak.

Digest değerini almak için öncelikle container registry’e aşağıdaki gibi bir HTTP isteği göndermek gerekmektedir.

GET /v2/exampleimage/manifests/latest HTTP/1.1
Host: cr.example.com
Accept: application/vnd.docker.distribution.manifest.v2+json
Authorization: Basic YXNkOmFzZA==
User-Agent: PostmanRuntime/7.15.0
Cache-Control: no-cache

Container registry’e bağlanmak için Basic Authentication kullanılmaktadır. Authorization header’ında ilgili kullanıcının adı ve şifre değeri gönderilmelidir. HTTP isteğinin path bölümünde bulunan exampleimage ise silinmek istenen image’ın ismidir. Path’in son kısmında yazan latest ise silinmek istenen image’ın sürümüdür.

İsteği gönderdikten sonra aşağıdakine benzer bir cevap gelmelidir.

{
    "schemaVersion": 2,
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "config": {
        "mediaType": "application/vnd.docker.container.image.v1+json",
        "size": 4617,
        "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
    },
    "layers": [
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 22496034,
            "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 17694102,
            "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 2977123,
            "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 62093437,
            "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 136,
            "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 18044387,
            "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
        }
    ]
}

Digest değerimiz gelen cevaptaki 7. satırda bulunmaktadır.

Digest değerimizi aldığımıza göre artık image’ı silme işlemine geçebiliriz. Silme işlemini gerçekleştirmek için aşağıdaki gibi bir HTTP isteği göndermemiz gerekmektedir.

DELETE /v2/exampleimage/manifests/sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 HTTP/1.1
Host: cr.example.com
Accept: application/vnd.docker.distribution.manifest.v2+json
Authorization: Basic YXNkOmFzZA==
User-Agent: PostmanRuntime/7.15.0
Cache-Control: no-cache

Yukarıdaki örnek HTTP isteğinde görülebileceği gibi http://cr.example.com//v2/exampleimage/manifests/sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 adresine DELETE isteği atmamız yeterlidir. Adresimizin sonunda sha256 ile başlayan ifade, bizim image’ımızın digest değeridir.

İsteği gönderdikten sonra herhangi bir cevap gelmeyecektir. İşlemin başarılı olup olmadığını gelen HTTP durum kodundan anlayabiliriz. Gelen durum kodu HTTP 202 Accepted ise image silme işlemi başarıyla gerçekleşmiş demektedir.

Bu işlemleri otomatik yapan .Net Core projesine https://github.com/mennan/DockerImageRemover adresinden ulaşabilirsiniz.

.Net Core’da HttpClient ile Proxy Kullanımı

Okuma Süresi: 2 dakika

.Net Core ile geliştirdiğimiz uygulamalarda HttpClient sınıfını kullanarak uç noktalara istek atmamız gerekebilir. Örnek olarak kurumsal firmalarda çalışacak olan uygulamanız internet ortamındaki bir adrese istek göndermek isteyebilir. Ancak uygulamanızın çalışmış olduğu sunucunun internet erişimi kısıtlandığından dolayı ilgili adrese erişemeyebilirsiniz veya HttpClient sınıfı kullanarak göndermiş olduğunuz isteği ve gelen cevabı Charles Web Debugging Proxy gibi uygulamalar ile araya girerek takip etmek isteyebilirsiniz. Bu işlemleri yapabilmeniz için HttpClient ile birlikte proxy kullanmanız gerekmektedir.

HttpClient ile atılan isteklerde proxy tanımı yapabilmek için HttpClientHandler ve WebProxy sınıflarına ihtiyacımız vardır.

Örnek uygulama olarak .Net Core’da bir console projesi oluşturacağız. Aşağıda bash komutları ile projemizi oluşturalım.

dotnet new console -n NetCoreHttpClientProxy
cd NetCoreHttpClientProxy

Projemizi kullanmış olduğumuz metin editörü veya IDE ile açarak aşağıdaki şekilde kodlarımızı yazalım.

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace NetCoreHttpClientProxy
{
	class Program
	{
		static async Task Main(string[] args)
		{
			const string ProxyUrl = "http://localhost:5001";
			const string ProxyUsername = "";
			const string ProxyPassword = "";
			const string RequestUrl = "https://google.com";

			var proxy = new WebProxy
			{
				Address = new Uri(ProxyUrl),
				Credentials = new NetworkCredential(ProxyUsername, ProxyPassword)
			};

			var httpClientHandler = new HttpClientHandler
			{
				Proxy = proxy,
				UseProxy = true
			};

			using (var client = new HttpClient(httpClientHandler))
			{
				var response = await client.GetAsync(RequestUrl);

				if (response.IsSuccessStatusCode)
				{
					var responseString = await response.Content.ReadAsStringAsync();
					Console.WriteLine(responseString);
				}
			}
		}
	}
}

Yapmış olduğumuz işlemleri açıklamak gerekirse https://google.com adresine http://localhost:5001 proxy adresini kullanarak istek göndermekteyiz.

Öncelikle WebProxy sınıfını kullanarak proxy adresini ve proxy’ye ait kullanıcı adı ve şifre tanımlamalarını yapıyoruz. Eğer proxy’ye ait herhangi bir kullanıcı adı ve şifre bilgisi bulunmuyorsa Credentials özelliğine değer vermenize gerek yoktur.

Sonrasında ise oluşturmuş olduğumuz WebProxy nesnesini HttpClientHandler tipindeki bir nesnenin Proxy özelliğine atıyoruz ve ilgili nesne örneğinin UseProxy özelliğini true olarak ayarlıyoruz.

En son işlem olarak ise HttpClient tipinin nesne örneğini oluştururken yapıcı metoda (constructor) HttpClientHandler tipindeki nesne örneğini vermek. Bu aşamadan sonra HttpClient tipindeki nesne örneğinden gönderilecek işlem proxy üzerinden geçerek atılacaktır.

Örnek kodlara https://github.com/mennan/netcore-httpclient-proxy adresinden ulaşabilirsiniz.

Entity Framework Core’da Global Query Filters Kullanımı

Okuma Süresi: 4 dakika

Global Query Filters, entity nesnelerinde özellikle Where sorgu operatörüyle gönderdiğimiz sorguların her sorguya otomatik olarak eklemek için kullanılır. Kullanım senaryolarına örnek vermek gerekirse soft delete veya multi tenancy sorguları verilebilir. Bu yazıda örnek olması açısından soft delete senaryosu üzerinden ilerleyeceğiz. Global Query Filter tanımları DbContext sınıfının virtual olan OnModelCreating metodunda tanımlanır.

Bu uygulamamızda yapacağımız örnekte Users tablosunda IsRemoved kolonunun değeri false olan kullanıcıların listesinin gelmesi sağlanacaktır. Global Query Filter kullanmadan önce yapacağımız sorgu aşağıki gibidir.

context.Users.Where(u => !u.IsRemoved).ToList();

Normalde bu kullanım doğrudur. Ancak kodumuzun birçok yerinde Users tablosundan veri çekerken Where ile sürekli IsRemoved kontrolünün yapılması gerekmektedir. Bu işlemde kodun bakım maliyetini artırmaktadır. Bu işlemi Global Query Filter ile otomatikleştirerek kodumuzu aşağıdaki şekilde kullanmaya başlayacağız.

context.Users.ToList();

Şimdi uygulamamızı geliştirmeye başlayabiliriz. Örnek uygulama için ASP.NET Core MVC projesi oluşturacağım. Aşağıdaki komutu terminale yazarak yeni bir proje oluşturuyorum.

dotnet new mvc -n EFCore.GlobalQueryFiltersSample

İlk önce projeme IRemovable isminde bir interface ekliyorum ve içeriğini aşağıdaki şekilde düzenliyorum.

public interface IRemovable
{
    bool IsRemoved { get; set; }
}

Projeme SampleDbContext isminde bir sınıf ekliyorum ve içeriğini aşağıdaki şekilde değiştiriyorum.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace EFCore.GlobalQueryFiltersSample
{
    [Table("Users")]
    public class User : IRemovable
    {
        [Key]
        public Guid UserId { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Name { get; set; }
        public bool IsRemoved { get; set; }
    }

    public class SampleDbContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        public SampleDbContext(DbContextOptions<SampleDbContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<User>().HasQueryFilter(u => !u.IsRemoved);
            base.OnModelCreating(modelBuilder);
        }
    }
}

Global Query Filter tanımını SampleDbContext sınıfının 29. satırında yaptık. Global Query Filter tanımını HasQueryFilter metodu aracılığıyla yapmaktayız. Parametre olarak aldığı Expression ile sorgu tanımımızı gerçekleştirdik. Bu sorgu tanımına göre User tipindeki nesnelere ait verileri getirirken IsRemoved alanındaki değeri false olanları getirmesini sağladık.

Bu işlemleri yaptıktan sonra Entity Framework’ün ayarlarını yapmaya geldi. Ben bu yazı için InMemory database kullanacağım. Entity framework’ü InMemory database ile kullanabilmek için Microsoft.EntityFrameworkCore.InMemory paketini projeye eklememiz gerekmektedir. Projeye paketi eklemek için aşağıdaki komutu terminale yazmamız gerekmektedir.

dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 2.2.4

İlgili paketi projeye ekledikten sonra DbContext’imizi ASP.NET Core uygulamıza tanıtmak kaldı. İlgili işlemi Startup.cs dosyamızda yapacağız. Startup.cs dosyamızın içeriği aşağıdaki gibidir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace EFCore.GlobalQueryFiltersSample
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddDbContext<SampleDbContext>(options => options.UseInMemoryDatabase("Sample"));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Şimdi controller’ımıza geçip uygulamamızı test etmeye geldi. Ben HomeController üzerinde çalışacağım. İçeriği aşağıdaki gibidir.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using EFCore.GlobalQueryFiltersSample.Models;

namespace EFCore.GlobalQueryFiltersSample.Controllers
{
    public class HomeController : Controller
    {
        private readonly SampleDbContext _context;

        public HomeController(SampleDbContext context)
        {
            _context = context;

            var users = new List<User>
                {
                    new User {
                        UserId = Guid.NewGuid(),
                        UserName = "mennan",
                        Name = "Mennan",
                        IsRemoved = false
                    },
                    new User {
                        UserId = Guid.NewGuid(),
                        UserName = "anil",
                        Name = "Anıl",
                        IsRemoved = true
                    }
                };

            _context.Users.AddRange(users);
            _context.SaveChanges();
        }

        public IActionResult Index()
        {
            var users = _context.Users.ToList();

            return View(users);
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

Controller’ın constructor metodunda DbContext’imizin instance’ı DI ile alıyoruz ve örnek verilerimizi veritabanına kaydediyoruz. Index action’ında ise veritabanındaki tüm kayıtların listesini alıyoruz. Şimdi Index view’ın içeriğini aşağıdaki şekilde değiştiriyoruz.

@model List<User>
@{
    ViewData["Title"] = "Home Page";
}

<ul>
    @foreach (var user in Model)
    {
        <li>
            @user.Name - @user.UserName
        </li>
    }
</ul>

Gerekli değişiklikleri yaptıktan sonra sıra uygulamamızı test etmeye geldi. Terminalden dotnet run komutunu yazarak uygulamamızı tarayıcı üzerinden açalım.

Normalde veritabanımızda 2 adet kayıt olduğu halde sadece IsRemoved değeri false olan kayıt gelmiştir.

Bazı durumlarda GlobalQueryFilter’ı pasif hale getirmemiz gerekebilir. Bunun için LINQ sorgunuza IgnoreQueryFilters metodunu eklemeniz gerekmektedir. Örnek kullanımı aşağıdaki gibidir.

context.Users.IgnoreQueryFilters().ToList();

IRemovable’dan türeyen tüm entity tipleri için tek tek Global Query Filter tanımı yapmamıza gerek yok. Yapmamız gereken reflection ile IRemovable arayüzünden türeyen tipleri bulup bunları DbContext’in OnModelCreating metodunda dinamik olarak eklemek. Bunun için SampleDbContext sınıfımızı aşağıdaki şekilde düzenliyoruz.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace EFCore.GlobalQueryFiltersSample
{
    [Table("Users")]
    public class User : IRemovable
    {
        [Key]
        public Guid UserId { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Name { get; set; }
        public bool IsRemoved { get; set; }
    }

    public class SampleDbContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        private static readonly MethodInfo ConfigureGlobalFiltersMethodInfo = typeof(SampleDbContext).GetMethod(nameof(ConfigureGlobalFilters), BindingFlags.Instance | BindingFlags.NonPublic);

        public SampleDbContext(DbContextOptions<SampleDbContext> options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                ConfigureGlobalFiltersMethodInfo
                    .MakeGenericMethod(entityType.ClrType)
                    .Invoke(this, new object[] { modelBuilder, entityType });
            }

            base.OnModelCreating(modelBuilder);
        }

        protected void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType entityType) where TEntity : class
        {
            if (entityType.BaseType != null || !ShouldFilterEntity<TEntity>(entityType)) return;
            var filterExpression = CreateFilterExpression<TEntity>();
            if (filterExpression == null) return;
            if (entityType.IsQueryType)
                modelBuilder.Query<TEntity>().HasQueryFilter(filterExpression);
            else
                modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
        }

        protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) where TEntity : class
        {
            return typeof(IRemovable).IsAssignableFrom(typeof(TEntity));
        }

        protected Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() where TEntity : class
        {
            Expression<Func<TEntity, bool>> expression = null;

            if (typeof(IRemovable).IsAssignableFrom(typeof(TEntity)))
            {
                Expression<Func<TEntity, bool>> removedFilter = e => !((IRemovable)e).IsRemoved;
                expression = expression == null ? removedFilter : CombineExpressions(expression, removedFilter);
            }

            return expression;
        }

        protected Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
        {
            return ExpressionCombiner.Combine(expression1, expression2);
        }
    }
}

DbContext’i bu şekilde düzenlediğimiz zaman artık elle IRemovable için elle tekrardan tanıtma işlemi yapmamıza gerek kalmamıştır.

Yazıda bahsi geçen uygulamanın kaynak kodlarına https://github.com/mennan/efcore-globalqueryfilters-sample adresinden ulaşabilirsiniz.

Visual Studio Code API’da Decorators Kullanımı

Okuma Süresi: 3 dakika

Visual Studio Code, kod editörüne tasarımsal olarak bazı eklemeler veya düzenlemeler için bize Decorator API isminde bir ortam sunmaktadır. Örnek olması açısından Visual Studio Code’da en çok kullanılan eklentilerden biri olan GitLens‘in kod editörüne yaptığı değişikliği gösterebiliriz.

Bu yazımda biz de buna benzer bir eklenti geliştireceğiz. Geliştireceğimiz eklenti o an açık olan dökümanda Console.WriteLine içeren ifadeleri kırmızı arkaplan ve beyaz yazı rengi ile vurgulayacak.

Daha önce Visual Studio Code için eklenti geliştirmemiş olanlar için https://code.visualstudio.com/api/get-started/your-first-extension adresindeki yazıyı okumalarını tavsiye ederim.

Eklenti oluşturmak için terminal ekranımızda aşağıdaki komutu yazmamız gerekmektedir.

yo code

Bu komutu yazdıktan sonra bize bazı sorular sorulacaktır. Geliştirme dili olarak JavaScript‘i, paket yöneticisi olarak olarak ise yarn‘ı seçtim. Gerekli bilgileri girip onayladıktan sonra eklenti projemiz oluşacak ve gerekli paketlerin kurulumları tamamlanacaktır.

İşlemler tamamlandıktan sonra oluşturulan klasörü Visual Studio Code ile açalım.

Eklenti, extension.js dosyasından başlayarak yüklenmeye başlayacaktır. Bunu değiştirmek isterseniz package.json dosyasındaki “main” bölümünü değiştirmeniz gerekmektedir. Biz şimdilik olduğu gibi bırakacağız.

package.json dosyasında geliştireceğimiz eklentinin ismi, açıklaması, kategorisi, geliştirici bilgileri, eklentide kullanılacak olan paketler gibi bilgiler bulunmaktadır. package.json dosyamızın içeriğini aşağıdaki şekilde değiştirelim.

{
  "name": "vscode-highlighter",
  "displayName": "vscode-highlighter",
  "description": "",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.33.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onCommand:extension.highlight"
  ],
  "main": "./extension.js",
  "contributes": {
    "commands": [
      {
        "command": "extension.highlight",
        "title": "Highlight"
      }
    ]
  },
  "scripts": {
    "postinstall": "node ./node_modules/vscode/bin/install",
    "test": "node ./node_modules/vscode/bin/test"
  },
  "devDependencies": {
    "typescript": "^3.3.1",
    "vscode": "^1.1.28",
    "eslint": "^5.13.0",
    "@types/node": "^10.12.21",
    "@types/mocha": "^2.2.42"
  }
}

Yaptığımız değişiklik ile Visual Studio Code üzerinden Highlight komutunu verdiğimiz zaman çalışacağınız belirttik.

Şimdi eklentimiz geliştirmeye başlamak için extension.js dosyasını açalım. Bu dosyayı açtığınızda sizi ilk olarak activate ve deactivate fonksiyonları karşılayacaktır.
active fonksiyonu eklenti komutu ilk defa çağrıldığında çalışacaktır.
deactivate fonksiyonu ise eklenti devre dışı bırakıldığı zaman çalışacaktır. Burayı kullanmış olduğunuz kaynakları temizlemek için kullanabilirsiniz.

Şimdi extension.js dosyasını aşağıdaki şekilde değiştirebiliriz.

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require("vscode");
const editor = vscode.window.activeTextEditor;
let decorationType = vscode.window.createTextEditorDecorationType({
  backgroundColor: "red",
  color: "white"
});
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
  // Use the console to output diagnostic information (console.log) and errors (console.error)
  // This line of code will only be executed once when your extension is activated
  console.log(
    'Congratulations, your extension "vscode-highlighter" is now active!'
  );

  // The command has been defined in the package.json file
  // Now provide the implementation of the command with  registerCommand
  // The commandId parameter must match the command field in package.json
  let disposable = vscode.commands.registerCommand(
    "extension.highlight",
    function() {
      // The code you place here will be executed every time your command is executed

      let decorationsArray = [];
      let documentContent = editor.document.getText();
      let sourceCodeArr = documentContent.split("\n");

      for (let line = 0; line < sourceCodeArr.length; line++) {
        let text = sourceCodeArr[line];
        let match = /Console\.WriteLine\(".*?"\);/.exec(text);

        if (match !== null &amp;&amp; match.index !== undefined) {
          let range = new vscode.Range(
            new vscode.Position(line, match.index),
            new vscode.Position(line, match.index + match[0].length)
          );

          let decoration = {
            range: range
          };

          decorationsArray.push(decoration);
        }
      }

      editor.setDecorations(decorationType, decorationsArray);
    }
  );

  context.subscriptions.push(disposable);
}
exports.activate = activate;

// this method is called when your extension is deactivated
function deactivate() {}

module.exports = {
  activate,
  deactivate
};

25. satırda Visual Studio Code üzerinden Highlight komutu gönderildiği zaman eklentinin yapacağı işle ilgili bir fonksiyon tanımlıyoruz.

31. satır ile o an açık olan dökümanın içeriğini alıyoruz ve Regular Expression yardımıyla satırın içerisinde Console.WriteLine ifadesini arıyoruz. Eğer eşleşme olursa 5. satırda tanımlamasını yapmış olduğumuz decorator stilini ilgili alana uyguluyoruz. Decorator objesinin veri tipi DecorationOptions‘tır.

DecorationOption hakkında daha detaylı bilgi almak için https://code.visualstudio.com/api/references/vscode-api#DecorationOptions adresini ziyaret edebilirsiniz.

Eklentiyi test etmek için F5 tuşuna basmamız yeterlidir. F5 tuşuna bastıktan sonra ekrana Extension Development Host penceresi gelecektir. Eklentiyi denemek için Extension Development Host’ta F1 tuşuna basınız ve Highlight yazarak Enter tuşuna basınız. Döküman içerinde Console.WriteLine ifadesi varsa eklenti gerekli olan işlemi gerçekleştirecektir.

Yazıda bahsedilen eklentinin kaynak kodlarına https://github.com/mennan/vscode-decorator-sample adresinden erişebilirsiniz.

ASP.NET Core Uygulamalarında Remote IP Adresi Problemi

Okuma Süresi: 1 dakikaASP.NET Core ile geliştirilmiş bir web uygulamasının load balancer arkasında çalışabilmesi için UseForwardedHeaders middleware’ının kullanılması gerekmektedir. Bu middleware load balancer’ın yönlendirdiği X-Forwarded-Proto ve X-Forwarded-For http headerlarından gelen değerin uygulamamız tarafından erişilebilmesini sağlar. Örnek kullanımı aşağıdaki gibidir.

var forwardingOptions = new ForwardedHeadersOptions()
{
 ForwardedHeaders = ForwardedHeaders.XForwardedAll
};

app.UseForwardedHeaders(forwardingOptions);

Bu middleware sayesinde kullanıcının IP adresini elde edebilirsiniz. Ancak Google Cloud gibi ortamlarda çalışan bir uygulamanız varsa IP adresi talebi yaptığınız zaman load balancer’ın IP adresini size verecektir. Bu sorunun sebebi ForwardedHeadersOptions sınıfının KnownProxies özellik değerinin IPAddress.Loopback olmasından dolayı kaynaklanmaktadır. Sorunun çözümü KnownProxies özelliğine ya load balancer’ın ve uygulamanın çalıştığı makinenin IP adresini vermek ya da bu özelliğin değerini sıfırlamak gerekir.

var forwardingOptions = new ForwardedHeadersOptions()
{
ForwardedHeaders = ForwardedHeaders.XForwardedAll
};
forwardingOptions.KnownNetworks.Clear();
forwardingOptions.KnownProxies.Clear();

app.UseForwardedHeaders(forwardingOptions);

Kaynak: https://stackoverflow.com/questions/43749236/net-core-x-forwarded-proto-not-working

JavaScript’te Seçilmiş Metindeki Seçimi Kaldırmak

Okuma Süresi: 1 dakikaJavaScript’te input bir alanda olan seçili metindeki seçimi kaldırmak için aşağıdaki yöntemden faydalanabiliriz.

if (window.getSelection) {
    window.getSelection().removeAllRanges();
} else if (document.selection) {
    document.selection.empty();
}

window.getSelection Internet Explorer 9+ ve diğer tarayıcılar tarafından desteklenmektedir. document.selection ise Internet Explorer 8 ve altı tarayıcılar tarafından desteklenmektedir.

Kaynak: https://www.thewebflash.com/select-or-deselect-text-inside-an-element-using-js/