حقن التبعيات في .NET MAUI 10: الدليل العملي الشامل لعام 2026

دليل عملي شامل لحقن التبعيات (DI) في .NET MAUI 10 مع IServiceCollection: تسجيل الخدمات، دورات الحياة، الخدمات المفتاحية، أنماط المصانع، وأمثلة كود جاهزة للإنتاج مع اختبارات الوحدات.

حقن التبعيات في .NET MAUI: دليل 2026

آخر تحديث: 10 يوليو 2026

حقن التبعيات (Dependency Injection) في .NET MAUI هو نمط تصميم مدمج يسمح لك بتسجيل الخدمات وViewModels والصفحات مركزياً في MauiAppBuilder.Services، ثم تسليمها تلقائياً عبر مُنشئات (constructors) الفئات التي تحتاجها. الحاوية المدمجة (Microsoft.Extensions.DependencyInjection) هي نفسها المستخدمة في ASP.NET Core، مما يعني أن نفس المبادئ التي تعرفها من الخوادم تنطبق على تطبيقات الموبايل، مع فارق واحد جوهري سنشرحه بعد قليل: دورة الحياة Scoped لا تعمل كما تتوقع في MAUI.

  • سجل جميع الخدمات وViewModels في MauiProgram.CreateMauiApp() عبر builder.Services لأن الحاوية تُبنى مرة واحدة عند إنشاء التطبيق.
  • استخدم AddSingleton للخدمات المشتركة (قاعدة البيانات، الإعدادات، HttpClient)، وAddTransient للصفحات وViewModels لضمان حالة نظيفة عند كل تنقل.
  • تجنب AddScoped في تطبيقات MAUI غير-Blazor: لا يوجد حدود طبيعية للنطاق فتتحول عملياً إلى Singleton مع تسريبات محتملة.
  • الحقن عبر المُنشئ (Constructor Injection) هو النمط المفضل؛ استعمل IServiceProvider مباشرة فقط كملاذ أخير في المحولات (Converters) أو السلوكيات (Behaviors).
  • الخدمات المفتاحية (Keyed Services) التي أضيفت في .NET 8 حلت مشكلة تعدد التطبيقات لنفس الواجهة، ولا حاجة لحاويات خارجية مثل Autofac في 95% من المشاريع.
  • فعّل ValidateScopes وValidateOnBuild في وضع التطوير لتصطاد الأخطاء البنيوية قبل أن تصل للإنتاج.

ما هو حقن التبعيات في .NET MAUI؟

حقن التبعيات في .NET MAUI هو آلية توفير الكائنات التي تعتمد عليها فئة ما بشكل خارجي بدلاً من إنشائها داخلياً. المكوّن الأساسي هو خاصية Services من نوع IServiceCollection على كائن MauiAppBuilder. جميع الأنواع المُسجّلة عبر هذه الخاصية تُقدَّم لحاوية DI عند استدعاء MauiAppBuilder.Build() في نهاية CreateMauiApp. هذه هي نفس الحاوية القياسية Microsoft.Extensions.DependencyInjection المستخدمة في ASP.NET Core و.NET Generic Host، وهي جاهزة داخل قالب MAUI الافتراضي بدون أي حزمة NuGet إضافية.

بصراحة، في فرقنا الهندسية نعتبر DI شرطاً غير قابل للتفاوض في أي تطبيق MAUI يتجاوز شاشتين. لماذا؟ لأنه بدونه ينتهي بك المطاف بكود مُقيَّد ببعضه: ViewModel يُنشئ خدمة REST بنفسه، الخدمة تُنشئ عميل HTTP خاصاً بها، والاختبار يصبح مستحيلاً. المطلوب هو الفصل. كل فئة تُصرّح بما تحتاجه عبر مُنشئها، والحاوية تتكفل بالباقي. هذا يُطبِّق مبدأ الانعكاس (Inversion of Control) عملياً، ويجعل كل خدمة قابلة للاستبدال بمُحاكاة (Mock) عند الاختبار.

الفارق الرئيسي عن Xamarin.Forms هو أن DependencyService القديم (الذي كان يعتمد على السمات والبحث بالانعكاس) قد اختفى بالكامل. من انتقل تطبيقه من Xamarin ورأى في دليل الترقية إلى MAUI 10 سيلاحظ أن الترحيل يتطلب إعادة تسجيل كل ما كان مُعلَّناً بـ [assembly: Dependency(...)] عبر واجهات صريحة داخل MauiProgram. عملياً، هذه أفضل فرصة لتنظيف طبقة الخدمات إن كانت متشعبة.

تسجيل الخدمات في MauiProgram.cs

يجب أن يتم تسجيل الأنواع التي تتطلب حقن التبعيات في مكان واحد يُستدعى مبكراً في دورة حياة التطبيق. عملياً هذا يعني الميثود CreateMauiApp داخل فئة MauiProgram. جربت سابقاً تقسيم التسجيل عبر ملفات جزئية أو ميثودات امتداد (Extension Methods)، وأوصي بذلك بمجرد أن يتجاوز عدد التسجيلات 20 خدمة، لأنه يحافظ على قراءة الملف الرئيسي، ويسمح بتنظيم منطقي للطبقات.

using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;

namespace MyApp;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        // البنية التحتية والوصول للبيانات
        builder.Services.RegisterInfrastructure();

        // منطق النطاق (Domain Services)
        builder.Services.RegisterDomainServices();

        // العرض: ViewModels والصفحات
        builder.Services.RegisterPresentation();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

public static class ServiceRegistrationExtensions
{
    public static IServiceCollection RegisterInfrastructure(this IServiceCollection services)
    {
        // HttpClient مُدار عبر IHttpClientFactory (يوصى به دائماً)
        services.AddHttpClient<IApiClient, ApiClient>(client =>
        {
            client.BaseAddress = new Uri("https://api.example.com/");
            client.Timeout = TimeSpan.FromSeconds(30);
        });

        services.AddSingleton<IDatabaseService, SqliteDatabaseService>();
        services.AddSingleton<IPreferencesService, PreferencesService>();

        return services;
    }

    public static IServiceCollection RegisterDomainServices(this IServiceCollection services)
    {
        services.AddSingleton<IAuthService, AuthService>();
        services.AddTransient<IOrderProcessor, OrderProcessor>();
        return services;
    }

    public static IServiceCollection RegisterPresentation(this IServiceCollection services)
    {
        // الصفحات وViewModels: Transient لضمان حالة نظيفة
        services.AddTransient<MainPage>();
        services.AddTransient<MainViewModel>();

        services.AddTransient<OrderDetailPage>();
        services.AddTransient<OrderDetailViewModel>();
        return services;
    }
}

لاحظ ثلاث نقاط عملية. أولاً، عملاء HTTP يُسجَّلون عبر AddHttpClient لأن IHttpClientFactory يحل مشكلة إعادة استخدام HttpMessageHandler، ويمنع استنزاف المقابس. تعمقنا في هذا في دليل استهلاك REST API في .NET MAUI. ثانياً، الصفحات نفسها مُسجَّلة كخدمات، وهذا يسمح لك بحلها عبر التنقل الصريح Shell.Current.GoToAsync مع تمرير معلمات نوعية. ثالثاً، اللاحقة this IServiceCollection تُنتج ميثودات امتداد قابلة للتكوين، مما يجعل كل طبقة قابلة للاختبار ومستقلة.

فهم دورات حياة الخدمات: Singleton مقابل Transient مقابل Scoped

اختيار دورة الحياة الخاطئة هو أكثر الأخطاء تكلفة الذي رأيته في مراجعات الكود لتطبيقات MAUI. الحاوية المدمجة تدعم ثلاث دورات: Singleton، Transient، وScoped. لكن في سياق MAUI (غير Blazor Hybrid) الأخيرة تتصرف بشكل غير بديهي كما سنرى.

الخاصية Singleton Transient Scoped
عدد النسخ نسخة واحدة لعمر التطبيق نسخة جديدة لكل طلب نسخة لكل نطاق (Scope)
سلامة الخيوط (Thread Safety) مطلوبة دائماً غير مطلوبة عادةً مطلوبة داخل النطاق
الاستخدام الأمثل في MAUI DB، HTTP، الإعدادات، الذاكرة المؤقتة ViewModels، الصفحات، المعالجات تجنبها في MAUI العادي
مخاطر الذاكرة تسريبات إذا احتفظت بمراجع ضخمة ضغط GC مع الإفراط يعمل كـSingleton فعلياً
مثال في MAUI SqliteConnection، ILogger MainViewModel، OrderPage غير موصى به

متى تستخدم Singleton؟

استخدم AddSingleton للخدمات التي تحمل حالة مشتركة أو تستهلك موارد باهظة الإنشاء. اتصالات قواعد البيانات (خاصة SqliteConnection المذكورة في دليل قواعد البيانات المحلية)، الإعدادات، وخدمات التسجيل هي مرشحات مثالية. لكن انتبه: الـSingleton يجب أن يكون آمناً على الخيوط (Thread-safe)، لأن نفس النسخة قد تُستخدم من عدة صفحات في نفس الوقت أثناء التنقل.

متى تستخدم Transient؟

استخدم AddTransient للصفحات وViewModels والمعالجات (Handlers) التي تحتاج حالة نظيفة عند كل تنقل. تخيّل السيناريو التالي: تفتح OrderDetailPage للطلب رقم 42 ثم ترجع وتفتحها للطلب 43. أنت لا تريد بيانات الطلب السابق أن تبقى. Transient يضمن أن كل تنقل يحصل على ViewModel جديد بحقول [ObservableProperty] فارغة. جربت سابقاً تسجيل ViewModel واحد كـSingleton لتوفير التحميل، وانتهى الأمر بأخطاء غامضة في الإنتاج عندما بقيت رسائل خطأ قديمة معروضة. لا تكرر هذا الخطأ.

لماذا يجب تجنب Scoped في MAUI؟

في ASP.NET Core، النطاق (Scope) يُنشأ ويُتلف مع كل طلب HTTP. في Blazor، النطاق يُنشأ لكل دائرة SignalR. أما في .NET MAUI، لا توجد حدود طبيعية للنطاق. عندما تُسجّل خدمة كـ AddScoped ثم تحلها عبر Application.Current.Handler.MauiContext.Services، فأنت تحلها من الجذر الافتراضي، أي أنها تتصرف عملياً كـSingleton طوال عمر التطبيق. الأسوأ: إذا حَقنتها في Transient ViewModel، ستحصل على نفس النسخة عبر ViewModels مختلفة، مما يسبب تلوث حالة عبر الصفحات (Cross-page state contamination).

كيف تحقن الخدمات في ViewModels والصفحات؟

النمط الموصى به هو حقن المُنشئ (Constructor Injection). ViewModel يُصرّح بتبعياته كمعاملات في المُنشئ، والحاوية تتكفل بتزويدها عندما تُطلب. هذا يجعل التبعيات صريحة، ويوثقها الكود نفسه، ويجبرك على التفكير بمسؤولية كل فئة. جنباً إلى جنب مع نمط MVVM الذي غطيناه في دليل MVVM مع CommunityToolkit، يصبح لديك بنية اختبار قوية.

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class OrderDetailViewModel : ObservableObject
{
    private readonly IOrderService _orderService;
    private readonly IAuthService _authService;
    private readonly ILogger<OrderDetailViewModel> _logger;

    [ObservableProperty]
    private Order? _order;

    [ObservableProperty]
    private bool _isBusy;

    public OrderDetailViewModel(
        IOrderService orderService,
        IAuthService authService,
        ILogger<OrderDetailViewModel> logger)
    {
        _orderService = orderService;
        _authService = authService;
        _logger = logger;
    }

    [RelayCommand]
    private async Task LoadAsync(int orderId)
    {
        try
        {
            IsBusy = true;
            var token = await _authService.GetTokenAsync();
            Order = await _orderService.GetOrderAsync(orderId, token);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "فشل تحميل الطلب {OrderId}", orderId);
        }
        finally
        {
            IsBusy = false;
        }
    }
}

في الصفحة نفسها، يمكنك حقن ViewModel بنفس الطريقة:

public partial class OrderDetailPage : ContentPage
{
    public OrderDetailPage(OrderDetailViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

عند التنقل عبر Shell، استخدم Shell.Current.GoToAsync("orderdetail")، وMAUI سيحل الصفحة تلقائياً من الحاوية مع كامل شجرة تبعياتها. إذا احتجت تمرير معلمات، سجل صفحاتك مع Routing.RegisterRoute، ومرر البيانات عبر QueryPropertyAttribute. الأمر بمعزل تام عن DI.

أنماط متقدمة: الخدمات المفتاحية وأنماط المصانع

ماذا لو كان لديك تطبيقان لنفس الواجهة؟ مثلاً IStorageService بتطبيقَين: LocalStorageService وCloudStorageService. قبل .NET 8 كنا نلجأ لأنماط ملتوية مثل التسجيل بواجهتين وسيطتين أو استخدام حاوية خارجية. اليوم، الخدمات المفتاحية (Keyed Services) تحل المشكلة أصلياً:

// التسجيل
builder.Services.AddKeyedSingleton<IStorageService, LocalStorageService>("local");
builder.Services.AddKeyedSingleton<IStorageService, CloudStorageService>("cloud");

// الاستهلاك
public class SyncOrchestrator
{
    private readonly IStorageService _local;
    private readonly IStorageService _cloud;

    public SyncOrchestrator(
        [FromKeyedServices("local")] IStorageService local,
        [FromKeyedServices("cloud")] IStorageService cloud)
    {
        _local = local;
        _cloud = cloud;
    }
}

نمط المصنع للتبعيات المشروطة

عندما يعتمد اختيار التطبيق على معلومة وقت-تشغيل (مثل تفضيل المستخدم أو حالة الاتصال)، الخدمات المفتاحية وحدها لا تكفي. استعمل مصنعاً مُسجَّلاً:

public interface IStorageServiceFactory
{
    IStorageService Get(StorageMode mode);
}

public class StorageServiceFactory : IStorageServiceFactory
{
    private readonly IServiceProvider _provider;

    public StorageServiceFactory(IServiceProvider provider)
    {
        _provider = provider;
    }

    public IStorageService Get(StorageMode mode) => mode switch
    {
        StorageMode.Local => _provider.GetRequiredKeyedService<IStorageService>("local"),
        StorageMode.Cloud => _provider.GetRequiredKeyedService<IStorageService>("cloud"),
        _ => throw new ArgumentOutOfRangeException(nameof(mode))
    };
}

// التسجيل
builder.Services.AddSingleton<IStorageServiceFactory, StorageServiceFactory>();

هذا النمط يبقي منطق الاختيار في مكان واحد، ويسمح لك بحل الخدمة الصحيحة ديناميكياً دون تلوث ViewModels بمنطق الاختيار.

حل الخدمات خارج المُنشئ

أحياناً تحتاج حل خدمة في محوّل قيمة (Value Converter)، سلوك (Behavior)، أو ميثود ثابت لا يمر عبر DI. اللغة الرسمية للتوثيق تسميه "الحل الصريح" (Explicit Resolution):

var services = IPlatformApplication.Current?.Services
    ?? throw new InvalidOperationException("حاوية DI غير متوفرة");
var logger = services.GetRequiredService<ILogger<MyConverter>>();

لكن أنبِّه: إذا وجدت نفسك تفعل هذا في أكثر من موضعين أو ثلاثة، فالمشكلة في التصميم لا في DI. حاول إعادة هيكلة الفئة لتقبل التبعية عبر خاصية أو معلمة ميثود.

الأخطاء الشائعة وكيفية تجنبها في الإنتاج

بعد عدة تطبيقات MAUI أوصلناها لملايين المستخدمين، جمعنا قائمة بأكثر الأخطاء تكراراً في مراجعات الكود المتعلقة بحقن التبعيات. تجنبها من البداية يوفر عليك ساعات تنقيح.

الخطأ 1: Captive Dependencies

هذا يحدث عندما يحمل Singleton مرجعاً لخدمة Transient. النتيجة: الـTransient يعيش عمر الـSingleton بأكمله بدلاً من أن يُتلف بعد الاستخدام. هذا الخطأ ضربني في مشروع سابق بشكل مضحك. كان ILogger مسجلاً كـTransient وحقن في DatabaseService Singleton، فبقيت مخازن السجلات مفتوحة إلى الأبد. القاعدة البسيطة: التبعيات يجب أن يكون عمرها ≥ عمر مالكها.

الخطأ 2: تسجيل ViewModel كـSingleton لتحسين "الأداء"

الإغراء واضح: تجنب إعادة تحميل البيانات عند العودة إلى شاشة سابقة. لكن الثمن أعلى بكثير: تلوث الحالة، أخطاء الاشتراكات المزدوجة، ورسائل تحقق مستمرة من زيارة سابقة. البديل الصحيح: احفظ البيانات في خدمة Singleton (مثل ICacheService) وأعد الـViewModel كـTransient يقرأ من الذاكرة المؤقتة.

الخطأ 3: إنشاء IServiceProvider جديد يدوياً

رأينا مطورين ينشئون new ServiceCollection().BuildServiceProvider() داخل الاختبارات أو داخل ميثود مساعد. هذا يخلق حاوية موازية معزولة عن الحاوية الأساسية، مما يعني أن Singleton المسجل عبر MAUI ليس نفس Singleton في الحاوية الجديدة. النتيجة: نسختان من SqliteConnection، وتصادمات قفل قاعدة البيانات في وضح النهار.

الخطأ 4: تجاهل التخلص (Disposal)

الحاوية تتخلص تلقائياً من الخدمات IDisposable عند إغلاق التطبيق، لكن Transient المُحلَّلة عبر IServiceProvider.GetService لن تُتلف إلا بإغلاق التطبيق. للمعالجة الصحيحة استخدم CreateScope:

using var scope = serviceProvider.CreateScope();
var worker = scope.ServiceProvider.GetRequiredService<IExpensiveWorker>();
await worker.DoWorkAsync();
// worker يتم التخلص منه هنا مع scope

اختبار الوحدات مع حقن التبعيات

الفائدة الأكبر من DI تظهر في الاختبار. لأن ViewModel يقبل تبعياته عبر واجهات، يمكنك تمرير مُحاكاة (Mocks) في اختبارات الوحدات دون الحاجة لتشغيل التطبيق. تعمقنا في هيكلة اختبارات MAUI في دليل اختبار تطبيقات .NET MAUI، وهذا مثال عملي مركّز على DI:

using NSubstitute;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

public class OrderDetailViewModelTests
{
    [Fact]
    public async Task LoadAsync_WhenServiceReturnsOrder_UpdatesOrderProperty()
    {
        // Arrange
        var orderService = Substitute.For<IOrderService>();
        var authService = Substitute.For<IAuthService>();
        var expectedOrder = new Order { Id = 42, Total = 199.99m };

        authService.GetTokenAsync().Returns("fake-token");
        orderService.GetOrderAsync(42, "fake-token").Returns(expectedOrder);

        var sut = new OrderDetailViewModel(
            orderService,
            authService,
            NullLogger<OrderDetailViewModel>.Instance);

        // Act
        await sut.LoadCommand.ExecuteAsync(42);

        // Assert
        Assert.Equal(expectedOrder, sut.Order);
        Assert.False(sut.IsBusy);
    }
}

لاحظ أننا لم نحتَج حاوية DI في الاختبار على الإطلاق. حقن المُنشئ يجعل كل شيء صريحاً: تصنع الـMocks، تمررها، تختبر النتيجة. هذا هو الفارق العملي بين تطبيق يستخدم DI بشكل صحيح، وتطبيق يبني تبعياته داخلياً. الأخير غير قابل للاختبار عملياً.

اختبار تسجيلات DI نفسها

يوصى أيضاً بكتابة اختبار تكاملي واحد يتأكد من أن كل تسجيل يُحل بنجاح. هذا يمنع كسر التطبيق عند إضافة تبعية جديدة ونسيان تسجيلها:

[Fact]
public void All_Registered_Services_Can_Be_Resolved()
{
    var services = new ServiceCollection();
    services.RegisterInfrastructure();
    services.RegisterDomainServices();
    services.RegisterPresentation();

    var provider = services.BuildServiceProvider(validateScopes: true);

    foreach (var descriptor in services)
    {
        var instance = provider.GetService(descriptor.ServiceType);
        Assert.NotNull(instance);
    }
}

هل تحتاج حاوية DI خارجية مثل Autofac؟

الإجابة القصيرة: في 95% من مشاريع MAUI، لا. الحاوية المدمجة نضجت كثيراً في .NET 8 و.NET 10، وأضافت الخدمات المفتاحية، والتحقق من البناء، وتحسينات كبيرة في الأداء. Autofac وDryIoc وSimpleInjector لا تزال حاويات ممتازة، لكنها تحل مشاكل نادرة في تطبيقات الموبايل.

الحالات القليلة التي قد تبرر حاوية خارجية:

  • حقن الخصائص (Property Injection): الحاوية المدمجة لا تدعمه، وAutofac يدعمه. لكن حقن الخصائص عادةً رائحة تصميم، فاستخدمه بحذر.
  • الاعتراض (Interception) والوكلاء الديناميكيون: إذا احتجت غلاف تلقائي حول كل استدعاء (تسجيل، ذاكرة مؤقتة)، Castle DynamicProxy مع Autofac يقدم ذلك.
  • التسجيل بالتجميع (Assembly Scanning): Scrutor يضيف هذا للحاوية المدمجة بدون تبديلها كلياً، فاستخدمه بدلاً من تبديل الحاوية.

خلاصة فرقنا: إذا كنت تبدأ مشروع MAUI جديداً في 2026، ابدأ بالحاوية المدمجة. سنة كاملة لاحقاً، احتمالية أن تحتاج تبديلها فعلياً أقل من 5%، وتكلفة التبديل لاحقاً منخفضة لأن الواجهات (IServiceCollection، IServiceProvider) قياسية.

للتوثيق الرسمي، راجع توثيق Microsoft لحقن التبعيات في .NET MAUI، وإرشادات .NET العامة لحقن التبعيات، ودليل معمارية Enterprise Application Patterns لـMAUI.

الأسئلة الشائعة

ما الفرق بين AddSingleton وAddTransient في .NET MAUI؟

AddSingleton ينشئ نسخة واحدة تُشارك طوال عمر التطبيق، مناسب للخدمات ذات الحالة المشتركة مثل قواعد البيانات والإعدادات. AddTransient ينشئ نسخة جديدة عند كل طلب، وهو الخيار الصحيح للصفحات وViewModels لضمان حالة نظيفة عند كل تنقل.

هل يمكنني استخدام AddScoped في تطبيق .NET MAUI؟

تقنياً نعم، لكن عملياً لا يُنصح به. MAUI ليس لديه حدود نطاق طبيعية مثل ASP.NET Core (لكل طلب HTTP) أو Blazor (لكل دائرة SignalR). خدمة Scoped تُحل من الجذر تتصرف كـSingleton فعلياً. إذا احتجت نطاقاً، أنشئه يدوياً عبر IServiceProvider.CreateScope().

كيف أحقن HttpClient في خدمة MAUI بشكل صحيح؟

استخدم builder.Services.AddHttpClient<IApiClient, ApiClient>() بدلاً من AddSingleton<HttpClient>. هذا يفعّل IHttpClientFactory الذي يدير HttpMessageHandler، ويمنع استنزاف المقابس عند إعادة الإنشاء المتكرر. كذلك يسمح بإضافة سياسات إعادة المحاولة عبر Polly في نفس السلسلة.

كيف أحل خدمة من ملف XAML أو Value Converter؟

استخدم IPlatformApplication.Current?.Services للحصول على IServiceProvider، ثم GetRequiredService<T>(). لكن هذا "حل صريح" (Explicit Resolution) ويُعتبر ملاذاً أخيراً. إذا وجدت نفسك تفعله كثيراً، أعد تصميم الفئة لتقبل التبعية عبر مُنشئ أو خاصية.

هل ما زلت بحاجة لـ Autofac أو Prism مع .NET MAUI 10؟

في معظم الحالات لا. الحاوية المدمجة أضافت الخدمات المفتاحية في .NET 8 والتحقق من البناء، وهو ما كان يميز الحاويات الخارجية. Autofac يبقى مفيداً لحقن الخصائص وInterception، وPrism يقدم بنية تنقل ونمط MVVM مكتمل. لكن للمشاريع الجديدة، ابدأ بالحاوية المدمجة وأضف حاوية خارجية فقط عند حاجة موثقة.

Priya Sharma
عن الكاتب Priya Sharma

Cross-platform engineering lead who's shipped apps to millions on both Play Store and App Store. Believes shared codebases shouldn't mean shared mediocrity.