স্বাগতম! আপনার কোর্সের Section 20 (Filters)-এর সর্বশেষ লেকচার— “Configure Services Extension” (লেকচার ৩০৭)-এ আমরা চলে এসেছি।

রিয়েল-ওয়ার্ল্ড প্রজেক্টে কাজ করার সময় Program.cs ফাইলটি আস্তে আস্তে অনেক বড় হয়ে যায় (কখনও কখনও হাজার লাইনেরও বেশি!)। এটি দেখতে যেমন খারাপ লাগে, তেমনি মেইনটেইন করাও কষ্টকর। আজ আমরা শিখব কীভাবে Program.cs ফাইলের সমস্ত সার্ভিস রেজিস্ট্রেশনের কোডকে একটি আলাদা ফাইলে সরিয়ে নিয়ে এক্সটেনশন মেথড (Extension Method) ব্যবহার করে কোড ক্লিন করা যায়। চলুন শুরু করি!


📝 Quick Revision Summary

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য আজকের লেকচারের মূল কাজগুলো নিচে দেওয়া হলো:

  • The Goal: Program.cs ফাইলটিকে ক্লিন করা এবং সার্ভিস কনফিগারেশনের কোডগুলো আলাদা ফাইলে নিয়ে যাওয়া।
  • How to do it (Extension Method): একটি স্ট্যাটিক ক্লাস এবং স্ট্যাটিক মেথড তৈরি করতে হয়, যার প্রথম প্যারামিটারে this IServiceCollection services থাকে।
  • Configuration Passing: builder.Configuration অবজেক্টটি Program.cs থেকে এই মেথডে আর্গুমেন্ট হিসেবে পাস করতে হয়।
  • Unit Test Fixes: 1. Controller-এ নতুন ডিপেন্ডেন্সি (ILogger) যুক্ত করায় ইউনিট টেস্টে Mock অবজেক্ট পাস করে এরর ফিক্স করা হয়েছে।
  1. Result Filter-এর OnResultExecuted-এ হেডার সেট করার কারণে Integration Test-এ আসা Headers already sent এরর ফিক্স করতে কোডটি OnResultExecuting-এ সরানো হয়েছে।
  2. Action Filter-এর কারণে মডেল ভ্যালিডেশনের টেস্ট কেস ইনভ্যালিড হয়ে যাওয়ায় তা মুছে ফেলা হয়েছে।

🔍 Comprehensive Breakdown

১. Refactoring Program.cs using Extension Method [Priority: 10/10]

The “Why”: যখন একটি প্রজেক্ট বড় হতে থাকে, তখন builder.Services.Add... লাইনের পর লাইন লেখা হতে থাকে। এই সার্ভিসগুলোকে আলাদা ফাইলে নিয়ে গেলে Program.cs অনেক পরিষ্কার থাকে।

Step 1: Create an Extension File StartupExtensions নামে একটি ফোল্ডার তৈরি করে তার ভেতর ConfigureServicesExtension.cs নামে একটি ফাইল নেওয়া হলো। (মনে রাখবেন, Extension Method বানাতে হলে ক্লাসটিকে অবশ্যই static হতে হবে)।

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
 
namespace CRUDExample
{
    public static class ConfigureServicesExtension
    {
        // Extension method-এর প্রথম প্যারামিটারে 'this' কিওয়ার্ড থাকতে হবে
        public static IServiceCollection ConfigureServices(this IServiceCollection services, IConfiguration configuration)
        {
            // Program.cs থেকে সমস্ত 'builder.Services...' কোড এখানে কাট-পেস্ট করা হয়েছে।
            // 'builder.Services'-কে 'services' দিয়ে রিপ্লেস করা হয়েছে।
            // 'builder.Configuration'-কে 'configuration' দিয়ে রিপ্লেস করা হয়েছে।
            
            services.AddControllersWithViews();
            
            // ... অন্যান্য সব সার্ভিস ...
            
            services.AddTransient<PersonsListActionFilter>();
            
            // সবশেষে IServiceCollection রিটার্ন করা হচ্ছে, যাতে চেইন কলিং (chain calling) করা যায়
            return services;
        }
    }
}
 

**Step 2: Calling it in Program.cs** এখন Program.cs ফাইলটি ম্যাজিকের মতো ছোট হয়ে গেল!

var builder = WebApplication.CreateBuilder(args);
 
// শুধুমাত্র একটি লাইন কল করেই শত শত সার্ভিস অ্যাড করে দেওয়া হলো!
builder.Services.ConfigureServices(builder.Configuration);
 
var app = builder.Build();
// ... Middleware configurations ...
app.Run();
 

২. Fixing Unit Tests & Integration Tests [Priority: 10/10]

প্রজেক্ট রিফ্যাক্টর করার পর লেকচারার যখন প্রজেক্ট বিল্ড (Build) করেন, তখন কিছু এরর ধরা পড়ে। রিয়েল-ওয়ার্ল্ডে এমনটা হরহামেশাই হয়। চলুন দেখি তিনি এগুলো কীভাবে ফিক্স করেছেন।

Fix 1: Mocking ILogger in Unit Tests আগের লেকচারগুলোতে আমরা PersonsController-এর কনস্ট্রাক্টরে ILogger<PersonsController> ইনজেক্ট করেছিলাম। কিন্তু আমাদের ইউনিট টেস্ট ফাইলে (যেখানে আমরা ফেক কন্ট্রোলার বানিয়ে টেস্ট করি), আমরা logger পাস করিনি।

  • সমাধান: PersonsControllerTest.cs-এ Mock<ILogger<PersonsController>> তৈরি করে সেটি কন্ট্রোলারের কনস্ট্রাক্টরে পাস করে দেওয়া হয়েছে।

Fix 2: Response Headers Exception in Integration Test ইন্টিগ্রেশন টেস্ট রান করার সময় একটি এরর আসে: “The response headers cannot be modified because the response has already started.”

  • কারণ: PersonsListResultFilter-এর OnResultExecuted (অর্থাৎ After Logic)-এ আমরা একটি কাস্টম হেডার অ্যাড করছিলাম। কিন্তু ততক্ষণে সার্ভার ব্রাউজারকে রেসপন্স পাঠানো শুরু করে দিয়েছে। রেসপন্স পাঠানো শুরু হলে নতুন করে হেডার যুক্ত করা যায় না।
  • সমাধান: ওই হেডার অ্যাড করার কোডটি কাট করে OnResultExecuting (Before Logic)-এ বসানো হয়েছে, কারণ তখনো রেসপন্স পাঠানো শুরু হয় না।

Fix 3: Invalid Model State Test Case আগে Controller-এর ভেতরেই if (!ModelState.IsValid) চেক করা হতো, তাই এর জন্য একটি টেস্ট কেসও লেখা ছিল।

  • কারণ: এখন আমরা এই ভ্যালিডেশন চেকিংটা PersonCreateAndEditPostActionFilter-এ (Filter) সরিয়ে নিয়েছি। ফলে Controller-এর ওই টেস্ট কেসটি এখন আর প্রাসঙ্গিক (relevant) নেই।
  • সমাধান: লেকচারার ওই নির্দিষ্ট টেস্ট কেসটি ডিলিট করে দিয়েছেন। (রিয়েল-ওয়ার্ল্ডে চাইলে Filter-এর জন্য আলাদা ইউনিট টেস্ট লেখা যায়, তবে লেকচারার স্কিপ করেছেন)।

🚀 Best Practices & The Modern Approach

Best Practices:

  1. Segregation of Duties: Program.cs-কে সবসময় পরিষ্কার রাখা উচিত। সার্ভিসগুলোকে লজিক্যাল ক্যাটাগরিতে ভাগ করে আলাদা এক্সটেনশন মেথড (যেমন AddDatabaseServices(), AddAuthServices()) বানানো ইন্ডাস্ট্রি স্ট্যান্ডার্ড।
  2. Do Not Modify Headers Late: এই লেকচারের বাগটি থেকে একটি গুরুত্বপূর্ণ শিক্ষা হলো— কখনোই রেজাল্ট জেনারেট হওয়া শুরু করার পর Response Header মডিফাই করার চেষ্টা করবেন না। এটি রানটাইম এক্সেপশন থ্রো করবে।

.NET 10 & Minimal APIs Context: .NET 10-এ যদি আপনি কাজ করেন, এক্সটেনশন মেথড লেখার কনসেপ্ট একদম একই। Minimal API-তে Program.cs আরও ছোট রাখতে আমরা অনেক সময় Endpoint-গুলোকেও আলাদা এক্সটেনশন ফাইলে সরিয়ে নিই:

// Example of extending Map endpoints
public static class PersonEndpointsExtension
{
    public static void MapPersonEndpoints(this WebApplication app)
    {
        app.MapGet("/api/persons", () => "List of persons");
        // ...
    }
}
 

আপনার কোর্স Outline অনুযায়ী আমরা সফলভাবে Section 20 (Filters) শেষ করলাম! এটি ছিল একটি দীর্ঘ এবং অত্যন্ত গুরুত্বপূর্ণ সেকশন। পরবর্তী সেকশন Section 21 (Error Handling)-এর জন্য আপনি প্রস্তুত হলে জানাবেন।