تزریق وابستگی پیشرفته با Autofac: فراتر از اصول اولیه

تزریق وابستگی (Dependency Injection - DI) یکی از قدرتمندترین الگوهای طراحی در مهندسی نرم‌افزار مدرن است که به ما امکان می‌دهد کدهایی با اتصال سست (Loosely Coupled)، تست‌پذیر و قابل نگهداری بنویسیم. در اکوسیستم دات‌نت، کانتینر توکار ASP.NET Core بسیاری از نیازهای اولیه را برآورده می‌کند، اما برای پروژه‌های بزرگ و پیچیده، نیاز به ابزاری قدرتمندتر و انعطاف‌پذیرتر احساس می‌شود. Autofac یکی از محبوب‌ترین و کامل‌ترین کانتینرهای وارونگی کنترل (Inversion of Control - IoC) است که قابلیت‌های پیشرفته‌ای را برای مدیریت وابستگی‌ها در اختیار توسعه‌دهندگان قرار می‌دهد.
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

تزریق وابستگی پیشرفته با Autofac: فراتر از اصول اولیه

73 بازدید 0 نظر ۱۴۰۴/۰۷/۰۲

چرا Autofac؟ گذری کوتاه بر مزایا

قبل از ورود به مباحث پیشرفته، بهتر است به یاد بیاوریم چرا Autofac یک انتخاب عالی است. کانتینر DI پیش‌فرض در ASP.NET Core برای بسیاری از سناریوها کافی است، اما Autofac در زمینه‌های زیر برتری دارد:

  • انعطاف‌پذیری در ثبت کامپوننت‌ها: Autofac روش‌های متنوعی برای ثبت سرویس‌ها ارائه می‌دهد، از جمله اسکن اسمبلی‌ها، ثبت بر اساس نام و کلید، و پشتیبانی از ژنریک‌های باز (Open Generics).

  • مدیریت چرخه حیات (Lifetime Management): کنترل دقیق‌تری بر روی چرخه حیات و نحوه آزادسازی (Disposal) نمونه‌ها فراهم می‌کند.

  • پشتیبانی از قابلیت‌های پیشرفته: ویژگی‌هایی مانند ماژول‌ها برای سازمان‌دهی ثبت‌ها، رهگیرها (Interceptors) برای پیاده‌سازی برنامه‌نویسی جنبه‌گرا (AOP) و پشتیبانی از وابستگی‌های دایره‌ای (Circular Dependencies) آن را متمایز می‌کند.

  • ادغام یکپارچه: به راحتی با فریمورک‌های مختلف دات‌نت، از جمله ASP.NET Core، ادغام می‌شود.

حال که مزیت‌های اصلی را مرور کردیم، بیایید به سراغ ویژگی‌های پیشرفته برویم.

 

۱. سازمان‌دهی با ماژول‌ها (Autofac Modules)

در پروژه‌های بزرگ، فایل Startup.cs یا Program.cs می‌تواند به سرعت با ده‌ها و یا حتی صدها خط کد برای ثبت سرویس‌ها شلوغ و غیرقابل مدیریت شود. اینجاست که ماژول‌های Autofac به کمک ما می‌آیند. ماژول‌ها کلاس‌هایی هستند که به ما اجازه می‌دهند ثبت‌های مرتبط با یکدیگر را در یک گروه منطقی دسته‌بندی کنیم.

 

مشکل چیست؟

تصور کنید در یک پروژه بزرگ، سرویس‌های مربوط به دسترسی به داده (Data Access)، سرویس‌های کسب‌وکار (Business Services) و سرویس‌های زیرساختی (Infrastructure) همگی در یک متد طولانی ثبت شده‌اند. این رویکرد نگهداری و توسعه کد را دشوار می‌کند.

 

 

راه‌حل: استفاده از Autofac.Module

برای حل این مشکل، می‌توانیم برای هر بخش منطقی از برنامه یک ماژول جداگانه ایجاد کنیم. هر ماژول از کلاس Autofac.Module ارث‌بری کرده و متد Load را بازنویسی (Override) می‌کند.

برای مثال، یک ماژول برای لایه دسترسی به داده می‌تواند به شکل زیر باشد:

public class DataAccessModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        // ثبت تمام ریپازیتوری‌ها
        builder.RegisterType().As();
        builder.RegisterType().As();
        
        // ثبت DbContext
        builder.RegisterType().As().InstancePerLifetimeScope();

        base.Load(builder);
    }
}

و یک ماژول دیگر برای سرویس‌های کسب‌وکار:

public class ApplicationServicesModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType().As();
        builder.RegisterType().As();
        
        base.Load(builder);
    }
}

سپس در نقطه شروع برنامه (معمولاً Program.cs)، به جای ثبت تک‌تک سرویس‌ها، تنها ماژول‌ها را ثبت می‌کنیم:

var builder = new HostBuilder()
    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureContainer(containerBuilder =>
    {
        containerBuilder.RegisterModule(new DataAccessModule());
        containerBuilder.RegisterModule(new ApplicationServicesModule());
    });

مزایای استفاده از ماژول‌ها:

  • خوانایی و سازمان‌دهی: کد ثبت وابستگی‌ها تمیزتر و بر اساس مسئولیت‌ها تقسیم‌بندی می‌شود.

  • قابلیت استفاده مجدد: ماژول‌ها را می‌توان به راحتی در پروژه‌های دیگر نیز استفاده کرد.

  • پیکربندی آسان: می‌توان ماژول‌ها را بر اساس شرایط مختلف (مانند محیط توسعه یا پروداکشن) فعال یا غیرفعال کرد.

 

۲. مدیریت پیشرفته چرخه حیات (Advanced Lifetime Scopes)

Autofac کنترل دقیقی بر روی چرخه حیات (Lifetime) کامپوننت‌ها ارائه می‌دهد. در حالی که مفاهیم Singleton، Scoped (که در Autofac معادل InstancePerLifetimeScope است) و Transient (معادل InstancePerDependency) مشترک هستند، Autofac امکانات بیشتری را فراهم می‌کند.

یکی از قدرتمندترین مفاهیم، Lifetime Scope است. یک Lifetime Scope یک واحد کاری (Unit of Work) را تعریف می‌کند. تمام سرویس‌هایی که در یک اسکوپ Resolve می‌شوند، تا زمانی که آن اسکوپ از بین نرفته، زنده می‌مانند و در پایان عمر اسکوپ، متد Dispose آن‌ها (اگر IDisposable باشند) فراخوانی می‌شود.

برای مثال، در یک برنامه وب، هر درخواست HTTP یک Lifetime Scope جدید ایجاد می‌کند (InstancePerRequest). در یک برنامه دسکتاپ یا سرویس پس‌زمینه، شما می‌توانید به صورت دستی اسکوپ‌های جدیدی برای هر عملیات ایجاد کنید.

// container اصلی برنامه
var container = builder.Build();

// شروع یک واحد کاری جدید
using (var scope = container.BeginLifetimeScope())
{
    // تمام سرویس‌های مورد نیاز برای این عملیات از این اسکوپ گرفته می‌شوند
    var worker = scope.Resolve();
    worker.DoWork();
} // در این نقطه، متد Dispose تمام سرویس‌های IDisposable که در این اسکوپ ایجاد شده‌اند، فراخوانی می‌شود.

این قابلیت به ویژه برای مدیریت منابعی مانند کانکشن‌های دیتابیس یا فایل‌ها بسیار مفید است و از نشت حافظه (Memory Leak) جلوگیری می‌کند.

 

۳. ثبت خودکار با اسکن اسمبلی (Assembly Scanning)

در پروژه‌های بزرگ، ثبت دستی ده‌ها یا صدها سرویس کاری خسته‌کننده و مستعد خطا است. Autofac با قابلیت اسکن اسمبلی این فرآیند را خودکار می‌کند. شما می‌توانید به Autofac بگویید که یک یا چند اسمبلی را اسکن کرده و تایپ‌ها را بر اساس قواعد مشخصی ثبت کند.

برای مثال، فرض کنید قرارداد کرده‌ایم که نام تمام کلاس‌های ریپازیتوری به Repository ختم شود و اینترفیس مربوط به خود را پیاده‌سازی کنند. می‌توانیم با یک خط کد تمام آن‌ها را ثبت کنیم.

var dataAccessAssembly = Assembly.GetExecutingAssembly(); // یا اسمبلی مورد نظر

builder.RegisterAssemblyTypes(dataAccessAssembly)
       .Where(t => t.Name.EndsWith("Repository")) // فیلتر کردن تایپ‌ها
       .AsImplementedInterfaces() // ثبت تایپ با اینترفیس‌های پیاده‌سازی شده
       .InstancePerLifetimeScope(); // تعیین چرخه حیات

این کد تمام تایپ‌های موجود در اسمبلی مشخص شده را که نامشان به Repository ختم می‌شود، پیدا کرده و آن‌ها را با اینترفیس‌هایشان در کانتینر ثبت می‌کند. این تکنیک به شدت حجم کدهای تکراری (Boilerplate) را کاهش می‌دهد و تضمین می‌کند که سرویس‌های جدید به محض اضافه شدن به پروژه، به طور خودکار ثبت شوند.

 

۴. کار با ژنریک‌ها (Generic Registrations)

بسیاری از الگوهای طراحی مدرن، مانند الگوی Repository، از ژنریک‌ها به طور گسترده استفاده می‌کنند. Autofac پشتیبانی فوق‌العاده‌ای از ثبت و بازیابی ژنریک‌های باز (Open Generics) دارد.

فرض کنید یک اینترفیس و کلاس ریپازیتوری ژنریک به شکل زیر داریم:

public interface IRepository where T : class
{
    T GetById(int id);
    void Add(T entity);
}

public class GenericRepository : IRepository where T : class
{
    // Implementation...
}

به جای ثبت یک GenericRepository برای هر موجودیت (Entity) به صورت دستی، می‌توانیم تایپ ژنریک باز را یک بار ثبت کنیم:

builder.RegisterGeneric(typeof(GenericRepository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();

اکنون هر زمان که به یک IRepository یا IRepository نیاز داشته باشیم، Autofac به طور خودکار تایپ بسته (Closed Generic) صحیح یعنی GenericRepository یا GenericRepository را ایجاد و تزریق می‌کند.

// درخواست IRepository
var customerRepo = scope.Resolve>(); 
// Autofac به صورت خودکار یک نمونه از GenericRepository را برمی‌گرداند.

این قابلیت نه تنها کد را کوتاه‌تر می‌کند، بلکه انعطاف‌پذیری سیستم را به شدت افزایش می‌دهد.

 

۵. برنامه‌نویسی جنبه‌گرا با رهگیرها (Interceptors)

گاهی اوقات نیاز داریم یک منطق مشترک را بر روی چندین متد یا کلاس اعمال کنیم، بدون اینکه کد آن منطق را در خود کلاس‌ها تکرار کنیم. این دغدغه‌های مشترک (Cross-Cutting Concerns) مانند لاگ‌برداری (Logging)، کش کردن (Caching)، مدیریت تراکنش‌ها (Transaction Management) یا اعتبارسنجی (Validation) بهترین کاندیداها برای استفاده از برنامه‌نویسی جنبه‌گرا (Aspect-Oriented Programming - AOP) هستند.

Autofac از طریق یک پکیج الحاقی به نام Autofac.Extras.DynamicProxy امکان استفاده از رهگیرها (Interceptors) را فراهم می‌کند. رهگیرها به شما اجازه می‌دهند که اجرای یک متد را "رهگیری" کرده و کدی را قبل، بعد یا به جای اجرای آن اجرا کنید.

برای مثال، بیایید یک رهگیر ساده برای لاگ کردن زمان اجرای متدها بنویسیم. ابتدا به یک کلاس رهگیر نیاز داریم که اینترفیس IInterceptor از کتابخانه Castle.DynamicProxy را پیاده‌سازی کند:

using Castle.DynamicProxy;
using System.Diagnostics;

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var stopwatch = Stopwatch.StartNew();
        Console.WriteLine($"Calling method: {invocation.Method.Name}...");

        // اجرای متد اصلی
        invocation.Proceed();

        stopwatch.Stop();
        Console.WriteLine($"Method {invocation.Method.Name} executed in {stopwatch.ElapsedMilliseconds} ms.");
    }
}

سپس، رهگیر را در کانتینر Autofac ثبت می‌کنیم و مشخص می‌کنیم که کدام سرویس‌ها باید رهگیری شوند:

// ثبت رهگیر
builder.Register(c => new LoggingInterceptor());

// ثبت سرویسی که می‌خواهیم رهگیری شود
builder.RegisterType()
       .As()
       .EnableInterfaceInterceptors() // فعال‌سازی رهگیری برای اینترفیس
       .InterceptedBy(typeof(LoggingInterceptor)); // مشخص کردن رهگیر

اکنون هر زمان که متدی از IMyHeavyService فراخوانی شود، LoggingInterceptor به طور خودکار اجرا شده و اطلاعات مربوط به زمان اجرا را لاگ می‌کند، بدون اینکه حتی یک خط کد مربوط به لاگ‌برداری در کلاس MyHeavyService نوشته باشیم.

 

نتیجه‌گیری

Autofac بسیار فراتر از یک کانتینر ساده برای تزریق وابستگی است. این کتابخانه مجموعه‌ای غنی از ابزارها و ویژگی‌های پیشرفته را ارائه می‌دهد که به توسعه‌دهندگان کمک می‌کند تا معماری‌های پیچیده را به شیوه‌ای تمیز، ماژولار و قابل نگهداری مدیریت کنند. با تسلط بر مفاهیمی مانند ماژول‌ها برای سازمان‌دهی، اسکن اسمبلی برای خودکارسازی، مدیریت چرخه حیات برای کنترل منابع، ثبت ژنریک‌ها برای انعطاف‌پذیری و رهگیرها برای پیاده‌سازی دغدغه‌های مشترک، می‌توانید کیفیت و ساختار پروژه‌های دات‌نت خود را به سطح بالاتری ارتقا دهید و کدی بنویسید که نه تنها امروز کار می‌کند، بلکه در آینده نیز به راحتی قابل توسعه و نگهداری خواهد بود.

 
 
لینک استاندارد شده: gGvs8T7C5H

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه