হাসিব, এই ট্রান্সক্রিপ্টটা মূলত ASP.NET Core-এ একদম বেসিক লেভেলের মিডলওয়্যার তৈরি করার হাতেখড়ি। এখানে ইনস্ট্রাক্টর দেখিয়েছেন কীভাবে app.Run() মেথড ব্যবহার করে একটা সিম্পল মিডলওয়্যার বানানো যায় এবং কেন এটা অন্যান্য মিডলওয়্যারের থেকে একটু আলাদা (Terminating nature)।

চলো ট্রান্সক্রিপ্টের প্রতিটি পয়েন্ট ভেঙে ভেঙে প্র্যাকটিক্যাল কোডসহ দেখি।

Topic Overview

এই লেকচারে মূলত Program.cs ফাইলে app.Run() মেথড দিয়ে একটা বেসিক মিডলওয়্যার (বা Request Delegate) তৈরি করা শেখানো হয়েছে। এছাড়া HttpContext কী, কেন এখানে async/await ব্যবহার করা হয় এবং একাধিক app.Run() ব্যবহার করলে কী সমস্যা হয় (Short-circuiting বা Terminating Middleware), তা নিয়ে আলোচনা করা হয়েছে।


Detailed Breakdown

ট্রান্সক্রিপ্টে বলা প্রতিটি পয়েন্টের বিস্তারিত ব্যাখ্যা এবং কোড নিচে দেওয়া হলো:

১. প্রোজেক্ট সেটআপ এবং ইনিশিয়াল কনফিগারেশন

Priority: Low (For your current stack) ইনস্ট্রাক্টর উইন্ডোজে ভিজ্যুয়াল স্টুডিও ব্যবহার করে middleware-example নামে একটা এম্পটি প্রোজেক্ট খুলেছেন। সেখানে তিনি HTTPS এবং Docker (Container support) আনচেক করতে বলেছেন, কারণ এগুলো অ্যাডভান্সড টপিক।

  • নোট: যেহেতু তুমি Fedora Linux-এ কাজ করো, তোমার ভিজ্যুয়াল স্টুডিওর গ্রাফিক্যাল চেকবক্সের দরকার নেই। টার্মিনালে জাস্ট dotnet new web -n middleware-example কমান্ড দিলেই একটা এম্পটি প্রোজেক্ট তৈরি হয়ে যাবে, যেখানে বাই ডিফল্ট কোনো ডকার কনফিগারেশন থাকবে না।

২. Program.cs এবং ApplicationBuilder

Priority: High প্রোজেক্ট তৈরি হলে Program.cs ফাইলে builder.Build() কল করার পর আমরা app নামের একটা অবজেক্ট পাই। এই app (যা মূলত WebApplication বা IApplicationBuilder এর অবজেক্ট) ব্যবহার করেই আমরা পাইপলাইনে মিডলওয়্যারগুলো সাজাই। তুমি যে সিকোয়েন্সে এগুলো লিখবে, ঠিক সেই সিকোয়েন্সেই রিকোয়েস্ট প্রসেস হবে।

৩. app.Run() দিয়ে মিডলওয়্যার তৈরি

Priority: High মিডলওয়্যার তৈরি করার একটা উপায় হলো app.Run() মেথড। এর ভেতরে আমরা একটা Lambda Expression বা Anonymous ফাংশন পাস করি।

  • কখন এক্সিকিউট হয়? অ্যাপ্লিকেশন যখন স্টার্ট হয়, তখন কিন্তু এই ভেতরের কোড রান করে না। এটা মেমরিতে বসে থাকে। যখনই ব্রাউজার থেকে কোনো রিকোয়েস্ট আসে, ঠিক তখনই এই ল্যাম্বডা এক্সপ্রেশনটা ফায়ার হয়।

৪. HttpContext কী?

Priority: Very High ল্যাম্বডা এক্সপ্রেশনটি একটা আর্গুমেন্ট রিসিভ করে, যার নাম দেওয়া হয় context। এর ডেটা টাইপ হলো HttpContext

  • কাজ: এটা এমন একটা অবজেক্ট, যার ভেতরে একটা রিকোয়েস্টের নারী-নক্ষত্র সব থাকে। ইউজারের পাঠানো Request, সার্ভার থেকে যাওয়ার জন্য রেডি হওয়া Response, Session ডেটা—সবকিছু এই একটা অবজেক্ট দিয়ে ম্যানেজ করা হয়।

৫. WriteAsync এবং async/await এর ব্যবহার

Priority: High আমরা যখন ব্রাউজারে রেসপন্স পাঠাই, তখন context.Response.WriteAsync("Hello") ব্যবহার করি। যেহেতু মেথডটার নামে “Async” আছে, তাই এর আগে অবশ্যই await বসাতে হবে। আর ভেতরে await বসানোর কারণে পুরো ল্যাম্বডা এক্সপ্রেশনটাকে async ডিক্লেয়ার করতে হবে।

  • কেন await জরুরি? ট্রান্সক্রিপ্টে ইনস্ট্রাক্টর খুব সুন্দর একটা কথা বলেছেন। যখন আমরা await করি, তখন এক্সিকিউশন কন্ট্রোল এই লাইনটা শেষ হওয়া পর্যন্ত অপেক্ষা করে। কিন্তু এর মানে এই না যে সার্ভার জ্যাম হয়ে বসে থাকে। এই অপেক্ষার সময়টাতে সার্ভার প্যারালালি অন্য ইউজারদের রিকোয়েস্ট হ্যান্ডেল করতে পারে। এতে সার্ভারের রিসোর্স খুব ইফিশিয়েন্টলি ব্যবহার হয়।

৬. একাধিক app.Run() এর সমস্যা (Short-circuiting)

Priority: Very High ইনস্ট্রাক্টর দেখিয়েছেন, যদি তুমি পরপর দুইটা app.Run() লেখো, তাহলে কোড সাকসেসফুলি বিল্ড হবে (কোনো কম্পাইল টাইম এরর খাবে না)। কিন্তু রান করার পর দেখবে, শুধু প্রথমটার রেসপন্স (“Hello”) ব্রাউজারে আসছে, দ্বিতীয়টার রেসপন্স (“Hello again”) আসছে না। তুমি ব্রেকপয়েন্ট বসিয়ে ডিবাগ করলেও দেখবে কোড দ্বিতীয় app.Run() এর ভেতরে ঢুকছেই না।

  • কারণ: app.Run() হলো একটা Terminating বা Short-circuiting মিডলওয়্যার। এর মানে হলো, ও রিকোয়েস্টটাকে প্রসেস করে নিজেই রেসপন্স দিয়ে দেয়, আর সামনের কোনো মিডলওয়্যারের কাছে রিকোয়েস্টটাকে ফরোয়ার্ড করে না।

C# Code Implementation

ট্রান্সক্রিপ্টে যে সিনারিওটা বোঝানো হয়েছে, তার ফুল রানেবল কোড নিচে দেওয়া হলো:

var builder = WebApplication.CreateBuilder(args);
 
// builder.Build() কল করার পর app অবজেক্ট তৈরি হয়
var app = builder.Build();
 
// ----- Middleware 1: Terminating Middleware -----
// ল্যাম্বডা এক্সপ্রেশনে 'context' হলো HttpContext টাইপের অবজেক্ট
app.Run(async (context) =>
{
    // WriteAsync ব্যবহার করায় await দিতে হয়েছে এবং মেথডটিকে async করতে হয়েছে
    await context.Response.WriteAsync("Hello from Middleware 1!");
    
    // এই মিডলওয়্যারটা এখানেই শেষ। সে পরবর্তী কারো কাছে রিকোয়েস্ট ফরোয়ার্ড করবে না।
});
 
// ----- Middleware 2: This will NEVER execute -----
// কোড বিল্ড হবে ঠিকই, কিন্তু প্রথম app.Run() রিকোয়েস্ট আটকে দেওয়ায় এখানে কখনোই কোড পৌঁছাবে না।
app.Run(async (context) =>
{
    await context.Response.WriteAsync("Hello again from Middleware 2!");
});
 
app.Run();
 

Added Context (Gaps Filled)

ট্রান্সক্রিপ্টে বলা হয়েছে app.Run() পরবর্তী কাউকে রিকোয়েস্ট ফরোয়ার্ড করে না, আর এটা সলভ করার উপায় পরবর্তী লেকচারে দেখানো হবে। কিন্তু আমি তোমাকে গ্যাপটা ফিল করে দিচ্ছি:

  • তাহলে app.Run() এর কাজ কী? যদি এটা ফরোয়ার্ডই না করে, তাহলে এটা কেন বানিয়েছে? আসলে app.Run() ব্যবহার করা হয় পাইপলাইনের একদম শেষ ধাপে (Terminal end)। যখন তুমি শিওর যে এই রিকোয়েস্ট আর কারো কাছে যাওয়ার দরকার নেই (যেমন: একটা ডিফল্ট 404 Not Found পেজ দেখানো, অথবা Health Check Endpoint বানানো), তখন app.Run() ব্যবহার করা পারফেক্ট।
  • নেক্সট লেকচারে কী আসবে? যেহেতু app.Run() ফরোয়ার্ড করে না, তাই চেইন বা সিকোয়েন্স বানানোর জন্য app.Use() ব্যবহার করা হয়। app.Use() এর ভেতরে একটা এক্সট্রা প্যারামিটার থাকে যার নাম next। ওই next() কে কল করলে রিকোয়েস্ট সুন্দর করে পরের মিডলওয়্যারে চলে যায়।

C# Best Practices

১. Don’t chain app.Run(): তোমার প্রোজেক্টে (যেমন Chatrabash-এর ব্যাকএন্ডে) কখনোই পরপর একাধিক app.Run() লিখবে না। এটা লজিক্যাল বাগ তৈরি করে, যা কম্পাইলার ধরতে পারে না। ২. Always prefer Asynchronous methods: মিডলওয়্যারের ভেতরে কখনোই সিঙ্ক্রোনাস Thread.Sleep() বা ব্লকিং আই/ও (I/O) কাজ করবে না। সবসময় async/await এবং ...Async() মেথড ব্যবহার করবে, নাহলে সার্ভারের থ্রেড পুল জ্যাম হয়ে যাবে এবং হাই-ট্রাফিকে অ্যাপ্লিকেশন স্লো হয়ে যাবে। ৩. Keep Middleware Light: মিডলওয়্যারের ভেতরে হেভি ক্যালকুলেশন বা বড় ডাটাবেস কোয়েরি এড়িয়ে চলবে। কারণ প্রতিটি রিকোয়েস্ট এই পাইপলাইনের ভেতর দিয়েই যায়।

হাসিব, app.Run() এর এই টার্মিনেটিং কনসেপ্টটা ক্লিয়ার হয়েছে? নেক্সট লেকচারের ট্রান্সক্রিপ্ট পেলে দিও, সেখানে app.Use() নিয়ে বিস্তারিত ব্রেকডাউন করে দেবো!