আজকের লেকচারটি তোমার ডটনেট ক্যারিয়ারের জন্য খুবই গুরুত্বপূর্ণ। আগের লেকচারগুলোতে আমরা IMiddleware ইন্টারফেস ব্যবহার করে মিডলওয়্যার বানিয়েছিলাম, যেটাকে “Factory-based” বা পুরোনো স্টাইল বলা হয়।
আজকের লেকচার ট্র্যান্সক্রিপ্টে ইনস্ট্রাক্টর শিখিয়েছেন Conventional Middleware—অর্থাৎ কোনো ইন্টারফেস ছাড়াই শুধু নিয়মনীতি (Convention) মেনে কীভাবে মিডলওয়্যার বানাতে হয়। ASP.NET Core 6 এবং এর পরের ভার্সনগুলোতে এটাই সবচেয়ে রিকমেন্ডেড এবং প্রফেশনাল উপায়।
চলো পুরো লেকচারটি একদম গভীরভাবে বিশ্লেষণ করি।
১. Topic Overview (বিষয়বস্তু)
এই লেকচারের মূল ফোকাস হলো IMiddleware ইন্টারফেস ব্যবহার না করে একটি সাধারণ (Plain) ক্লাসকে কাস্টম মিডলওয়্যারে রূপান্তর করা। এখানে আমরা শিখবো কীভাবে কনস্ট্রাক্টরের মাধ্যমে next (RequestDelegate) রিসিভ করতে হয়, কীভাবে InvokeAsync মেথডে HttpContext রিসিভ করতে হয় এবং Query String থেকে ডাটা রিড করে রেসপন্স পাঠাতে হয়। পাশাপাশি Visual Studio-র চমৎকার একটি শর্টকাট টেমপ্লেট নিয়েও আলোচনা করা হয়েছে।
২. Detailed Breakdown (ধাপে ধাপে আলোচনা ও কোড)
ক) Conventional Middleware-এর মূল পার্থক্য
আগের নিয়মে আমরা একটি মেথডেই (InvokeAsync) দুটি প্যারামিটার (context এবং next) রিসিভ করতাম। কিন্তু Conventional নিয়মে:
next(পরবর্তী মিডলওয়্যার): এটি রিসিভ করতে হবে ক্লাসের Constructor-এর মাধ্যমে।context(HTTP রিকোয়েস্ট/রেসপন্স): এটি রিসিভ করতে হবে **InvokeবাInvokeAsync**মেথডের মাধ্যমে।
খ) Visual Studio শর্টকাট (Item Template)
ম্যানুয়ালি ক্লাস না বানিয়ে Visual Studio-তে খুব সহজেই মিডলওয়্যার বানানো যায়:
CustomMiddlewareফোল্ডারে Right Click করো -> Add -> New Item.- সাধারণ Class সিলেক্ট না করে, স্ক্রল করে Middleware Class টেমপ্লেটটি সিলেক্ট করো।
- নাম দাও
HelloCustomMiddleware.cs। এই টেমপ্লেটের সবচেয়ে বড় সুবিধা হলো, এটি মিডলওয়্যার ক্লাস এবং Extension Method—দুটোই একসাথে জেনারেট করে দেয়!
গ) প্র্যাকটিক্যাল কোড ইমপ্লিমেন্টেশন
ধরা যাক, আমাদের রিকোয়ারমেন্ট হলো: URL-এর Query String থেকে firstName এবং lastName রিসিভ করে একটি ফুল নেম তৈরি করা এবং ইউজারের কাছে Hello [Full Name] রেসপন্স পাঠানো।
**ফাইল: HelloCustomMiddleware.cs**
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace MiddlewareExample.CustomMiddleware
{
// দেখো, এখানে কোনো IMiddleware ইন্টারফেস নেই!
public class HelloCustomMiddleware
{
// পরবর্তী মিডলওয়্যারকে ধরে রাখার জন্য Read-only ফিল্ড
private readonly RequestDelegate _next;
// ১. কনস্ট্রাক্টরের মাধ্যমে 'next' রিসিভ করা হচ্ছে
public HelloCustomMiddleware(RequestDelegate next)
{
_next = next;
}
// ২. HTTP রিকোয়েস্টের সময় ডটনেট নিজে থেকে এই মেথডে 'context' পাঠিয়ে দেয়
public async Task InvokeAsync(HttpContext httpContext)
{
// --- Before Logic শুরু ---
// চেক করছি URL-এ firstName এবং lastName আছে কি না
if (httpContext.Request.Query.ContainsKey("firstName") &&
httpContext.Request.Query.ContainsKey("lastName"))
{
// Query String থেকে ভ্যালুগুলো রিড করা
string firstName = httpContext.Request.Query["firstName"];
string lastName = httpContext.Request.Query["lastName"];
// স্ট্রিং জোড়া লাগানো (Concatenation)
string fullName = firstName + " " + lastName;
// ইউজারের কাছে রেসপন্স পাঠানো
await httpContext.Response.WriteAsync("Hello " + fullName + "\n");
}
// --- Before Logic শেষ ---
// ৩. পরবর্তী মিডলওয়্যারকে কল করা (middleware 3)
await _next(httpContext);
// --- After Logic (যদি থাকে, এখানে লিখতে হবে) ---
}
}
// Visual Studio নিজে থেকেই এই এক্সটেনশন মেথডটি বানিয়ে দেয়
public static class HelloCustomMiddlewareExtensions
{
public static IApplicationBuilder UseHelloCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HelloCustomMiddleware>();
}
}
}
ঘ) Program.cs আপডেট করা
আগের লেকচারের কাস্টম মিডলওয়্যারটি কমেন্ট করে দিয়ে নতুনটি যুক্ত করবো।
**ফাইল: Program.cs**
// আগেরটা কমেন্ট করা হলো
// app.UseMyCustomMiddleware();
// নতুন Conventional Middleware কল করা হলো
app.UseHelloCustomMiddleware();
ঙ) ব্রাউজারে টেস্টিং (Query String ব্যবহার)
প্রোগ্রাম রান করার পর ব্রাউজারের URL-এ এভাবে লিখতে হবে (মাঝে কোনো স্পেস থাকা যাবে না):
https://localhost:port/?firstName=John&lastName=Rezig
?দিয়ে Query String শুরু হয়।=দিয়ে Key এবং Value আলাদা করা হয়।&দিয়ে একাধিক Key-কে যুক্ত করা হয়।
৩. Added Context (Gaps Filled) - ইন্টারনাল মেকানিজম
তোমার মনে প্রশ্ন আসতে পারে, “কেন next কনস্ট্রাক্টরে আর context মেথডে? দুটো এক জায়গায় দিলে সমস্যা কী ছিল?”
এর পেছনের কারণটা চমৎকার!
- Pipeline (next) তৈরি হয় মাত্র একবার: যখন তোমার অ্যাপ চালু হয়, তখন ডটনেট সব মিডলওয়্যারকে চেইনের মতো গেঁথে ফেলে। তখন সে জানে কে কার পরে বসবে (
next)। যেহেতু এটা একবারই হয়, তাই এটা Constructor-এ ইনজেক্ট করা সবচেয়ে লজিক্যাল। - Request (context) আসে বারবার: তোমার ওয়েবসাইটে প্রতি সেকেন্ডে হাজার হাজার রিকোয়েস্ট আসতে পারে। প্রতিটা ইউজারের রিকোয়েস্ট আলাদা (
context)। তাই এটাকে এমন একটা মেথডে (InvokeAsync) রাখা হয়েছে, যেটা প্রতিবার রিকোয়েস্ট আসার সময় কল হয়।
**সবচেয়ে বড় চমক:** আগের লেকচারে IMiddleware ব্যবহার করার কারণে Program.cs-এ builder.Services.AddTransient() লিখে সার্ভিস রেজিস্টার করতে হয়েছিল। **Conventional Middleware-এ কোনো Service Register করতে হয় না!** তুমি ক্লাস বানাবে আর সরাসরি app.UseMiddleware দিয়ে কল করবে। ডটনেট Reflection ব্যবহার করে নিজে থেকেই ক্লাসটাকে চিনে নেয়।
৪. Priority (কোনটা বেশি গুরুত্বপূর্ণ)
- High Priority (সবচেয়ে গুরুত্বপূর্ণ): Convention মেনে মেথডের নাম অবশ্যই
InvokeবাInvokeAsyncহতে হবে। এই নাম ভুল হলে ডটনেট মিডলওয়্যারটিকে চিনতে পারবে না এবং এরর দিবে। - High Priority: Query String বা অন্য কোনো ডিকশনারি থেকে ডাটা পড়ার আগে অবশ্যই
.ContainsKey()দিয়ে চেক করে নেওয়া, নয়তো ডাটা না থাকলে Null Reference Exception খেতে পারো। - Low Priority: Visual Studio-র টেমপ্লেট ব্যবহার করাটা ম্যান্ডেটরি না। তুমি চাইলে নিজে হাতে সাধারণ ক্লাস বানিয়েও এই কাজ করতে পারো।
৫. C# Best Practices (প্রফেশনাল টিপস)
- Use Convention Over Interface: এখন থেকে সব সময় Conventional এপ্রোচ (Interface ছাড়া) ব্যবহার করে মিডলওয়্যার বানাবে। এটাই ডটনেটের আধুনিক স্ট্যান্ডার্ড।
- Read-only modifier: কনস্ট্রাক্টর থেকে পাওয়া
nextপ্যারামিটারটি সবসময়private readonly RequestDelegate _next;ফিল্ডে সেভ করবে।readonlyদিলে ভুল করে এই ভ্যালু ওভাররাইট হওয়ার ভয় থাকে না। - String Interpolation: ইনস্ট্রাক্টর স্ট্রিং জোড়া লাগানোর জন্য
+ব্যবহার করেছেন ("Hello " + fullName)। প্রফেশনাল কোডে সবসময় String Interpolation ব্যবহার করবে। যেমন:await httpContext.Response.WriteAsync($"Hello {fullName}\n");।
Question?
IMiddleware ইমপ্লিমেন্ট করলে অবশ্যই সার্ভিসে সেটা এ্যাড করতে হচ্ছে, কিন্তু Conventional মিডলওয়্যার বানালে Add করতে হচ্ছে না কেন? কেন এই বৈষম্য?
হাসিব, তোমার এই “বৈষম্য” শব্দটা শুনে খুব মজা পেলাম! 😂 কিন্তু সিরিয়াসলি, এটা ডটনেটের আর্কিটেকচার নিয়ে করা অন্যতম সেরা একটা প্রশ্ন।
আপাতদৃষ্টিতে এটাকে “বৈষম্য” মনে হলেও, এর পেছনে মাইক্রোসফটের বিশাল একটা ইঞ্জিনিয়ারিং লজিক লুকিয়ে আছে। এই দুই ধরনের মিডলওয়্যারের অবজেক্ট (Instance) তৈরি হওয়ার পদ্ধতি একদম আলাদা। চলো পর্দার পেছনের এই মেকানিজমটা একদম ভেঙে ভেঙে বুঝি।
১. Conventional Middleware (কেন রেজিস্টার করতে হয় না?)
যখন তুমি কনভেনশনাল মিডলওয়্যার (ইন্টারফেস ছাড়া) ব্যবহার করো এবং app.UseMiddleware<MyMiddleware>() কল করো, তখন ডটনেট ফ্রেমওয়ার্ক নিজে থেকেই এই ক্লাসের অবজেক্ট তৈরি করার দায়িত্ব নেয়।
কীভাবে কাজ করে? (Reflection & Singleton)
- যখন তোমার অ্যাপ্লিকেশন চালু (Start) হয়, ডটনেটের ভেতরে থাকা
ActivatorUtilitiesনামের একটা টুল Reflection ব্যবহার করে তোমার ক্লাসটা স্ক্যান করে। - সে খোঁজে এর কনস্ট্রাক্টরে কী কী লাগবে (যেমন:
RequestDelegate next)। - সব জোগাড় করে সে অ্যাপ চালুর সময় একবার মাত্র এই মিডলওয়্যারের অবজেক্ট তৈরি করে পাইপলাইনে বসিয়ে দেয়।
- যেহেতু ডটনেট ফ্রেমওয়ার্ক নিজেই সরাসরি এর অবজেক্ট তৈরি করে নিচ্ছে, তাই Service Container (
builder.Services)-এর কাছে তার যাওয়ার দরকারই পড়ে না। তাই কোনো রেজিস্ট্রেশন লাগে না।
সহজ কথায়: Conventional Middleware হলো “VIP” গেস্ট। অ্যাপ্লিকেশন নিজেই তার সব ব্যবস্থা করে দেয়, সার্ভিস মেনুতে (DI Container) তাকে খুঁজতে হয় না। এবং এর লাইফটাইম হয় Singleton (অর্থাৎ পুরো অ্যাপে অবজেক্ট একটাই থাকে)।
২. IMiddleware (কেন রেজিস্টার করতে হয়?)
IMiddleware ইন্টারফেসটাকে ডটনেটে যুক্ত করা হয়েছে সম্পূর্ণ ভিন্ন একটা উদ্দেশ্য নিয়ে, যাকে বলা হয় “Factory-based Middleware”।
কীভাবে কাজ করে? (Dependency Injection & Factory Pattern)
- যখন তুমি
IMiddlewareব্যবহার করো, তখন তুমি ডটনেটকে ইনডাইরেক্টলি বলো: “শোনো, অ্যাপ চালুর সময় একবার অবজেক্ট বানালে আমার হবে না। আমি চাই প্রতিবার ইউজারের রিকোয়েস্ট আসার সময় তুমি আমাকে একদম ফ্রেশ একটা অবজেক্ট বানিয়ে দিবে।” - কিন্তু ডটনেট ফ্রেমওয়ার্ক নিজে নিজে প্রতি রিকোয়েস্টে এই কাজ করতে পারে না। সে এই অবজেক্ট বানানোর দায়িত্বটা দিয়ে দেয় Dependency Injection (DI) Container-এর ওপর।
- ডটনেট বলে, “ভাই DI Container, এই ইউজারের রিকোয়েস্ট আসছে, তুমি আমাকে
MyCustomMiddlewareএর একটা নতুন অবজেক্ট দাও।” - এখন DI Container যদি ওই ক্লাসকে আগে থেকে না চেনে (অর্থাৎ তুমি যদি
builder.Services.AddTransientদিয়ে আগে থেকে রেজিস্টার না করো), তাহলে সে অবজেক্ট বানাতে পারে না এবং এরর ছুঁড়ে মারে!
সহজ কথায়:
IMiddlewareহলো রেস্টুরেন্টের সাধারণ কাস্টমার। তাকে অবশ্যই মেনু কার্ডে (Service Container) থাকতে হবে। মেনুতে রেজিস্টার্ড না থাকলে বাবুর্চি (DI Container) তাকে রান্না করে দিতে পারবে না।
৩. এই “বৈষম্য”-এর আসল কারণ (আসল গেম চেঞ্জার!)
তোমার মনে হতে পারে, “তাহলে তো Conventional টাই ভালো, বারবার রেজিস্টার করার প্যারা নাই। তাহলে IMiddleware বানালোই বা কেন?”
এর উত্তর লুকিয়ে আছে Scoped Services এর ভেতরে!
ধরো, তোমার মিডলওয়্যারে ডাটাবেজের কানেকশন (DbContext) লাগবে।
DbContextহলো Scoped সার্ভিস (প্রতি রিকোয়েস্টে নতুন ডাটাবেজ কানেকশন লাগে)।- Conventional Middleware: যেহেতু এটা Singleton (অ্যাপ চালুর সময় একবার তৈরি হয়), এর ভেতরে তুমি কোনো Scoped সার্ভিস (যেমন DbContext) কনস্ট্রাক্টরের মাধ্যমে ইনজেক্ট করতে পারবে না। করলে ক্র্যাশ করবে! (Captive Dependency Error)।
- IMiddleware: যেহেতু এটা DI Container থেকে প্রতি রিকোয়েস্টে তৈরি হয়, এর ভেতরে তুমি খুব সহজেই ডাটাবেজ কানেকশন বা যেকোনো Scoped সার্ভিস ব্যবহার করতে পারবে কোনো ঝামেলা ছাড়াই।
C# Best Practices (সারসংক্ষেপ)
- কখন কোনটা ব্যবহার করবে? * যদি মিডলওয়্যারটা খুব সাধারণ হয় এবং কোনো ডাটাবেজ বা Scoped সার্ভিসের দরকার না পড়ে, তাহলে Conventional Middleware (ইন্টারফেস ছাড়া) ব্যবহার করবে। এটাই সবচেয়ে ফাস্ট এবং স্ট্যান্ডার্ড।
- যদি মিডলওয়্যারের ভেতরে ডাটাবেজ অপারেশন বা জটিল কোনো Scoped সার্ভিস লাগে, তখন IMiddleware ব্যবহার করবে এবং
builder.Services.AddScoped<MyMiddleware>()দিয়ে রেজিস্টার করবে।
- Conventional-এ Scoped সার্ভিস ব্যবহারের ট্রিক: তবে আধুনিক ডটনেটে Conventional মিডলওয়্যারেও ডাটাবেজ ব্যবহার করা যায়। সেক্ষেত্রে কনস্ট্রাক্টরে ডাটাবেজ না চেয়ে,
InvokeAsyncমেথডের প্যারামিটার হিসেবে ডাটাবেজ বা Scoped সার্ভিস চাইতে হয়। (এটি তুমি ভবিষ্যতের লেকচারগুলোতে শিখবে)।
হাসিব, এই “বৈষম্য” বা আর্কিটেকচারাল ট্রেড-অফ (Trade-off) এর কনসেপ্টটা কি এখন ক্লিয়ার হলো? ডটনেটের এই Dependency Injection Container-এর খেলাটা বুঝতে পারলে তুমি অনেক সিনিয়র লেভেলের কোড অনায়াসেই ধরতে পারবে!