مطالب
Globalization در ASP.NET MVC - قسمت هفتم
در قسمت قبل مطالب تکمیلی تولید پرووایدر سفارشی منابع دیتابیسی ارائه شد. در این قسمت نحوه بروزرسانی ورودی‌های منابع در زمان اجرا بحث می‌شود.

.

تولید یک پرووایدر منابع دیتابیسی - بخش سوم

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

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

اما همان‌طور که در قسمت قبل نیز اشاره شد، نکته‌ای که باید درنظر داشت این است که مدیریت تمامی نمونه‌های تولیدشده از کلاس‌های موردبحث کاملا برعهده ASP.NET است، بنابراین دسترسی مستقیمی به این نمونه‌ها در بیرون و در زمان اجرا وجود ندارد تا این ویژگی را بتوان در مورد آن‌ها پیاده کرد.

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


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


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


پیاده‌سازی امکان پاک‌سازی مقادیر کش‌شده

برای این‌کار باید تغییراتی در کلاس DbResourceManager داده شود تا بتوان این کلاس را از تغییرات بوجود آمده آگاه ساخت. روشی که من برای این کار درنظر گرفتم استفاده از یک اینترفیس حاوی اعضای موردنیاز برای پیاده‌سازی این امکان است تا مدیریت این ویژگی در ادامه راحت‌تر شود.


اینترفیس IDbCachedResourceManager

این اینترفیس به صورت زیر تعریف شده است:

namespace DbResourceProvider
{
  internal interface IDbCachedResourceManager
  {
    string ResourceName { get; }

    void ClearAll();
    void Clear(string culture);
    void Clear(string culture, string resourceKey);
  }
}

در پراپرتی فقط خواندنی ResourceName نام منبع کش شده ذخیره خواهد شد.

متد ClearAll برای پاک‌سازی تمامی ورودی‌های کش‌شده استفاده می‌شود.

متدهای Clear برای پاک‌سازی ورودی‌های کش‌شده یک کالچر به خصوص و یا یک ورودی خاص استفاده می‌شود.

با استفاده از این اینترفیس، پیاده‌سازی کلاس DbResourceManager به صورت زیر تغییر می‌کند:

using System.Collections.Generic;
using System.Globalization;
using DbResourceProvider.Data;
namespace DbResourceProvider
{
  internal class DbResourceManager : IDbCachedResourceManager
  {
    private readonly string _resourceName;
    private readonly Dictionary<string, Dictionary<string, object>> _resourceCacheByCulture;
    public DbResourceManager(string resourceName)
    {
      _resourceName = resourceName;
      _resourceCacheByCulture = new Dictionary<string, Dictionary<string, object>>();
    }
    public object GetObject(string resourceKey, CultureInfo culture) { ... }
    private object GetCachedObject(string resourceKey, string cultureName) { ... }

    #region Implementation of IDbCachedResourceManager
    public string ResourceName
    {
      get { return _resourceName; }
    }
    public void ClearAll()
    {
      lock (this)
      {
        _resourceCacheByCulture.Clear(); 
      }
    }
    public void Clear(string culture)
    {
      lock (this)
      {
        if (!_resourceCacheByCulture.ContainsKey(culture)) return;
        _resourceCacheByCulture[culture].Clear(); 
      }
    }
    public void Clear(string culture, string resourceKey)
    {
      lock (this)
      {
        if (!_resourceCacheByCulture.ContainsKey(culture)) return;
        _resourceCacheByCulture[culture].Remove(resourceKey); 
      }
    }
    #endregion
  }
}

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

برای استفاده از این امکانات جدید همان‌طور که در بالا نیز اشاره شد باید بتوان نمونه‌های تولیدی از کلاس DbResourceManager توسط ASP.NET درون متغیری استاتیک ذخیره شوند. برای اینکار از کلاس جدیدی با عنوان DbResourceCacheManager استفاده می‌شود که برخلاف تمام کلاس‌های تعریف‌شده تا اینجا با سطح دسترسی public تعریف می‌شود.


کلاس DbResourceCacheManager

مدیریت نمونه‌های تولیدی از کلاس DbResourceManager در این کلاس انجام می‌شود. این کلاس پیاده‌سازی ساده‌ای به‌صورت زیر دارد:

using System.Collections.Generic;
using System.Linq;
namespace DbResourceProvider
{
  public static class DbResourceCacheManager
  {
    internal static List<IDbCachedResourceManager> ResourceManagers { get; private set; }
    static DbResourceCacheManager()
    {
      ResourceManagers = new List<IDbCachedResourceManager>();
    }
    public static void ClearAll()
    {
      ResourceManagers.ForEach(r => r.ClearAll());
    }
    public static void Clear(string resourceName)
    {
      GetResouceManagers(resourceName).ForEach(r => r.ClearAll());
    }
    public static void Clear(string resourceName, string culture)
    {
      GetResouceManagers(resourceName).ForEach(r => r.Clear(culture));
    }
    public static void Clear(string resourceName, string culture, string resourceKey)
    {
      GetResouceManagers(resourceName).ForEach(r => r.Clear(culture, resourceKey));
    }

    private static List<IDbCachedResourceManager> GetResouceManagers(string resourceName)
    {
      return ResourceManagers.Where(r => r.ResourceName.ToLower() == resourceName.ToLower()).ToList();
    }
  }
}

ازآنجاکه نیازی به تولید نمونه ای از این کلاس وجود ندارد، این کلاس به صورت استاتیک تعریف شده است. بنابراین تمام اعضای درون آن نیز استاتیک هستند.

از پراپرتی ResourceManagers برای نگهداری لیستی از نمونه‌های تولیدی از کلاس DbResourceManager استفاده می‌شود. این پراپرتی از نوع <List<IDbCachedResourceManager تعریف شده است و برای جلوگیری از دسترسی بیرونی، سطح دسترسی آن internal درنظر گرفته شده است.

در کانستراکتور استاتیک این کلاس (اطلاعات بیشتر درباره static constructor در اینجا) این پراپرتی با مقداردهی به یک نمونه تازه از لیست، اصطلاحا initialize می‌شود.

سایر متدها نیز برای فراخوانی متدهای موجود در اینترفیس IDbCachedResourceManager پیاده‌سازی شده‌اند. تمامی این متدها دارای سطح دسترسی public هستند. همان‌طور که می‌بینید از خاصیت ResourceName برای مشخص‌کردن نمونه موردنظر استفاده شده است که دلیل آن در قسمت قبل شرح داده شده است.

دقت کنید که برای اطمینان از انتخاب درست همه موارد موجود در شرط انتخاب نمونه موردنظر در متد GetResouceManagers از متد ToLower برای هر دو سمت شرط استفاده شده است.


نکته مهم: درباره علت برگشت یک لیست از متد انتخاب نمونه موردنظر از کلاس DbResourceManager در کد بالا (یعنی متد GetResouceManagers) باید نکته‌ای اشاره شود. در قسمت قبل عنوان شد که سیستم مدیریت منابع ASP.NET نمونه‌های تولیدی از پرووایدرهای منابع را به ازای هر منبع کش می‌کند. اما یک نکته بسیار مهم که باید به آن توجه کرد این است که این کش برای «عبارات بومی‌سازی ضمنی» و نیز «متد مربوط به منابع محلی» موجود در کلاس HttpContext و یا نمونه مشابه آن در کلاس TemplateControl (همان متد GetLocalResourceObject که درباره این متدها در قسمت سوم این سری شرح داده شده است) از یکدیگر جدا هستند و استفاده از هریک از این دو روش موجب تولید یک نمونه مجزا از پرووایدر مربوطه می‌شود که متاسفانه کنترل آن از دست برنامه نویس خارج است. دقت کنید که این اتفاق برای منابع کلی رخ نمی‌دهد.

بنابراین برای پاک کردن مناسب ورودی‌های کش‌شده در کلاس فوق به جای استفاده از متد Single در انتخاب نمونه موردنظر از کلاس DbResourceManager (در متد GetResouceManagers) از متد Where استفاده شده و یک لیست برگشت داده می‌شود. چون با توجه به توضیح بالا امکان وجود دو نمونه DbResourceManager از یک منبع درخواستی محلی در لیست نمونه‌های نگهداری شده در این کلاس وجود دارد.

.

افزودن نمونه‌ها به کلاس DbResourceCacheManager

برای نگهداری نمونه‌های تولید شده از DbResourceManager، باید در یک قسمت مناسب این نمونه‌ها را به لیست مربوطه در کلاس DbResourceCacheManager اضافه کرد. بهترین مکان برای انجام این عمل در کلاس پایه BaseDbResourceProvider است که درخواست تولید نمونه را در متد EnsureResourceManager درصورت نال بودن آن می‌دهد. بنابراین این متد را به صورت زیر تغییر می‌دهیم:

private void EnsureResourceManager()
{
  if (_resourceManager != null) return;
  {
    _resourceManager = CreateResourceManager();
    DbResourceCacheManager.ResourceManagers.Add(_resourceManager);
  }
}

تا اینجا کار پیاده‌سازی امکان مدیریت مقادیر کش‌شده در کتابخانه تولیدی به پایان رسیده است.

استفاده از کلاس DbResourceCacheManager

پس از پیاده‌سازی تمامی موارد لازم، حالتی را درنظر بگیرید که مقادیر ورودی‌های تعریف شده در منبع "dir1/page1.aspx" تغییر کرده است. بنابراین برای بروزرسانی مقادیر کش‌شده کافی است تا از کدی مثل کد زیر استفاده شود:

DbResourceCacheManager.Clear("dir1/page1.aspx");

کد بالا کل ورودی‌های کش‌شده برای منبع "dir1/page1.aspx" را پاک می‌کند. برای پاک کردن کالچر یا یک ورودی خاص نیز می‌توان از کدهایی مشابه زیر استفاده کرد:

DbResourceCacheManager.Clear("Default.aspx", "en-US");
DbResourceCacheManager.Clear("GlobalTexts", "en-US", "Yes");

.

دریافت کد پروژه

کد کامل پروژه DbResourceProvider به همراه مثال و اسکریپت‌های دیتابیسی مربوطه از لینک زیر قابل دریافت است:

DbResourceProvider.rar

برای استفاده از این مثال ابتدا باید کتابخانه Entity Framework (با نام EntityFramework.dll) را مثلا از طریق نوگت دریافت کنید. نسخه‌ای که من در این مثال استفاده کردم نسخه 4.4 با حجم حدود 1 مگابایت است.

نکته: در این کد یک بهبود جزئی اما مهم در کلاس ResourceData اعمال شده است. در قسمت سوم این سری، اشاره شد که نام ورودی‌های منابع Case Sensitive نیست. بنابراین برای پیاده‌سازی این ویژگی، متدهای این کلاس باید به صورت زیر تغییر کنند:

public Resource GetResource(string resourceKey, string culture)
{
  using (var data = new TestContext())
  {
    return data.Resources.SingleOrDefault(r => r.Name.ToLower() == _resourceName.ToLower() && r.Key.ToLower() == resourceKey.ToLower() && r.Culture == culture);
  }
}

public List<Resource> GetResources(string culture)
{
  using (var data = new TestContext())
  {
    return data.Resources.Where(r => r.Name.ToLower() == _resourceName.ToLower() && r.Culture == culture).ToList();
  }
}
تغییرات اعمال شده همان استفاده از متد ToLower در دو طرف شرط مربوط به نام منابع و کلید ورودی‌هاست.


در آینده...

در ادامه مطالب، بحث تهیه پرووایدر سفارشی فایلهای resx. برای پیاده‌سازی امکان به‌روزرسانی در زمان اجرا ارائه خواهد شد. بعد از پایان تهیه این پرووایدر سفارشی، این سری مطالب با ارائه نکات استفاده از این پرووایدرها در ASP.NET MVC پایان خواهد یافت.


منابع

http://msdn.microsoft.com/en-us/library/aa905797.aspx

http://www.west-wind.com/presentations/wwdbresourceprovider

اشتراک‌ها
نصب و پیکربندی Microsoft Team Foundation Server 2015
در کار تیمی بر روی پروژه‌های بزرگ ، نرم افزار‌هایی همچون Microsoft Team Foundation Server و ... باعث شده که خیلی از دغدغه‌های تیم‌های توسعه دهنده  نرم افزار برطرف شود . 

برای نصب و پیکربندی :
روش اول : اگر دسترسی به سرور ندارید ، مایکروسافت  برای توسعه دهندگان Net. ،  روی سرور‌های خود Microsoft Team Foundation Server را پیاده کرده است و در این روش ما به ویژوال استودیو  و به یک حساب کاربری مایکروسافت نیاز داریم و با مراجعه به سایت www.visualstudio.com وارد حساب کاربری مایکروسافت خود شده و بر روی Create Account کلیک کرده و اطلاعات مورد نیاز را وارد می‌کنیم .در مرحله بعد طبق تصویر زیر اطلاعات پروژه ای که قرار است توسعه داده شود را وارد می‌کنیم .

که وارد قسمت مدیریتی می‌شویم طبق تصویر زیر :

 در مرحله بعد با مراجعه به ویژوال استودیو پروژه خود را با ویژوال استودیو ایجاد می‌کنیم و پروژه خود را به Team Foundation Serve مایکروسافت متصل خواهیم کرد . با کلیک بر روی Team از منوی ویژوال استودیو Manage Connections را انتخاب کرده و طبق تصویر زیر اطلاعات در خواستی را وارد می‌کنیم و بر روی Connect کلیک می‌کنیم


در مرحله بعد بر روی Solution پروژه راست کلیک کرده و Add Solution to Source Control را انتخاب میکنیم و با مشاهده پنجره Add Solution to Source Control بر روی ok کلیک کرده و با مراجعه به Solution Explorer پروژه مشاهده می‌کنیم که علامت + سبز رنگ در کنار Item‌های پروژه مشاهد می‌شود و این نشان دهنده این می‌باشد که پروژه ما  هنوز به Team Foundation Server مایکروسافت   منتقل نشده است . و برای انتقال بر روی Solution  پروژه راست کلیلک کرده و ..Check In را انتخاب می‌کنیم و در قسمت Comment می‌توانیم توضیحاتی را اضافه کرده و دوباره بر روی Check کلیک کرده و Item‌های پروژه ما منتقل سرور مایکروسافت می‌شود . 

روش دوم : 

در این روش ما نیاز به Windows Server 2012 R2  و  نرم افزار 2015 Microsoft Team Foundation Server  داریم و مراحل را در این روش طبق مقاله زیر جلو می‌بریم که می‌توانید به مراجعه به لینک زیر به مقاله دسترسی داشته باشید . 

Benday TFS 2015 Install guide v1.0

Team Foundation Server 2015 (TFS2015) Installation Guide

  
نصب و پیکربندی Microsoft Team Foundation Server 2015
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
در قسمت قبل موفق شدیم بر اساس OpenAPI specification endpoint تنظیم شده، رابط کاربری خودکاری را توسط ابزار Swagger-UI تولید کنیم. در ادامه می‌خواهیم این مستندات تولید شده را غنی‌تر کرده و کیفیت آن‌را بهبود دهیم.


استفاده از XML Comments برای بهبود کیفیت مستندات API

نوشتن توضیحات XML ای برای متدها و پارامترها در پروژه‌های دات‌نتی، روشی استاندارد و شناخته شده‌است. برای نمونه در AuthorsController، می‌خواهیم توضیحاتی را به اکشن متد GetAuthor آن اضافه کنیم:
/// <summary>
/// Get an author by his/her id
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <returns>An ActionResult of type Author</returns>
[HttpGet("{authorId}")]
public async Task<ActionResult<Author>> GetAuthor(Guid authorId)
در این حالت اگر برنامه را اجرا کنیم، این توضیحات XMLای هیچ تاثیری را بر روی OpenAPI specification تولیدی و در نهایت Swagger-UI تولید شده‌ی بر اساس آن، نخواهد داشت. برای رفع این مشکل، باید به فایل OpenAPISwaggerDoc.Web.csproj مراجعه نمود و تولید فایل XML متناظر با این توضیحات را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
پس از تنظیم خاصیت GenerateDocumentationFile به true، با هر بار Build برنامه، فایل xml ای مطابق نام اسمبلی برنامه، در پوشه‌ی bin آن تشکیل خواهد شد؛ مانند فایل bin\Debug\netcoreapp2.2\OpenAPISwaggerDoc.Web.xml در این مثال.
اکنون نیاز است وجود این فایل را به تنظیمات SwaggerDoc در کلاس Startup برنامه، اعلام کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
                    // ... 
                   );

                var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);
                setupAction.IncludeXmlComments(xmlCommentsFullPath);
            });
        }
در متد IncludeXmlComments، بجای ذکر صریح نام و مسیر فایل OpenAPISwaggerDoc.Web.xml، بر اساس نام اسمبلی جاری، نام فایل XML مستندات تعیین و مقدار دهی شده‌است.
پس از این تنظیمات اگر برنامه را اجرا کنیم، در Swagger-UI حاصل، این تغییرات قابل مشاهده هستند:




افزودن توضیحات به Response

تا اینجا توضیحات پارامترها و متدها را افزودیم؛ اما response از نوع 200 آن هنوز فاقد توضیحات است:


علت را نیز در تصویر فوق مشاهده می‌کنید. قسمت responses در OpenAPI specification، اطلاعات خودش را از اسکیمای مدل‌های مرتبط دریافت می‌کند. بنابراین نیاز است کلاس DTO متناظر با Author را به نحو ذیل تکمیل کنیم:
using System;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author with Id, FirstName and LastName fields
    /// </summary>
    public class Author
    {
        /// <summary>
        /// The id of the author
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// The first name of the author
        /// </summary>
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        public string LastName { get; set; }
    }
}
مشکل! در این حالت اگر برنامه را اجرا کنیم، خروجی این توضیحات را در قسمت schemas مشاهده نخواهیم کرد. علت اینجا است که چون اسمبلی OpenAPISwaggerDoc.Models با اسمبلی OpenAPISwaggerDoc.Web یکی نیست و آن‌را از پروژه‌ی اصلی خارج کرده‌ایم، به همین جهت نیاز است ابتدا به فایل OpenAPISwaggerDoc.Models.csproj مراجعه و GenerateDocumentationFile آن‌را فعال کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
</Project>
سپس باید فایل xml مستندات آن‌را به صورت مجزایی به تنظیمات ابتدایی برنامه معرفی نمود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
                setupAction.SwaggerDoc(
  // ...
                   );
                var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly).ToList();
                xmlFiles.ForEach(xmlFile => setupAction.IncludeXmlComments(xmlFile));
            });
        }
با توجه به اینکه تمام فایل‌های xml تولید شده در آخر به پوشه‌ی bin\Debug\netcoreapp2.2 کپی می‌شوند، فقط کافی است حلقه‌ای را تشکیل داده و تمام آن‌ها را یکی یکی توسط متد IncludeXmlComments به تنظیمات AddSwaggerGen اضافه کرد.

در این حالت اگر مجددا برنامه را اجرا کنیم، خروجی ذیل را در قسمت schemas مشاهده خواهیم کرد:



بهبود مستندات به کمک Data Annotations

اگر به اکشن متد UpdateAuthor در کنترلر نویسندگان دقت کنیم، چنین امضایی را دارد:
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
جائیکه موجودیت Author را در پروژه‌ی OpenAPISwaggerDoc.Entities تعریف کرده‌ایم، نام و نام خانوادگی اجباری بوده و دارای حداکثر طول 150 حرف، هستند. قصد داریم همین ویژگی‌ها را به DTO دریافتی این متد، یعنی AuthorForUpdate نیز اعمال کنیم:
using System.ComponentModel.DataAnnotations;

namespace OpenAPISwaggerDoc.Models
{
    /// <summary>
    /// An author for update with FirstName and LastName fields
    /// </summary>
    public class AuthorForUpdate
    {
        /// <summary>
        /// The first name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string FirstName { get; set; }

        /// <summary>
        /// The last name of the author
        /// </summary>
        [Required]
        [MaxLength(150)]
        public string LastName { get; set; }
    }
}
پس از افزودن ویژگی‌های Required و MaxLength به این DTO، خروجی Sawgger-UI به صورت زیر بهبود پیدا می‌کند:



بهبود مستندات متد HttpPatch با ارائه‌ی یک مثال

دو نگارش از اکشن متد UpdateAuthor در این مثال موجود هستند:
یکی HttpPut است
[HttpPut("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(Guid authorId, AuthorForUpdate authorForUpdate)
و دیگری HttpPatch:
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
            Guid authorId,
            JsonPatchDocument<AuthorForUpdate> patchDocument)
این مورد آرام آرام در حال تبدیل شدن به یک استاندارد است؛ چون امکان Partial updates را فراهم می‌کند. به همین جهت نسبت به HttpPut، کارآیی بهتری را ارائه می‌دهد. اما چون پارامتر دریافتی آن از نوع ویژه‌ی JsonPatchDocument است و مثال پیش‌فرض مستندات آن، آنچنان مفهوم نیست:


بهتر است در این حالت مثالی را به استفاده کنندگان از آن ارائه دهیم تا در حین کار با آن، به مشکل برنخورند:
/// <summary>
/// Partially update an author
/// </summary>
/// <param name="authorId">The id of the author you want to get</param>
/// <param name="patchDocument">The set of operations to apply to the author</param>
/// <returns>An ActionResult of type Author</returns>
/// <remarks>
/// Sample request (this request updates the author's first name) \
/// PATCH /authors/id \
/// [ \
///     { \
///       "op": "replace", \
///       "path": "/firstname", \
///       "value": "new first name" \
///       } \
/// ] \
/// </remarks>
[HttpPatch("{authorId}")]
public async Task<ActionResult<Author>> UpdateAuthor(
    Guid authorId,
    JsonPatchDocument<AuthorForUpdate> patchDocument)
در اینجا در حین کامنت نویسی، می‌توان از المان remarks، برای نوشتن توضیحات اضافی مانند ارائه‌ی یک مثال، استفاده کرد که در آن op و path معادل‌های بهتری را نسبت به مستندات پیش‌فرض آن پیدا کرده‌‌اند. در اینجا برای ذکر خطوط جدید باید از \ استفاده کرد؛ وگرنه خروجی نهایی، در یک سطر نمایش داده می‌شود:



روش کنترل warningهای کامنت‌های تکمیل نشده

با فعالسازی GenerateDocumentationFile در فایل csproj برنامه، کامپایلر، بلافاصله برای تمام متدها و خواص عمومی که دارای کامنت نیستند، یک warning را صادر می‌کند. یک روش برطرف کردن این مشکل، افزودن کامنت به تمام قسمت‌های برنامه است. روش دیگر آن، تکمیل خواص کامپایلر، جهت مواجه شدن با عدم وجود کامنت‌ها در فایل csproj برنامه است:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
    <WarningsAsErrors>NU1605;</WarningsAsErrors>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>
توضیحات:
- اگر می‌خواهید خودتان را مجبور به کامنت نویسی کنید، می‌توانید نبود کامنت‌ها را تبدیل به error کنید. برای این منظور خاصیت TreatWarningsAsErrors را به true تنظیم کنید. در این حالت هر کامنت نوشته نشده، به صورت یک error توسط کامپایلر گوشزد شده و برنامه کامپایل نخواهد شد.
- اگر TreatWarningsAsErrors را خاموش کردید، هنوز هم می‌توانید یکسری از warningهای انتخابی را تبدیل به error کنید. برای مثال NU1605 ذکر شده‌ی در خاصیت WarningsAsErrors، مربوط به package downgrade detection warning است.
- اگر به warning نبود کامنت‌ها دقت کنیم به صورت عبارات warning CS1591: Missing XML comment for publicly visible type or member شروع می‌شود. یعنی  CS1591 مربوط به کامنت‌های نوشته نشده‌است. می‌توان برای صرفنظر کردن از آن، شماره‌ی این خطا را بدون CS، توسط خاصیت NoWarn ذکر کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-03.zip

در قسمت بعد، مشکل خروجی تولید response از نوع 200 را که در قسمت دوم به آن اشاره کردیم، بررسی خواهیم کرد.
نظرات اشتراک‌ها
ورک فلو نسخه راست چین شده بوت استرپ 4
این نوع افزونه‌ها تا حدودی درست کار می‌کنند و مواردی هم هستند که نیاز به مدیریت دستی دارند. پروژه‌ی اصلی بوت استرپ، در نگارش سوم آن به کمک همین افزونه‌ها قرار بود نسخه‌ی RTL را ارائه دهد که توسط گردانندگان اصلی bootstrap-rtl از این کار منصرف شدند و حتی نمونه‌های پیش نمایش آن‌را هم حذف کردند. داستان آن‌را در اینجا می‌توانید مطالعه کنید.
اشتراک‌ها
پلاگین نوار منوی کناری (Sidebar) واکنشگرا

پروژه bootstrap-sidebar : پلاگین نوار منوی کناری (Sidebar) بر روی BootStap 3.0

اگر منوهای شما داخل یک نوار منو (menubar ) افقی، زیاد هستند یا شما احتیاج به نوار منوی کناری واکنشگرای سازگار با Bootstrap3 دارید، می‌توانید از این پلاگین برای نمایش لیست منوهای خودتان در صفحات واکنشگرای وب استفاده نمایید. 

نسخه‌ی نمایشی (دمو)

پلاگین نوار منوی کناری (Sidebar) واکنشگرا
نظرات مطالب
BulkInsert در EF CodeFirst
سلام، من از bulk رو  nuget نصب کردم و به using هم اضافه کردم ولی وقتی که به این خط می‌رسه
db.BulkInsert(entities);
میگه BulkInsertExtention.cs NotFound، یه پنجره باز میشه که این فایلو انتخاب کنم!
کسی به این مشکل برخورده؟
نظرات نظرسنجی‌ها
از چند مانیتور برای برنامه نویسی استفاده میکنید؟
  • خطایابی بهتر (استفاده از پنجره output یا intermediate ویژوال استودیو)
  • توسعه سریعتر (بخصوص برای طراحان وب)
  • مقایسه پذیری بالاتر
  • همزمانی استفاده (در یک زمان قصد کار برروی وهله از sql server داشته باشید)
مطالب
Gulp #4
همانطورکه در مقاله‌ی قبلی پایه‌ی ورک فلوی خود را راه اندازی کردیم، در این مقاله می‌خواهیم با طراحی یک صفحه، با بوت استرپ شخصی سازی شده، در عمل با کارایی گالپ آشنا شویم.
دمو پایانی:


به هنگام سازی مرورگر و بارگذاری مجدد به صورت خودکار

یکی از موارد فوق العاده تکراری در هنگام توسعه‌ی وب، برای یک توسعه دهنده سمت کاربر (Front end Developer)  ریلود کردن مرورگر است. همچنین تست وب سایت یا آپلود در موبایل و سایر داستگاه‌ها، متداول است. با پلاگین گالپ می‌توان این مشکل را به صورت بهینه‌ای حل کرد.

نصب

برای نصب دستور زیر را در مسیر پروژه، در ترمینال سیستم عامل خود وارد کنید.
npm install browser-sync gulp --save-dev
می‌دانیم برای استفاده از یک پلاگین باید توسط متد require آن را به یک متغیر انتساب دهیم؛ به صورت زیر:
var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    notify = require('gulp-notify'),
    browserSync = require('browser-sync'), // Add browser syns plugin
    bower = require('gulp-bower');
توجه کنید هر پلاگینی که اضافه می‌کنید، باید تسک مربوط به آن را بنویسیم تا بتوانیم از آن استفاده کنیم. برای این پلاگین فقط مشخص کردن مسیر root سرور کافی است.
gulp.task('browserSync', function() {
    browserSync({
        server: {
            baseDir: './' //our server root
        }
    });
});
حال می‌خواهیم با زدن gulp watch، تمام کارهای ما به صورت خودکار انجام شوند. اما این دستور که در جلسه‌ی قبل آن‌را تعریف کردیم، فقط منتظر انجام یک تغییر است. تسک watch را به گونه‌ای تغییر می‌دهیم که ابتدا تسک‌های css , brower sync انجام شوند (به دلیل اینکه باید ابتدا، سرور راه اندازی شود) سپس گالپ منتظر تغییرات باشد و آنها را اعمال کند. 
gulp.task('watch', [ 'css','browserSync'], function() {

})
تسک‌های html,css,browserSync قبل از تسک watch اجرا می‌شوند. طبق مستندات، این پلاگین یکی از توابع API متد watch است و کار آن همانند متد مشابهی در گالپ است. آن را برای ریلود خودکار مرورگر استفاده می‌کنیم.
// Rerun the task when a file changes
gulp.task('watch', ['html', 'css','browserSync'], function() {
    gulp.watch(config.sassPath + '/**/*.scss', ['css']);
    gulp.watch(config.htmlPath , ['html'] )
    browserSync.watch("./*.html").on("change", browserSync.reload); // browserSync watch task
});
می‌خواهیم بعد از کامپایل، فایل‌های sass هم مرورگر دوباره بارگذاری شوند. کد زیر را به انتهای تسک css اضافه می‌کنیم:
.pipe(browserSync.reload({
      stream: true
  }));
بسیار خوب با انجام این کار‌ها پلاگین باید به‌درستی کار کند.

شخصی سازی بوت استرپ

برای شخصی سازی بوت استرپ کافی است ابتدا فایل‌های sass بوت استرپ و FontAwesome را در style.scss ایمپورت کنیم؛ به این صورت:
@import "bootstrap";
@import "font-awesome";
حال دستور gulp را می‌زنیم. با اینکار فایل style.scss کامپایل می‌شود. می‌خواهیم یک فونت فارسی و یک قالب فلت را به پروژه‌ا‌مان اعمال کنیم. من فایل‌ها را اضافه کرده‌ام و شما با یک نگاه می‌توانید، چیزی را که گفتم درک کنید.
@import "fonts-fa";
@import "variable";
@import "bootstrap";
@import "font-awesome";
@import "rtl.scss";
@import "typography";
نکته : سعی کنید برای استایل هر قسمت، یک فایل مجزا درست کنید؛ مانند مثال بالا که در پروژه لحاظ شده.
برای توسعه‌ی پروژه، ابتدا مخزن گیت هاب را فورک کرده و با زدن دستورات زیر کار خود را آغاز کنید:
  1. sudo npm install
  2. gulp
  3. gulp watch


مخزن گیت هاب : کامیت : 

Add : browserSync plugin and index.html 
نظرات مطالب
استفاده از JSON.NET در ASP.NET MVC
نسخه‌ی بهبود یافته JsonNetValueProviderFactory را در اینجا می‌توانید مطالعه کنید. نسخه‌ی JsonNetResult آن جالب نیست چون از string استفاده کرده بجای stream.
JsonNetValueProviderFactory.cs
+ نحوه‌ی ثبت بهتر این کلاس دقیقا در همان ایندکس اصلی آن:
        public static void RegisterFactory()
        {
            var defaultJsonFactory = ValueProviderFactories.Factories
                .OfType<JsonValueProviderFactory>().FirstOrDefault();
            var index = ValueProviderFactories.Factories.IndexOf(defaultJsonFactory);
            ValueProviderFactories.Factories.Remove(defaultJsonFactory);
            ValueProviderFactories.Factories.Insert(index, new JsonNetValueProviderFactory());
        }
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت ششم - دخالت در مراحل وهله سازی اشیاء توسط IoC Container
روش متداول کار با تزریق وابستگی‌های برنامه‌های مبتنی بر NET Core.، عموما با ثبت و معرفی یک سرویس به صورت زیر، توسط متدهای AddTransient، AddSingleton و AddScoped است:
public class Startup 
{ 
    public void ConfigureServices(IServiceCollection services) 
    { 
        // ... 
         
        services.AddTransient<ICustomerService, DefaultCustomerService>(); 
         
        // ... 
    } 
}
و سپس استفاده‌ی از این سرویس، با تزریق آن در سازنده‌ی یک کنترلر که نمونه‌های بیشتری از آن‌را در قسمت چهارم بررسی کردیم:
public class SupportController 
{ 
    // DefaultCustomerService will be injected here: 
    public SupportController(ICustomerService customerService) 
    { 
        // ... 
    } 
}
در اینجا کار وهله سازی DefaultCustomerService به صورت خودکار و راسا توسط IoC Container توکار برنامه صورت می‌گیرد و ما هیچگونه دخالتی را در آن نداریم. اما اگر در این بین نیاز باشد پس از وهله سازی DefaultCustomerService، یک خاصیت آن نیز بر اساس شرایط جاری مقدار دهی شود و حاصل نهایی در اختیار SupportController فوق قرار گیرد چه باید کرد؟
برای سفارشی سازی مراحل وهله سازی اشیاء توسط IoC Container توکار برنامه و امکان دخالت در آن، قابلیتی تحت عنوان «factory registration» نیز پیش بینی شده‌است که در ادامه آن‌را بررسی می‌کنیم.


Factory Registration چیست؟

اگر در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions و فضای نام Microsoft.Extensions.DependencyInjection آن به کلاس ServiceCollectionServiceExtensions که متدهای الحاقی مانند AddScoped را ارائه می‌کند، بیشتر دقت کنیم، تک تک این متدها امضاهای دیگری را نیز دارند:
namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceCollectionServiceExtensions
    {
        public static IServiceCollection AddScoped<TService>(
     this IServiceCollection services) where TService : class;
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType, Type implementationType);
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType, 
 Func<IServiceProvider, object> implementationFactory);
        public static IServiceCollection AddScoped<TService, TImplementation>(this IServiceCollection services)
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType);
        public static IServiceCollection AddScoped<TService>(
     this IServiceCollection services, 
 Func<IServiceProvider, TService> implementationFactory) where TService : class;
        public static IServiceCollection AddScoped<TService, TImplementation>(
     this IServiceCollection services, 
 Func<IServiceProvider, TImplementation> implementationFactory)
// ...
    }
}
همانطور که ملاحظه می‌کنید، امضای تعدادی از این overloadها، دارای پارامترهایی از نوع Func نیز هست و هدف آن‌ها فراهم آوردن روشی برای سفارشی سازی مراحل وهله سازی سرویسی‌های بازگشتی از طریق سیستم تزریق وابستگی‌های برنامه است. توسط این پارامتر، پیش از وهله سازی سرویس درخواستی، IServiceProvider جاری یا همان root container را در اختیار شما قرار می‌دهد (اطلاعات بیشتر در مورد IServiceProvider را در قسمت دوم بررسی کردیم) و توسط آن می‌توان ابتدا وهله‌ای از سرویس یا سرویس‌های خاصی را دریافت کرد و پس از ترکیب و سفارشی سازی آن‌ها، در آخر یک object را بازگشت داد که در نهایت به عنوان وهله‌ی اصلی این سرویس درخواستی، در سراسر برنامه مورد استفاده قرار می‌گیرد. در ادامه با مثال‌هایی، کاربردهای این پارامتر از نوع Func، یا Implementation Factory را بررسی می‌کنیم.


مثال 1 : تزریق وابستگی‌ها در حالتیکه کلاس سرویس مدنظر دارای تعدادی پارامتر ثابت است

IoC Container توکار برنامه‌های NET Core.، به صورت خودکار وابستگی‌های تزریق شده‌ی در سازنده‌های سرویس‌های مختلف را تا هر چند سطح ممکن، به صورت خودکار وهله سازی می‌کند؛ به شرطی‌که این وابستگی‌های تزریق شده نیز خودشان سرویس بوده باشند و در تنظیمات ابتدایی آن ثبت و معرفی شده باشند. به عبارتی زمانیکه با سیستم تزریق وابستگی‌ها کار می‌کنیم، مهم نیست که نگران مقدار دهی پارامترهای سازنده‌ی تزریق شده‌ی در سازنده‌های سرویسی خاص باشیم. اما ... برای نمونه سرویس زیر را که یک رشته را در سازنده‌ی خود دریافت می‌کند درنظر بگیرید:
namespace CoreIocServices
{
    public interface IParameterizedService
    {
        string GetConstructorParameter();
    }

    public class ParameterizedService : IParameterizedService
    {
        private readonly string _connectionString;

        public ParameterizedService(string connectionString)
        {
            _connectionString = connectionString;
        }

        public string GetConstructorParameter()
        {
            return _connectionString;
        }
    }
}
اینبار دیگر نمی‌توان این سرویس را از طریق متداول زیر ثبت و معرفی کرد:
services.AddTransient<IParameterizedService, ParameterizedService>();
چون IoC Container نمی‌داند که چگونه و از کجا باید پارامتر رشته‌ای درخواستی در سازنده‌ی کلاس ParameterizedService را تامین کند. همچنین ثبت سرویس‌ها نیز در کلاس ServiceCollectionServiceExtensions معرفی شده‌ی در ابتدای بحث، به قید «where TService : class» محدود شده‌است. اینجا است که روش factory registration به کمک ما خواهد آمد تا بتوانیم مراحل وهله سازی این سرویس را سفارشی سازی کنیم:
services.AddTransient<IParameterizedService>(serviceProvider =>
{
   return new ParameterizedService("some value ....");
});
البته چون بدنه‌ی این Func، صرفا از یک return تشکیل شده‌است، معادل ساده شده‌ی زیر را هم می‌تواند داشته باشد:
services.AddTransient<IParameterizedService>(serviceProvider => new ParameterizedService("some value ...."));
اینبار در سراسر برنامه اگر سرویس IParameterizedService درخواست شود، وهله‌ای از کلاس ParameterizedService را با پارامتر سازنده‌ی "some value ...."، دریافت خواهد کرد.

در اینجا چون serviceProvider نیز در اختیار ما است، حتی می‌توان این مقدار را از سرویسی دیگر دریافت کرد و سپس مورد استفاده قرار داد:
services.AddTransient<IParameterizedService>(serviceProvider =>
{
   var config = serviceProvider.GetRequiredService<ITestService>().GetConfigValue();
   return new ParameterizedService(config);
});

نمونه‌ی دیگری از این دست، کار با IUrlHelper توکار ASP.NET Core است. این سرویس برای اینکه پاسخ درستی را ارائه دهد، نیاز به ActionContext جاری را دارد تا بتواند از طریق آن به تمام جزئیات اکشن متد یک کنترلر و درخواست رسیده دسترسی داشته باشد. در این حالت برای ساده سازی کار با آن، بهتر است تامین وابستگی‌های لحظه‌ای این سرویس را با سفارشی سازی نحوه‌ی وهله سازی آن، انجام دهیم، تا اینکه این قطعه کد تکراری را در هر جائیکه به IUrlHelper نیاز است، تکرار کنیم:
services.AddScoped<IUrlHelper>(serviceProvider =>
{
   var actionContext = serviceProvider.GetRequiredService<IActionContextAccessor>().ActionContext;
   var urlHelperFactory = serviceProvider.GetRequiredService<IUrlHelperFactory>();
   return urlHelperFactory.GetUrlHelper(actionContext);
});
اکنون اگر IUrlHelper را به سازنده‌ی یک کنترلر تزریق کنیم، دیگر نیازی به سه سطر نوشته‌ی تامین factory و action context آن نخواهد بود.


مثال 2: وهله سازی در صورت نیاز به وابستگی‌های یک سرویس، به کمک Lazy loading

فرض کنید دو سرویس را در سازنده‌ی سرویس دیگری تزریق کرده‌اید:
namespace Services
{
    public class OrderHandler : IOrderHandler
    {
        private readonly IAccounting _accounting;
        private readonly ISales _sales;
        public OrderHandler(IAccounting accounting, ISales sales)
        {
بعد در این کلاس، در یک متد، از سرویس accounting استفاده می‌شود و در متدی دیگر از سرویس sales. یعنی هرچند در زمان وهله سازی شیء OrderHandler هر دو وابستگی تزریق شده‌ی در سازنده‌ی آن نیز وهله سازی خواهند شد، اما در بسیاری از شرایط، بسته به متد مورد استفاده، فقط از یکی از آن‌ها استفاده می‌کنیم. اکنون این سؤال مطرح می‌شود که آیا می‌توان سربار وهله سازی تمام سازنده‌های این کلاس را به زمان استفاده‌ی از آن‌ها منتقل کرد؟ یعنی سرویس accounting تزریق شده فقط زمانی وهله سازی شود که واقعا قرار است از آن استفاده کنیم.
روش انجام یک چنین کارهایی با استفاده از کلاس Lazy اضافه شده‌ی به NET 4x. قابل انجام است:
   public class OrderHandlerLazy : IOrderHandler
    {
        public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales)
        {
 و برای معرفی آن در اینجا می‌توان از روش factory registration استفاده کرد:
services.AddTransient<IOrderHandler, OrderHandlerLazy>();
services.AddTransient<IAccounting, Accounting>()
            .AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>()));
services.AddTransient<ISales, Sales>()
           .AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- در اینجا در ابتدا تمام سرویس‌ها (حتی آن‌هایی که قرار است به صورت Lazy استفاده شوند) یکبار به صورت متداولی معرفی می‌شوند.
- سپس سرویس‌هایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.

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


مثال 3: چگونه بجای اینترفیس‌ها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگی‌ها درخواست کنیم؟

فرض کنید سرویسی را به صورت زیر به سیستم تزریق وابستگی‌ها معرفی کرده‌اید:
services.AddTransient<IMyDisposableService, MyDisposableService>();
در ادامه اگر سرویس IMyDisposableService را از این سیستم درخواست کنیم، برنامه بدون مشکل اجرا می‌شود؛ اما اگر خود MyDisposableService را تزریق کنیم چطور؟
public class AnotherController 
{ 
    public AnotherController(MyDisposableService customerService) 
    { 
        // ... 
    } 
}
در این حالت برنامه با استثنای زیر متوقف می‌شود و عنوان می‌کند که نمی‌داند چگونه باید این وابستگی تزریق شده را تامین کند:
An unhandled exception occurred while processing the request. 
InvalidOperationException: Unable to resolve service for type ‘MyDisposableService’ while attempting to activate ‘AnotherController’. 
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
این مورد را نیز می‌توان توسط factory registration به نحو زیر مدیریت کرد:
services.AddTransient<IMyDisposableService, MyDisposableService>();
services.AddTransient<MyDisposableService>(serviceProvider =>
serviceProvider.GetRequiredService<IMyDisposableService>() as MyDisposableService);
هر زمانیکه وهله‌ای از کلاس MyDisposableService درخواست شود، وهله‌ای از سرویس IMyDisposableService را بازگشت می‌دهیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-06.zip