ASP.NET Core uygulamaları, cross-platform uygulamalar olduğu için, bildiğiniz üzere geliştirdiğimiz web uygulamalar için artık IIS gereksinimi zorunlu değil. ASP.NET Core tarafında geliştirilen yeni “host” modeli sayesinde, uygulamaların nasıl host edileceği, kod seviyesinden yönetilip, geliştirilebiliyor. Bu ASP.NET Core’un ilk versiyonundan beri olan oldukça güzel, kolay ve yeni kapılar açan bir özellik bildiğiniz üzere.
Kod seviyesinde, bu host özellikleri ASP.NET Core’un IWebHost ve IWebHostBuilder arayüzlerinden yaratılan sınıflar ile gerçekleşiyor. ASP.NET Core uygulamalarının ilk giriş noktası olan kodlarda, WebHost.CreateDefaultBuilder() eminim dikkatinizi çekmiştir.
Bu method varsayılan host modelini oluşturmak için ve arka tarafta bazı standart işlemleri yapıp, geliştiricilerin işini kolaylaştırmak için sunulmaktadır. Özetle WebHost.CreateDefaultBuilder() ile web uygulamamızın, varsayılan bazı özellikleri kendiliğinden tanımlanmaktadır.
GitHub’dan da bakarsanız, varsayılan bazı özellikleri zaten görebilirsiniz. Açık kaynak geliştirmeyi bu yüzden çok seviyorum 🙂
Loglama, konfigürasyon, uygulama root dizini, Kestrel kullanımı, IIS entegrasyonu ve “dependency-injection” gibi temel şeyler bu şekilde biz direkt çok kafa yormadan mümkün oluyor ve Web uygulamasının çalışması sağlanıyor. Daha basitçe özetlemek için; basit bir konsol uygulaması ile bir web uygulamasını ayağa kaldırmak ve çalıştırmak bu sayede mümkün olabiliyor. ASP.NET Core’un en büyük artı noktalarından biri de bu. Az önce dediğim temel şeyler aslında “cross-cutting concerns” olarak adlandırdığımız tüm yazılım sistemlerinde olabilecek konular.
Farklı tipteki uygulamalar için nasıl oluyor?
WebHost, doğal olarak sadece web uygulamaları için olan özellikler de içerdiğinden farklı tipte uygulamalarda bu tarz şeyleri kullanmak çok mümkün değildi. WebHost sınıfında olduğu için, HTTP dışında,herhangi farklı bir tipte protokolü host eden bir uygulama; mesela konsol, için bu şekilde “cross-cutting concerns” leri eklemek uğraştırıcı ya da mevcut şekilde pek mümkün değildi .NET Core’un mevcut versiyonlarında. .NET Core 2.1 ile beraber farklı uygulama tiplerinde de “host” yaklaşımı ile “cross-cutting” konularını kolayca geliştirmek mümkün oluyor.
Şu an Preview aşamasında olan, ama önümüzdeki günlerde RTM olacak .NET Core 2.1 ile hayatımıza yeni bir “host” modeli geliyor. HostBuilder, IHostedService, IHostBuilder gibi, arayüz ve sınıflar ile farklı uygulama tiplerini de geliştirmek ya da arka planda sürekli çalışan operasyonları servis olarak geliştirmek mümkün. Sadece “Web Uygulaması” yaklaşımı değil de biraz daha genel kullanıma imkanlar sunan bir servis yaklaşımı, .NET Core 2.1 ile geliştirici hayatımıza girecek.
Kısaca ve özetle farklı Network servisleri(TCP,UDP…vs.), Message Queue(MQ) servisleri ya da “arka plan işleri” gibi yapılarda da “cross-cutting” konularını kolayca geliştirmek mümkün olacak .NET Core 2.1 ile.
Örnek olarak; aşağıda oluşturduğum bir tane .NET Core konsol uygulamasından bahsederek bazı kavramları netleştirmeye çalışmak isterim.
namespace SomeKindOfProcessor { class Program { static async Task Main(string[] args) { var hostBuilder = new HostBuilder() .ConfigureHostConfiguration(config=> { config.AddEnvironmentVariables(); }) .ConfigureAppConfiguration((hostContext, config) => { config.SetBasePath(Directory.GetCurrentDirectory()); config.AddJsonFile("appsettings.json", optional: true); config.Build(); }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); }) .ConfigureServices((hostContext, services) => { services.AddSingleton<ICustomProcessorData>(serviceProvider => { return new CustomProcessorData("Dummy Processor"); }); services.AddScoped<IHostedService, CustomProcessor>(); }); await hostBuilder.RunConsoleAsync(); } } }
Temel olarak dikkat edersiniz ki ASP.NET Core uygulamalarındaki giriş noktası olan StartUp sınıflarına çok benziyor. Host konfigürasyonu, uygulama konfigürasyonu, dependency-injection ve loglama gibi “cross-cutting concerns” böylece bir konsol uygulaması tarafında da kolayca yapılabiliyor.
Konsol uygulaması tipinde bir uygulama da, servis yaklaşımı ile belli operasyonları host etmek için IHostedService arayüzünden üretilen bir sınıf geliştirmemiz gerekmekte. Neden olduğunu anlamak için aşağıdaki IHostedService arayüzüne kısaca bir bakalım. Microsoft.Extensions.Hosting namespace’inde yer alan bu arayüz, geliştireceğimiz servis ya da operasyonların, host tarafında, başladığında ya da durduğunda bazı işlemleri yapmamıza kapı açıyor. Farklı bir thread ile çalışan ek bir operasyonu bu şekilde kolaylıkla oluşturabiliyor.
using System.Threading; using System.Threading.Tasks; namespace Microsoft.Extensions.Hosting { // // Summary: // Defines methods for objects that are managed by the host. public interface IHostedService { // // Summary: // Triggered when the application host is ready to start the service. // // Parameters: // cancellationToken: // Indicates that the start process has been aborted. Task StartAsync(CancellationToken cancellationToken); // // Summary: // Triggered when the application host is performing a graceful shutdown. // // Parameters: // cancellationToken: // Indicates that the shutdown process should no longer be graceful. Task StopAsync(CancellationToken cancellationToken); } }
Biraz daha netleşmesi ve anlamlı olması için örnek bir CustomProcessor sınıfı yaratalım. Bu sınıf ile temel olarak arka planda çalışacak işi ve işin giriş noktasını oluşturuyoruz.
using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; namespace SomeKindOfProcessor { public class CustomProcessor : Microsoft.Extensions.Hosting.IHostedService { private readonly ICustomProcessorData _customProcessorData; private CancellationTokenSource _cancellationToken = new CancellationTokenSource(); public static IConfiguration Configuration { get; set; } private Task _backgroundTask; private int _repeatCount = 0; public CustomProcessor(ICustomProcessorData data, IConfiguration config) { this._customProcessorData = data; Configuration = config; } public Task StartAsync(CancellationToken cancellationToken) { //StartAsync() methodu, servisimiz başladığı zaman ilk //olarak belli tanımları yapabileceğimiz giriş noktası. _repeatCount = Int32.Parse($"{Configuration["BatchCount"]}"); //Arka planda çalışacak işi örnek olması adına, Task objesi ile yaratıyoruz. //Bu DoSomeBackgroundTask() methodumuz ayrı bir thread yaklaşımı ile bu sayede //arkada çalışan bir işlem olacak. Task t = Task.Factory.StartNew(() => { return DoSomeBackgroundTask(); }); _cancellationToken = new CancellationTokenSource(); _cancellationToken.CancelAfter(900000); _backgroundTask = t; Console.WriteLine($"Hello {_customProcessorData.Name}"); Console.WriteLine("Initialiaztion is finished...Let's start to do some work."); if (_backgroundTask.IsCompleted) { return _backgroundTask; } return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { //StopAsync() methodu, servisimiz durduğu zaman ilk //olarak belli işlemlerin yapılması için gerekli olan method. //Bu şekilde çeşitli kaynakların boşaltılması gibi işlemler yapılabilir. //...Dispose(), DisConnect() vs. gibi işlemler. if (_backgroundTask == null) { return; } Console.WriteLine("CustomProcessor is finished batch..."); try { _cancellationToken.Cancel(); } finally { await Task.WhenAny(_backgroundTask, Task.Delay(Timeout.Infinite, cancellationToken)); } } public virtual void Dispose() { _cancellationToken.Cancel(); } private async Task DoSomeBackgroundTask() { //Arkaplanda çalışacak işimizi simule eden basit bir method. int i = 0; while (!_cancellationToken.IsCancellationRequested) { i++; if (i == _repeatCount) { i = 0; _cancellationToken.Cancel(); } Console.WriteLine("CustomProcessor is doing some background process."); await Task.Delay(TimeSpan.FromSeconds(0.5)); } if (_cancellationToken.IsCancellationRequested) { _backgroundTask = null; await StopAsync(_cancellationToken.Token); Console.WriteLine("Do you want to restart?"); var repeat = Console.ReadLine(); if (repeat == "y") { await StartAsync(_cancellationToken.Token); } else Environment.Exit(-1); } } } }
Yukarıdaki kodun çıktısını, sürekli çalışan bir arka plan uygulaması olarak ele alabilirsiniz. Bu arada kodları GitHub’a da koydum. https://github.com/ardacetinkaya/HostBuilderAPI-Demo adresinden erişmeniz mümkün. 🙂
Temel olarak, yukarıdaki kodlar ile basit bir konsol uygulamasında host edilebilecek bir uygulama yapmış oluyoruz.ASP.NET Core tipi bir web uygulamasındaki benzer yaklaşımı, bir konsol uygulamasında yapmak bu sayede oldukça basit. Bu şekilde mesela; TCP, UDP gibi farklı network protokolleri için host uygulamaları geliştirmek, arka planda sürekli çalışan uygulamaları host etmek gibi ihtiyaçları yapabiliyoruz. .NET Core 2.1 ile gelecek bu tarz yenilikler ile “cross-platform” olarak geliştirilebilecek uygulama tiplerinin sayısı artacak.
Bir sonraki yazıya kadar mutlu kodlamalar… 🙂