مطالب
جایگزین کردن StructureMap با سیستم توکار تزریق وابستگی‌ها در ASP.NET Core 1.0
مدل برنامه زیر را در نظر بگیرید:
 public class Service
    {
        public int ServiceId { get; set; }
        public string ServiceName { get; set; }
    }
اینترفیس ICoreService عمل بازیابی اطلاعات کلاس بالا را بر عهده دارد:
 public interface ICoreService
    {
        Service LoadDefaultService();
    }
نتیجه تزریق وابستگی ICoreService برای کنترلر Home در یک پروژه ASP.NET Core 1.0/Asp.Net Mvc 6 چنین استثنایی بود:
An unhandled exception occurred while processing the request
  InvalidOperationException: Unable to resolve service for type 'WebApplication1.Models.ICoreService' while attempting to activate 'WebApplication1.Controllers.HomeController'
Microsoft.Extensions.Internal.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
 
یعنی زمانیکه Activator میخواست کنترلر Home را فعالسازی کند، نتوانسته وابستگی ICoreService را برای کنترلر فراهم کند. این استثناء در Microsoft.Extensions.Internal.ActivatorUtilities.GetService اتفاق افتاده‌است.

در نسخه‌های قدیمی MVC (منظور نسخه‌های قبل از 6)، برای تزریق وابستگی‌ها از یک Controller Factory یا Dependency Resolver سفارشی استفاده می‌شد. اما در نسخه جدید MVC دیگری خبری از روشهای قدیمی نیست. چونکه یک سیستم تزریق وابستگی توکار، همراه با MVC یکپارچه شده‌است که عملیات تزریق وابستگی‌ها را انجام می‌دهد. سیستم تزریق وابستگی پیش فرض، تنها از 4 حالت عملیاتی پشتیبانی می‌کند:
1- Instance : در همه حال ، تنها یک نمونه خاص ارائه شده و شما مسئول وهله سازی آن هستید.
2- Transient : در هر بار (مثلا در هر درخواست) یک نمونه جدید ساخته میشود.
3- Singleton
4- Scoped : تنها یک نمونه در Scope فعلی ساخته می‌شود.

 تیم Asp.Net برای فراهم آوردن امکان تزریق وابستگی‌ها، تصمیم به انتزاعی کردن ویژگی‌های مشترک محبوبترین Ioc Containerها و اجازه دادن به میان افزارها، جهت ارتباط با این اینترفیس‌ها برای دستیابی به تزریق وابستگی بود.
حال بیایم نگاهی به این اینترفیس‌ها بیندازیم.
اگر به استثنای فوق نگاهی بیندازیم، می‌بینیم که متد GetService یک پارامتر از نوع IServiceProvider را میگیرد. پس اولین اینترفیس IServiceProvider می باشد. همانطور که از اسمش پیداست، کارش فرآهم آوردن سرویس می‌باشد. این اینترفیس فقط یک متد دارد، متد GetService. متد GetService مانند Container.GetInstance در StructureMap می‌باشد. تمام میان افزارها به 2 روش می‌توانند به نمونه‌ای از IServiceProvider دسترسی داشته باشند:
1- Application-Level : از طریق HttpContext.ApplicationServices برای میان افزار قابل دسترسی خواهد بود.
2- Request-Level : از طریق HttpContext.RequestServices. این Service Scope Provider توسط میان افزار در شروع هر Request Pipeline، برای هر درخواست ایجاد و در پایان درخواست توسط همان میان افزار نابود می‌گردد.
اینترفیس بعدی IServiceScope می‌باشد. همان طور که قبلا گفتیم RequestServices یک Scope Container را برای هر درخواست ساخته و در پایان همان درخواست، آن را نابود میکند. اما این کار چگونه مدیریت می‌شود؟ پاسخ، اینترفیس IServiceScope می باشد. این اینترفیس مانند یک Wrapper حول Scope Container عمل میکند و در پایان هر درخواست آن را نابود می‌کند. حال سوال اینجاست که چه کسی مسئول ساخت IServiceScope می‌باشد؟ پاسخ اینترفیس IServiceScopeFactory می‌باشد. این اینترفیس توسط متد CreateScope اقدام به ساخت یک نمونه از اینترفیس IserviceScope می‌نماید.
مورد بعدی ServiceLifeTime می‌باشد. یک Enum که حاوی سه مقدار زیر می‌باشد:
namespace Microsoft.Extensions.DependencyInjection
{
    //
    // Summary:
    //     Specifies the lifetime of a service in an Microsoft.Extensions.DependencyInjection.IServiceCollection.
    public enum ServiceLifetime
    {
        //
        // Summary:
        //     Specifies that a single instance of the service will be created.
        Singleton = 0,
        //
        // Summary:
        //     Specifies that a new instance of the service will be created for each scope.
        //
        // Remarks:
        //     In ASP.NET Core applications a scope is created around each server request.
        Scoped = 1,
        //
        // Summary:
        //     Specifies that a new instance of the service will be created every time it is
        //     requested.
        Transient = 2
    }
}
آخرین مورد کلاس ServiceDescriptor می‌باشد.  این کلاس اطلاعاتی را که Container برای رجیستر کردن سرویس به آنها نیاز دارد، در خود نگهداری می‌کند. این جمله را در نظر بگیرید : " هی Container، وقتی میخواهی این سرویس را رجیستر کنی، اطمینان حاصل کن که Singleton باشد و یک نمونه از نوع X را پیاده سازی کند." تمامی اطلاعات جمله قبل در ServiceDescriptor نگهداری می‌شود.
خوب! حال بیایم تا سرویس خود را رجیستر کنیم. در کلاس StartUp پروژه در متد ConfigurationServices خط زیر را اضافه می‌کنیم:
public void ConfigureServices(IServiceCollection services)
        {                        
            ServiceDescriptor descriptor = new ServiceDescriptor(typeof(ICoreService),typeof(CoreServise),ServiceLifetime.Transient);
            services.Add(descriptor);
            services.AddMvc();          
        }
حال اگر برنامه را اجرا کنیم مشکل برطرف شده است.

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

services.AddTransient<ICoreService,CoreServise>();

حال که یک دید کلی از نحوه کار مکانیزم تزریق وابستگی بدست آوردیم، میخواهیم این مکانیزم را با StructureMap جایگزین کنیم. بدین منظور ابتدا پکیج StructureMap را نصب میکنم.

در مرحله اول باید کلاسهایی را تدارک ببینیم که اینترفیس‌های بالا را پیاده سازی نمایند. یعنی کلاسهای ما باید بتوانند همان کاری را انجام دهند که مکانیزم پیش فرض MVC انجام می‌دهد. 

اولین مورد، کلاس StructureMapServiceProvider می‌باشد.

internal class StructureMapServiceProvider : IServiceProvider
    {
        private readonly IContainer _container;

        public StructureMapServiceProvider(IContainer container, bool scoped = false)
        {            
            _container = container;
        }

        public object GetService(Type type)
        {
            try
            {
                return _container.GetInstance(type);
            }
            catch
            {
                return null;
            }
        }
    }

مورد دوم کلاس StructureMapServiceScope می‌باشد:

internal class StructureMapServiceScope : IServiceScope
    {
        private readonly IContainer _container;
        private readonly IContainer _childContainer;
        private IServiceProvider _provider;

        public StructureMapServiceScope(IContainer container)
        {
            _container = container;
            _childContainer = _container.GetNestedContainer();
            _provider = new StructureMapServiceProvider(_childContainer, true);
        }

        public IServiceProvider ServiceProvider => _provider;

        public void Dispose()
        {
            _provider = null;
            if (_childContainer != null)
                _childContainer.Dispose();
        }
    }

مورد سوم StructureMapServiceScopeFactory می‌باشد:

internal class StructureMapServiceScopeFactory : IServiceScopeFactory
    {
        private IContainer _container;

        public StructureMapServiceScopeFactory(IContainer container)
        {
            _container = container;
        }

        public IServiceScope CreateScope()
        {
            return new StructureMapServiceScope(_container);
        }
    }

مورد بعدی کلاس StructureMapPopulator می‌باشد. وظیفه این کلاس جمع آوری اطلاعات مربوط به سرویس‌ها می‌باشد.

internal class StructureMapPopulator
    {
        private IContainer _container;

        public StructureMapPopulator(IContainer container)
        {
            _container = container;
        }

        public void Populate(IEnumerable<ServiceDescriptor> descriptors)
        {
            _container.Configure(c =>
            {
                c.For<IServiceProvider>().Use(new StructureMapServiceProvider(_container));
                c.For<IServiceScopeFactory>().Use<StructureMapServiceScopeFactory>();

                foreach (var descriptor in descriptors)
                {
                    switch (descriptor.Lifetime)
                    {
                        case ServiceLifetime.Singleton:
                            Use(c.For(descriptor.ServiceType).Singleton(), descriptor);
                            break;
                        case ServiceLifetime.Transient:
                            Use(c.For(descriptor.ServiceType), descriptor);
                            break;
                        case ServiceLifetime.Scoped:
                            Use(c.For(descriptor.ServiceType), descriptor);
                            break;
                    }
                }
            });
        }

        private static void Use(GenericFamilyExpression expression, ServiceDescriptor descriptor)
        {
            if (descriptor.ImplementationFactory != null)
            {
                expression.Use(Guid.NewGuid().ToString(), context => { return descriptor.ImplementationFactory(context.GetInstance<IServiceProvider>()); });
            }
            else if (descriptor.ImplementationInstance != null)
            {
                expression.Use(descriptor.ImplementationInstance);
            }
            else if (descriptor.ImplementationType != null)
            {
                expression.Use(descriptor.ImplementationType);
            }
            else
            {
                throw new InvalidOperationException("IServiceDescriptor is invalid");
            }
        }
    }

و در آخر کلاس StructureMapRegistration می‌باشد:

public static class StructureMapRegistration
    {
        public static void Populate(this IContainer container, IEnumerable<ServiceDescriptor> descriptors)
        {
            var populator = new StructureMapPopulator(container);
            populator.Populate(descriptors);
        }
    }

نهایتاً باید متد ConfigurationServices در کلاس StartUp را اندکی تغییر دهیم.

public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
           
            var container = new Container();
            container.Configure(configure =>
            {
                configure.For<ICoreService>().Use<CoreServise>();
            });

            container.Populate(services);

            return container.GetInstance<IServiceProvider>();
        }

در کد بالا، متد ConfigurationServices به جای آنکه Void برگرداند، نمونه‌ای از اینترفیس IServiceProvider را برمی‌گرداند. حال اگر برنامه را اجرا کنیم، وابستگی‌ها توسط StructureMap تزریق شده و برنامه بدون هیچ مشکلی اجرا می‌شود.

مطالب
پیاده سازی ماژولار Autofac
یکی از مشکلاتی که در برخی از طراحی‌هایی که تا کنون دیده‌ام وجود دارد، عدم استفاده از قابلیت ماژولار نویسی تنظیمات Autofac  و عدم استفاده از Interfaceها برای ارتباط بین قسمت‌های مختلف سیستم است. به این صورت که تمام تنظیمات مربوط به Autofac را در بالاترین لایه سمت سرور خود یعنی Service یا Web انجام می‌دهند که باعث می‌شود این لایه به تمامی لایه‌های پایین خود از جمله DataAccess دسترسی مستقیم داشته باشد. در یک سیستم بزرگ به دلایل بسیار از جمله حساسیت داده‌ها، مدیریت درست بر روی قسمت‌های مختلف و یکبار نویسی هر قسمت، بهتر است تمام تغییرات از فیلتر Business و بصورت مدیریت شده بر روی داده‌های ما صورت بپذیرد. در این نوع سیستم ما نمی‌توانیم دسترسی مستقیمی را به لایه DataAccess به تمام توسعه دهندگان بدهیم و امیدوار باشیم که کسی از آن استفاده نکند. برای رفع این مشکل می‌توانیم تنظیمات قسمت Autofac را در هر لایه بصورت جداگانه انجام دهیم. به اینصورت که لایه‌های پایین‌تر اطلاعات خود را تنها در اختیار لایه‌هایی که باید به آنها دسترسی داشته باشند، قرار می‌دهند و در نهایت با تجمیع این اطلاعات در بالاترین لایه می‌توانیم بصورت کنترل شدهی از آنها استفاده کنیم. دسترسی هر قسمت نیز تنها می‌تواند از طریق Interface هایی که در اختیار سایر قسمت‌ها گذاشته می‌شود صورت بپذیرد. به این صورت می‌توان از طریق Interfaceها دسترسی‌های کنترل شده‌ای را در اختیار سایر قسمتهایی که بصورت مستقیم یا غیر مستقیم به لایه مربوطه دسترسی دارند، قرار دهیم. در اینجا نکته دیگری را که باید در نظر بگیرید این است که هدف اصلی DI، حذف وابستگی‌های کامل و تبدیل آنها به وابستگی‌های محدود است و این عمل از طریق Interface‌ها صورت می‌پذیرد. پس قاعدتا نباید بی دلیل بصورت مستقیم از کلاسها استفاده شود، یا آنها را در اختیار سایر لایه‌ها قرار بدهیم. تمام ارتباطات می‌توانند از طریق Interface‌ها بصورت کاملا کنترل شده انجام بپذیرند.



طراحی لایه DataAccess

در این نوع طراحی، هر لایه تنها از طریق Interfaceها به لایه بالاتر از خودش سرویس می‌دهد و در مواردی که ما در نظر بگیریم، می‌توانیم به لایه‌های دیگر نیز دسترسی‌های غیر مستقیم و کنترل شده‌ای را بدهیم. بطور مثال هر کلاس Repository می‌تواند به‌صورت Internal تعریف شود؛ پس تنها در لایه DataAccess در دسترس است. برای دسترسی سایر لایه‌ها به Repository‌ها، هر Repository می‌تواند از یک IRepository (این Interface دسترسی خواندنی نوشتنی به کلاس Repository دارد) که در لایه DataAccess بصورت public تعریف شده و تنها در لایه Business قابل دسترس است و یک IRepositoryReadOnly (این Interface دسترسی فقط خواندنی به کلاس Repository دارد) که در قسمت Common تعریف شده، ارث ببرد. به این صورت همان طور که در شکل بالا نیز می‌بینید، دسترسی‌هایی که از بیرون به لایه DataAccess صورت می‌گیرد، به دو قسمت تقسیم می‌شوند: اول دسترسی کامل به IRepository که تنها از طریق Business صورت می‌پذیرد و دوم دسترسی از طریق IRepositoryReadOnly که می‌تواند از هر جایی در سیستم که به قسمت Common دسترسی دارد صورت بپذیرد (البته استفاده از IRepositoryReadOnly به سیاست‌هایی که شما در نظر می‌گیرید بستگی دارد). با این کار مطمئن می‌شوید که تغییرات تنها می‌توانند از طریق Business صورت بپذیرند. همچنین حتی در صورتیکه نیاز بدانید، یک دسترسی فقط خواندنی را نیز به توسعه دهندگان سایر قسمت‌ها داده‌اید.

حال با توجه به توضیحات فوق، تنظیمات مربوط به Autofac را در لایه DataAccess انجام می‌دهیم.


طراحی تنظیمات Autofac در لایه DataAccess

public class DataAccessSetupDependency : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);
            builder.RegisterType<EFContext>().As<IDbContext>();
            builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
             .Where(x => x.Namespace.EndsWith("Repositories"))
             .AsImplementedInterfaces();
        }
    }
همانطور که می‌بینید در این قسمت تنها تنظیمات وابستگی‌های لایه DataAccess در ماژول DataAccessSetupDependency انجام شده‌است.


طراحی لایه Business

در لایه Business نیز دسترسی‌های خود را تنها از طریق Interface هایی که کلاسهای Business از آنها ارث برده‌اند و آنها را پیاده سازی کرده‌اند، به قسمت Web می‌دهید.

طراحی تنظیمات Autofac در لایه Business

public class BusinessSetupDependency : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);

            builder.RegisterAssemblyModules(typeof(SqlServerDataAccess.SetupDependencies.DataAccessSetupDependency).Assembly);

            //builder.RegisterAssemblyModules(typeof(CassandraDataAccess.SetupDependecies.SetupDependency).Assembly);

            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
           .Where(x => x.Namespace.EndsWith("Business.Core"))
           .AsImplementedInterfaces();

            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
            .Where(x => x.Namespace.EndsWith("Business.Businesses"))
            .AsImplementedInterfaces();
        }
    }
 همانطور که می‌بینید در ماژول BusinessSetupDependency ابتدا تنظیمات وابستگی‌های موجود در لایه DataAccess بدست آمده و در ادامه سایر تنظیمات موجود در لایه Business انجام شده‌اند.


طراحی لایه Web

جمع بندی تمام تنظیمات Autofac در لایه Web

public class WebSetupDependency : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);
            builder.RegisterAssemblyModules(typeof(Business.SetupDependencies.BusinessSetupDependency).Assembly);
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            //سایر تنظیمات
            var container = builder.Build();
            CommonDependencyResolver.SetContainer(container);
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

        }
    }
 public class AutofacConfig
    {
        public static void SetupContainer()
        {
             var builder = new ContainerBuilder();
            builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
            var container = builder.Build();
            CommonDependencyResolver.SetContainer(container);
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
        }
    }
ماژول WebSetupDependency در واقع جمع بندی از تمام وابستگی‌های لایه‌های موجود در سیستم است. بصورتیکه ابتدا تنظیمات وابستگی‌های لایه Business که خود شامل تنظیمات وابستگی‌های لایه DataAccess نیز هست، بدست می‌آیند و سپس لایه Web تنظیمات مرتبط با خودش را بر روی آنها اعمال می‌کند.

همچنین کلاس AutofacConfig مسئول جمع بندی تمام ماژول‌ها و ایجاد Container آنهاست و سپس این Container را در DependencyResolver  ثبت می‌کند. نکته‌ای را که باید در اینجا در نظر بگیرید، CommonDependencyResolver است که مسئول ثبت Container در قسمت Common است. به این صورت دیگر تنها لایه‌ای که به Container دسترسی دارد، لایه Web نیست. در واقع با ثبت Container در قسمت Common شما دسترسی کنترل شده‌ای را از Container به سایر لایه‌های سیستم  داده‌اید و در این حالت در صورتیکه لایه‌های دیگر مانند DataAccess یا Business به Container نیاز پیدا کنند، می‌توانند از طریق CommonDependencyResolver این دسترسی را داشته باشند.


جمع بندی

با طراحی ماژولار تنظیمات Autofac و استفاده از Interface‌ها برای ارتباط با دیگر قسمتها، دیگر نیازی نیست دسترسی‌های بی‌موردی از یک لایه را  به سایر قسمت‌ها داد و دیگر نیازی نیست شما نگران این باشید که ممکن است یکی  از توسعه دهندگان به‌دلایلی مانند کم تجربگی، کاری را خارج از زیرساختی که شما یا گروه پیاده سازی زیرساخت، پیاده سازی کرده‌اید انجام دهد. همه چیز آنطور که شما می‌خواهید و برنامه ریزی کرده‌اید، انجام می‌شود.
مطالب
پیاده‌سازی الگوی Transaction Per Request در EF
قبلاً در سایت جاری در رابطه با پیاده‌سازی الگوی Context Per Request مطالبی منتشر شده است. در ادامه می‌خواهیم تمامی درخواست‌های خود را اتمیک کنیم. همانطور که قبلاً در این مطلب مطالعه کردید یکی از مزایای الگوی Context Per Request، استفاده‌ی صحیح از تراکنش‌ها می‌باشد. به عنوان مثال اگر در حین فراخوانی متد SaveChanges، خطایی رخ دهد، کلیه‌ی عملیات RollBack خواهد شد. اما حالت زیر را در نظر بگیرید:
_categoryService.AddNewCategory(category);
_uow.SaveAllChanges();

throw new InvalidOperationException();

return RedirectToAction("Index");
همانطور که در کدهای فوق مشاهده می‌کنید، قبل از ریدایرکت شدن صفحه، یک استثناء را صادر کرده‌ایم. در این حالت، تغییرات درون دیتابیس ذخیره می‌شوند! یعنی حتی اگر یک استثناء نیز در طول درخواست رخ دهد، قسمتی از درخواست که در اینجا ذخیره‌سازی گروه محصولات است، درون دیتایس ذخیره خواهد شد؛ در نتیجه درخواست ما اتمیک نیست.
برای رفع این مشکل می‌توانیم یکسری وظایف (Tasks) را تعریف کنیم که در نقاط مختلف چرخه‌ی حیات برنامه اجرا شوند. هر کدام از این وظایف تنها کاری که انجام می‌دهند فراخوانی متد Execute خودشان است. در ادامه می‌خواهیم از این وظایف جهت پیاده‌سازی الگوی Transaction Per Request استفاده کنیم. در نتیجه اینترفیس‌های زیر را ایجاد خواهیم کرد:
public interface IRunAtInit
{
       void Execute();
}
public interface IRunAfterEachRequest
{
       void Execute(); 
}
public interface IRunAtStartUp
{
       void Execute(); 
}
public interface IRunOnEachRequest
{
       void Execute(); 
}
public interface IRunOnError
{
       void Execute(); 
}
خوب، این اینترفیس‌ها همانطور که از نامشان پیداست، همان اعمال را پیاده سازی خواهند کرد:
IRunAtInit: اجرای وظایف در زمان بارگذاری اولیه‌ی برنامه.
IRunAfterEachRequest: اجرای وظایف بعد از اینکه درخواستی فراخوانی (ارسال) شد.
IRunAtStartUp: اجرای وظایف در زمان StartUp برنامه.
IRunOnEachRequest: اجرای وظایف در ابتدای هر درخواست.
IRunOnError: اجرای وظایف در زمان بروز خطا یا استثناء‌های مدیریت نشده‌ی برنامه.
خوب، یک کلاس می‌تواند با پیاده‌سازی هر کدام از اینترفیس‌های فوق تبدیل به یک task شود. همچنین از این جهت که اینترفیس‌های ما ساده هستند و هر اینترفیس یک متد Execute دارد، عملکرد آن‌ها تنها اجرای یکسری دستورات در حالات مختلف می‌باشد.
قدم بعدی افزودن قابلیت پشتیبانی از این وظایف در برنامه‌مان است. اینکار را با پیاده‌سازی ریجستری زیر انجام خواهیم داد:
public class TaskRegistry : StructureMap.Configuration.DSL.Registry
{
        public TaskRegistry()
        {
            Scan(scan =>
            {
                scan.Assembliy("yourAssemblyName");
                scan.AddAllTypesOf<IRunAtInit>();
                scan.AddAllTypesOf<IRunAtStartUp>();
                scan.AddAllTypesOf<IRunOnEachRequest>();
                scan.AddAllTypesOf<IRunOnError>();
                scan.AddAllTypesOf<IRunAfterEachRequest>();
            });
        }
}
با این کار استراکچرمپ اسمبلی معرفی شده را بررسی کرده و هر کلاسی که اینترفیس‌های ذکر شده را پیاده‌سازی کرده باشد، رجیستر می‌کند. قدم بعدی افزودن رجیستری فوق و بارگذاری آن درون کانتینرمان است:
ioc.AddRegistry(new TaskRegistry());
اکنون وظایف درون کانتینرمان بارگذاری شده‌اند. سپس نوبت به استفاده‌ی از این وظایف است. 
خوب، باید درون فایل Global.asax کدهای زیر را قرار دهیم. چون همانطور که عنوان شد وظایف ایجاد شده می‌بایستی در نقاط مختلف برنامه اجرا شوند:
protected void Application_Start()
{
   // other code
   foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAtInit>())
   {
                task.Execute();
    }
}
protected void Application_BeginRequest()
 {
           foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnEachRequest>())
           {
                task.Execute();
           }
}
protected void Application_EndRequest(object sender, EventArgs e)
{
            try
            {
                foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAfterEachRequest>())
                {
                    task.Execute();
                }
            }
            finally
            {
                HttpContextLifecycle.DisposeAndClearAll();
                MiniProfiler.Stop();
            }
}
protected void Application_Error()
{
            foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnError>())
            {
                task.Execute();
            }
}
همانطور که مشاهده می‌کنید، هر task در قسمت خاص خود فراخوانی خواهد شد. مثلاً IRunOnError درون رویداد Application_Error و دیگر وظایف نیز به همین ترتیب.
اکنون برنامه به صورت کامل از وظایف پشتیبانی می‌کند. در ادامه، کلاس زیر را ایجاد خواهیم کرد. این کلاس چندین اینترفیس را از اینترفیس‌های ذکر شده، پیاده‌سازی می‌کند:
public class TransactionPerRequest : IRunOnEachRequest, IRunOnError, IRunAfterEachRequest
{
        private readonly IUnitOfWork _uow;
        private readonly HttpContextBase _httpContext;
        public TransactionPerRequest(IUnitOfWork uow, HttpContextBase httpContext)
        {
            _uow = uow;
            _httpContext = httpContext;
        }


        void IRunOnEachRequest.Execute()
        {
            _httpContext.Items["_Transaction"] =
                _uow.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);
        }

        void IRunOnError.Execute()
        {
            _httpContext.Items["_Error"] = true;
        }

        void IRunAfterEachRequest.Execute()
        {
            var transaction = (DbContextTransaction) _httpContext.Items["_Transaction"];
            if (_httpContext.Items["_Error"] != null)
            {
                transaction.Rollback();
            }
            else
            {
                transaction.Commit();
            }
        }
}
توضیحات کلاس فوق:
در کلاس TransactionPerRequest به دو وابستگی نیاز خواهیم داشت: IUnitOfWork برای کار با تراکنش‌ها و HttpContextBase برای دریافت درخواست جاری. همانطور که مشاهده می‌کنید در متد IRunOnEachRequest.Execute یک تراکنش را آغاز کرده‌ایم و در IRunAfterEachRequest.Execute یعنی در پایان یک درخواست، تراکنش را commit کرده‌ایم. این مورد را با چک کردن یک فلگ در صورت عدم بروز خطا انجام داده‌ایم. اگر خطایی نیز وجود داشته باشد، کل عملیات roll back خواهد شد. لازم به ذکر است که فلگ خطا نیز درون متد IRunOnError.Execute به true مقداردهی شده است.
خوب، پیاده‌سازی الگوی Transaction Per Request به صورت کامل انجام گرفته است. اکنون اگر برنامه را در حالت زیر اجرا کنید:
_categoryService.AddNewCategory(category);
_uow.SaveAllChanges();

throw new InvalidOperationException();

return RedirectToAction("Index");
خواهید دید که عملیات roll back شده و تغییرات در دیتابیس (در اینجا ذخیره سازی گروه محصولات) اعمال نخواهد شد.
مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
پروژه‌ی ASP.NET Identity که نسل جدید سیستم Authentication و Authorization مخصوص ASP.NET است، دارای دو سری مثال رسمی است:
الف) مثال‌های کدپلکس
 ب) مثال نیوگت

در ادامه قصد داریم مثال نیوگت آن‌را که مثال کاملی است از نحوه‌ی استفاده از ASP.NET Identity در ASP.NET MVC، جهت اعمال الگوی واحد کار و تزریق وابستگی‌ها، بازنویسی کنیم.


پیشنیازها
- برای درک مطلب جاری نیاز است ابتدا دور‌ه‌ی مرتبطی را در سایت مطالعه کنید و همچنین با نحوه‌ی پیاده سازی الگوی واحد کار در EF Code First آشنا باشید.
- به علاوه فرض بر این است که یک پروژه‌ی خالی ASP.NET MVC 5 را نیز آغاز کرده‌اید و توسط کنسول پاور شل نیوگت، فایل‌های مثال Microsoft.AspNet.Identity.Samples را به آن افزوده‌اید:
 PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre


ساختار پروژه‌ی تکمیلی

همانند مطلب پیاده سازی الگوی واحد کار در EF Code First، این پروژه‌ی جدید را با چهار اسمبلی class library دیگر به نام‌های
AspNetIdentityDependencyInjectionSample.DataLayer
AspNetIdentityDependencyInjectionSample.DomainClasses
AspNetIdentityDependencyInjectionSample.IocConfig
AspNetIdentityDependencyInjectionSample.ServiceLayer
تکمیل می‌کنیم.


ساختار پروژه‌ی AspNetIdentityDependencyInjectionSample.DomainClasses

مثال Microsoft.AspNet.Identity.Samples بر مبنای primary key از نوع string است. برای نمونه کلاس کاربران آن‌را به نام ApplicationUser در فایل Models\IdentityModels.cs می‌توانید مشاهده کنید. در مطلب جاری، این نوع پیش فرض، به نوع متداول int تغییر خواهد یافت. به همین جهت نیاز است کلاس‌های ذیل را به پروژه‌ی DomainClasses اضافه کرد:
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNet.Identity.EntityFramework;
 
namespace AspNetIdentityDependencyInjectionSample.DomainClasses
{
  public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
  {
   // سایر خواص اضافی در اینجا
 
   [ForeignKey("AddressId")]
   public virtual Address Address { get; set; }
   public int? AddressId { get; set; }
  }
}

using System.Collections.Generic;
 
namespace AspNetIdentityDependencyInjectionSample.DomainClasses
{
  public class Address
  {
   public int Id { get; set; }
   public string City { get; set; }
   public string State { get; set; }
 
   public virtual ICollection<ApplicationUser> ApplicationUsers { set; get; }
  }
}

using Microsoft.AspNet.Identity.EntityFramework;
 
namespace AspNetIdentityDependencyInjectionSample.DomainClasses
{
  public class CustomRole : IdentityRole<int, CustomUserRole>
  {
   public CustomRole() { }
   public CustomRole(string name) { Name = name; }
 
 
  }
}

using Microsoft.AspNet.Identity.EntityFramework;
 
namespace AspNetIdentityDependencyInjectionSample.DomainClasses
{
  public class CustomUserClaim : IdentityUserClaim<int>
  {
 
  }
}

using Microsoft.AspNet.Identity.EntityFramework;
 
namespace AspNetIdentityDependencyInjectionSample.DomainClasses
{
  public class CustomUserLogin : IdentityUserLogin<int>
  {
 
  }
}

using Microsoft.AspNet.Identity.EntityFramework;
 
namespace AspNetIdentityDependencyInjectionSample.DomainClasses
{
  public class CustomUserRole : IdentityUserRole<int>
  {
 
  }
}
در اینجا نحوه‌ی تغییر primary key از نوع string را به نوع int، مشاهده می‌کنید. این تغییر نیاز به اعمال به کلاس‌های کاربران و همچنین نقش‌های آن‌ها نیز دارد. به همین جهت صرفا تغییر کلاس ابتدایی ApplicationUser کافی نیست و باید کلاس‌های فوق را نیز اضافه کرد و تغییر داد.
بدیهی است در اینجا کلاس پایه کاربران را می‌توان سفارشی سازی کرد و خواص دیگری را نیز به آن افزود. برای مثال در اینجا یک کلاس جدید آدرس تعریف شده‌است که ارجاعی از آن در کلاس کاربران نیز قابل مشاهده است.
سایر کلاس‌های مدل‌های اصلی برنامه که جداول بانک اطلاعاتی را تشکیل خواهند داد نیز در آینده به همین اسمبلی DomainClasses اضافه می‌شوند.


ساختار پروژه‌ی AspNetIdentityDependencyInjectionSample.DataLayer جهت اعمال الگوی واحد کار

اگر به همان فایل Models\IdentityModels.cs ابتدایی پروژه که اکنون کلاس ApplicationUser آن‌را به پروژه‌ی DomainClasses منتقل کرده‌ایم، مجددا مراجعه کنید، کلاس DbContext مخصوص ASP.NET Identity نیز در آن تعریف شده‌است:
 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
این کلاس را به پروژه‌ی DataLayer منتقل می‌کنیم و از آن به عنوان DbContext اصلی برنامه استفاده خواهیم کرد. بنابراین دیگر نیازی نیست چندین DbContext در برنامه داشته باشیم. IdentityDbContext، در اصل از DbContext مشتق شده‌است.
اینترفیس IUnitOfWork برنامه، در پروژه‌ی DataLayer چنین شکلی را دارد که نمونه‌ای از آن‌را در مطلب آشنایی با نحوه‌ی پیاده سازی الگوی واحد کار در EF Code First، پیشتر ملاحظه کرده‌اید.
using System.Collections.Generic;
using System.Data.Entity;
 
namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context
{
  public interface IUnitOfWork
  {
   IDbSet<TEntity> Set<TEntity>() where TEntity : class;
   int SaveAllChanges();
   void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
   IList<T> GetRows<T>(string sql, params object[] parameters) where T : class;
   IEnumerable<TEntity> AddThisRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
   void ForceDatabaseInitialize();
  }
}
اکنون کلاس ApplicationDbContext منتقل شده به DataLayer یک چنین امضایی را خواهد یافت:
public class ApplicationDbContext :
  IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>,
  IUnitOfWork
{
  public DbSet<Category> Categories { set; get; }
  public DbSet<Product> Products { set; get; }
  public DbSet<Address> Addresses { set; get; }
تعریف آن باید جهت اعمال کلاس‌های سفارشی سازی شده‌ی کاربران و نقش‌های آن‌ها برای استفاده از primary key از نوع int به شکل فوق، تغییر یابد. همچنین در انتهای آن مانند قبل، IUnitOfWork نیز ذکر شده‌است. پیاده سازی کامل این کلاس را از پروژه‌ی پیوست انتهای بحث می‌توانید دریافت کنید.
کار کردن با این کلاس، هیچ تفاوتی با DbContext‌های متداول EF Code First ندارد و تمام اصول آن‌ها یکی است.

در ادامه اگر به فایل App_Start\IdentityConfig.cs مراجعه کنید، کلاس ذیل در آن قابل مشاهده‌است:
 public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
نیازی به این کلاس به این شکل نیست. آن‌را حذف کنید و در پروژه‌ی DataLayer، کلاس جدید ذیل را اضافه نمائید:
using System.Data.Entity.Migrations;
 
namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context
{
  public class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
  {
   public Configuration()
   {
    AutomaticMigrationsEnabled = true;
    AutomaticMigrationDataLossAllowed = true;
   }
  }
}
در این مثال، بحث migrations به حالت خودکار تنظیم شده‌است و تمام تغییرات در پروژه‌ی DomainClasses را به صورت خودکار به بانک اطلاعاتی اعمال می‌کند. تا همینجا کار تنظیم DataLayer به پایان می‌رسد.


ساختار پروژ‌ه‌ی AspNetIdentityDependencyInjectionSample.ServiceLayer

در ادامه مابقی کلاس‌‌های موجود در فایل App_Start\IdentityConfig.cs را به لایه سرویس برنامه منتقل خواهیم کرد. همچنین برای آن‌ها یک سری اینترفیس جدید نیز تعریف می‌کنیم، تا تزریق وابستگی‌ها به نحو صحیحی صورت گیرد. اگر به فایل‌های کنترلر این مثال پیش فرض مراجعه کنید (پیش از تغییرات بحث جاری)، هرچند به نظر در کنترلرها، کلاس‌های موجود در فایل App_Start\IdentityConfig.cs تزریق شده‌اند، اما به دلیل عدم استفاده از اینترفیس‌ها، وابستگی کاملی بین جزئیات پیاده سازی این کلاس‌ها و نمونه‌های تزریق شده به کنترلرها وجود دارد و عملا معکوس سازی واقعی وابستگی‌ها رخ نداده‌است. بنابراین نیاز است این مسایل را اصلاح کنیم.

الف) انتقال کلاس ApplicationUserManager به لایه سرویس برنامه
کلاس ApplicationUserManager فایل App_Start\IdentityConfig.c را به لایه سرویس منتقل می‌کنیم:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNetIdentityDependencyInjectionSample.DomainClasses;
using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.DataProtection;
 
namespace AspNetIdentityDependencyInjectionSample.ServiceLayer
{
  public class ApplicationUserManager
   : UserManager<ApplicationUser, int>, IApplicationUserManager
  {
   private readonly IDataProtectionProvider _dataProtectionProvider;
   private readonly IIdentityMessageService _emailService;
   private readonly IApplicationRoleManager _roleManager;
   private readonly IIdentityMessageService _smsService;
   private readonly IUserStore<ApplicationUser, int> _store;
 
   public ApplicationUserManager(IUserStore<ApplicationUser, int> store,
    IApplicationRoleManager roleManager,
    IDataProtectionProvider dataProtectionProvider,
    IIdentityMessageService smsService,
    IIdentityMessageService emailService)
    : base(store)
   {
    _store = store;
    _roleManager = roleManager;
    _dataProtectionProvider = dataProtectionProvider;
    _smsService = smsService;
    _emailService = emailService;
 
    createApplicationUserManager();
   }
 
 
   public void SeedDatabase()
   {
   }
 
   private void createApplicationUserManager()
   {
    // Configure validation logic for usernames
    this.UserValidator = new UserValidator<ApplicationUser, int>(this)
    {
      AllowOnlyAlphanumericUserNames = false,
      RequireUniqueEmail = true
    };
 
    // Configure validation logic for passwords
    this.PasswordValidator = new PasswordValidator
    {
      RequiredLength = 6,
      RequireNonLetterOrDigit = true,
      RequireDigit = true,
      RequireLowercase = true,
      RequireUppercase = true,
    };
 
    // Configure user lockout defaults
    this.UserLockoutEnabledByDefault = true;
    this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
    this.MaxFailedAccessAttemptsBeforeLockout = 5;
 
    // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
    // You can write your own provider and plug in here.
    this.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser, int>
    {
      MessageFormat = "Your security code is: {0}"
    });
    this.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser, int>
    {
      Subject = "SecurityCode",
      BodyFormat = "Your security code is {0}"
    });
    this.EmailService = _emailService;
    this.SmsService = _smsService;
 
    if (_dataProtectionProvider != null)
    {
      var dataProtector = _dataProtectionProvider.Create("ASP.NET Identity");
      this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, int>(dataProtector);
    }
   } 
  }
}
تغییراتی که در اینجا اعمال شده‌اند، به شرح زیر می‌باشند:
- متد استاتیک Create این کلاس حذف و تعاریف آن به سازنده‌ی کلاس منتقل شده‌اند. به این ترتیب با هربار وهله سازی این کلاس توسط IoC Container به صورت خودکار این تنظیمات نیز به کلاس پایه UserManager اعمال می‌شوند.
- اگر به کلاس پایه UserManager دقت کنید، به آرگومان‌های جنریک آن یک int هم اضافه شده‌است. این مورد جهت استفاده از primary key از نوع int ضروری است.
- در کلاس پایه UserManager تعدادی متد وجود دارند. تعاریف آن‌ها را به اینترفیس IApplicationUserManager منتقل خواهیم کرد. نیازی هم به پیاده سازی این متدها در کلاس جدید ApplicationUserManager نیست؛ زیرا کلاس پایه UserManager پیشتر آن‌ها را پیاده سازی کرده‌است. به این ترتیب می‌توان به یک تزریق وابستگی واقعی و بدون وابستگی به پیاده سازی خاص UserManager رسید. کنترلری که با IApplicationUserManager بجای ApplicationUserManager کار می‌کند، قابلیت تعویض پیاده سازی آن‌را جهت آزمون‌های واحد خواهد یافت.
- در کلاس اصلی ApplicationDbInitializer پیش فرض این مثال، متد Seed هم قابل مشاهده‌است. این متد را از کلاس جدید Configuration اضافه شده به DataLayer حذف کرده‌ایم. از این جهت که در آن از متدهای کلاس ApplicationUserManager مستقیما استفاده شده‌است. متد Seed اکنون به کلاس جدید اضافه شده به لایه سرویس منتقل شده و در آغاز برنامه فراخوانی خواهد شد. DataLayer نباید وابستگی به لایه سرویس داشته باشد. لایه سرویس است که از امکانات DataLayer استفاده می‌کند.
- اگر به سازنده‌ی کلاس جدید ApplicationUserManager دقت کنید، چند اینترفیس دیگر نیز به آن تزریق شده‌اند. اینترفیس IApplicationRoleManager را ادامه تعریف خواهیم کرد. سایر اینترفیس‌های تزریق شده مانند IUserStore، IDataProtectionProvider و IIdentityMessageService جزو تعاریف اصلی ASP.NET Identity بوده و نیازی به تعریف مجدد آن‌ها نیست. فقط کلاس‌های EmailService و SmsService فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل کرده‌ایم. این کلاس‌ها بر اساس تنظیمات IoC Container مورد استفاده، در اینجا به صورت خودکار ترزیق خواهند شد. حالت پیش فرض آن، وهله سازی مستقیم است که مطابق کدهای فوق به حالت تزریق وابستگی‌ها بهبود یافته‌است.


ب) انتقال کلاس ApplicationSignInManager به لایه سرویس برنامه
کلاس ApplicationSignInManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل می‌کنیم.
using AspNetIdentityDependencyInjectionSample.DomainClasses;
using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
 
namespace AspNetIdentityDependencyInjectionSample.ServiceLayer
{
  public class ApplicationSignInManager :
   SignInManager<ApplicationUser, int>, IApplicationSignInManager
  {
   private readonly ApplicationUserManager _userManager;
   private readonly IAuthenticationManager _authenticationManager;
 
   public ApplicationSignInManager(ApplicationUserManager userManager,
              IAuthenticationManager authenticationManager) :
    base(userManager, authenticationManager)
   {
    _userManager = userManager;
    _authenticationManager = authenticationManager;
   }
  }
}
در اینجا نیز اینترفیس جدید IApplicationSignInManager را برای مخفی سازی پیاده سازی کلاس پایه توکار SignInManager، اضافه کرده‌ایم. این اینترفیس دقیقا حاوی تعاریف متدهای کلاس پایه SignInManager است و نیازی به پیاده سازی مجدد در کلاس ApplicationSignInManager نخواهد داشت.


ج) انتقال کلاس ApplicationRoleManager به لایه سرویس برنامه
کلاس ApplicationRoleManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل خواهیم کرد:
using AspNetIdentityDependencyInjectionSample.DomainClasses;
using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts;
using Microsoft.AspNet.Identity;
 
namespace AspNetIdentityDependencyInjectionSample.ServiceLayer
{
  public class ApplicationRoleManager : RoleManager<CustomRole, int>, IApplicationRoleManager
  {
   private readonly IRoleStore<CustomRole, int> _roleStore;
   public ApplicationRoleManager(IRoleStore<CustomRole, int> roleStore)
    : base(roleStore)
   {
    _roleStore = roleStore;
   }
 
 
   public CustomRole FindRoleByName(string roleName)
   {
    return this.FindByName(roleName); // RoleManagerExtensions
   }
 
   public IdentityResult CreateRole(CustomRole role)
   {
    return this.Create(role); // RoleManagerExtensions
   }
  }
}
روش کار نیز در اینجا همانند دو کلاس قبل است. اینترفیس جدید IApplicationRoleManager را که حاوی تعاریف متدهای کلاس پایه توکار RoleManager است، به لایه سرویس اضافه می‌کنیم. کنترلرهای برنامه با این اینترفیس بجای استفاده مستقیم از کلاس ApplicationRoleManager کار خواهند کرد.

تا اینجا کار تنظیمات لایه سرویس برنامه به پایان می‌رسد.


ساختار پروژه‌ی AspNetIdentityDependencyInjectionSample.IocConfig 

پروژه‌ی IocConfig جایی است که تنظیمات StructureMap را به آن منتقل کرده‌ایم:
using System;
using System.Data.Entity;
using System.Threading;
using System.Web;
using AspNetIdentityDependencyInjectionSample.DataLayer.Context;
using AspNetIdentityDependencyInjectionSample.DomainClasses;
using AspNetIdentityDependencyInjectionSample.ServiceLayer;
using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using StructureMap;
using StructureMap.Web;
 
namespace AspNetIdentityDependencyInjectionSample.IocConfig
{
  public static class SmObjectFactory
  {
   private static readonly Lazy<Container> _containerBuilder =
    new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
   public static IContainer Container
   {
    get { return _containerBuilder.Value; }
   }
 
   private static Container defaultContainer()
   {
    return new Container(ioc =>
    {
      ioc.For<IUnitOfWork>()
        .HybridHttpOrThreadLocalScoped()
        .Use<ApplicationDbContext>();
 
      ioc.For<ApplicationDbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>();
      ioc.For<DbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>();
 
      ioc.For<IUserStore<ApplicationUser, int>>()
       .HybridHttpOrThreadLocalScoped()
       .Use<UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>>();
 
      ioc.For<IRoleStore<CustomRole, int>>()
       .HybridHttpOrThreadLocalScoped()
       .Use<RoleStore<CustomRole, int, CustomUserRole>>();
 
      ioc.For<IAuthenticationManager>()
        .Use(() => HttpContext.Current.GetOwinContext().Authentication);
 
      ioc.For<IApplicationSignInManager>()
        .HybridHttpOrThreadLocalScoped()
        .Use<ApplicationSignInManager>();
 
      ioc.For<IApplicationUserManager>()
        .HybridHttpOrThreadLocalScoped()
        .Use<ApplicationUserManager>();
 
      ioc.For<IApplicationRoleManager>()
        .HybridHttpOrThreadLocalScoped()
        .Use<ApplicationRoleManager>();
 
      ioc.For<IIdentityMessageService>().Use<SmsService>();
      ioc.For<IIdentityMessageService>().Use<EmailService>();
      ioc.For<ICustomRoleStore>()
        .HybridHttpOrThreadLocalScoped()
        .Use<CustomRoleStore>();
 
      ioc.For<ICustomUserStore>()
        .HybridHttpOrThreadLocalScoped()
        .Use<CustomUserStore>();
 
      //config.For<IDataProtectionProvider>().Use(()=> app.GetDataProtectionProvider()); // In Startup class
 
      ioc.For<ICategoryService>().Use<EfCategoryService>();
      ioc.For<IProductService>().Use<EfProductService>();
    });
   }
  }
}
در اینجا نحوه‌ی اتصال اینترفیس‌های برنامه را به کلاس‌ها و یا نمونه‌هایی که آن‌ها را می‌توانند پیاده سازی کنند، مشاهده می‌کنید. برای مثال IUnitOfWork به ApplicationDbContext مرتبط شده‌است و یا دوبار تعاریف متناظر با DbContext را مشاهده می‌کنید. از این تعاریف به صورت توکار توسط ASP.NET Identity زمانیکه قرار است UserStore و RoleStore را وهله سازی کند، استفاده می‌شوند و ذکر آن‌ها الزامی است.
در تعاریف فوق یک مورد را به فایل Startup.cs موکول کرده‌ایم. برای مشخص سازی نمونه‌ی پیاده سازی کننده‌ی IDataProtectionProvider نیاز است به IAppBuilder کلاس Startup برنامه دسترسی داشت. این کلاس آغازین Owin اکنون به نحو ذیل بازنویسی شده‌است و در آن، تنظیمات IDataProtectionProvider را به همراه وهله سازی CreatePerOwinContext مشاهده می‌کنید:
using System;
using AspNetIdentityDependencyInjectionSample.IocConfig;
using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.DataProtection;
using Owin;
using StructureMap.Web;
 
namespace AspNetIdentityDependencyInjectionSample
{
  public class Startup
  {
   public void Configuration(IAppBuilder app)
   {
    configureAuth(app);
   }
 
   private static void configureAuth(IAppBuilder app)
   {
    SmObjectFactory.Container.Configure(config =>
    {
      config.For<IDataProtectionProvider>()
        .HybridHttpOrThreadLocalScoped()
        .Use(()=> app.GetDataProtectionProvider());
    });
    SmObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase();
 
    // Configure the db context, user manager and role manager to use a single instance per request
    app.CreatePerOwinContext(() => SmObjectFactory.Container.GetInstance<IApplicationUserManager>());
 
    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a third party login provider
    // Configure the sign in cookie
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
      AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
      LoginPath = new PathString("/Account/Login"),
      Provider = new CookieAuthenticationProvider
      {
       // Enables the application to validate the security stamp when the user logs in.
       // This is a security feature which is used when you change a password or add an external login to your account.
       OnValidateIdentity = SmObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity()
      }
    });
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
 
    // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
    app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
 
    // Enables the application to remember the second login verification factor such as phone or email.
    // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
    // This is similar to the RememberMe option when you log in.
    app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); 
   }
 
  }
}
این تعاریف از فایل پیش فرض Startup.Auth.cs پوشه‌ی App_Start دریافت و جهت کار با IoC Container برنامه، بازنویسی شده‌اند.


تنظیمات برنامه‌ی اصلی ASP.NET MVC، جهت اعمال تزریق وابستگی‌ها

الف) ابتدا نیاز است فایل Global.asax.cs را به نحو ذیل بازنویسی کنیم:
using System;
using System.Data.Entity;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using AspNetIdentityDependencyInjectionSample.DataLayer.Context;
using AspNetIdentityDependencyInjectionSample.IocConfig;
using StructureMap.Web.Pipeline;
 
namespace AspNetIdentityDependencyInjectionSample
{
  public class MvcApplication : HttpApplication
  {
   protected void Application_Start()
   {
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
 
 
    setDbInitializer();
    //Set current Controller factory as StructureMapControllerFactory
    ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
   }
 
   protected void Application_EndRequest(object sender, EventArgs e)
   {
    HttpContextLifecycle.DisposeAndClearAll();
   }
 
   public class StructureMapControllerFactory : DefaultControllerFactory
   {
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
      if (controllerType == null)
       throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.RawUrl));
      return SmObjectFactory.Container.GetInstance(controllerType) as Controller;
    }
   }
 
   private static void setDbInitializer()
   {
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());
    SmObjectFactory.Container.GetInstance<IUnitOfWork>().ForceDatabaseInitialize();
   }
  }
}
در اینجا در متد setDbInitializer، نحوه‌ی استفاده و تعریف فایل Configuration لایه Data را ملاحظه می‌کنید؛ به همراه متد آغاز بانک اطلاعاتی و اعمال تغییرات لازم به آن در ابتدای کار برنامه. همچنین ControllerFactory برنامه نیز به StructureMapControllerFactory تنظیم شده‌است تا کار تزریق وابستگی‌ها به کنترلرهای برنامه به صورت خودکار میسر شود. در پایان کار هر درخواست نیز منابع Disposable رها می‌شوند.

ب) به پوشه‌ی Models برنامه مراجعه کنید. در اینجا در هر کلاسی که Id از نوع string وجود داشت، باید تبدیل به نوع int شوند. چون primary key برنامه را به نوع int تغییر داده‌ایم. برای مثال کلاس‌های EditUserViewModel و RoleViewModel باید تغییر کنند.

ج) اصلاح کنترلرهای برنامه جهت اعمال تزریق وابستگی‌ها

اکنون اصلاح کنترلرها جهت اعمال تزریق وابستگی‌ها ساده‌است. در ادامه نحوه‌ی تغییر امضای سازنده‌های این کنترلرها را جهت استفاده از اینترفیس‌های جدید مشاهده می‌کنید:
  [Authorize]
public class AccountController : Controller
{
  private readonly IAuthenticationManager _authenticationManager;
  private readonly IApplicationSignInManager _signInManager;
  private readonly IApplicationUserManager _userManager;
  public AccountController(IApplicationUserManager userManager,
          IApplicationSignInManager signInManager,
          IAuthenticationManager authenticationManager)
  {
   _userManager = userManager;
   _signInManager = signInManager;
   _authenticationManager = authenticationManager;
  }

  [Authorize]
public class ManageController : Controller
{
  // Used for XSRF protection when adding external logins
  private const string XsrfKey = "XsrfId";
 
  private readonly IAuthenticationManager _authenticationManager;
  private readonly IApplicationUserManager _userManager;
  public ManageController(IApplicationUserManager userManager, IAuthenticationManager authenticationManager)
  {
   _userManager = userManager;
   _authenticationManager = authenticationManager;
  }

  [Authorize(Roles = "Admin")]
public class RolesAdminController : Controller
{
  private readonly IApplicationRoleManager _roleManager;
  private readonly IApplicationUserManager _userManager;
  public RolesAdminController(IApplicationUserManager userManager,
           IApplicationRoleManager roleManager)
  {
   _userManager = userManager;
   _roleManager = roleManager;
  }


  [Authorize(Roles = "Admin")]
public class UsersAdminController : Controller
{
  private readonly IApplicationRoleManager _roleManager;
  private readonly IApplicationUserManager _userManager;
  public UsersAdminController(IApplicationUserManager userManager,
           IApplicationRoleManager roleManager)
  {
   _userManager = userManager;
   _roleManager = roleManager;
  }
پس از این تغییرات، فقط کافی است بجای خواص برای مثال RoleManager سابق از فیلدهای تزریق شده در کلاس، مثلا roleManager_ جدید استفاده کرد. امضای متدهای یکی است و تنها به یک search و replace نیاز دارد.
البته تعدادی اکشن متد نیز در اینجا وجود دارند که از string id استفاده می‌کنند. این‌ها را باید به int? Id تغییر داد تا با نوع primary key جدید مورد استفاده تطابق پیدا کنند.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
AspNetIdentityDependencyInjectionSample


معادل این پروژه جهت ASP.NET Core Identity : «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه »
مطالب
ایجاد HTTP API توسط Feather HTTP
Feather HTTP یک فریم‌ورک HTTP سبک، برای ایجاد APIهای NET Core. است، در واقع یک wrapper بر روی APIهای موجود ASP.NET Core می‌باشد که به ما امکان ایجاد HTTP API را در کمترین زمان میدهد. در این مطلب نحوه ایجاد یک API را توسط این فریم‌ورک بررسی خواهیم کرد.

معرفی قالب FeatherHttp.Templates به سیستم dotnet
برای شروع می‌توانیم قالب پروژه Feather HTTP را به لیست قالب‌های از پیش نصب شده‌ی dotnet اضافه کنیم. برای اینکار کافی است در خط فرمان دستور زیر را وارد کنیم:
dotnet new -i FeatherHttp.Templates::0.1.67-alpha.g69b43bed72 --nuget-source https://f.feedz.io/featherhttp/framework/nuget/index.json
پس از نصب قالب می‌توانید Feather HTTP را در لیست قالب‌ها توسط دستور dotnet new --list مشاهده کنید:
Templates                                         Short Name               Language          Tags
----------------------------------------------------------------------------------------------------------------------------------
FeatherHttp                                       feather                  [C#]              Web/ASP.NET/FeatherHttp

نحوه‌ی ایجاد یک پروژه‌ی جدید بر اساس قالب جدید
برای ایجاد یک پروژه‌ی جدید کافی است از دستور dotnet new feather استفاده کنید، در ادامه یک پروژه جدید تحت عنوان todoAPI ایجاد خواهیم کرد:
dotnet new feather --name todoAPI
خروجی دستور فوق یک پروژه با ساختار ذیل است:

همانطور که مشاهده می‌کنید پروژه‌ی فوق تنها شامل دو فایل .csproj و Program.cs است. درون Program.cs و متد Main کار initialize کردن سرور HTTP صورت گرفته است. WebApplication.Create دقیقا همانند Host.CreateDefaultBuilder پروژه‌های ASP.NET Core عمل می‌کند؛ یعنی پیکربندی pipeline از قبیل اضافه کردن متغیرهای محیطی، خواندن از فایل JSON و ... را انجام میدهد اما با کد boilerplate کمتر. بنابراین خروجی WebApplication.Create یک ASP.NET Core Pipeline با قابلیت اضافه کردن تنظیمات دلخواه است. در ادامه جهت بررسی بیشتر Feather HTTP، یک مدل را به همراه یک سری دیتای In-memory به پروژه اضافه خواهیم کرد:

using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Linq;

namespace todoAPI.Models
{
    public class Todo
    {
        [JsonPropertyName("id")]
        public int Id { get; set; }
        [JsonPropertyName("title")]
        public string Title { get; set; }
        [JsonPropertyName("completed")]
        public bool Completed { get; set; }
    }

    public class TodoData
    {
        private readonly IList<Todo> _db = new List<Todo>
        {
            new Todo { Id = 1, Title = "Read book" },
            new Todo { Id = 2, Title = "Watch an episode of Dark" },
            new Todo { Id = 3, Title = "Publish a post on dotnettips" },
            new Todo { Id = 4, Title = "Skype with my friend" },
        };
        public IList<Todo> GetAllToDoItmes()
        {
            return _db;
        }
        public void AddTodo(Todo item)
        {
            _db.Add(item);
        }
        public void ToggleTodo(int id)
        {
            var todo = _db.FirstOrDefault(x => x.Id == id);
            todo.Completed = !todo.Completed;
        }

        public void DeleteTodo(int id)
        {
            var todo = _db.FirstOrDefault(x => x.Id == id);
            _db.Remove(todo);
        }
    }
}

در مثال فوق برای نگاشت نام خواص، از System.Text.Json توکار NET Core 3.0. استفاده شده‌است. در ادامه نیز از یک کلاس برای شبیه‌سازی CRUD یک Todo استفاده شده‌است. سپس برای داشتن اندپوینت‌های موردنظر به ازای هر کدام از متدهای فوق درون متد Main، از app.Map... استفاده کرده‌ایم:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using todoAPI.Models;

namespace todoAPI
{
    class Program
    {
        private static readonly TodoData db = new TodoData();
        static async Task Main(string[] args)
        {
            var app = WebApplication.Create(args);

            app.MapGet("/", GetTodos);
            app.MapPost("/api/todos", CreateTodo);
            app.MapPost("/api/todos/{id}", ToggleTodo);
            app.MapDelete("/api/todos/{id}", DeleteTodo);

            await app.RunAsync();
        }

        static async Task GetTodos(HttpContext http)
        {
            var todos = db.GetAllToDoItmes();
            await http.Response.WriteJsonAsync(todos);
        }

        static async Task CreateTodo(HttpContext http)
        {
            var todo = await http.Request.ReadJsonAsync<Todo>();
            db.AddTodo(todo);
            http.Response.StatusCode = 204;
        }

        static async Task ToggleTodo(HttpContext http)
        {
            if (!http.Request.RouteValues.TryGet("id", out int id))
            {
                http.Response.StatusCode = 400;
                return;
            }
            db.ToggleTodo(id);
            http.Response.StatusCode = 204;
        }

        static async Task DeleteTodo(HttpContext http)
        {
            if (!http.Request.RouteValues.TryGet("id", out int id))
            {
                http.Response.StatusCode = 400;
                return;
            }
            db.DeleteTodo(id);
            http.Response.StatusCode = 204;
        }
    }
}


هر کدام از اندپوینت‌های فوق، یک ورودی HttpContext دریافت خواهند کرد. توسط این شیء می‌توانیم به درخواست جاری و همچنین به پاسخ درخواست، دسترسی داشته باشیم. 


استفاده از سیستم DI توکار NET Core.

همانطور که در ابتدای مطلب نیز عنوان شد، Feather HTTP یک wrapper بر روی APIهای موجود ASP.NET Core است، بنابراین می‌توانیم از همان سرویس DI که درون پروژه‌های ASP.NET Core در اختیار داریم در اینجا نیز استفاده کنیم. در ادامه یک پوشه‌ی جدید را به مثال قبل، با نام Controllers اضافه خواهیم کرد و درون آن یک فایل TodoController را با محتویات زیر ایجاد خواهیم کرد:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using todoAPI.Models;
using todoAPI.Services;

namespace todoAPI.Controllers
{
    public class TodoController
    {
        private readonly ITodoService _todoService;

        public TodoController(ITodoService todoService)
        {
            _todoService = todoService;
        }

        public async Task GetTodos(HttpContext http)
        {
            var todos = _todoService.GetAllToDoItmes();
            await http.Response.WriteJsonAsync(todos);
        }

        public async Task CreateTodo(HttpContext http)
        {
            var todo = await http.Request.ReadJsonAsync<Todo>();
            _todoService.AddTodo(todo);
            http.Response.StatusCode = 204;
        }

        public async Task ToggleTodo(HttpContext http)
        {
            if (!http.Request.RouteValues.TryGet("id", out int id))
            {
                http.Response.StatusCode = 400;
                return;
            }
            _todoService.ToggleTodo(id);
            http.Response.StatusCode = 204;
        }

        public async Task DeleteTodo(HttpContext http)
        {
            if (!http.Request.RouteValues.TryGet("id", out int id))
            {
                http.Response.StatusCode = 400;
                return;
            }
            _todoService.DeleteTodo(id);
            http.Response.StatusCode = 204;
        }
    }
}


کاری که انجام شده است، انتقال تمامی متدهای static به کلاس فوق و سپس جایگزین کردن کلمه‌ی کلیدی static با public است. همچنین یه ارجاع به اینترفیس جدید با عنوان ITodoService اضافه شده است؛ درون پیاده‌سازی این اینترفیس همان متدهای کلاس TodoData را اضافه کرده‌ایم:

using System.Collections.Generic;
using todoAPI.Models;
using System.Linq;

namespace todoAPI.Services
{
    public interface ITodoService
    {
        void AddTodo(Todo item);
        void DeleteTodo(int id);
        IList<Todo> GetAllToDoItmes();
        void ToggleTodo(int id);
    }

    public class TodoService : ITodoService
    {
        private readonly IList<Todo> _db = new List<Todo>
        {
            new Todo { Id = 1, Title = "Read book" },
            new Todo { Id = 2, Title = "Watch an episode of Dark" },
            new Todo { Id = 3, Title = "Publish a post on dotnettips" },
            new Todo { Id = 4, Title = "Skype with my friend" },
        };
        public IList<Todo> GetAllToDoItmes()
        {
            return _db;
        }
        public void AddTodo(Todo item)
        {
            _db.Add(item);
        }
        public void ToggleTodo(int id)
        {
            var todo = _db.FirstOrDefault(x => x.Id == id);
            todo.Completed = !todo.Completed;
        }

        public void DeleteTodo(int id)
        {
            var todo = _db.FirstOrDefault(x => x.Id == id);
            _db.Remove(todo);
        }
    }
}


نکته: برای ایجاد اینترفیس از روی یک کلاس درون VS Code می‌توانیم اینگونه عمل کنیم:



تغییرات فایل Program.cs

ابتدا باید using مربوط به DI را در ابتدای فایل اضافه کنیم:

using Microsoft.Extensions.DependencyInjection;


سپس توسط ServiceProvider یک وهله از کلاس موردنظر را ایجاد کرده‌ایم و همچنین سرویس‌های موردنظر را درون DI Container اضافه کرده‌ایم:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using todoAPI.Controllers;
using todoAPI.Services;

namespace todoAPI
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            builder.Services.AddTransient<TodoController>();
            builder.Services.AddTransient<ITodoService, TodoService>();

            var serviceProvider = builder.Services.BuildServiceProvider();
            var todoController = serviceProvider.GetService<TodoController>();

            var app = WebApplication.Create(args);

            app.MapGet("/", todoController.GetTodos);
            app.MapPost("/api/todos", todoController.CreateTodo);
            app.MapPost("/api/todos/{id}", todoController.ToggleTodo);
            app.MapDelete("/api/todos/{id}", todoController.DeleteTodo);

            await app.RunAsync();
        }
    }
}



Convention Over Configuration

در کد قبلی به صورت دستی TodoController را توسط Service Location از DI درخواست کرده‌ایم. اینکار را در ادامه می‌توانیم به Feather HTTP سپرده تا کار وهله‌سازی را براساس قواعد توکار برایمان انجام دهد:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using todoAPI.Services;

namespace todoAPI
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            builder.Services.AddControllers();

            builder.Services.AddSingleton<ITodoService, TodoService>();

            var serviceProvider = builder.Services.BuildServiceProvider();

            var app = builder.Build();

            app.MapControllers();

            await app.RunAsync();
        }
    }
}


سپس در ادامه برای دسترسی به HTTP Context درون TodoController از IHttpContextAccessor استفاده کرده‌ایم:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using todoAPI.Models;
using todoAPI.Services;

namespace todoAPI.Controllers
{
    public class TodoController
    {
        private readonly ITodoService _todoService;
        private readonly IHttpContextAccessor _accessor;
        public TodoController(ITodoService todoService, IHttpContextAccessor accessor)
        {
            _todoService = todoService;
            _accessor = accessor;
        }

        [HttpGet("/todos")]
        public async Task GetTodos()
        {
            var todos = _todoService.GetAllToDoItmes();
            await _accessor.HttpContext.Response.WriteJsonAsync(todos);
        }

        [HttpPost("/todos")]
        public async Task CreateTodo()
        {
            var todo = await _accessor.HttpContext.Request.ReadJsonAsync<Todo>();
            _todoService.AddTodo(todo);
            _accessor.HttpContext.Response.StatusCode = 204;
        }

        [HttpPost("/todos/{id}")]
        public async Task ToggleTodo(int id)
        {
            _todoService.ToggleTodo(id);
            _accessor.HttpContext.Response.StatusCode = 204;
        }

        [HttpDelete("/todos/{id}")]
        public async Task DeleteTodo(int id)
        {
            _todoService.DeleteTodo(id);
            _accessor.HttpContext.Response.StatusCode = 204;
        }
    }
}


کدهای کامل مطلب را می‌توانید از اینجا دریافت کنید.

نظرات مطالب
EF Code First #12
یکی از اشکالات repository این بود که در عمل و دنیای واقعی، قابلیتی برای تعویض ORM ایجاد نمی‌کرد و گفتید در صورتی می‌توان این کار را کرد که دست و پای ORM را ببندیم و از مشترکات استفاده کنیم. اما در الگویی که معرفی کردید یعنی IUnitOfWork هم ظاهرا دست و پایمان برای پیاده سازی متدهای خاص بسته است مانند متد ساده زیر
public void ChangeState<T>(T entity, EntityState state) where T : class
{
     Entry<T>(entity).State = state;
}
یا متدی که در بالا ذکر شد یعنی:
 DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
اینطور نیست؟
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویس‌ها و تزریق وابستگی‌ها
پیشنیازها (الزامی)

«بررسی مفاهیم معکوس سازی وابستگی‌ها و ابزارهای مرتبط با آن»
«اصول طراحی SOLID»
«مطالعه‌ی بیشتر»


تزریق وابستگی‌ها (یا Dependency injection = DI) به معنای ارسال نمونه‌ای/وهله‌ای از وابستگی (یک سرویس) به شیء وابسته‌ی به آن (یک کلاینت) است. در فرآیند تزریق وابستگی‌ها، یک کلاس، وهله‌های کلاس‌های دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت می‌کند. بنابراین بجای نوشتن newها در کلاس جاری، آن‌ها را به صورت وابستگی‌هایی در سازنده‌ی کلاس تعریف می‌کنیم تا توسط یک IoC Container تامین شوند. در اینجا به فریم ورک‌هایی که کار وهله سازی این وابستگی‌ها را انجام می‌دهند، IoC Container و یا DI container می‌گوییم (IoC =  inversion of control ).
چندین نوع تزریق وابستگی‌ها وجود دارند که دو حالت زیر، عمومی‌ترین آن‌ها است:
الف) تزریق در سازنده‌ی کلاس: لیست وابستگی‌های یک کلاس، به عنوان پارامترهای سازنده‌ی آن ذکر می‌شوند.
ب) تزریق در خواص یا Setter injection: کلاینت خواصی get و set را به صورت public معرفی می‌کند و سپس IoC Container با وهله سازی آن‌ها، وابستگی‌های مورد نیاز را تامین خواهد کرد.


تزریق وابستگی‌ها در ASP.NET Core

برخلاف نگارش‌های قبلی ASP.NET، این نگارش جدید از ابتدا با دید پشتیبانی کامل از DI طراحی شده‌است و این مفهوم، در سراسر اجزای آن به صورت یکپارچه‌ای پشتیبانی می‌شود. همچنین به همراه یک minimalistic DI container توکار نیز هست .
این IoC Container توکار از 4 حالت طول عمر ذیل پشتیبانی می‌کند:
- instance: در هربار نیاز به یک وابستگی خاص، تنها یک وهله از آن در اختیار مصرف کننده قرار می‌گیرد و در اینجا شما هستید که مسئول تعریف نحوه‌ی وهله سازی این شیء خواهید بود (برای بار اول).
- transient: هربار که نیاز به وابستگی خاصی بود، یک وهله‌ی جدید از آن توسط IoC Container تولید و ارائه می‌شود.
- singleton: در این حالت تنها یک وهله از وابستگی درخواست شده در طول عمر برنامه تامین می‌شود.
- scoped: در طول عمر یک scope خاص، تنها یک وهله از وابستگی درخواست شده، در اختیار مصرف کننده‌ها قرار می‌گیرد. برای مثال مرسوم است که به ازای یک درخواست وب، تنها یک وهله از شیء‌ایی خاص در اختیار تمام مصرف کننده‌های آن قرار گیرد (single instance per web request).

طول عمر singleton، برای سرویس‌ها و کلاس‌های config مناسب هستند. به این ترتیب به کارآیی بالاتری خواهیم رسید و دیگر نیازی نخواهد بود تا هر بار این اطلاعات خوانده شوند. حالت scoped برای وهله سازی الگوی واحد کار و پیاده سازی تراکنش‌ها مناسب است. برای مثال در طی یک درخواست وب، یک تراکنش باید صورت گیرد.
حالت scoped در حقیقت نوع خاصی از حالت transient است. در حالت transient صرفنظر از هر حالتی، هربار که وابستگی ویژه‌ای درخواست شود، یک وهله‌ی جدید از آن تولید خواهد شد. اما در حالت scoped فقط یک وهله‌ی از وابستگی مورد نظر، در بین تمام اشیاء وابسته‌ی به آن، در طول عمر آن scope تولید می‌شود.
بنابراین در برنامه‌های وب دو نوع singleton برای معرفی کلاس‌های config و نوع scoped برای پیاده سازی تراکنش‌ها و همچنین بالابردن کارآیی برنامه در طی یک درخواست وب (با عدم وهله سازی بیش از اندازه‌ی از کلاس‌های مختلف مورد نیاز)، بیشتر از همه به کار برده می‌شوند.


یک مثال کاربردی: بررسی نحوه‌ی تزریق یک سرویس سفارشی به کمک IoC Container توکار ASP.NET Core


مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شده‌است، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشه‌ی src، کلیک راست کرده و گزینه‌ی Add new project را انتخاب کنید و سپس یک پروژه‌ی جدید از نوع NET Core -> Class library. را به آن، با نام Core1RtmEmptyTest.Services اضافه کنید (تصویر فوق).
در ادامه کلاس نمونه‌ی سرویس پیام‌ها را به همراه اینترفیس آن، با محتوای زیر به آن اضافه کنید:
namespace Core1RtmEmptyTest.Services
{
    public interface IMessagesService
    {
        string GetSiteName();
    }
 
    public class MessagesService : IMessagesService
    {
        public string GetSiteName()
        {
            return "DNT";
        }
    }
}
در ادامه به پروژه‌ی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.Services را انتخاب کنید، تا اسمبلی آن‌را بتوان در پروژه‌ی جاری استفاده کرد.


انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{
    "dependencies": {
        // same as before
        "Core1RtmEmptyTest.Services": "1.0.0-*"
    },
در ادامه قصد داریم این سرویس را به متد Configure کلاس Startup تزریق کرده و سپس خروجی رشته‌ای آن‌را توسط میان افزار Run آن نمایش دهیم. برای این منظور فایل Startup.cs را گشوده و امضای متد Configure را به نحو ذیل تغییر دهید:
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
همانطور که در قسمت قبل نیز عنوان شد، متد Configure دارای امضای ثابتی نیست و هر تعداد سرویسی را که نیاز است، می‌توان در اینجا اضافه کرد. یک سری از سرویس‌ها مانند IApplicationBuilder و IHostingEnvironment پیشتر توسط IoC Container توکار ASP.NET Core معرفی و ثبت شده‌اند. به همین جهت، همینقدر که در اینجا ذکر شوند، کار می‌کنند و نیازی به تنظیمات اضافه‌تری ندارند. اما سرویس IMessagesService ما هنوز به این IoC Container معرفی نشده‌است. بنابراین نمی‌داند که چگونه باید این اینترفیس را وهله سازی کند.
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
{ 
    app.Run(async context =>
    {
        var siteName = messagesService.GetSiteName();
        await context.Response.WriteAsync($"Hello {siteName}");
    });
}
در این حالت اگر برنامه را اجرا کنیم، به این خطا برخواهیم خورد:
 System.InvalidOperationException
No service for type 'Core1RtmEmptyTest.Services.IMessagesService' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)

System.Exception
Could not resolve a service of type 'Core1RtmEmptyTest.Services.IMessagesService' for the parameter 'messagesService' of method 'Configure' on type 'Core1RtmEmptyTest.Startup'.
at Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
برای رفع این مشکل، به متد ConfigureServices کلاس Startup مراجعه کرده و سیم کشی‌های مرتبط را انجام می‌دهیم. در اینجا باید اعلام کنیم که «هر زمانیکه به IMessagesService رسیدی، یک وهله‌ی جدید (transient) از کلاس MessagesService را به صورت خودکار تولید کن و سپس در اختیار مصرف کننده قرار بده»:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IMessagesService, MessagesService>();
    }
در اینجا نحوه‌ی ثبت یک سرویس را در IoC Containser توکار ASP.NET Core ملاحظه می‌کنید. تمام حالت‌های طول عمری که در ابتدای بحث عنوان شدند، یک متد ویژه‌ی خاص خود را در اینجا دارند. برای مثال حالت transient دارای متد ویژه‌ی AddTransient است و همینطور برای سایر حالت‌ها. این متدها به صورت جنریک تعریف شده‌اند و آرگومان اول آن‌ها، اینترفیس سرویس و آرگومان دوم، پیاده سازی آن‌ها است (سیم کشی اینترفیس، به کلاس پیاده سازی کننده‌ی آن).

پس از اینکار، مجددا برنامه را اجرا کنید. اکنون این خروجی باید مشاهده شود:


و به این معنا است که اکنون IoC Cotanier توکار ASP.NET Core، می‌داند زمانیکه به IMessagesService رسید، چگونه باید آن‌را وهله سازی کند.


چه سرویس‌هایی به صورت پیش فرض در IoC Container توکار ASP.NET Core ثبت شده‌اند؟

در ابتدای متد ConfigureServices یک break point را قرار داده و برنامه را در حالت دیباگ اجرا کنید:


همانطور که ملاحظه می‌کنید، به صورت پیش فرض 16 سرویس در اینجا ثبت شده‌اند که تاکنون با دو مورد از آن‌ها کار کرده‌ایم.


امکان تزریق وابستگی‌ها در همه جا!

در مثال فوق، سرویس سفارشی خود را در متد Configure کلاس آغازین برنامه تزریق کردیم. نکته‌ی مهم اینجا است که برخلاف نگارش‌های قبلی ASP.NET MVC (یعنی بدون نیاز به تنظیمات خاصی برای قسمت‌های مختلف برنامه)، می‌توان این تزریق‌ها را در کنترلرها، در میان افزارها، در فیلترها در ... همه جا و تمام اجزای ASP.NET Core 1.0 انجام داد و دیگر اینبار نیازی نیست تا نکته‌ی ویژه‌ی نحوه‌ی تزریق وابستگی‌ها در فیلترها یا کنترلرهای ASP.NET MVC را یافته و سپس اعمال کنید. تمام این‌ها از روز اول کار می‌کنند. همینقدر که کار ثبت سرویس خود را در متد ConfigureServices انجام دادید، این سرویس در سراسر اکوسیستم ASP.NET Core، قابل دسترسی است.


نیاز به تعویض IoC Container توکار ASP.NET Core

قابلیت تزریق وابستگی‌های توکار ASP.NET Core صرفا جهت برآورده کردن نیازمندی‌های اصلی آن طراحی شده‌است و نه بیشتر. بنابراین توسط آن قابلیت‌های پیشرفته‌ای را که سایر IoC Containers ارائه می‌دهند، نخواهید یافت. برای مثال تعویض امکانات تزریق وابستگی‌های توکار ASP.NET Core با StructureMap این مزایا را به همراه خواهد داشت:
 • امکان ایجاد child/nested containers (پشتیبانی از سناریوهای چند مستاجری)
 • پشتیبانی از Setter Injection
 • امکان انتخاب سازنده‌ای خاص (اگر چندین سازنده تعریف شده باشند)
 • سیم کشی خودکار یا Conventional "Auto" Registration (برای مثال اتصال اینترفیس IName به کلاس Name به صورت خودکار و کاهش تعداد تعاریف ابتدای برنامه)
 • پشتیبانی توکار از Lazy و Func
 • امکان وهله سازی از نوع‌های concrete (یا همان کلاس‌های معمولی)
 • پشتیبانی از مفاهیمی مانند Interception و AOP
 • امکان اسکن اسمبلی‌های مختلف جهت یافتن اینترفیس‌ها و اتصال خودکار آن‌ها (طراحی‌های افزونه پذیر)


روش تعویض IoC Container توکار ASP.NET Core با StructureMap

جزئیات این جایگزین کردن را در مطلب «جایگزین کردن StructureMap با سیستم توکار تزریق وابستگی‌ها در ASP.NET Core 1.0» می‌توانید مطالعه کنید.
یا می‌توانید از روش فوق استفاده کنید و یا اکنون قسمتی از پروژه‌ی رسمی استراکچرمپ در آدرس https://github.com/structuremap/structuremap.dnx جهت کار با NET Core. طراحی شده‌است. برای کار با آن نیاز است این مراحل طی شوند:
الف) دریافت بسته‌ی نیوگت StructureMap.Dnx
برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن، StructureMap.Dnx را جستجو کرده و نصب نمائید (تیک مربوط به انتخاب pre releases هم باید انتخاب شده باشد):


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{
    "dependencies": {
        // same as before  
        "StructureMap.Dnx": "0.5.1-rc2-final"
    },
ب) جایگزین کردن Container استراکچرمپ با Container توکار ASP.NET Core
پس از نصب بسته‌ی StructureMap.Dnx، به کلاس آغازین برنامه مراجعه کرده و این تغییرات را اعمال کنید:
public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddDirectoryBrowser();
 
        var container = new Container();
        container.Configure(config =>
        {
            config.Scan(_ =>
            {
                _.AssemblyContainingType<IMessagesService>();
                _.WithDefaultConventions();
            });
            //config.For<IMessagesService>().Use<MessagesService>();
 
            config.Populate(services);
        });
        container.Populate(services);
 
        return container.GetInstance<IServiceProvider>();
    }
در اینجا ابتدا خروجی متد ConfigureServices، به IServiceProvider تغییر کرده‌است تا استراکچرمپ این تامین کننده‌ی سرویس‌ها را ارائه دهد. سپس Container مربوط به استراکچرمپ، وهله سازی شده و همانند روال متداول آن، یک سرویس و کلاس پیاده سازی کننده‌ی آن معرفی شده‌اند (و یا هر تنظیم دیگری را که لازم بود باید در اینجا اضافه کنید). در پایان کار متد Configure آن و پس از این متد، نیاز است متدهای Populate فراخوانی شوند (اولی تعاریف را اضافه می‌کند و دومی کار تنظیمات را نهایی خواهد کرد).
سپس وهله‌ای از IServiceProvider، توسط استراکچرمپ تامین شده و بازگشت داده می‌شود تا بجای IoC Container توکار ASP.NET Core استفاده شود.
در این مثال چون در متد Scan، کار بررسی اسمبلی لایه سرویس برنامه با قراردادهای پیش فرض استراکچرمپ انجام شده‌است، دیگر نیازی به سطر تعریف config.For نیست. در اینجا هرگاه IName ایی یافت شد، به کلاس Name متصل می‌شود (name هر نامی می‌تواند باشد).
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 20 - بررسی تغییرات فیلترها
- دقیقا مشابه AddHeaderAttribute مطلب جاری است (موارد تعریف و کاربرد AddHeaderAttribute را در صفحه جاری جستجو کنید ).
- یک مثال دیگر:
تعریف یک فیلتر سفارشی با دریافت دو پارامتر رشته‌ای و یک اینترفیس در سازنده‌ی آن:
    public class CustomActionFilterAttribute : Attribute, IActionFilter
    {
        private readonly string _param1;
        private readonly string _param2;
        private readonly IJob _job;

        public CustomActionFilterAttribute(string param1, string param2, IJob job)
        {
            _param1 = param1;
            _param2 = param2;
            _job = job;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            throw new NotImplementedException();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            throw new NotImplementedException();
        }
    }
این اینترفیس هم به صورت زیر تعریف شده‌است:
    public interface IJob
    {
        void Start();
    }

    public class Job1 : IJob
    {
        public void Start()
        {

        }
    }
و نحوه‌ی تامین وابستگی‌های ترزیق آن، در کلاس آغازین برنامه به صورت ذیل ثبت و معرفی شده‌است:
public void ConfigureServices(IServiceCollection services)
{
      services.AddTransient<IJob, Job1>();

پس از این تنظیمات، روش فراخوانی این فیلتر به صورت ذیل است:
[TypeFilter(typeof(CustomActionFilterAttribute),
                     Arguments = new object[] { "param1Value", "param2Value" })]
public IActionResult About()
{

اکنون اگر برنامه را اجرا کنیم، با رسیدن به مسیر About، مقدار دهی صحیح پارامترهای تزریق شده‌ی به سازنده‌ی فیلتر سفارشی مشخص هستند:

مطالب
مقدمه ای بر CQRS و Event Sourcing
به صورت عام، functionality اکثر پروژه‌های نرم افزاری تجاری خلاصه میشود به مخفف معروفی به نام CRUD، که object‌ها را میسازیم، آن‌ها را میخوانیم و تغییر میدهیم.
اپلیکیشن‌های طراحی شده بدین صورت، قابلیت خوانایی بالایی خواهند داشت و دیاگرام طراحی آنها چیزی شبیه به تصویر زیر میباشد

در واقع ما یک سیستمی داریم که شامل مدلی است از دیتا‌های ما و از این مدل برای کوئری گرفتن از دیتابیس استفاده میشود، که البته برای بیشتر پروژه‌های نرم افزاری، معماری درست و ترجیح داده شده‌ای هم میباشد.

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

ذات CQRS بر آن است که شما مدل‌های مختلفی را برای خواندن و نوشتن دیتا داشته باشید. الگوی آن چیزی شبیه به تصویر زیر است

چیزی که در این روش مشهود است این میباشد که برنامه نویسان باید قسمت‌های Command و Query را به صورت جداگانه طراحی نمایند.
CQRS این قابلیت را به شما میدهد که interface و Datastore و حتی بطور کامل Technology مجزایی در قسمت‌های CQ داشته باشید.

Event Sourcing
قسمت دوم و متمایز، معماری Event Sourcing یا ES میباشد که بصورت کوتاه، ES یک روش متفاوت برای Data storage میباشد.
اکثرا ما از Datastore‌هایی که مدلی از دیتا را انعکاس می‌دهند استفاده میکنیم. به مثال ساده‌ی زیر توجه کنید

ES از برنامه نویسان میخواهد که مدل سنتی CRUD را فراموش کرده و بجای آن تغییراتی را که روی دیتا صورت گرفته، نیز درج نمایند. اینکار به وسیله‌ی یک دیتابیس Append-only انجام میشود که به نام Event Store شناخته میشود.

در این معماری ما همه‌ی تغییرات روی دیتا را به صورت Serialize Event ذخیره میکنیم که میتواند دوباره در هر زمانی اجرا شده و current state هر objectی را در اختیار بگذارد.

این روش به ما کمک بزرگی میکند تا وضعیت یک object را در گذشته به راحتی پیدا کنیم و از آن میتوان به غیر از فوایدی که دارد، به عنوان یک Logger نیز استفاده نمود. به دلیل اینکه جزء به جزء تغییرات بر روی state سیستم، در آن ثبت شده است. از آنجاییکه دیتا بصورت serialize ذخیره میشود، بارگزاری آن نیز با سرعت بالایی انجام خواهد شد.

پس بصورت خلاصه در معماری Command Query Responsibility Segregation ابتدا باید به این موضوع توجه داشت که قسمت‌های Read و Write نرم افزار به صورت مجزایی طراحی میشوند و Event Sourcing شامل تغییراتی که روی data انجام شده است، میباشد و به‌صورت Serialize شده ذخیره میشود. ما تنها به یک دیتابیس و یک جدول برای نمایش event store نیازمندیم (بستگی به نیازتان میتوان تعداد آن را نیز بیشتر نمود و همچنین حتما لزومی ندارد که از دیتابیس‌های رابطه‌ای استفاده شود؛ بصورت مثال پیاده سازی این قسمت را میتوان با استفاده از Redis که دیتابیسی غیر رابطه‌ای و باسرعت میباشد استفاده نمود).
برای شروع کار (نه پیاده سازی کامل) باید با قسمت‌های مختلف طراحی در این معماری آشنا شویم:
Domain Object
نکته: SimpleCqrs فریم ورکی برای پیاده سازی معماری CQRS , ES میباشد که برای ساده‌تر شدن کار، از آن استفاده شده است (شما حتی میتوانید پیاده سازی خود را داشته باشید)
مدل Movie از کلاسی به نام AggregateRoot ارث بری کرده‌است که توسط SimpleCQRS پیاده سازی شده‌است و یک guid key در آن تعبیه شده است (Aggregate root از مباحث Domain Driven برگرفته شده است و آشنایی با آن کمک شایانی به درک عمیق‌تر روی این مباحث مینماید).
public class Movie : AggregateRoot  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public Movie() { }

    public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes)
    {
        //پیاده سازی خواهد شد
    }
}
توجه: SimpleCQRS فقط پیاده سازی guid برای کلید مربوط به هر مدل را پیاده سازی نموده است؛ بنابراین کلید مدل نمیتواند integer باشد.

Commands
command دستوراتی است که توسط end user فراخوانی میشود که باعث تغییرات خواهد شد. وقتی اپلیکیشن یک command را دریافت مینماید، command handler به پردازش آن برای فهمیدن خواسته کاربر میپردازد و پس از آن event مربوطه را برای اجرای آن وظیفه‌ی خاص صدا میزند.
همه‌ی command‌ها تغییراتی بر روی state جاری خواهند داشت. در نتیجه دیتا‌های ذخیره شده درون دیتابیس تغییرات خواهند کرد. هر commandی که تغییری بر روی State سیستم نداشته باشد، یک دستور غلط محسوب شده و باید در سمت query‌ها آن را پیاده سازی نمود.
در نتیجه Commnad‌ها دستوراتی هستند که از طرف کاربر برای تغییرات بر روی دیتا‌های ذخیره شده، ارسال میشوند.
فرض کنید Domain Objectی برای Movie تعریف کرده‌ایم و میخواهیم دستور اضافه کردن فیلم را پیاده سازی نماییم
public class CreateMovieCommand : ICommand  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }
    public CreateMovieCommand(string title, DateTime releaseDate, int runningTime)
    {
        Title = title;
        ReleaseDate = releaseDate;
        RunningTimeMinutes = runningTime;
    }
}
توجه: ICommand از طریق SimpleCQRS اضافه شده‌است.

Command Handler
بعد از اینکه Command مورد نیاز نوشته شد، حال احتیاج به پیاده سازی CommandHandler مربوطه که دستور متناظر را پردازش میکند، داریم.
public class CreateMovieCommandHandler : CommandHandler<CreateMovieCommand>  
{
    protected IDomainRepository _repository;

    public CreateMovieCommandHandler(IDomainRepository repository)
    {
        _repository = repository;
    }

    public override void Handle(CreateMovieCommand command)  
    {
        var movie = new Domain.Movie(Guid.NewGuid(), command.Title, 
    command.ReleaseDate, command.RunningTimeMinutes);

        _repository.Save(movie);
    }
}
Command Handler باید از کلاس جنریک <CommandHandler<T ارث بری نماید و T باید از نوع Command در نظر گرفته شود و همچنین IDomainRepository اینترفیسی است که توسط SimpleCQRS تعریف شده‌است و ما احتیاجی به پیاده سازی آن نداریم (در قسمت‌های بعدی پیکربندی آن را انجام میدهیم).
برای رسیدگی کردن به دستور مربوطه احتیاج به override کردن متد Handle میباشد.
کار اساسی توسط متد Save انجام میشود که همه‌ی event‌های pending شده توسط Domain Object را گرفته و آنها را به Event Store میفرستد.

Events
event‌ها تغییراتی هستند بر روی State جاری سیستم که توسط کاربر به وسیله‌ی Commandها فراخوانی میشوند.
رویداد‌ها serialize میشوند و درون Event Store ذخیره میشوند؛ بنابراین میتوان فراخوانی آنها را در هر لحظه انجام داد.
هر تعداد Event میتواند توسط یک دستور raise شود.
ساخت یک Event:
قبلا دستوری را برای ساخت یک movie نوشتیم و حال احتیاج به event مربوطه را داریم:
public class MovieCreatedEvent : DomainEvent  
{
    public Guid MovieId
    {
        get { return AggregateRootId; }
        set { AggregateRootId = value;}
    }

    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public MovieCreatedEvent(Guid movieId, string title, DateTime releaseDate, int runningTime)
    {
        MovieId = movieId;
        Title = title;
        ReleaseDate = releaseDate;
        RunningTimeMinutes = runningTime;
    }
}
فراموش نکنید که این کلاس آبجکتی خواهد بود که Serialize شده و در دیتابیس ذخیره خواهد شد. باید همه‌ی پراپرتی‌های لازم که با استفاده از این Event ممکن است تغییر کنند را شامل شود (بدیهی است که این پراپرتی‌ها از Domain Object گرفته میشود).
public class Movie : AggregateRoot  
{
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public int RunningTimeMinutes { get; set; }

    public Movie(Guid movieId, string title, DateTime releaseDate, int runningTimeMinutes)
    {
        Apply(new MovieCreatedEvent(Guid.NewGuid(), title, releaseDate, runningTimeMinutes));
    }
}
به Aggregate فوق که در اوایل بحث صحبت شده‌است دقت کنید. حال متد Apply باعث میشود که event مربوطه درون بخش لوکال aggregate root ذخیره شود. بنابراین بعدا میتواند به صورت فیزیکی درون Event Store ذخیره شود.

Event Handler
هر Event Handler  میتواند تعداد زیادی از IHandleDomainEvents ‌ها را پیاده سازی نماید. حال متد Handle این اینترفیس را پیاده سازی نمودیم. 
public class MovieEventHandler : IHandleDomainEvents<MovieCreatedEvent>  
{
    public void Handle(MovieCreatedEvent createdEvent)
    {
        using (MoviesContext entities = new MoviesContext())
        {
            entities.Movies.Add(new Movie()
            {
                Id = createdEvent.AggregateRootId,
                Title = createdEvent.Title,
                ReleaseDate = createdEvent.ReleaseDate,
                RunningTimeMinutes = createdEvent.RunningTimeMinutes
            });

            entities.SaveChanges();
        }
    }
}
مثلا در این قسمت با استفاده از ORM، شیء مورد نظر به صورت فیزیکی درون دیتابیس ذخیره میشود.
در قسمت آخر نیازمندیم که تغییرات زیر را به Movie اضافه نماییم.
درون Doamin Objectی که قبلا تعریف کرده بودیم متدی را به صورت زیر پیاده سازی مینماییم
protected void OnMovieCreated(MovieCreatedEvent domainEvent)
        {
            Id = domainEvent.AggregateRootId;
            Title = domainEvent.Title;
            ReleaseDate = domainEvent.ReleaseDate;
            RunningTimeMinutes = domainEvent.RunningTimeMinutes;
        }
باعث میشود پس از فراخوانی شدن Event، تغییرات صورت گرفته‌ی بر state سیستم، بر روی Domain Object اعمال شود و آن را بروزرسانی نماید. این متد دقیقا بصورت اتوماتیک وقتی که event مربوطه raise میشود، فراخوانی میشود.
پس از ترکیب CQRS و ES معماری اولیه‌ی سیستم چیزی شبیه به دیاگرام زیر خواهد بود (بسته به سناریوهای خاص میتواند سفارشی سازی شود)

خلاصه:
کاربر دستوری را از طریق برنامه به سیستم ارسال مینماید.
command مربوطه دریافت میشود و به روی Command Bus قرار داده میشود.
Command Handler وظیفه‌ی تفسیر کردن Command مربوطه را به عهده میگیرد و به وسیله‌ی Domain object آن event مورد نظر فراخوانی خواهد شد و باعث میشود domain object بروزرسانی گردد.
Event همان objectی است که باید به صورت serialize شده درون append only database ذخیره شود.
Event handler رویداد مربوطه را گرفته و بصورت فیزیکی مقادیر مورد نظر را در دیتابیس ذخیره مینماید.

Query
از آنجاییکه قسمت Read، در سیستم به صورت CQRS طراحی میشود، به راحتی میتوان query‌ها را optimize کرده و به صورت مثال به جای استفاده از ORM‌های معمول بطور مستقیم Stored Procedure فراخوانی کرده، تا جای ممکن کیفیت query‌ها بهترین حالت ممکن باشند. در حالیکه در مدل CRUD بهینه کردن بخش read بسیار پیچیده و بعضا غیر ممکن میباشد.

مزایای استفاده از این مدل
Distributed Systems Capabilities
یکی از مهمترین مزیت‌های این مدل تسهیم گسترش پذیری سیستم بر روی ماشین‌های فیزیکی مختلف از طریق messaging pattern میباشد.
High Availability
از آنجایی که سیستم توزیع پذیر طراحی شده‌است، هر قسمت از آن میتواند بدون توجه به fail شدن قسمت‌های دیگر به کار خود ادامه دهد.
Reduce Complexity
در domain‌های پیچیده طراحی و پیاده سازی objectهایی که مسئول دو قسمت read و write هستند، میتواند کار را بیش از حد پیچیده کرده و در این صورت چون business logic و read logic در هم ترکیب میشوند، مدیریت کردن موارد multiple user, shared data, performance, transactions, consistency سخت و سخت‌تر میشود.
Facilitates Building Task-based UI
وقتی شما به پیاده سازی الگوی CQRS میپردازید، اصولا هر عملی که توسط End user از طریق ui ارسال میشود، معادل command مربوط به آن وجود دارد. به همین جهت میتوان عملیات لازم برای اجرای یک پروسه را بصورت واضحی درک کرد.
 Maintenance And Flexibility
هر چند پیاده سازی این مدل سخت خواهد بود، اما در ابعاد وسیع‌تر به دلیل اینکه هر قسمت به صورت مجزایی طراحی شده و اینکه دستورات و رویداد‌ها به صورت تفکیک شده پیاده سازی شده‌اند، همچنین وجود ES، قابلیت زیادی به debug سیستم می‌دهد.
نکته: ES مدل مورد قبولی برای اکثر معماری‌های نوین سیستم‌های نرم افزاری امروزی میباشد و فقط مختص به CQRS نمیباشد. بطور مثال در معماری Microservices به وفور از Event Sourcing استفاده میشود.

مشکلات استفاده از این مدل
  • ذاتا پیاده سازی این مدل سخت و دشوار است و از آنجاییکه سادگی در پیاده سازی سیستم‌های نرم افزاری، یک اصل مهم محسوب میشود، بنابراین استفاده از این مدل محدود میشود به سیستم‌های نرم افزاری که مزیت‌های گفته شده در قسمت فوق برایشان حیاتی محسوب شود.
  • برای پیاده سازی سیستمی با این مدل احتیاج به تیم توسعه‌ای است که با مفاهیم آن کاملا آشنا باشد.
  • هر چند امروزه فضای فیزیکی برای ذخیره سازی دیتا ارزان محسوب میشود، اما به هر حال استفاده از این مدل به همراه ES، حجم زیادی از Disk space را خواهد گرفت.
  • همانطور که دیدید برای پیاده سازی یک Insert ساده، حجم زیادی کد نوشته شده‌است. بنابراین تولید اینگونه نرم افزار‌ها به زمان بیشتری نیاز دارد.
بنابراین باید در انتخاب معماری سیستم بسیار دقت شود؛ هر چند که این مدل برای سیستم‌های بزرگ و پیچیده خیلی کارآمد محسوب میشود و باعث یک Domain object غنی ، History Tracking، شفافیت در مشکلات Concurrency و همچنین Scalability و غیره خواهد شد، اما پیدا کردن برنامه نویسانی با داشتن درک عمیق روی این مباحث کمی سخت به نظر میرسد.
در قسمت بعدی بصورت کامل به پیاده سازی این الگو در یک اپلیکشن دات نتی خواهیم پرداخت.
مطالب دوره‌ها
آشنایی با AOP Interceptors
در حین استفاده از Interceptors، کار مداخله و تحت نظر قرار دادن قسمت‌های مختلف کدها، توسط کامپوننت‌های خارجی صورت خواهد گرفت. این کامپوننت‌های خارجی، به صورت پویا، تزئین کننده‌هایی را جهت محصور سازی قسمت‌های مختلف کدهای شما تولید می‌کنند. این‌ها، بسته به توانایی‌هایی که دارند، در زمان اجرا و یا حتی در زمان کامپایل نیز قابل تنظیم می‌باشند.


ابزارهایی جهت تولید AOP Interceptors

متداول‌ترین کامپوننت‌های خارجی که جهت تولید AOP Interceptors مورد استفاده قرار می‌گیرند، همان IOC Containers معروف هستند مانند StructureMap، Ninject، MS Unity و غیره.
سایر ابزارهای تولید AOP Interceptors، از روش تولید Dynamic proxies بهره می‌گیرند. به این ترتیب مزین کننده‌هایی پویا، در زمان اجرا، کدهای شما را محصور خواهند کرد. (نمونه‌ای از آن‌را شاید در حین کار با ORMهای مختلف دیده باشید).


نگاهی به فرآیند Interception

زمانیکه از یک IOC Container در کدهای خود استفاده می‌کنید، مراحلی چند رخ خواهند داد:
الف) کد فراخوان، از IOC Container، یک شیء مشخص را درخواست می‌کند. عموما اینکار با درخواست یک اینترفیس صورت می‌گیرد؛ هرچند محدودیتی نیز وجود نداشته و امکان درخواست یک کلاس از نوعی مشخص نیز وجود دارد.
ب) در ادامه IOC Container به لیست اشیاء قابل ارائه توسط خود نگاه کرده و در صورت وجود، وهله سازی شیء درخواست شده را انجام و نهایتا شیء مطلوب را بازگشت خواهد داد.
ج) سپس، کد فراخوان، وهله دریافتی را مورد پردازش قرار داده و شروع به استفاده از متدها و خواص آن خواهد نمود.

اکنون با اضافه کردن Interception به این پروسه، چند مرحله دیگر نیز در این بین به آن اضافه خواهند شد:
الف) در اینجا نیز در ابتدا کد فراخوان، درخواست وهله‌ای را بر اساس اینترفیسی خاص به IOC Container ارائه می‌دهد.
ب) IOC Container نیز سعی در وهله سازی درخواست رسیده بر اساس تنظیمات اولیه خود می‌کند.
ج) اما در این حالت IOC Container تشخیص می‌دهد، نوعی که باید بازگشت دهد، علاوه بر وهله سازی، نیاز به مزین سازی توسط  Aspects و پیاده سازی Interceptors را نیز دارد. بنابراین نوع مورد انتظار را در صورت وجود، به یک Dynamic Proxy، بجای بازگشت مستقیم به فراخوان ارائه می‌دهد.
د) در  ادامه Dynamic Proxy، نوع مورد انتظار را توسط Interceptors محصور کرده و به فراخوان بازگشت می‌دهد.
ه) اکنون فراخوان، در حین استفاده از امکانات شیء وهله سازی شده، به صورت خودکار مراحل مختلف اجرای یک Aspect را که در قسمت قبل بررسی شدند، سبب خواهد شد.


نحوه ایجاد Interceptors

برای ایجاد یک Interceptor دو مرحله باید انجام شود:
الف) پیاده سازی یک اینترفیس
ب) اتصال آن به کدهای اصلی برنامه

در ادامه قصد داریم از یک IOC Container معروف به نام StructureMap در یک برنامه کنسول استفاده کنیم. برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نوگت ویژوال استودیو فراخوانی کنید:
 PM> Install-Package structuremap
پس از آن یک برنامه کنسول جدید را ایجاد کنید. (هدف از استفاده از این نوع پروژه خاص، توضیح جزئیات یک فناوری، بدون درگیر شدن با لایه UI است)
البته باید دقت داشت که برای استفاده از StructureMap نیاز است به خواص پروژه مراجعه و سپس حالت Client profile را به Full profile تغییر داد تا برنامه قابل کامپایل باشد.
using System;
using StructureMap;

namespace AOP00
{
    public interface IMyType
    {
        void DoSomething(string data, int i);
    }

    public class MyType : IMyType
    {
        public void DoSomething(string data, int i)
        {
            Console.WriteLine("DoSomething({0}, {1});", data, i);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                x.For<IMyType>().Use<MyType>();
            });

            var myType = ObjectFactory.GetInstance<IMyType>();
            myType.DoSomething("Test", 1);
        }
    }
}
اکنون کدهای این برنامه را به نحو فوق تغییر دهید.
در اینجا یک اینترفیس نمونه و پیاده سازی آن‌را ملاحظه می‌کنید. همچنین نحوه آغاز تنظیمات StructureMap و نحوه دریافت یک وهله متناظر با IMyType نیز بیان شده‌اند.
نکته‌ی مهمی که در اینجا باید به آن دقت داشت، وضعیت شیء myType حین فراخوانی متد myType.DoSomething است. شیء myType در اینجا، دقیقا یک وهله‌ی متداول از کلاس myType است و هیچگونه دخل و تصرفی در نحوه اجرای آن صورت نگرفته است.
خوب! تا اینجای کار را احتمالا پیشتر نیز دیده بودید. در ادامه قصد داریم یک Interceptor را طراحی و مراحل چهارگانه اجرای یک Aspect را در اینجا بررسی کنیم.

در ادامه نیاز خواهیم داشت تا یک Dynamic proxy را نیز مورد استفاده قرار دهیم؛ از این جهت که StructureMap تنها دارای Interceptorهای وهله سازی اطلاعات است و نه Method Interceptor. برای دسترسی به Method Interceptors نیاز به یک Dynamic proxy نیز می‌باشد. در اینجا از Castle.Core استفاده خواهیم کرد:
 PM> Install-Package Castle.Core
برای دریافت آن تنها کافی است دستور پاور شل فوق را در خط فرمان کنسول پاورشل نوگت در VS.NET اجرا کنید.
سپس کلاس ذیل را به پروژه جاری اضافه کنید:
using System;
using Castle.DynamicProxy;

namespace AOP00
{
    public class LoggingInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            try
            {
                Console.WriteLine("Logging On Start.");

                invocation.Proceed(); //فراخوانی متد اصلی در اینجا صورت می‌گیرد

                Console.WriteLine("Logging On Success.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Logging On Error.");
                throw;
            }
            finally
            {
                Console.WriteLine("Logging On Exit.");
            }
        }
    }
}
در کلاس فوق کار Method Interception توسط امکانات Castle.Core انجام شده است. این کلاس باید اینترفیس IInterceptor را پیاده سازی کند. در این متد سطر invocation.Proceed دقیقا معادل فراخوانی متد مورد نظر است. مراحل چهارگانه شروع، پایان، خطا و موفقیت نیز توسط try/catch/finally پیاده سازی شده‌اند.

اکنون برای معرفی این کلاس به برنامه کافی است سطرهای ذیل را اندکی ویرایش کنیم:
        static void Main(string[] args)
        {
            ObjectFactory.Initialize(x =>
            {
                var dynamicProxy = new ProxyGenerator();
                x.For<IMyType>().Use<MyType>();
                x.For<IMyType>().EnrichAllWith(myTypeInterface => dynamicProxy.CreateInterfaceProxyWithTarget(myTypeInterface, new LoggingInterceptor()));
            });

            var myType = ObjectFactory.GetInstance<IMyType>();
            myType.DoSomething("Test", 1);
        }
در اینجا تنها سطر EnrichAllWith آن جدید است. ابتدا یک پروکسی پویا تولید شده است. سپس این پروکسی پویا کار دخالت و تحت نظر قرار دادن اجرای متدهای اینترفیس IMyType را عهده دار خواهد شد.
برای مثال اکنون با فراخوانی متد myType.DoSomething، ابتدا کنترل برنامه به پروکسی پویای تشکیل شده توسط Castle.Core منتقل می‌شود. در اینجا هنوز هم متد DoSomething فراخوانی نشده است. ابتدا وارد بدنه متد public void Intercept خواهیم شد. سپس سطر invocation.Proceed، فراخوانی واقعی متد DoSomething اصلی را انجام می‌دهد. در ادامه باز هم فرصت داریم تا مراحل موفقیت، خطا یا خروج را لاگ کنیم.
تنها زمانیکه کار متد public void Intercept به پایان می‌رسد، سطر پس از فراخوانی متد  myType.DoSomething اجرا خواهد شد.
در این حالت اگر برنامه را اجرا کنیم، چنین خروجی را نمایش می‌دهد:
 Logging On Start.
DoSomething(Test, 1);
Logging On Success.
Logging On Exit.
بنابراین در اینجا نحوه دخالت و تحت نظر قرار دادن اجرای متدهای یک کلاس عمومی خاص را ملاحظه می‌کنید. برای اینکه کنترل کامل را در دست بگیریم، کلاس پروکسی پویا وارد عمل شده و اینجا است که این کلاس پروکسی تصمیم می‌گیرد چه زمانی باید فراخوانی واقعی متد مورد نظر انجام شود.
برای اینکه فراخوانی قسمت On Error را نیز ملاحظه کنید، یک استثنای عمدی را در متد DoSomething قرار داده و مجددا برنامه را اجرا کنید.