مطالب
روش یافتن لیست تمام کنترلرها و اکشن‌ متدهای یک برنامه‌ی ASP.NET Core
یک نمونه روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامه‌ی ASP.NET MVC 5.x را در مطلب «نحوه ایجاد یک نقشه‌ی سایت پویا با استفاده از قابلیت Reflection» می‌توانید ملاحظه کنید. استفاده‌ی از این روش با ASP.NET Core الزاما به پاسخ مناسبی نخواهد رسید؛ چون در اینجا POCO controllers هم اضافه شده‌اند. به علاوه می‌توان اسمبلی‌های دیگری را در زمان آغاز برنامه به تنظیمات AddMvc اضافه کرد و تمام آن‌ها هم می‌توانند حاوی کنترلرها و ویووها خاص خودشان باشند. روش بهتر این است که از خود ASP.NET Core سؤال کنیم چه مواردی را به عنوان کنترلر تشخیص داده‌ای؟ در ادامه این نکته را بیشتر بررسی خواهیم کرد.


معرفی سرویس IActionDescriptorCollectionProvider در ASP.NET Core

فرض کنید می‌خواهیم لیست تمام کنترلرهای یک برنامه‌ی ASP.NET Core را با ساختار ذیل تهیه کنیم که شامل نام کنترلر، نام اکشن متد و نام ناحیه‌ی متناظر با آن (در صورت تنظیم) می‌باشد:
public class MvcActionViewModel
{
    public string ControllerName { get; set; }
 
    public string ActionName { get; set; }
 
    public string AreaName { get; set; } 
}
یکی از سرویس‌های از پیش ثبت شده‌ی ASP.NET Core که لیست تمام کنترلرها و اکشن متدهای تشخیص داده شده‌ی توسط آن را به همراه دارد، سرویس IActionDescriptorCollectionProvider می‌باشد. برای شروع به کار با آن، ابتدا این سرویس را به سازنده‌ی یک کلاس دلخواه تزریق می‌کنیم:
public interface IMvcActionsDiscoveryService
{
    ICollection<MvcActionViewModel> MvcActions { get; }
}
 
public class MvcActionsDiscoveryService : IMvcActionsDiscoveryService
{
    public MvcActionsDiscoveryService(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
    {
        var actionDescriptors = actionDescriptorCollectionProvider.ActionDescriptors.Items;
        foreach (var actionDescriptor in actionDescriptors)
        {
            var descriptor = actionDescriptor as ControllerActionDescriptor;
            if (descriptor == null)
            {
                continue;
            }
 
            var controllerTypeInfo = descriptor.ControllerTypeInfo;
            var actionMethodInfo = descriptor.MethodInfo;
            MvcActions.Add(new MvcActionViewModel
            {
                ControllerName = descriptor.ControllerName,
                ActionName = descriptor.ActionName,
                AreaName = controllerTypeInfo.GetCustomAttribute<AreaAttribute>()?.RouteValue
            });
        }
    }
 
    public ICollection<MvcActionViewModel> MvcActions { get; } = new HashSet<MvcActionViewModel>(); 
}
توضیحات:
- در کلاس آغازین برنامه نیازی به ثبت سرویس IActionDescriptorCollectionProvider نیست و اینکار پیشتر توسط خود ASP.NET Core انجام شده‌است.
- این provider حاوی لیست اطلاعات تمام اکشن متدهای ثبت شده‌ی توسط ASP.NET Core است. در اینجا تنها کافی است حلقه‌ای را بر روی لیست آیتم‌های آن تشکیل داده و سپس مقادیر ControllerName و یا ActionName را بدست بیاوریم.
- اگر نیاز به اطلاعات بیشتری از کنترلر و اکشن متد جاری در حال بررسی توسط حلقه‌ی تهیه شده بود، می‌توان از ControllerTypeInfo و MethodInfo آن استفاده کرد. این TypeInfoها با استفاده از Reflection، امکان دسترسی به اطلاعاتی مانند ویژگی‌های اعمال شده‌ی به کنترلر یا اکشنی خاص را میسر می‌کنند. برای مثال در اینجا توسط اطلاعات نوع یک کنترلر در حال بررسی توانسته‌ایم متد GetCustomAttribute را فراخوانی کرده و سپس بررسی کنیم که آیا دارای ویژگی جدید Area هست یا خیر؟ و اگر بله، مقدار RouteValue آن را که در حقیقت مقدار یا نام Area آن کنترلر است، بازگشت می‌دهیم.


نحوه‌ی استفاده از سرویس IMvcActionsDiscoveryService تهیه شده

اگر دقت کرده باشید اطلاعات لیست MvcActions، در سازنده‌ی این کلاس مقدار دهی شده‌اند. علت اینجا است که اگر این کلاس را به صورت singleton ثبت کنیم، تنها یکبار در طول عمر برنامه و در همان آغاز کار، این لیست پر شده و سپس کش خواهد شد. بنابراین دسترسی‌های بعدی به MvcActions، شامل فراخوانی سازنده‌ی این کلاس نخواهند بود:
public static class MvcActionsDiscoveryServiceExtensions
{
    public static IServiceCollection AddMvcActionsDiscoveryService(this IServiceCollection services)
    {
        services.AddSingleton<IMvcActionsDiscoveryService, MvcActionsDiscoveryService>();
        return services;
    }
}
پس از تعریف متد الحاقی کمکی فوق برای افزودن سرویس تهیه شده به صورت singleton، برای ثبت آن در برنامه و در کلاس آغازین آن، خواهیم داشت:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcActionsDiscoveryService();
}
در ادامه هر کنترلری و یا سرویس دیگری که نیاز به اطلاعات تمامی اکشن متدهای برنامه داشت، می‌تواند سرویس IMvcActionsDiscoveryService را به سازنده‌ی خود تزریق کرده و سپس از اطلاعات لیست MvcActions استفاده کند. این کاملترین لیستی که می‌توان تهیه کرد؛ زیرا زیرساخت ASP.NET Core نیز از همین سرویس IActionDescriptorCollectionProvider استفاده می‌کند.
مطالب
ارتقاء به 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 هر نامی می‌تواند باشد).
نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
با سلام؛ اگه بخواهیم mvc core با این jwt و صفحات razor فعلا اجرا کنیم، مشکل اولین صفحه و Authorization مربوط به اون رو چطور انجام بدیم که کاربر هر درخواست صفحه ای داشت ابتدا به صفحه login هدایت بشه؟
بازخوردهای دوره
تزریق وابستگی‌ها در فیلترهای ASP.NET MVC
- در MVC 5 این نوع وابستگی‌های فیلترها را به صورت خواص عمومی Func تعریف کنید (بهترین راه حل ممکن برای آن؛ تا حالت Transient بجای Singleton پیدا کنند). یک مثال
Public Func<IUsersService> UsersService {set;get;}
- در ASP.NET Core نیازی به این روش نیست و تزریق وابستگی مستقیم در سازنده‌ی فیلترها پشتیبانی رسمی می‌شود: فیلترها در ASP.NET Core (قسمت «تزریق وابستگی‌ها در فیلترها»ی آن). 
اشتراک‌ها
دوره پیاده سازی معماری تمیز در ASP.NET Core

ASP.NET Core - Clean Architecture - Full Course
In this video, we'll be talking about ASP.NET Core - Clean Architecture. We'll cover the concept of clean architecture, how it helps with scaling and maintainability, works with the MVC pattern, and uses Fluent Validation.
 

دوره پیاده سازی معماری تمیز در ASP.NET Core
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
در ساختار درونی سیستم ASP.NET Core Identity، در عمل چیزی به نام Role وجود خارجی ندارد. این Roleهای ظاهری فقط برای سازگاری با سیستم‌های membership خیلی قدیمی وجود دارند. تمام سیستم Identity بر اساس User Claims کار می‌کند. تمام Roleها و غیره در پشت صحنه ابتدا تبدیل به user claims می‌شوند و سپس استفاده خواهند شد. اطلاعات بیشتر: «ASP.NET Core Identity چگونه اطلاعات جدول AppUserClaims را پردازش می‌کند؟»