بازخوردهای دوره
آشنایی با نحوه ایجاد یک IoC Container
پاسخ به این سؤال نیاز به مطالعه قسمت‌های بعدی دارد. 
- هدف از قسمت جاری آشنایی اولیه با مراحل ابتدایی کار با یک IoC Container است.
- در حالت اول هنوز شما هستید که مسئول وهله سازی‌های اولیه می‌باشید و کار وهله سازی را به لایه‌ای دیگر واگذار نکرده‌اید.
- در کد حالت اول نمی‌شود این وابستگی ارسالی به سازنده کلاس را به سادگی تعویض کرد. در حالیکه با استفاده از یک IoC container فقط کافی است تنظیمات اولیه نگاشت‌های آن‌را مشخص کنیم تا نوع کلاسی که باید در سازنده‌ها تزریق شوند مشخص شود. مزیت اینکار ساده‌تر شدن نوشتن آزمون‌های واحد و تهیه کلاس‌های Fake است؛ بدون نیازی به تغییری حتی در حد یک سطر در کدهای اصلی برنامه.
- در مورد وهله سازی خودکار چند سطح وابستگی‌ها، در قسمت‌های بعد تحت عنوان Object graph بیشتر بحث شده است و مثال زده شده. همیشه با یک کلاس ساده ویزا مانند مثال فوق سر و کار نداریم. عموما با سرویس‌هایی سر و کار داریم که خودشان نیز از سرویس‌های دیگری استفاده می‌کنند. برای مثال یک سرویس ارسال ایمیل از سرویس کاربران برای دریافت ایمیل‌های کاربران کمک می‌گیرد. وهله سازی تمام این وابستگی‌ها را در چند سطح می‌شود با استفاده از IoC Containers خودکار کرد و به کد‌های نهایی بسیار تمیزتری رسید.
- در حالت اول، طول عمر یک شیء را نمی‌شود مشخص کرد (یا حداقل نیاز به کد نویسی قابل توجهی دارد). برای مثال با استفاده از یک IoC Container می‌شود وهله ایجاد شده را Singleton کرد تا در سراسر برنامه یک وهله از آن استفاده شود یا حتی می‌شود در طول یک درخواست رسیده وب، یک وهله را در اختیار تمام کلاس‌های درگیر قرار داد. به این ترتیب به سربار کمتری در حالت‌های خاصی مانند وهله سازی ObjectContext یا DbContext در EF خواهیم رسید.
- زمان استفاده از IoC Containerها کارهای فراتری از تزریق وابستگی‌ها را هم می‌شود انجام داد. برای مثال فراخوانی‌های متدها را هم تحت نظر قرار داد (برنامه نویسی AOP یا جنبه گرا) و مثلا بدون نوشتن کد اضافه‌ای در برنامه، خروجی متدها را کش کرد. AOP یک سری بحث مفصل را در طی یک دوره جدا به همراه دارد.

اشتراک‌ها
پیاده سازی سرویس دیسکاوری با Consul

بعد از مدت‌ها دوری از این پلی لیست، گفتیم یکمی این رو هم ببریم جلو و یه ویدیو براش بسازیم در ادامه بحث مایکروسرویس ها، این سری رفتیم سراغ سرویس دیسکاوری و با Consul که از کمپانی HashiCorp هست، کار کردیم و تونستیم مایکروسرویس هارو در زمان لود رجیستر کنیم و Ocelot رو هم به این سرویس دیسکاوری وصل کنیم. 

00:00 Ocelot, API Gateway 

05:00 Implementing 2 Microservices and API Gateway 

10:08 What is Consul 

10:37 Running Consul in Docker 

15:31 Consul UI 

16:40 Self Registration Microservice into Consul 

20:28 IHostedService 

27:15 Register Services into IServiceCollection 

35:00 Connect Ocelot to Consul 

مدت زمان ویدیو : 41 دقیقه 

پیاده سازی سرویس دیسکاوری با Consul
مطالب
بومی سازی منابع در پروژه‌های ASP.NET Core Web API
اگر پروژه‌ی ما فقط از یک Web API تشکیل شده و نیاز است در قسمت‌های مختلف آن، مانند کنترلرها، سرویس‌ها، اعتبارسنج‌ها و غیره از منابع بومی شده استفاده شود، می‌توان از یک راه حل ساده‌ی «SharedResource» استفاده کرد؛ با این مزایا و شرایط:
 - تمام تعاریف بومی سازی مورد نیاز برنامه در یک تک فایل SharedResource.fa.resx قرار می‌گیرند. این فایل نیز در یک اسمبلی مستقل از برنامه‌ی اصلی اضافه می‌شود.
 - با استفاده از تزریق سرویس IStringLocalizer می‌توان به کلیدهای فایل SharedResource.fa.resx در هر قسمتی از برنامه‌ی Web API دسترسی یافت.
 - در این بین اگر کلیدی یافت نشد، خطایی با ذکر دقیق جزئیات منبع جستجو شده، لاگ می‌شود.
 - کلیدهای بومی سازی data annotations نیز قابل دریافت از فایل SharedResource.fa.resx می‌باشند.
 
در ادامه روش پیاده سازی یک چنین امکاناتی را بررسی می‌کنیم.
 
 
قرار دادن فایل منبع اشتراکی در اسمبلی ExternalResources

پس از ایجاد پروژه‌ی ابتدایی Web API به نام Core3xSharedResource.WebApi، یک اسمبلی جدید را برای مثال به نام Core3xSharedResource.ExternalResources تعریف کرده و در داخل آن پوشه‌ی جدید Resources را تعریف می‌کنیم. به این پوشه، فایل منبع جدیدی را به نام SharedResource.fa.resx اضافه می‌کنیم. در کنار آن باید یک کلاس خالی به نام SharedResource.cs نیز وجود داشته باشد.

کار با ین فایل (و یا فایل‌های دیگری مانند SharedResource.en.resx) همانند تمام فایل‌های منبع استاندارد است و نکته‌ی خاصی را به همراه ندارد.


معرفی فایل منبع اشتراکی به سرویس‌های بومی سازی برنامه

پس از ایجاد و تکمیل فایل منبع اشتراکی، برای معرفی آن به برنامه، ابتدا کلاس جدید LocalizationConfig را تعریف کرده و در آن متد جدید AddCustomLocalization را به صورت زیر معرفی می‌کنیم:
    public static class LocalizationConfig
    {
        public static IMvcBuilder AddCustomLocalization(this IMvcBuilder mvcBuilder, IServiceCollection services)
        {
            mvcBuilder.AddDataAnnotationsLocalization(options =>
                    {
                        const string resourcesPath = "Resources";
                        string baseName = $"{resourcesPath}.{nameof(SharedResource)}";
                        var location = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName).Name;

                        options.DataAnnotationLocalizerProvider = (type, factory) =>
                        {
                            // to use `SharedResource.fa.resx` file
                            return factory.Create(baseName, location);
                        };
                    });

            services.AddLocalization();
            services.AddScoped<IStringLocalizer>(provider =>
                            provider.GetRequiredService<IStringLocalizer<SharedResource>>());

            services.AddScoped<ISharedResourceService, SharedResourceService>();
            return mvcBuilder;
        }
    }
- در اینجا در ابتدا توسط متد AddDataAnnotationsLocalization، کار معرفی اسمبلی ثالثی که باید تعاریف بومی سازی را از آن دریافت کرد، صورت گرفته‌است.
- سپس با استفاده از متد AddLocalization، سرویس‌های پایه‌ی بومی سازی ASP.NET Core به برنامه اضافه می‌شوند. برای مثال پس از این تعریف اگر در هر جائی از برنامه سرویس <IStringLocalizer<SharedResource را تزریق کنید، می‌توان به مداخل فایل منبع اشتراکی، دسترسی یافت.
- در ادامه امکان تزریق سرویس غیرجنریک IStringLocalizer را نیز میسر کرده‌ایم که تعاریف خودش را از همان سرویس توکار <IStringLocalizer<SharedResource دریافت می‌کند. مزیت اینکار، فراهم شدن امکانات بومی سازی، برای مثال در کتابخانه‌هایی مانند Fluent Validation است که دقیقا از سرویس غیرجنریک IStringLocalizer برای دریافت منابع استفاده می‌کنند.
- در آخر تعریف یک سرویس سفارشی را نیز مشاهده می‌کنید که در ادامه‌ی بحث تکمیل خواهد شد.

هدف از متد AddCustomLocalization فوق، خلوت کردن فایل startup برنامه است. این متد به صورت زیر مورد استفاده قرار می‌گیرد:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHttpContextAccessor();
            services.AddControllers().AddCustomLocalization(services);
        }

پس از آن نیاز است میان‌افزار بومی سازی را نیز فعال کرد. متد UseCustomRequestLocalization زیر، اینکار را انجام می‌دهد:
    public static class LocalizationConfig
    {
        public static IApplicationBuilder UseCustomRequestLocalization(this IApplicationBuilder app)
        {
            var requestLocalizationOptions = new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture(new CultureInfo("fa-IR")),
                SupportedCultures = new[]
                {
                    new CultureInfo("en-US"),
                    new CultureInfo("fa-IR")
                },
                SupportedUICultures = new[]
                {
                    new CultureInfo("en-US"),
                    new CultureInfo("fa-IR")
                }
            };
            app.UseRequestLocalization(requestLocalizationOptions);
            return app;
        }
    }
محل قرارگیری متد UseCustomRequestLocalization فوق در فایل آغازین برنامه، باید به صورت زیر باید باشد:
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseCustomRequestLocalization();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }


تعریف مدل برنامه به همراه ویژگی‌های بومی سازی شده

در اینجا تعریف RegisterModel را مشاهده می‌کنید که ErrorMessage‌های آن هرچند به ظاهر یک رشته‌ی معمولی هستند، اما در عمل از فایل منبع اشتراکی خوانده می‌شوند:
using System.ComponentModel.DataAnnotations;

namespace Core3xSharedResource.Models.Account
{
    public class RegisterModel
    {
        [Required(ErrorMessage = "Please enter an email address")] // -->> from the shared resources
        [EmailAddress(ErrorMessage = "Please enter a valid email address")] // -->> from the shared resources
        public string Email { get; set; }
    }
}

فایل resx ما دارای یک چنین کلیدهایی است:
<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="&lt;b&gt;Hello&lt;/b&gt;&lt;i&gt; {0}&lt;/i&gt;" xml:space="preserve">
    <value>&lt;b&gt;سلام&lt;/b&gt;&lt;i&gt; {0}&lt;/i&gt;</value>
  </data>
  <data name="About Title" xml:space="preserve">
    <value>درباره</value>
  </data>
  <data name="DNT" xml:space="preserve">
    <value>.NET Tips</value>
  </data>
  <data name="SiteName" xml:space="preserve">
    <value>DNT</value>
  </data>
  <data name="Please enter an email address" xml:space="preserve">
    <value>لطفا ایمیلی را وارد کنید</value>
  </data>
  <data name="Please enter a valid email address" xml:space="preserve">
    <value>لطفا ایمیل معتبری را وارد کنید</value>
  </data>
</root>
یک نکته: در اینجا بهتر است کلیدها را به صورت جملات کامل انگلیسی وارد کرد، تا اگر منبع فارسی معادل آن‌ها یافت نشدند، دقیقا از همان کلید، به عنوان مقدار خروجی سیستم بومی سازی استفاده کند.


آزمایش برنامه

اکنون برنامه‌ی Web API، ‌برای آزمایش آماده‌است. برای مثال در کنترلر زیر، سرویس عمومی IStringLocalizer به سازنده‌ی کلاس تزریق شده‌است و سپس قصد بازگشت مقدار کلید «About Title» را دارد. همچنین خطاهای بومی شده‌ی مدل برنامه را نیز بررسی می‌کنیم:
using Core3xSharedResource.Models.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace Core3xSharedResource.WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class NormalIStringLocalizerController : ControllerBase
    {
        private readonly IStringLocalizer _localizer;

        public NormalIStringLocalizerController(IStringLocalizer localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public ActionResult<string> Get()
        {
            var localizedString = _localizer["About Title"];
            if (localizedString.ResourceNotFound)
            {
                return NotFound($"The localization resource with ID:`{localizedString.Name}` not found. SearchedLocation: `{localizedString.SearchedLocation}`.");
            }
            return localizedString.Value;
        }

        [HttpPost]
        public ActionResult<RegisterModel> Post(RegisterModel model)
        {
            return model;
        }
    }
}


حالت get را در تصویر فوق مشاهده می‌کنید. در Web API برای تنظیم زبان مورد استفاده می‌توان از هدری به نام Accept-Language استفاده کرد که برای مثال در اینجا به fa تنظیم شده‌است و نتیجه‌ی آن مراجعه به فایل SharedResource.fa.resx خواهد بود. اگر en-us وارد شود، نیاز خواهد بود تا فایل منبع اشتراکی دیگری را تعریف کنید. البته اگر این هدر تنظیم نشود، با توجه به تنظیمات متد UseCustomRequestLocalization، مقدار پیش‌فرض آن همان fa-IR خواهد بود.

حالت post را نیز در تصویر زیر می‌توان مشاهده کرد:


در اینجا چون ایمیل وارد نشده، هر دو خطای تنظیم شده‌ی در مدل برنامه را دریافت کرده‌ایم و این خطاها نیز فارسی هستند. به این معنا که بومی سازی data annotations نیز به درستی کار می‌کند.


تعریف یک سرویس عمومی برای محصور سازی قابلیت‌های بومی سازی، در برنامه‌های Web API

در ادامه تعریف سرویس SharedResourceService را مشاهده می‌کنید که ثبت آن‌را پیشتر انجام دادیم:
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;

namespace Core3xSharedResource.Services
{
    public interface ISharedResourceService
    {
        string this[string index] { get; }

        IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures);
        string GetString(string name, params object[] arguments);
        string GetString(string name);
    }

    public class SharedResourceService : ISharedResourceService
    {
        private readonly IStringLocalizer _sharedLocalizer;
        private readonly ILogger<SharedResourceService> _logger;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public SharedResourceService(
            IStringLocalizer sharedHtmlLocalizer,
            IHttpContextAccessor httpContextAccessor,
            ILogger<SharedResourceService> logger
            )
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _sharedLocalizer = sharedHtmlLocalizer ?? throw new ArgumentNullException(nameof(sharedHtmlLocalizer));
            _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
        {
            return _sharedLocalizer.GetAllStrings(includeParentCultures);
        }

        public string this[string index] => GetString(index);

        public string GetString(string name, params object[] arguments)
        {
            var result = _sharedLocalizer.GetString(name, arguments);
            logError(name, result);
            return result;
        }

        private void logError(string name, LocalizedString result)
        {
            if (result.ResourceNotFound)
            {
                var acceptLanguage = _httpContextAccessor?.HttpContext?.Request?.Headers["Accept-Language"];
                _logger.LogError($"The localization resource with Accept-Language:`{acceptLanguage}` & ID:`{name}` not found. SearchedLocation: `{result.SearchedLocation}`.");
            }
        }

        public string GetString(string name)
        {
            var result = _sharedLocalizer.GetString(name);
            logError(name, result);
            return result;
        }
    }
}
این سرویس نه فقط دسترسی به IStringLocalizer را محصور می‌کند، بلکه در متد logError آن اینبار خطای بسیار مفیدی جهت دیباگ کردن سیستم بومی سازی لاگ خواهد شد. اگر کلیدی یافت نشود، فایلی یافت نشود و یا زبان ارسالی تنظیمی یافت نشود، خطای آن‌را در لاگ‌های برنامه می‌توانید مشاهده کنید که در حالت عادی کار با IStringLocalizer، لاگ نمی‌شوند و همچنین هیچ خطا و یا استثنائی را نیز سبب نمی‌شوند. به همین جهت دیباگ کردن سیستم بومی سازی بدون این لاگ‌ها، تقریبا غیرممکن است. برای مثال مقدار baseNameهایی را که در کدهای این مطلب مشاهده می‌کنید، بر اساس همین لاگ‌ها تشخیص داده شدند و بدون آن‌ها تشکیل این مقادیر غیرممکن بودند.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Core3xSharedResource.zip
مطالب
دریافت اطلاعات از پایگاه داده بواسطه Stored Procedure در EF Core 2.0
همواره در تکنولوژی  EF CodeFirst، چه در ASP.NET MVC و چه در ASP.NET Core، استفاده از امکانات بومی پایگاه‌های داده با محدودیت‌هایی مواجه بوده‌است. یکی از این اشکالات، عدم توانایی این تکنولوژی در گرفتن لیستی از اطلاعات که منطبق بر بیشتر از یک مدل می‌باشد، هست. در این مقاله تمرکز بر روی رفع این اشکال، بدون نیاز به اضافه کردن مدخل جدیدی به پروژه می‌باشد. بنابراین پیشنیاز ضروری این مبحث، مطالعه «شروع به کار با EF Core 1.0» ، مخصوصا «استفاده از امکانات بومی بانک‌های اطلاعاتی» است.

Stored Procedure چیست ؟

Stored Procedure  یا  SP  یا به زبان فارسی «رویه‌های ذخیره شده» اشیایی اجرا پذیر در بانک اطلاعاتی SQL Server هستند که شامل یک یا چندین دستور SQL می‌شوند. این رویه‌ها می‌توانند پارامتر‌های ورودی و خروجی داشته باشند؛ همچنین می‌توانند لیستی از موجودیت‌ها را نیز برگردانند و یا می‌توان داخل این رویه‌ها به زبان T-SQL برنامه نویسی کرد.
مهم‌ترین کاربر این رویه‌ها، ذخیره کردن دستورات Select , Insert , Update , Delete هست و یا ترکیبی از این‌ها .


اشکال راه حل‌های پیش فرض مبتنی بر Context

برای استفاده از راه حل‌های پیش فرض  مبتنی بر Context، همانطور که در مقاله «استفاده از امکانات بومی بانک‌های اطلاعاتی» به آن پرداخته شده، سه روش کلی برای استفاده از Stored Procedure  پیشنهاد شده‌است:
- روش اول استفاده از متد fromsql است. اشکال این متد، محدودیت استفاده برای یک موجودیت برنامه  است و به زبان ساده نمی‌توان در کوئری پایگاه داده از join  استفاده کرد.
- روش دوم استفاده از متد ExecuteSqlCommand موجود در context برنامه است . اشکال این متد void بودن آن است که باعث می‌شود بازگشتی از پایگاه داده حاصل نشود.
- روش سوم استفاده از متد ExecuteScalar  موجود در Context برنامه است. اشکالی که به این متد گرفته می‌شود، Scalar  بودن مقدار بازگشتی از آن است که باعث می‌شود نتوانیم لیستی از موجودیت‌ها را به ViewModel مورد نظر نگاشت کنیم.

راه حل این مشکل

برای حل این مشکلات که بسیار هم مهم هستند، اول باید قطعه کد زیر را به Context برنامه اضافه نمود:
public void OpenConnection()
{
   Database.OpenConnection();
}

public DbCommand Command()
{
   DbCommand cmd = Database.GetDbConnection().CreateCommand();
   return cmd;
}
سپس در اینترفیس IUnitOfWork  که در مطلب «لایه بندی و تزریق وابستگی‌ها» در مورد آن بحث شده، متد OpenConnection و Command را اضافه می‌کنیم:
void OpenConnection();
DbCommand Command();
حال کلاس و اینترفیس جدیدی را برای پیاده سازی سرویس اتصال به Stored Procedure ایجاد کرده و  در کلاس آغازین برنامه، به‌صورت AddScopped این سرویس را برای استفاده از تزریق وابستگی توکار ASP.NET Core  معرفی می‌کنیم:
public void ConfigureServices(IServiceCollection services)
{
     services.AddScoped<IUnitOfWork, ApplicationDbContext>();
     services.AddScoped<ISpReader, SpReader>();
}
سپس در سازنده کلاس این سرویس، اینترفیس IUnitOfWork را تزریق کرده تا بتوانیم از متد‌های نوشته شده در Context استفاده کنیم. حال اقدام به پیاده سازی متد GetFromSp بصورت زیر می‌کنیم :
public List<ViewModel> GetFromSp <ViewModel>(string[,] Parametr, string NameSp) where ViewModel  : new()
        {
            _uow.OpenConnection();
            DbCommand cmd = _uow.Command();
            cmd.CommandText = NameSp;
            cmd.CommandType = CommandType.StoredProcedure;
            var countParametr = Parametr.GetLength(0);

            for (int i = 0; i < countParame tr; i++)
            {
                cmd.Parameters.Add(new SqlParameter { ParameterName = Parametr[i, 0], Value = Parametr[i, 1] });
            }

            List<ViewModel> list = new List<ViewModel >();
            using (var reader = cmd.ExecuteReader())
            {
                if (reader != null && reader.HasRows)
                {
                    var entity = typeof(ViewModel);
                    var propDict = new Dictionary<string, PropertyInfo>();
                    var props = entity.GetProperties
           (BindingFlags.Instance | BindingFlags.Public);
                    propDict = props.ToDictionary(p => p.Name.ToUpper(), p => p);
                    while (reader.Read())
                    {
                        ViewModel  newobject = new ViewModel ();

                        for (int index = 0; index < reader.FieldCount; index++)
                        {
                            if (propDict.ContainsKey(reader.GetName(index).ToUpper()))
                            {
                                var info = propDict[reader.GetName(index).ToUpper()];
                                if ((info != null) && info.CanWrite)
                                {
                                    var val = reader.GetValue(index);
                                    info.SetValue(newobject, (val == DBNull.Value) ? null : val, null);
                                }
                            }
                        }
                        list.Add(newobject);
                    }

                }
                return list;
            }
در این متد، اول با استفاده از OpenConnection، اتصالی را به پایگاه داده، باز کرده سپس شیئ از DbCommand را می‌سازیم و نام Stored Procedure و نوع کوئری ارسالی را معین می‌کنیم. حال با استفاده از  حلقه for، نام و مقدار پارامتر‌های ارسال شده به متد را به شیئ cmd اضافه می‌کنیم. در مرحله بعد، لیستی را از کلاس مدلی که باید مقادیر بازگشتی به آن نگاشت شوند و بعنوان کلاس جنریک به متد ارسال شده است، می‌سازیم. با متد ExecuteReader که در شیئ ساخته شده از Command موجود می‌باشد، اقدام به خواندن اطلاعات از Stored Procedure کرده و در شیئ Reader نگه داری می‌کنیم و سپس اطلاعات خوانده شده را با استفاده از Dictionary و متد Add به لیست ساخته شده اضافه می‌کنیم. در آخر لیست ساخته شده در حلقه While را بعنوان نتیجه متد باز می‌گردانیم.

همچنین می‌توان برای استفاده این متد برای رویه‌های بدون پارامتر ورودی، از OverLoad این متد، با حذف قطعات کد زیر:
var countParametr = Parametr.GetLength(0);
for (int i = 0; i < countParametr; i++)
{
     cmd.Parameters.Add(new SqlParameter { ParameterName = Parametr[i, 0], Value = Parametr[i, 1] });
}
و حذف آرایه string[,] Parameter  از ورودی متد، استفاده نمود .

روش استفاده از این متد

برای استفاده از این متد، لازم است چند نکته رعایت شوند:
1- خروجی Stored Procedure دقیقا منطبق بر ViewModel ارسالی به متد جهت تشکیل لیست باشد.
2- لیست پارامتر‌ها باید بصورت آرایه دوبعدی باشد که اندازه بعد اول، تعداد پارامتر‌ها و اندازه بعد دوم 2 باشد.
3- در ماتریسی که از این پارامتر‌ها ساخته می‌شود، ستون اول نام پارامتر و ستون دوم مقدار پارامتر ست می‌شود.

بطور مثال Stored Procedure  زیر حاوی سه پارامتر است :
CREATE PROCEDURE [dbo].[isRelation](
@TableName as varchar(50),
@FieldOfRelation as varchar(70),
@ValueOfField as int)
برای دسترسی به این رویه ابتدا در سرویس استفاده کننده، ISpReader را تزریق می‌کنیم و سپس بصورت زیر مقدمات استفاده از این سرویس را فراهم می‌کنیم:

public class EntityServices : IEntityService
    {
        private ISpreader _Reader;
        public EntityServices( ISpreader reader)
        {
            _Reader = reader;
        }

        public List<StoreProcedureResultViewModel>  IsRelation(string tableName , int keyValue, string keyFieldName)
        {
            List<StoreProcedureResultViewModel> IsContact;
            try
            {
                string[,] Parametr = new string[3, 2];
                Parametr[0, 0] = "@TableName";
                Parametr[0, 1] = tableName ;
                Parametr[1, 0] = "@ValueOfField";
                Parametr[1, 1] = keyValue.ToString().Trim();
                Parametr[2, 0] = "@FieldOfRelation";
                Parametr[2, 1] = keyFieldName.Trim();
                IsContact = _Reader.GetSp<StoreProcedureResultViewModel>(Parametr, "IsRelation");
                return IsContact;
            }
            catch (Exception ex)
            {
            }
        }
    }
بدین ترتیب با استفاده از این متد توانستیم لیستی از ViewModel منطبق بر خروجی Stored Procedure  را بدست آوریم.  
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت دوم - سرویس‌های پایه
Identity به همراه اینترفیس برای سرویس‌ها و کلاس‌های اصلی آن نیست و «... از این جهت که در سایر لایه‌های برنامه نمی‌خواهیم از تزریق مستقیم کلاس‌ها استفاده کنیم؛ بلکه می‌خواهیم اینترفیس ها را در موارد ضروری، به سازنده‌های کلاس‌های تزریق نمائیم ...» به همین جهت متدهای مورد نیاز را استخراج و استفاده می‌کنیم.
نظرات مطالب
نوشتن Middleware سفارشی در ASP.NET Core

یک نکته‌ی تکمیلی: کار با اینترفیس IMiddleware جهت تعریف میان‌افزارهای سفارشی

اگر امروز قصد تعریف میان‌افزارهای سفارشی را دارید، بهتر است از روش باز public async Task Invoke(HttpContext context) که در این مطلب معرفی شد، دیگر استفاده نکنید؛ چون مهم‌ترین محدودیت‌های آن، داشتن طول عمر Singleton غیرقابل تغییر و همچنین عدم امکان پیاده سازی اینترفیس IDisposable در آن جهت پاکسازی خودکار منابع است. امروز روش توصیه شده‌، استفاده از اینترفیس IMiddleware است. در این حالت متد Task Invoke فوق، به متد مشخص و ثابت Task InvokeAsync(HttpContext context, RequestDelegate next) تغییر می‌کند. چون در این حالت دیگر نمی‌توان پارامترهای این متد مشخص را مانند قبل که اینترفیسی را پیاده سازی نمی‌کرد، به صورت پویا کم و زیاد کرد، می‌توان سرویس‌های مدنظر را به سازنده‌ی کلاس، تزریق کرد. به همین جهت نیاز است، آن‌را به نحو زیر به سیستم تزریق وابستگی‌ها معرفی کرد:

builder.Services.AddTransient<MyNewMiddleware>();

این الزام به تعریف آن به صورت یک سرویس رسمی، مزیت‌های زیر را به همراه دارد:

الف) می‌توان طول عمری، غیر از Singleton را هم در صورت نیاز، تعریف کرد (و مشکل کار با سرویس‌هایی با طول عمرهای غیر از Singleton کمتر می‌شود).

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

نکته 1: روش معرفی آن به سیستم تزریق وابستگی‌ها، به صورت Concrete type است؛ یعنی اصل کلاس باید معرفی شود (مانند سطر فوق) و نه اینکه به صورت متداول زیر به همراه ذکر اینترفیس IMiddleware باشد:

builder.Services.AddTransient<IMiddleware, MyNewMiddleware>();

مابقی کار با آن، با میان‌افزارهای متداول، تفاوتی ندارد. یعنی قسمت UseMiddleware آن یکی است:

app.UseMiddleware<MyNewMiddleware>();

بنابراین این روش نسبت به روش متداول قبلی، دو تفاوت پیاده سازی اینترفیس مشخص IMiddleware و ثبت کلاس آن به صورت یک سرویس رسمی را دارد؛ مابقی نکات آن، مانند قبل است.

نکته 2: اگر از Scrutor برای ثبت خودکار سرویس‌های برنامه استفاده می‌کنید، روش ثبت خودکار اینگونه سرویس‌ها به صورت زیر و با استفاده از متد ()AsSelf است:

services.Scan(scan => scan.FromAssembliesOf(typeof(IDataSeedersRunner))
            .AddClasses(classes => classes.Where(type =>
            {
                var allInterfaces = type.GetInterfaces();

                return allInterfaces.Contains(typeof(IMiddleware)) && 
                       allInterfaces.Contains(typeof(ISingletonService));
            }))
            .AsSelf()
            .WithSingletonLifetime());

در این مثال، تمام IMiddleware هایی که با نشانگر ISingletonService هم مزین شده‌اند، یافت شده و به صورت Concrete type هایی، با طول عمر Singleton، به سیستم تزریق وابستگی‌ها اضافه می‌شوند.

مطالب
ASP.NET MVC #23

اجرای برنامه‌های ASP.NET MVC توسط نگارش‌های متفاوت IIS

تا اینجا برای اجرای برنامه‌های ASP.NET MVC از وب سرور توکار VS.NET استفاده شد که صرفا جهت آزمایش برنامه‌ها طراحی شده است. تا این تاریخ سه رده از وب سرورهای مایکروسافت ارائه شده‌اند که برای نصب ASP.NET MVC می‌توانند مورد استفاده قرار گیرند و هر کدام هم نکته‌های خاص خودشان را دارند که در ادامه به بررسی آن‌ها خواهیم پرداخت.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP

پس از ایجاد یک دایرکتوری مجازی بر روی پوشه یک برنامه ASP.NET MVC و سعی در اجرای برنامه، بلافاصله پیغام خطای HTTP 403 forbidden مشاهده می‌شود.
اولین کاری که برای رفع این مساله باید صورت گیرد، کلیک راست بر روی نام دایرکتوری مجازی در کنسول IIS، انتخاب گزینه خواص و سپس مراجعه به برگه «ASP.NET» آن است. در اینجا شماره نگارش دات نت فریم ورک مورد استفاده را به 4 تغییر دهید (برای نمونه ASP.NET MVC 3.0 مبتنی بر دات نت فریم ورک 4 است).
بعد از این تغییر، بازهم موفق به اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x نخواهیم شد؛ چون در آن زمان مفاهیم مسیریابی و Routing که اصل و پایه ASP.NET MVC هستند وجود خارجی نداشتند. این نگارش از IIS به صورت پیش فرض تنها قادر به پردازش درخواست‌های رسیده‌ای که به یک فایل فیزیکی بر روی سرور اشاره می‌کند، می‌باشد (یعنی مشکلی با اجرای برنامه‌های ASP.NET Web forms ندارد).
برای رفع این مشکل، مجددا بر روی نام دایرکتوری مجازی برنامه در کنسول IIS کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه ظاهر شده، در برگه «Virtual directory» آن، بر روی دکمه «Configuration» کلیک نمائید. در صفحه باز شده مجددا بر روی دکمه «Add» کلیک کنید.
در صفحه باز شده، مسیر Executable را C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll وارد کرده و Extension را به .* (دات هرچی) تنظیم کنید. همین مقدار تنظیم، برای اجرای برنامه‌های ASP.NET MVC بر روی IIS 5.x ویندوز XP کفایت می‌کند.

کاری که در اینجا انجام شده است، نگاشت تمام درخواست‌های رسیده صرفنظر از پسوند فایل‌ها، به موتور ASP.NET می‌باشد. به صورت پیش فرض در IIS 5.x درخواست‌ها تنها بر اساس پسوند فایل‌ها پردازش می‌شوند. مثلا اگر فایل درخواستی aspx است، درخواست رسیده به aspnet_isapi.dll یاد شده هدایت خواهد شد. اگر پسوند فایل php است به isapi مخصوص آن (در صورت نصب) هدایت می‌گردد و به همین ترتیب برای سایر سیستم‌های دیگر. زمانیکه Extension به «دات هرچی» و Executable به aspnet_isapi.dll دات نت 4 تنظیم می‌شود، دایرکتوری مجازی تنظیم شده تنها جهت سرویس دهی به یک برنامه ASP.NET عمل خواهد کرد و تمام درخواست‌های رسیده به آن، به موتور اجرایی ASP.NET هدایت می‌شوند.

بدیهی است تنظیمات فوق تنها به یک دایرکتوری مجازی اعمال شدند. اگر نیاز باشد تا بر روی تمام سایت‌ها تاثیر گذار شود، اینبار در کنسول IIS 5.x بر روی «Default web site» کلیک راست کرده و گزینه خواص را انتخاب کنید. در صفحه باز شده به برگه «Home directory» مراجعه کرده و مراحل ذکر شده را تکرار کنید.

مشکل! این روش بهینه نیست.
روش فوق خوبه، کار می‌کنه، اما بهینه نیست؛ از این جهت که «نگاشت تمام درخواست‌ها به موتور ASP.NET» یعنی پروسه پردازش درخواست یک فایل تصویری، js یا css هم باید از فیلتر موتور ASP.NET عبور کند که ضروری نیست.
برای رفع این مشکل، توصیه شده است که سیستم مسیریابی ASP.NET MVC را در IIS 5.x «پسوند دار» کنید. به این نحو که با مراجعه به فایل Global.asax.cs، تعاریف مسیریابی را به نحو زیر ویرایش کنید:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
new Route("{controller}.aspx/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
})
});



اینبار برای مثال مسیر http://localhost/MyMvcApp/home.aspx/index به علت داشتن پسوند aspx وارد موتور پردازشی ASP.NET خواهد شد. البته در این حالت URL های تمیز ASP.NET MVC را از دست خواهیم داد و مدام باید دقت داشت که مسیرهای کنترلرها حتما باید به aspx ختم شوند. ضمنا با این تنظیم، دیگر نیازی به تغییر تعاریف نگاشت‌ها در کنسول مدیریتی IIS، نخواهد بود.


اجرای برنامه‌های ASP.NET MVC بر روی IIS 6.x ویندوز سرور 2003

تمام نکات عنوان شده جهت IIS 5.x در IIS 6.x نیز صادق هستند. به علاوه برای اجرای برنامه‌های ASP.NET بر روی IIS 6.x باید به دو نکته مهم دیگر نیز دقت داشت:
الف) ASP.NET 4 به صورت پیش فرض در IIS 6.x غیرفعال است که باید با مراجعه به قسمت Web Services Extensions در کنسول مدیریتی IIS، آن‌را از حالت prohibited خارج کرد.
ب) در هر Application pool تنها از یک نگارش دات نت فریم ورک می‌توان استفاده کرد. برای مثال اگر هم اکنون AppPool1 مشغول سرویس دهی به یک سایت ASP.NET 3.5 است، از آن نمی‌توانید جهت اجرای برنامه‌های ASP.NET MVC 3 به بعد استفاده کنید. زیرا برای مثال ASP.NET MVC 3 مبتنی بر دات نت فریم ورک 4 است. به همین جهت حتما نیاز است تا یک Application pool مجزا را برای برنامه‌های دات نت 4 در IIS 6 اضافه نمائید و سپس در تنظیمات سایت، از این Application pool جدید استفاده نمائید.
البته روش صحیح و اصولی کار با IIS از نگارش 6 به بعد هم مطابق شرحی است که عنوان شد. برای دستیابی به بهترین کارآیی و امنیت بیشتر، بهتر است به ازای هر سایت، از یک Application pool مجزا استفاده نمائید.

اطلاعات تکمیلی:
نکات نصب برنامه‌های ASP.NET 4.0 بر روی IIS 6
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS



اجرای برنامه‌های ASP.NET MVC بر روی IIS 7.x ویندوز 7 و ویندوز سرور 2008

اگر برنامه ASP.NET MVC در IIS 7.x در حالت یکپارچه (integrated mode) اجرا شود، بدون نیاز به هیچگونه تغییری در تنظیمات سرور یا برنامه، بدون مشکل قابل اجرا خواهد بود. بدیهی است در اینجا نیز بهتر است به ازای هر برنامه، یک Application pool مجزا را ایجاد کرد.
اما در حالت classic (که برای برنامه‌های جدید توصیه نمی‌شود) نیاز است همان مراحل IIS 5,x تکرار شود. البته اینبار مسیر زیر را باید طی کرد تا به صفحه افزودن نگاشت‌ها رسید:
Right-click on a web site -> Properties -> Home Directory tab -> click on the Configuration button -> Mappings tab



نکته‌ای مهم در تمام نگارش‌های IIS

ترتیب نصب دات نت فریم ورک 4 و IIS مهم است. اگر ابتدا IIS نصب شود و سپس دات نت فریم ورک 4، به صورت خودکار، کار نگاشت اطلاعات ASP.NET به IIS صورت خواهد گرفت.
اگر ابتدا دات نت فریم ورک 4 نصب شود و سپس IIS، برای مثال دیگر از برگه ASP.NET در IIS 6.x خبری نخواهد بود. برای رفع این مشکل دستور زیر را در خط فرمان اجرا کنید:

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe /i

به این ترتیب، اطلاعات مرتبط با موتور ASP.NET مجددا به تنظیمات IIS اضافه خواهند شد.


نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 8 - فعال سازی ASP.NET MVC
در ASP.NET Core 3.0، بنابر درخواست استفاده کنندگان که به نظر آن‌ها AddMvc تعداد زیادی از سرویس‌ها را به سیستم اضافه می‌کند که شاید مورد استفاده قرار نگیرند (برای مثال کسانیکه فقط با Web API کار می‌کنند، نیازی به سرویس‌های Viewها ندارند)، متدهای اختصاصی‌تری طراحی شده‌اند. البته AddMvc حذف نخواهد شد و همانند قبل رفتار می‌کند.
هدف
سرویس‌های اضافه شده
متد تنظیم سرویس‌ها
- مناسب برای توسعه‌ی Web API
- اما باید بخاطر داشت که این سرویس‌ها را به صورت پیش‌فرض اضافه نمی‌کند:
  • Antiforgery
  • Temp Data
  • Views
  • Pages
  • Tag Helpers
  • Memory Cache 
  • Controllers
  • Model Binding
  • API Explorer (OpenAPI integration)
  • Authorization [Authorize]
  • CORS [EnableCors]
  • Data Annotations validation [Required]
  • Formatter Mappings (translate a file-extension to a content-type) 
 ()AddControllers 
شبیه به ASP.NET Core 1.X عمل می‌کند؛ یعنی سرویس Pages را که مرتبط با Razor Pages است، به صورت پیش‌فرض ثبت نمی‌کند.
  • Controllers
  • Model Binding
  • API Explorer (OpenAPI integration)
  • Authorization [Authorize]
  • CORS [EnableCors]
  • Data Annotations validation [Required]
  • Formatter Mappings (translate a file-extension to a content-type)
  • Antiforgery
  • Temp Data
  • Views
  • Tag Helpers
  • Memory Cache 
 ()AddControllersWithViews
 برای کار با Razor pages بوده و این سرویس‌ها را به صورت پیش‌فرض به همراه ندارد:
  • API Explorer (OpenAPI integration)
  • CORS [EnableCors]
  • Formatter Mappings (translate a file-extension to a content-type) 
  • Pages
  • Controllers
  • Model Binding
  • Authorization [Authorize]
  • Data Annotations validation [Required]
  • Antiforgery
  • Temp Data
  • Views
  • Tag Helpers
  • Memory Cache 
 ()AddRazorPages
مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت پنجم

دانای اطلاعات ( Information Expert )

بر طبق این اصل می­توان برای واگذاری هر مسئولیت، کلاسی را انتخاب کرد که بیشترین اطلاعات را در مورد انجام آن در اختیار دارد و لذا نیاز کمتری به ایجاد ارتباط با دیگر مولفه‌ها خواهد داشت.

در مثال زیر مشاهده میکنید که کلاس User، اطلاعات کاملی را از عملیات اضافه کردن آیتمی را به لیست خرید و تسویه حساب، ندارد و پیاده سازی این عملیات در این کلاس، نیاز به ایجاد وابستگی‌های پیچیده‌ای دارد. 

public class User
    {
        public ShoppingCart ShoppingCart { get; set; }
        public void AddItem(string name) {
            // User class must know how to create OrderItem
            var item = new OrderItem() { Name = name };

            // User class must know how to add item to shopping cart
            ShoppingCart.Items.Add(item);
            
        }
        public void CheckOut() {
            // User class must know logic behind cost and discount calculations:
            // check for discount
            // check shipping method
            // check promotions
            // calculate total cost of items 
        }
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public string Name { get; set; }

    }
    public class ShoppingCart
    {
        public int Id { get; set; }
        public List<OrderItem> Items { get; set; }
       
    }

بنابراین به جای این طراحی، مسئولیت‌ها را به ShoppingCart منتقل میکنیم:

 public class User
    {
        public ShoppingCart ShoppingCart { get; set; }
        
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public string Name { get; set; }

    }
    public class ShoppingCart
    {
        public int Id { get; set; }
        public List<OrderItem> Items { get; set; }
        public void AddItem(string name)
        {
            // ShoppingCart class know how to create OrderItem
            var item = new OrderItem() { Name = name };

            // ShoppingCart class already know how to add item 
            Items.Add(item);

        }
        public void CheckOut()
        {
            // ShoppingCart class know logic behind cost and discount calculations:
            // check for discount
            // check shipping method
            // check promotions
            // calculate total cost of items 
        }
    }


اتصال ضعیف ( Low Coupling )

با اتصال ضعیف نیز که از ویژگی‌های یک طراحی خوب است آشنا هستیم. هر چه تعداد و نوع اتصال بین مولفه‌ها کم‌تر و ضعیف‌تر باشد، اعمال تغییرات راحت‌تر صورت خواهد گرفت. طراحی با اتصال مناسب سه ویژگی را دارد:

-  وابستگی بین کلاس‌ها کم است.

-  تغییرات در یک کلاس، اثر کمی بر دیگر کلاس‌ها دارد.

-  پتانسیل استفاده‌ی مجدد از مؤلفه‌ها بالا است.

چنانچه قبلا هم اشاره کردم، نوشتن نرم افزاری بدون اتصال، ممکن نیست و باید مؤلفه‌ها با هم همکاری کرده و وظایف را انجام دهند. با این حال میتوان نوع اتصالات و تعداد آنرا بهبود بخشید.


چند ریختی ( Polymorphism )

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

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

public class ShapeWithoutPolymorphism
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
        public double Area(string shapeType)
        {
            switch (shapeType)
            {
                case "square":
                    return X * Y;
                case "rectangle":
                    return X * Y;
                case "trapze":
                    return (X + Z) * Y / 2;
                default:
                    return 0;
            }
        }
    }

با استفاده از چندریختی، طراحی به این صورت در خواهد آمد:

  public abstract class Shape
    {
        public double X { get; set; }
        public double Y { get; set; }
        public virtual double Area()
        {
            return X * Y;
        }
    }
    public class Rectangle : Shape
    {
        // No need to override
    }
    public class Square : Shape
    {
        // No need to override
    }
    public class Trapze : Shape
    {
        public double Z { get; set; }
        public override double Area()
        {
            return (X + Z) * Y / 2;

        }
    }


مصنوع خالص ( Pure Fabrication )

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

برای تشخیص زمان استفاده از این اصل میتوان گفت زمانیکه رفتاری را نمیدانیم به کدام کلاس واگذار کنیم، کلاس جدیدی را ایجاد میکنیم. در اینجا بجای آنکه به زور مسئولیتی را به کلاس نامربوطی بچسبانیم، آنرا به کلاس جدیدی که فقط رفتاری را دارد، منتقل میکنیم. با اینکار انسجام کلاس‌ها را حفظ کرده‌ایم و هیچ اشکالی ندارد که کلاسی بدون داده بوده و فقط متد داشته باشد. اگر به یاد داشته باشید، در اصل واسطه گری (Indirection ) کلاس جدیدی برای ایجاد ارتباط ساختیم. در حقیقت مسئولیت برقراری ارتباط بین مؤلفه‌ها را به کلاس دیگری واگذار کردیم که چنانچه میبینید، بدون آنکه بدانیم، برای حل مشکل از اصل مصنوع خالص استفاده کردیم.

در مثال زیر این مساله مشهود است:

public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }

    public class LibraryManagement
    {
        public User CurrentUser { get; set; }
        public void AddBookToLibrary(int bookId)
        {
            // check for CurrentUser authority:
            // not user's responsibility nor LibraryManagement
        }
        public void RearrangeBook(int bookId, int shelfId)
        {
            // check for CurrentUser authority
            // not user's responsibility nor LibraryManagement
        }
    }
    public class UserManagement
    {
        public User CurrentUser { get; set; }
        public void AddUser(string name)
        {
            // check for CurrentUser authority:
            // not user's responsibility nor UserManagement
        }
        public void ChangeUserRole(int userId, int roleId)
        {
            // check for CurrentUser authority
            // not user's responsibility nor UserManagement
        }
    }
    public class AuthorizationService
    {
        public bool IsAuthorized(int userId, int roleId)
        {
            // get user roles from data base
            // return true if user has the authority
        }
    }

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


حفاظت از تاثیر تغییرات ( Protected Variations )

این اصل میگوید که کلاس‌ها باید از تغییرات یکدیگر مصون بمانند. در واقع این اصل غایت یک طراحی خوب است. تمام اصولی را که تا به حال بررسی کرده‌ایم، به منظور دستیابی به چنین رفتاری از طراحی بوده‌است. بدین منظور باید از اصول Open/Closed برای واسط­ها، چند ریختی در توارث و ... استفاده کرد تا از تاثیرات زنجیره‌ای تغییرات در امان بمانیم.

مطالب دوره‌ها
تزریق وابستگی‌ها
خوب! بالاخره بعد از دو قسمت قبل، به بحث اصلی تزریق وابستگی‌ها رسیدیم. بحثی که بسیاری از برنامه نویس‌های تصور می‌کنند همان IoC است که پیشتر در مورد آن بحث شد.

تزریق وابستگی‌ها یا DI چیست؟

تزریق وابستگی‌ها یکی از انواع IoC بوده که در آن ایجاد و انقیاد (binding) یک وابستگی، در خارج از کلاسی که به آن نیاز دارد صورت می‌گیرد. روش‌های متفاوتی برای ارائه این وابستگی وهله سازی شده در خارج از کلاس به آن وجود دارد که در ادامه مورد بررسی قرار خواهند گرفت.
یک مثال: بسته غذایی را که با خود به سر کار می‌برید درنظر بگیرید. تمام اجزای مورد نیاز تشکیل دهنده یک بسته غذا عموما داخل آن قرار گرفته و حمل می‌شوند. حالت عکس آن زمانی است که در محل کار به شما غذا می‌دهند. این دقیقا همان حالتی است که تزریق وابستگی‌ها کار می‌کند؛ یک سری سرویس‌های خارجی، نیازهای کلاس جاری را برآورده می‌سازند.


در تصویر فوق یک طراحی مبتنی بر معکوس سازی کنترل‌ها را مشاهده می‌کنید. وابستگی‌های یک کلاس توسط اینترفیسی مشخص شده و سپس کلاسی وجود دارد که این وابستگی را پیاده سازی کرده است.
همچنین در این تصویر یک خط منقطع از کلاس MyClass به کلاس Dependency رسم شده است. این خط، مربوط به حالتی است که خود کلاس، مستقیما کار وهله سازی وابستگی مورد نیاز خود را انجام دهد؛ هر چند اینترفیسی نیز در این بین تعریف شده باشد. بنابراین اگر در بین کدهای این کلاس، چنین کدی مشاهده شد:
 IDependency dependency= new Dependency();
تعریف dependency از نوع IDependency به معنای معکوس سازی کنترل نبوده و عملا همان معماری سابق و متداول بکارگرفته شده است. برای بهبود این وضعیت، از تزریق وابستگی‌ها کمک خواهیم گرفت:


در اینجا یک کلاس دیگر به نام Injector اضافه شده است که قابلیت تزریق وابستگی‌ها را به کلاس نیازمند آن به روش‌های مختلفی دارا است. کار کلاس Injector، وهله سازی MyClass و همچنین وابستگی‌های آن می‌باشد؛ سپس وابستگی را دراختیار MyClass قرار می‌دهد.


تزریق وابستگی‌ها در سازنده کلاس

یکی از انواع روش‌های تزریق وابستگی‌ها، Constructor Injection و یا تزریق وابستگی‌ها در سازنده کلاس می‌باشد که متداول‌ترین نوع آن‌ها نیز به‌شمار می‌رود. در این حالت، وابستگی پس از وهله سازی، از طریق پارامترهای سازنده یک کلاس، در اختیار آن قرار می‌گیرند.
یک مثال:
public class Shopper
{
   private readonly ICreditCard _creditCard;
   public Shopper(ICreditCard creditCard)
   {
      _creditCard = creditCard;
   }
}
در اینجا شما یک کلاس خریدار را مشاهده می‌کنید که وابستگی مورد نیاز خود را به شکل یک اینترفیس از طریق سازنده کلاس دریافت می‌کند.
 ICreditCard creditCard = new MasterCard();
Shopper shopper = new Shopper(creditCard);
برای نمونه، وهله‌ای از مستر کارتی که  ICreditCard را پیاده سازی کرده باشد، می‌توان به سازنده این کلاس ارسال کرد. کار وهله سازی وابستگی و واژه کلیدی new به خارج از کلاس استفاده کننده از وابستگی منتقل شده است. بنابراین همانطور که ملاحظه می‌کنید، این مفاهیم آنچنان پیچیده نبوده و به همین سادگی قابل تعریف و اعمال هستند.


تزریق در خواص عمومی کلاس

Setter Injection و یا تزریق در خواص عمومی یک کلاس، یکی دیگر از روش‌های تزریق وابستگی‌ها است (setter در اینجا منظور همان get و set یک خاصیت می‌باشد).
در حالت تزریق وابستگی‌ها در سازنده کلاس، امکان وهله سازی آن کلاس بدون ارسال وابستگی‌ها به سازنده آن ممکن نیست؛ اما در اینجا خیر و امکان وهله سازی و استفاده از یک کلاس، پیش از اعمال وابستگی‌ها نیز وجود دارد. بنابراین امکان تغییر و تعویض وابستگی‌ها پس از وهله سازی کلاس نیز میسر است.
public class Shopper
{
   public ICreditCard CreditCard { get; set; }
}

ICreditCard creditCard = new MasterCard();
Shopper shopper = new Shopper();
shopper.CreditCard = creditCard;
نمونه‌ای از این روش را در مثال فوق مشاهده می‌کنید. در این کلاس، وابستگی مورد نیاز از طریق یک خاصیت عمومی دریافت شده است.


تزریق اینترفیس‌ها

تزریق اینترفیس‌ها نیز یکی دیگر از روش‌های تزریق وابستگی‌ها است؛ اما آنچنان استفاده گسترده‌ای برخلاف دو روش قبل نیافته است.
در این روش نه از سازنده کلاس استفاده می‌شود و نه از یک خاصیت عمومی. ابتدا یک اینترفیس که بیانگر ساختار کلاسی که قرار است تزریق شود ایجاد می‌گردد. سپس این اینترفیس را در کلاس وابستگی مورد نظر پیاده سازی خواهیم کرد. در این اینترفیس نیاز است متد خاصی تعریف شود تا کار تزریق وابستگی را انجام دهد.
یک مثال:
public interface IDependOnCreditCard
{
   void Inject(ICreditCard creditCard);
}

public class Shopper : IDependOnCreditCard
{
  private ICreditCard creditCard;
  public void Inject(ICreditCard creditCard)
  {
     this.creditCard = creditCard;
  }
}  

ICreditCard creditCard = new MasterCard();
Shopper shopper = new Shopper();
((IDependOnCreditCard)shopper).Inject(creditCard);
در اینجا یک اینترفیس به نام IDependOnCreditCard  تعریف گردیده و متد خاصی را به نام Inject تدارک دیده است که نوعی از کردیت کارد را دریافت می‌کند.
در ادامه کلاس خریدار، اینترفیس IDependOnCreditCard را پیاده سازی کرده است. به این ترتیب کلاس Injector تنها کافی است بداند تا این متد خاص را باید جهت تنظیم و تزریق وابستگی‌ها فراخوانی نماید. این روش نسبتا شبیه به روش Setter injection است.
این روش بیشتر می‌تواند جهت فریم ورک‌هایی که قابلیت یافتن کلیه کلاس‌های مشتق شده از IDependOnCreditCard  را داشته و سپس می‌دانند که باید متد Inject آن‌ها را فراخوانی کنند، مناسب است.


نکاتی که باید حین کار با تزریق وابستگی‌ها درنظر داشت

الف) حین استفاده از تزریق وابستگی‌ها، وهله سازی به تاخیر افتاده وابستگی‌ها میسر نیست. برای مثال زمانیکه یک وابستگی قرار است در سازنده کلاسی تزریق شود، وهله سازی آن باید پیش از نیاز واقعی به آن صورت گیرد. البته امکان استفاده از اشیاء Lazy دات نت 4 برای مدیریت این مساله وجود دارد اما در حالت کلی، دیگر همانند قبل و روش‌های متداول، وهله سازی تنها زمانیکه نیاز به آن وابستگی خاص باشد، میسر نیست. به همین جهت باید به تعداد وابستگی‌هایی که قرار است در یک کلاس استفاده شوند نیز جهت کاهش مصرف حافظه دقت داشت.
ب) یکی از مزایای دیگر تزریق وابستگی‌ها، ساده‌تر شدن نوشتن آزمون‌های واحد است. زیرا تهیه Mocks در این حالت کار با اینترفیس‌ها بسیار ساده‌تر است. اما باید دقت داشت، کلاسی که در سازنده آن حداقل 10 اینترفیس را به عنوان وابستگی دریافت می‌کند، احتمالا دچار مشکلاتی در طراحی و همچنین مصرف حافظه می‌باشد.