Asp.NET Core 2’de Middleware Yapısı ve Kullanımı

Merhaba,

Web yazılımları, genellikle kullanıcılarla etkileşim açısından oldukça yoğun bir yapı arz etmektedirler. Kullanıcılar web sitesinden bir istekte bulunabilmekte ve bu isteğe karşılık server tarafından uygun cevaplar üretilip gönderilmektedir. Ve tüm süreç bu şekilde cereyan etmekte, teknoloji ve yapılar ne kadar yenilenip güçlendirilsede tüm mesele bu esasta seyr etmektedir.

Biz yazılımcılar ise bu süreçte bazen yapılan isteklere karşılık verilecek cevaplara müdahale ederek varlığımızı göstermek yahut arada farklı işlemler gerçekleştirerek sürecin gidişatına farklı yön vermek isteyebiliriz. Doğal olarak bu isteğimize karşılık yazılım dünyasında bir kavram bizlere eşlik edecek ve yapısal olarak belli kurallarla gayemiz doğrultusunda bizlere hizmet edecektir.

İşte kullanıcı isteği ile o isteğe karşılık üretilen cevap arasına girmemizi ve bu noktada hertürlü işi yürütmemizi sağlayacak olan bu kavram Middleware(Ara Katman) olarak adlandırılmaktadır. Middleware yapıları mantık olarak request ile response arasına girip işlem yapmamızı sağlamakla birlikte, birden fazla olma durumlarında sıralı adımlar eşliğinde işlenmekte ve son middleware işlemide bittiği an ilgili response kullanıcıya gönderilip süreç sona erdirilmektedir.

Peki middleware nasıl kullanılır? sorusuna karşılık genel bir cevap vermemiz gerekirse eğer kullanılan programlama dilleri arasında farklılık göstermekle birlikte, aynı çatı altında bulunan birden fazla teknoloji arasında bile kullanım farkı söz konusu olabilmektedir. Örneğin; Node.js modülü olan Express.js’de middleware yapısının nasıl kullanıldığına dair Express.js – Middleware(Ara Katman) Nedir? Nasıl Kullanılır? başlıklı makalemizi kaleme almıştık. Bu içeriğimizde ise Asp.NET Core 2 mimarisinde middleware yapılarının nasıl kullanıldığına dair bir incelemede bulunacağımız için ara katman mantığının iki farklı platform arasındaki farkını rahatlıkla değerlendirebilirsiniz.

Şimdi fazla uzatmadan Asp.NET Core 2 mimarisinde middleware yapısına ufaktan giriş yapmaya başlayalım 🙂

Asp.NET Core 2’de Middleware Yapısı

Asp.NET Core 2’de middleware yapıları projenin “Startup” sınıfı içerisindeki “Configure” isimli metot tarafından barındırılmaktadır. Bu metot, yapılan isteğe karşılık verilecek cevaplara istinaden ara katman olarak çalışmakta ve öncelik olarak belli başlı işlemleri gerçekleştirmektedir. Zaten şu ana kadar Asp.NET Core projelerinde ilgili metot içerisinde kullandığımız çoğu fonksiyon aslında bir middleware görevi gören yapılardı.

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();

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

Yukarıdaki kod bloğunda olduğu gibi;

  • UseStaticFiles,
  • UseExceptionHandler,
  • UseDeveloperExceptionPage ve hatta
  • UseMvc
  • .vs .vs

bir middleware yapısında fonksiyonlardır. Evet… sonuncu olan “UseMvc” fonksiyonuda bir middleware’dir. İlginçtir ama öyledir. MVC; yapısal olarak sade ve sadece bir tasarım desenidir. Klasik Asp.NET MVC bu tasarım desenini özümseyerek mimariyle bütünselleştirmiştir. Her ne kadar kullanışlı ve harika bir tasarım deseni olsada bir yaklaşımdan ibaret olması, hiç yoktan tercih edilebilir olmasını gerektirmekteyken klasik Asp.NET MVC kayıtsız şartsız MVC yaklaşımını altyapı olarak zorunlu tutmayı tercih etmiştir. Asp.NET Core mimarisi ise MVC tasarım desenini tercihimize bırakarak biz geliştiricilere seçme özgürlüğü sağlamaktadır. Dolayısıyla “UseMvc” metodu bir middleware olarak proje ayağa kaldırılırken komple sistemin genel yaklaşımını belirleyecek tarzda tasarlanmıştır.

Özel Middleware Oluşturma

Asp.NET Core 2’de middleware yapısı oluşturmak için yapmamız gereken tek şey “Startup” sınıfına kodlarımızı uygun bir şekilde yerleştirmektir. “Hocam, uygun bir şekilde yerleştirmekten kastın nedir?” diye sorarsanız eğer onun cevabını yazımızın devamında “short circuit(kısa devre)” konusunu ele alırken vereceğim. Örneğin; projemiz açılırken Dependency Injection ile database sınıfı olarak tanımlanan “DbContext” türevi sınıfımızı middleware’de elde edip, varsa migrationları migrate edelim.

    public class Startup
    {
        public IConfiguration Configuration { get; set; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();

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

            #region Custom Middleware
            DatabaseContext context = app.ApplicationServices.GetRequiredService<DatabaseContext>();
            context.Database.Migrate();
            #endregion
        }
    }

Eğer bu şekilde middleware’i yazarsanız aşağıdaki hatayla karşılaşırsınız.
Asp.NET Core 2'de Middleware Oluşturma
Hatanın metinsel halide aşağıdaki gibi olacaktır;

System.InvalidOperationException: ‘Cannot resolve scoped service ‘MiddlewareCore.Models.Context.DatabaseContext’ from root provider.’

Bu hatayı iki farklı yöntemle çözebilirsiniz;

  1. Scope oluşturarak;
    Uygulama servislerine erişebilmek için scope oluşturabilir ve ardından ilgili scope üzerinden servisi talep edebilirsiniz.

    .
    .
    .
                #region Custom Middleware
                using (var serviceScope = app.ApplicationServices.CreateScope())
                {
                    DatabaseContext context = serviceScope.ServiceProvider.GetRequiredService<DatabaseContext>();
                    context.Database.Migrate();
                }
                #endregion
    .
    .
    .
    
  2. “Program.cs” sınıfında “ValidateScopes” özelliğine “false” değerini vererek;

        public class Program
        {
            public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    
            public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>()
                .UseDefaultServiceProvider(option => option.ValidateScopes = false);
        }
    

Her iki yöntemden biri uygulandığı taktirde uygulama tetiklenirken middleware devreye girecek ve önceden dahil edilmiş “DbContext” türevi nesneyi elde ederek, üzerinden migrate işlemini gerçekleştirecektir.

Middlewarelerin Register Edilme Sırası

Yukarıdaki satırlarda short circuit(kısa devre) konusuna atıfta bulunmuş, olası bir sorunuza istinaden cevabımızı sonraya saklamıştık. İşte şimdi o sorunun cevabını vereceğimiz satırlara gelmiş bulunmaktayız. (soruyu hatırlamıyorsanız “Özel Middleware Oluşturma” alt başlığının ilk paragrafına hızlıca göz atınız.)

Eğer ki, birden fazla middleware ile çalışacaksanız bu middlewarelerin yazılma sırası oldukça önem arz etmektedir. Bunun nedeni; kimi middleware’ler short circuit(kısa devre) yaparak direkt işleyişi neticelendirmekte, kimileri ise işlevlerini bitirdikten sonra bir sonraki middleware’e işleyişi aktarmaktadırlar. Söz gelimi aşağıdaki örneği incelerseniz anlatılmaya çalışanı daha net izah etmiş olacağız;

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();

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

Yukarıdaki örnek kod bloğunda; “UseDeveloperExceptionPage“, “UseStaticFiles“, “UseExceptionHandler” ve “UseMvc” middleware’leri kullanılmaktadır. Lakin dikkat ederseniz “UseDeveloperExceptionPage” middleware’i çağrılırken “IsDevelopment” kontrolü yapılmakta yani development modundaysak bu middleware’i çalıştırılmaktadır. Bu kontrolün nedeni; “UseDeveloperExceptionPage” middleware’inin short circuit(kısa devre) bir middleware olmasından kaynaklanmaktadır. Eğer ilgili fonksiyon tetiklenirse işlevsel açıdan süreci neticelendireceğinden dolayı diğer fonksiyonlara akışı sağlamayacaktır. Dolayısıyla “UseDeveloperExceptionPage” fonksiyonu kısa devre özelliğinden dolayı kontrollü bir şekilde kullanılmaktadır. Diğer fonksiyonlarımız ise kendi işlevsellikleri bittikten sonra akışı sonlandırmayıp bir sonraki middleware’e aktaracağından dolayı direkt olarak kullanılabilmektedirler. İşte bu mantıktan dolayı middleware’lerin register edilme sıraları büyük önem taşımaktadır.

Run Metodu

Run metodu yukarıda ele aldığımız short circuit olayını yapmamızı sağlayan bir yapıya sahiptir. Örneğin aşağıdaki kod bloğunu inceleyiniz;

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Run(async context => await context.Response.WriteAsync("Middleware 1."));
            app.Run(async context => await context.Response.WriteAsync("Middleware 2."));
        }
.
.
.

Asp.NET Core 2'de Middleware Yapısı ve Kullanımı
Gördüğünüz gibi Run metodu iki kere kullanıldığı halde ekrana sadece “Middleware 1.” sonucunu yazdırmış ve kısa devre ile tüm akışı sonlandırmıştır.

Use Metodu

Run metoduna nazaran, devreye girdikten sonra süreçte sıradaki middleware’i çağırarak ardından o middleware’ın işlevi bittikten sonra geriye dönüp devam edebilen bir yapıya sahiptir.

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                Debug.WriteLine("Middleware başladı.");

                await next.Invoke();

                Debug.WriteLine("Middleware sonlandırılıyor.");
            });

            app.Run(async context =>
            {
                Debug.WriteLine("Kısa devre | short circuit yapılıyor.");
                await context.Response.WriteAsync("--- Short Circuit ---");
            });
        }
.
.
.

Yukarıdaki kod bloğunu incelerseniz eğer “Use” fonksiyonu içerisinde “next.Invoke()” komutu ile bir sonraki middleware’i yani “Run” fonksiyonunu çağırmaktadır. “Run” fonksiyonunun işlevi bittiği vakit compiler kaldığı noktaya geri dönüp devam edecektir.

ÇıktıOutput
Asp.NET Core 2'de Middleware Yapısı ve KullanımıAsp.NET Core 2'de Middleware Yapısı ve Kullanımı

“Use” metoduyla ilgili bir başka örnek ele alalım;

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                Debug.WriteLine("1. Middleware başladı.");
                await next.Invoke();
                Debug.WriteLine("1. Middleware sonlandı.");
            });

            app.Use(async (context, next) =>
            {
                Debug.WriteLine("2. Middleware başladı.");
                await next.Invoke();
                Debug.WriteLine("2. Middleware sonlandı.");
            });

            app.Use(async (context, next) =>
            {
                Debug.WriteLine("3. Middleware başladı.");
                await next.Invoke();
                Debug.WriteLine("3. Middleware sonlandı.");
            });
        }
.
.
.

Asp.NET Core 2'de Middleware Yapısı ve Kullanımı

Map Metodu

Bazen middleware’i talep gönderilen path’e göre filtrelemek isteyebiliriz. Bunun için “Use” ya da “Run” fonksiyonlarında if kontrolü sağlayabilir ya da “Map” metodu ile de bu işlemi gerçekleştirebiliriz.

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                Debug.WriteLine("Use middleware tetiklendi.");
                await next.Invoke();
            });

            app.Map("/example", internalApp =>
                internalApp.Run(async context =>
                    {
                        Debug.WriteLine("/example middleware tetiklendi.");
                        await context.Response.WriteAsync("/example middleware tetiklendi.");
                    }));
        }
.
.
.

Yukarıdaki örnek kod bloğunu incelerseniz eğer “/example” path’ine gelen talepler neticesinde “Map” fonksiyonunun ikinci parametresinde tanımlanan middleware devreye sokulacaktır.

Path : “/”Path : “/example”
OutputAsp.NET Core 2'de Middleware Yapısı ve KullanımıAsp.NET Core 2'de Middleware Yapısı ve Kullanımı
ÇıktıAsp.NET Core 2'de Middleware Yapısı ve KullanımıAsp.NET Core 2'de Middleware Yapısı ve Kullanımı

Görüldüğü üzere middleware’in path’e özel tetiklenmesini “Map” fonksiyonu sayesinde gerçekleştirmiş bulunmaktayız.

MapWhen Metodu

“Map” metodu ile sadece requestin yapıldığı path’e göre filtreleme yapılabilirken, “MapWhen” metodu ile ise herhangi bir şart ile filtreleme işlemi gerçekleştirebiliriz.

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.Use(async (context, next) =>
            {
                Debug.WriteLine("Use middleware tetiklendi.");
                await next.Invoke();
            });
            
            app.MapWhen(x => x.Request.Method == "GET", internalApp =>
            {
                internalApp.Run(async context => await context.Response.WriteAsync("MapWhen Middleware"));
            });
        }
.
.
.

Yukarıdaki kod bloğunu incelerseniz eğer “GET” tipinde olan tüm requestlerde bu middleware tetiklenecektir.

Özel Extension Middleware Oluşturma

İlk olarak middleware görevini görecek sınıfımızı inşa edelim.

    public class HelloMiddleware
    {
        readonly RequestDelegate _next;
        public HelloMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task Invoke(HttpContext context)
        {
            Debug.WriteLine("Hello 1");
            await _next.Invoke(context);
            Debug.WriteLine("Hello 2");
        }
    }

Ardından bu sınıfı kullanan extension metodumuzu yazalım.

    static public class HelloMiddlewareExtension
    {
        public static IApplicationBuilder UseHello(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<HelloMiddleware>();
        }
    }

Şimdi ise oluşturduğumuz middleware’i kullanalım.

.
.
.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseHello();

            app.Run(async context => await context.Response.WriteAsync("Run middleware"));
        }
.
.
.
ÇıktıOutput
Asp.NET Core 2'de Middleware Yapısı ve KullanımıAsp.NET Core 2'de Middleware Yapısı ve Kullanımı

Evet, gördüğünüz gibi asıl custom middleware’i bu şekilde oluşturmuş bulunmaktayız.

Okuduğunuz için teşekkür ederim.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…

İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*

Copy Protected by Chetan's WP-Copyprotect.