হাসিব, তোমার দেওয়া ASP.NET Core Middleware-এর কনটেন্টগুলো আমি বাংলায় গুছিয়ে এবং সুন্দর ফরম্যাটিং (Markdown) করে দিলাম, যাতে পড়তে এবং বুঝতে সুবিধা হয়।


Middleware কী?

ASP.NET Core-এ মিডলওয়্যার হলো মূলত কিছু কম্পোনেন্টের সমষ্টি, যা একটি পাইপলাইন তৈরি করে। প্রতিটি HTTP রিকোয়েস্ট এবং রেসপন্স এই পাইপলাইনের মধ্য দিয়ে যায়। প্রতিটি মিডলওয়্যার নিচের কাজগুলো করতে পারে:

  • আসা রিকোয়েস্টগুলো পরীক্ষা করা: ইনকামিং রিকোয়েস্ট চেক করা।
  • মডিফাই করা: প্রয়োজনে রিকোয়েস্ট বা রেসপন্স পরিবর্তন করা।
  • পরবর্তী ধাপে পাঠানো বা থামিয়ে দেওয়া: পাইপলাইনের পরবর্তী মিডলওয়্যারকে কল করা, অথবা মাঝপথেই প্রসেস থামিয়ে (short-circuit) সরাসরি রেসপন্স জেনারেট করা।

এই পাইপলাইনের মাধ্যমে অ্যাপ্লিকেশনের লজিকগুলো মডিউলারাইজ করা যায়। অথেনটিকেশন, লগিং, এরর হ্যান্ডলিং, রাউটিংয়ের মতো ফিচারগুলো খুব পরিষ্কারভাবে যুক্ত করা সম্ভব হয়।

রিকোয়েস্ট পাইপলাইন (Middleware Chain)

রিকোয়েস্ট পাইপলাইনকে পরপর জোড়া লাগানো কিছু পানির পাইপের মতো ভাবতে পারো। প্রতিটি মিডলওয়্যার হলো এক একটি ভালভ (valve), যা ইনফরমেশনের ফ্লো কন্ট্রোল করে এবং বিভিন্ন ধাপে নির্দিষ্ট অপারেশন চালায়। তুমি যে ক্রমে (order) মিডলওয়্যারগুলো রেজিস্টার করবে, সেগুলো ঠিক সেই ক্রমেই এক্সিকিউট হবে।


**app.Use বনাম app.Run**

পাইপলাইনে মিডলওয়্যার যুক্ত করার জন্য এই দুটি মেথড ব্যবহার করা হয়, তবে এদের মধ্যে মূল কিছু পার্থক্য আছে:

app.Use (Non-Terminal Middleware)

  • কী করে: এটি সাধারণত নির্দিষ্ট কোনো কাজ করে এবং কন্ট্রোল পরবর্তী মিডলওয়্যারে পাঠানোর জন্য next ডেলিগেটকে কল করে।
  • মডিফিকেশন: এটি পরবর্তী ধাপে যাওয়ার আগে রিকোয়েস্ট বা রেসপন্স পরিবর্তন করতে পারে।
  • উদাহরণ: Authentication, logging, custom headers ইত্যাদি।

app.Run (Terminal Middleware)

  • কী করে: এটি next-কে কল করে না। এটি পাইপলাইনের সমাপ্তি ঘটায় এবং সরাসরি রেসপন্স জেনারেট করে।
  • ব্যবহার: যেসব রিকোয়েস্টে আর কোনো প্রসেসিংয়ের দরকার নেই (যেমন: একটি সিম্পল মেসেজ দেখানো), সেখানে এটি ব্যবহৃত হয়।
  • সীমাবদ্ধতা: যেহেতু এটি চেইনের শেষে থাকে, তাই এটি রিকোয়েস্ট মডিফাই করে অন্য কোথাও পাঠাতে পারে না।

কোড উদাহরণ

১. একাধিক app.Run কল করার ফলাফল

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello");
});
 
app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello again");
});
 
app.Run();
 

ফলাফল: এখানে শুধু প্রথম app.Run এক্সিকিউট হবে। এটি রেসপন্সে “Hello” লিখে পাইপলাইন শেষ করে দেবে, তাই পরের “Hello again” কখনোই রান হওয়ার সুযোগ পাবে না।

২. app.Use এবং app.Run দিয়ে চেইনিং করা

// Middleware 1
app.Use(async (context, next) => {
    await context.Response.WriteAsync("Hello ");
    await next(context); 
});
 
// Middleware 2
app.Use(async (context, next) => {
    await context.Response.WriteAsync("Hello again ");
    await next(context);
});
 
// Middleware 3
app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello again");
});
 

ফলাফল: এটি চেইনিংয়ের একটি সঠিক উপায়। প্রথম এবং দ্বিতীয় app.Use কাজ শেষ করে next কল করবে এবং শেষে app.Run পাইপলাইন শেষ করবে। আউটপুট হবে: Hello Hello again Hello again


কাস্টম মিডলওয়্যার (Custom Middleware)

ASP.NET Core-এ অনেক বিল্ট-ইন মিডলওয়্যার থাকলেও মাঝে মাঝে নির্দিষ্ট প্রয়োজনের জন্য কাস্টম মিডলওয়্যার বানাতে হয়।

কেন বানাবো?

  • লজিক এনক্যাপসুলেট (Encapsulate) করতে (যেমন: লগিং, সিকিউরিটি চেক)।
  • অ্যাপ্লিকেশনের প্রয়োজন অনুযায়ী বিহেভিয়ার কাস্টমাইজ করতে।
  • কোড ক্লিন এবং মেইনটেইনেবল রাখতে।

কাস্টম কনভেনশনাল মিডলওয়্যারের গঠন

কনভেনশনাল মিডলওয়্যার মূলত একটি ক্লাস হিসেবে তৈরি করা হয়।

// HelloCustomMiddleware.cs
public class HelloCustomMiddleware
{
    private readonly RequestDelegate _next;
 
    // Constructor Injection
    public HelloCustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }
 
    // Invoke Method
    public async Task Invoke(HttpContext httpContext)
    {
        if (httpContext.Request.Query.ContainsKey("firstname") && 
            httpContext.Request.Query.ContainsKey("lastname"))
        {
            string fullName = httpContext.Request.Query["firstname"] + " " + httpContext.Request.Query["lastname"];
            await httpContext.Response.WriteAsync(fullName);
        }
        await _next(httpContext); // পরবর্তী ধাপে পাঠানো
    }
}
 
// সহজে রেজিস্ট্রেশনের জন্য এক্সটেনশন মেথড
public static class HelloCustomModdleExtensions
{
    public static IApplicationBuilder UseHelloCustomMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<HelloCustomMiddleware>();
    }
}
 
  • Constructor: RequestDelegate রিসিভ করে, যা পরের মিডলওয়্যারকে কল করতে লাগে।
  • Invoke Method: এখানেই মূল লজিক থাকে। কাজ শেষে await _next(httpContext); কল করা খুব জরুরি, নাহলে রিকোয়েস্ট আটকে যাবে।

মিডলওয়্যার পাইপলাইনের সঠিক ক্রম (Ideal Order)

তুমি কোন সিরিয়ালে মিডলওয়্যার বসাচ্ছো, সেটা খুবই গুরুত্বপূর্ণ। নিচে স্ট্যান্ডার্ড অর্ডার দেওয়া হলো:

  1. Exception/Error Handling: (UseExceptionHandler) যেকোনো এরর শুরুতেই ধরে ফেলার জন্য।
  2. HTTPS Redirection: (UseHttpsRedirection) সিকিউরিটির জন্য HTTP থেকে HTTPS-এ রিডাইরেক্ট করতে।
  3. Static Files: (UseStaticFiles) ইমেজ, CSS, JS ফাইলগুলো ক্লায়েন্টকে দেওয়ার জন্য।
  4. Routing: (UseRouting) URL অনুযায়ী রিকোয়েস্ট নির্দিষ্ট এন্ডপয়েন্টে পাঠানোর জন্য।
  5. CORS: (UseCors) ভিন্ন ডোমেইন থেকে আসা রিকোয়েস্ট হ্যান্ডেল করতে।
  6. Authentication: (UseAuthentication) ইউজার আসল কিনা তা ভেরিফাই করতে।
  7. Authorization: (UseAuthorization) ভেরিফাইড ইউজারের নির্দিষ্ট রিসোর্সে অ্যাক্সেস আছে কিনা তা চেক করতে।
  8. Custom Middleware: তোমার নিজের তৈরি করা লজিক বা মিডলওয়্যার।
  9. Endpoints: (UseEndpoints) সবশেষে কনট্রোলার বা পেজ ম্যাপ করার জন্য।

UseWhen() এর ব্যবহার

শর্তের (condition) ওপর ভিত্তি করে পাইপলাইনে ডাইনামিকভাবে মিডলওয়্যার যোগ করতে UseWhen() ব্যবহার করা হয়।

app.UseWhen(
    context => context.Request.Query.ContainsKey("username"), 
    app => {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("Hello from Middleware branch\n");
            await next(); 
        });
    });
 
app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from middleware at main chain");
});
 
  • কীভাবে কাজ করে: যদি URL-এর কুয়েরি স্ট্রিংয়ে username থাকে (যেমন: ?username=Hasib), তবেই ভেতরের ব্লকটি রান করবে। রান করার পর সেটি আবার মূল পাইপলাইনে ফিরে এসে পরের কাজগুলো সম্পন্ন করবে।

এক নজরে গুরুত্বপূর্ণ বিষয় (Key Takeaways)

  • অর্ডার খুবই গুরুত্বপূর্ণ: মিডলওয়্যারগুলো কোডে যে ক্রমে লেখা হয়, সেভাবেই কাজ করে।
  • app.Use বনাম app.Run: app.Use ব্যবহার করো যখন প্রসেস করে পরের ধাপে পাঠাতে হবে। app.Run ব্যবহার করো যখন প্রসেস শেষ করে রেসপন্স দিতে হবে।
  • Short-Circuiting: মিডলওয়্যার চাইলে next কল না করে মাঝপথেই পাইপলাইন থামিয়ে রেসপন্স পাঠাতে পারে।
  • ডাইনামিক ব্রাঞ্চিং: নির্দিষ্ট কোনো রুলে মিডলওয়্যার চালাতে UseWhen ব্যবহার করতে হয়।