مطالب
انجمن سایت LLBLGEN سورس باز شد

LLBLGEN یکی از ORM های تجاری بسیار با کیفیت دات نت است و علاوه بر اینکه هویت مستقلی دارد، امکان تولید کدهای مخصوص Entity framework و NHibernate را هم دارا است.
اخیرا این شرکت تصمیم گرفته است که سیستم پشتیبانی مشتریان خودش را که نمونه‌ای از آن‌را در اینجا می‌توانید ملاحظه کنید، سورس باز کند.
حداقل پیشنیازهای نصب این انجمن به شرح زیر است:
- ASP.NET 2.0+
- SQL Server 2000 or higher / CE Desktop 3.5
- NET 3.5+
لطفا جهت دریافت آن به این آدرس و جهت ملاحظه‌ی قابلیت‌های آن به این آدرس مراجعه نمائید.

مطالب
Soft Delete در Entity Framework 6
برای حذف نمودن یک رکورد از دیتابیس 2 راه وجود دارد : 1- حذف به صورت فیزیکی 2- حذف به صورت منطقی ( مورد بحث این مطلب )
در حذف رکورد به صورت منطقی، طراحان دیتابیس، فیلدی را با نام‌های متفاوتی همچون Flag , IsDeleted , IsActive , و غیره، در جداول ایجاد می‌نمایند. خوب، این روش مزایا و معایب خاص خودش را دارد. مثلا شما در هر پرس و جویی که ایجاد می‌نمایید، بایستی این مورد را چک نموده و رکوردهایی را فراخوانی نمایید که فیلد IsDeleted آن برابر با false باشد. و همچنین در زمان حذف رکورد، برنامه نویس بایستی از متد Update به جای حذف فیزیکی استفاده نماید که تمام این موارد حاکی از مشکلات خاص این روش است. 
در این مقاله سعی داریم که مشکلات ذکر شده در بالا را با ایجاد SoftDelete در EF 6 برطرف نماییم .*یکی از پیش نیاز‌های این پست مطالعه ( سری آموزشی EF CodeFirst ) در سایت جاری می‌باشد.
برای شروع، ما نیاز به داشتن یک Attribute برای مشخص ساختن موجودیت هایی داریم که بایستی بر روی آنها SoftDelete فعال گردد. پس برای اینکار کلاسی را به شکل زیر طراحی مینماییم:
using System.Data.Entity.Core.Metadata.Edm;
public class SoftDeleteAttribute : Attribute
    {
        public string ColumnName { get; set; }
        public SoftDeleteAttribute(string column)
        {
            ColumnName = column;
        }
        public static string GetSoftDeleteColumnName(EdmType type)
        {
            MetadataProperty column = type.MetadataProperties.Where(x => x.Name.EndsWith("customannotation:SoftDeleteColumnName")).SingleOrDefault();
            return column == null ? null : (string)column.Value;
        }
    }
توضیحات کد بالا: در متد سازنده، نام فیلدی را که قرار است بر روی آن SoftDelete به صورت اتوماتیک ایجاد شود، دریافت می‌نماییم و متد GetSoftDeleteColumnName در واقع با استفاده از متادیتاهایی که بر روی فیلد‌ها وجود دارد، فیلدی که انتهای نام آن متادیتای "customannotation:SoftDeleteColumnName" را دارد، انتخاب نموده و برگشت می‌دهد.
سؤال: متادیتای  "customannotation:SoftDeleteColumnName"  از کجا آمد؟ برای پاسخ به این سوال کافیست ادامه‌ی مطلب را کامل مطالعه نمایید.
حال این Attribute برای استفاده در موجودیت‌های ما آمده است. برای استفاده کافیست به روش زیر عمل نمایید .
    [SoftDelete("IsDeleted")]
    public class TblUser 
    {        
        [Key]
        public int TblUserID { get; set; }

        [MaxLength(30)]
        public string Name { get; set; }

        public bool IsDeleted { get; set; }
    }
برای معرفی این قابلیت جدید به EF 6 کافیست در DbContext برنامه در متد OnModelCreating به نحو زیر عمل نماییم.
 protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            var Conv = new AttributeToTableAnnotationConvention<SoftDeleteAttribute, string>(
                "SoftDeleteColumnName",
                (type, attribute) => attribute.Single().ColumnName);
            modelBuilder.Conventions.Add(Conv);

        }
در واقع ما در اینجا به Ef می‌گوییم که یک Annotation جدید، با نام SoftDeleteColumnName به Entity که توسط این Attribute مزین شده است، اضافه نماید و همچنین مقدار این Annotation را نام فیلدی که در متد سازنده SoftDeleteAttribute معرفی گردیده است قرار دهد.
برای اطمینان حاصل کردن از اینکه آیا Annotation جدید به مدل برنامه اضافه شده است یا نه کافیست بر روی فایل cs کانتکست DbContext، کلیک راست نموده و در منوی نمایش داده شده گزینه‌ی EntityFramework و سپس گزینه View Entity Data Model را انتخاب نمایید . مانند تصویر زیر:

در پنجره باز شده به قسمت سوم یعنی <StorageModels> مراجعه نمایید و بایستی گزینه زیر را مشاهده نمایید .

 <EntityType Name="TblUser" customannotation:SoftDeleteColumnName="IsDeleted">

تا اینجای کار ما توانستیم یک Annotation جدید را به Ef اضافه نماییم .

در مرحله بعد بایستی به Ef دستور دهیم که در تولید Query بر روی این Entity، این مورد را نیز لحاظ کند.

برای این کار کلاسی را ایجاد می‌نماییم که از اینترفیس IDbCommandTreeInterceptor ارث بری می‌نماید. مانند کد زیر :

public class SoftDeleteInterceptor : IDbCommandTreeInterceptor
    {
        public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
        {
            if (interceptionContext.OriginalResult.DataSpace == System.Data.Entity.Core.Metadata.Edm.DataSpace.SSpace)
            {
                var QueryCommand = interceptionContext.Result as DbQueryCommandTree;
                if (QueryCommand != null)
                {
                    var newQuery = QueryCommand.Query.Accept(new SoftDeleteQueryVisitor());
                    interceptionContext.Result = new DbQueryCommandTree(QueryCommand.MetadataWorkspace, QueryCommand.DataSpace, newQuery);
                }
            }
       }
}

در ابتدا تشخیص داده می‌شود که نوع خروجی Query آیا از نوع Storage Model است . ( برای توضیحات بیشتر ) سپس پرس و جوی تولید شده را با استفاده از الگوی visitor تغییر داده و Query جدید را تولید نموده و در انتها Query جدیدی را به جای Query قبلی جایگزین می‌نماییم.

در اینجا ما نیاز به داشتن کلاس  SoftDeleteQueryVisitor  برای تغییر دادن Query و اضافه نمودن IsDeleted <>1 به Query می‌باشیم.

یک کلاس دیگری با نام  SoftDeleteQueryVisitor  به شکل زیر  به برنامه اضافه می‌نماییم.

  public class SoftDeleteQueryVisitor : DefaultExpressionVisitor
    {
        public override DbExpression Visit(DbScanExpression expression)
        {
            var column = SoftDeleteAttribute.GetSoftDeleteColumnName(expression.Target.ElementType);
            if (column!=null)
            {
                var Binding = DbExpressionBuilder.Bind(expression);
                return DbExpressionBuilder.Filter(Binding, DbExpressionBuilder.NotEqual(DbExpressionBuilder.Property(DbExpressionBuilder.Variable(Binding.VariableType, Binding.VariableName), column), DbExpression.FromBoolean(true)));
            }
            else
            {
                return base.Visit(expression);
            }
        }
    }
در متد Visit تشخیص داده می‌شود که آیا Query ساخته شده دارای customannotation:SoftDeleteColumnName است؟ چنانچه این Annotation را دارا باشد، نام فیلدی را که بالای Entity ذکر شده است، بازگشت می‌دهد و در خط بعدی، نام این فیلد را با مقدار مخالف True به Query تولید شده اضافه می‌نماید.

در نهایت برای اینکه EF تشخیص دهد که یک‌چنین Interceptor ایی وجود دارد، بایستی در کلاس DbContextConfig، کلاس SoftDeleteInterceptor را اضافه نماییم؛ همانند کد زیر:

 public class DbContextConfig : DbConfiguration
    {
        public DbContextConfig()
        {
             AddInterceptor(new SoftDeleteInterceptor());
        }
    }

تا اینجا در تمام Query‌های تولید شده بر روی Entity که با خاصیت SoftDelete مزین شده است، مقدار IsDeleted <> 1 را به صورت اتوماتیک اعمال می‌نماید. حتی به صورت هوشمند چنانچه این موجودیت در یک Join استفاده شده باشد این شرط را قبل از Join به Query تولید شده اضافه می‌نماید.

در مقاله بعدی در مورد تغییر کد Remove به کد Update توضیح داده خواهد شد.


برای مطالعه بیشتر

Entity Framework: Building Applications with Entity Framework 6

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت دهم - پیاده سازی الگوی Decorator
الگوی decorator، امکان محصور کردن یک شیء مفروض را با لایه‌ای بر فراز آن میسر می‌کند. برای مثال بجای اینکه در تمام متدهای سرویسی از try/catch استفاده کنیم، می‌توانیم این متدها را با یک ExceptionHandlingDecorator مزین کنیم و یا از این دست اعمال تکراری می‌توان به لاگ کردن ورودی و خروجی‌های یک متد و یا کش کردن اطلاعات آن‌ها نیز اشاره کرد. حتی عملیاتی مانند تشخیص خواص تغییر یافته‌ی یک شیء در Entity framework نیز به کمک همین مزین کننده‌ها که شیء اصلی در حال استفاده را با ایجاد لایه‌ای بر روی آن‌ها محصور می‌کنند، انجام می‌شود. به این عملیات Aspect oriented programming و یا AOP نیز می‌گویند؛ در اینجا واژه‌ی Aspect به اعمال مشترک و متداول موجود در برنامه اشاره می‌کند. در این مطلب قصد داریم نمونه‌ای از این تزئین کننده‌ها را به کمک سیستم تزریق وابستگی‌های NET Core. پیاده سازی کنیم.


پیاده سازی الگوی Decorator به کمک سیستم تزریق وابستگی‌های NET Core.

مثال زیر را در نظر بگیرید که در آن یک سرویس تعریف شده‌است و در این بین استثنائی رخ داده‌است.
    public interface ITaskService
    {
        void Run();
    }

    public class MyTaskService : ITaskService
    {
        public void Run()
        {
            throw new InvalidOperationException("An exception from the MyTaskService!");
        }
    }
می‌خواهیم بدون تغییری در کدهای این کلاس، به متدهای آن در حین اجرای نهایی، یک try/catch را به همراه logging، اضافه کنیم. به همین جهت نیاز خواهیم داشت تا یک محصور کننده (تزئین کننده یا decorator در اینجا) را برای آن طراحی کنیم:
using System;
using Microsoft.Extensions.Logging;
namespace CoreIocServices
{
    public class MyTaskServiceDecorator : ITaskService
    {
        private readonly ILogger<MyTaskServiceDecorator> _logger;
        private readonly ITaskService _decorated;

        public MyTaskServiceDecorator(
            ILogger<MyTaskServiceDecorator> logger,
            ITaskService decorated)
        {
            _logger = logger;
            _decorated = decorated;
        }

        public void Run()
        {
            try
            {
                _decorated.Run();
            }
            catch (Exception ex)
            {
                _logger.LogCritical(ex, "An unhandled exception has been occurred.");
            }
        }
    }
}
این محصور کننده نیز دقیقا همان ITaskService را پیاده سازی می‌کند؛ اما در سازنده‌ی آن یک ITaskService را نیز دریافت می‌کند. علت اینجا است که توسط آن بتوان متدهای ITaskService تزریقی را اجرا کرد و بر روی آن اعمالی مانند کش کردن، لاگ کردن و مدیریت استثناءها و غیره را انجام داد. برای مثال در متد Run آن مشاهده می‌کنید که متد Run همان وهله‌ی تزریقی اجرا شده‌است؛ اما درون یک try/catch به همراه لاگ کردن جزئیات استثنای رخ داده.
مزیت این‌کار، پیاده سازی اصل DRY یا Don't repeat yourself است. کاری که برای رفع این مشکل قرار است انجام دهیم، استفاده از یک تزئین کننده (محصور کننده)، کپسوله سازی اعمال تکراری و سپس اتصال آن به قسمت‌های مختلف برنامه است. همچنین در این حالت اصل open closed principle نیز بهتر رعایت خواهد شد. از این جهت که کدهای تکراری برنامه به یک لایه‌ی دیگر منتقل شده‌اند و دیگر نیازی نیست برای تغییر آن‌ها، کدهای قسمت‌های اصلی برنامه را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر).

پس از طراحی این تزئین کننده، اکنون نوبت به معرفی آن به سیستم تزریق وابستگی‌های NET Core. است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<MyTaskService>();
            services.AddTransient<ITaskService>(serviceProvider =>
                new MyTaskServiceDecorator(
                     serviceProvider.GetService<ILogger<MyTaskServiceDecorator>>(),
                     serviceProvider.GetService<MyTaskService>())
            );
روش انجام اینکار را نیز در «قسمت ششم - دخالت در مراحل وهله سازی اشیاء توسط IoC Container» بیشتر بررسی کرده‌ایم.
در اینجا هم می‌توان در صورت نیاز اصل کلاس MyTaskService را بدون هیچ نوع تزئین کننده‌ای از سیستم تزریق وابستگی‌ها دریافت کرد و یا اگر وهله‌ای از سرویس ITaskService را از آن درخواست کردیم، ابتدا شیء MyTaskServiceDecorator وهله سازی شده و سپس توسط آن یک نمونه‌ی محصور شده و تزئین شده‌ی MyTaskService به فراخوان بازگشت داده خواهد شد.


ساده سازی معرفی تزئین کننده‌ها به سیستم تزریق وابستگی‌های NET Core. به کمک Scrutor

در «قسمت هشتم - ساده سازی معرفی سرویس‌ها توسط Scrutor» با کتابخانه‌ی Scrutor آشنا شدیم. یکی دیگر از قابلیت‌های آن، امکان ساده سازی تعریف تزئین کنند‌ها است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITaskService, MyTaskService>();
            services.Decorate<ITaskService, MyTaskServiceDecorator>();
در اینجا معادل کدهایی را که با روش factory خود NET Core. نوشتیم، ملاحظه می‌کنید. ابتدا نیاز است خود سرویس اصلی غیر تزئین شده، به نحو متداولی به سیستم معرفی شود. سپس متد الحاقی جدید <,>Decorate را با همان اینترفیس و اینبار با Decorator مدنظر معرفی می‌کنیم. کاری که Scrutor در اینجا انجام می‌دهد، یافتن سرویس ITaskService معرفی شده‌ی پیشین و تعویض آن با MyTaskServiceDecorator می‌باشد. بنابراین نیاز است تعریف services.AddTransient پیش از تعریف services.Decorate انجام شده باشد. این روش تمیزتر از روش قبلی به نظر می‌رسد و شامل وهله سازی مستقیم MyTaskServiceDecorator به همراه فراهم آوردن تمام پارامترهای سازنده‌ی آن توسط ما نیست.
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 5
پس از ایجاد متدها، نوبت به تغییرات App.Config می‎رسد. هرچند خود Visual Studio برای کلاس پیش‌گزیده‌ی خود تنظیماتی را در App.Config افزوده است ولی چنان‎چه در در خاطر دارید ما آن فایل‎ها را حذف کردیم و فایل‎های جدیدی به جای آن افزودیم. از این رو مراحل زیر را انجام دهید:
1- فایل App.Config را از Solution Explorer باز کنید.
2- به جای عبارت MyNewsWCFLibrary.Service1 در قسمت Service Name این عبارت را بنویسید: MyNewsWCFLibrary.MyNewsService
3- در قسمت BaseAddress عبارت Design_Time_Addresses را حذف کنید.
4- در قسمت BaseAddress شماره پورت را به 8080 تغییر دهید.
5- در قسمت BaseAddress به جای Service1 بنویسید: MyNewsService
6- در قسمت endpoint به جای عبارت MyNewsWCFLibrary.IService1 بنویسید: MyNewsWCFLibrary.IMyNewsService 
در پایان تگ Service در App.Config باید همانند کد زیر باشد:
   <services>
      <service name="MyNewsWCFLibrary.MyNewsService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/MyNewsWCFLibrary/MyNewsService/" />
          </baseAddresses>
        </host>
        <!-- Service Endpoints -->
        <!-- Unless fully qualified, address is relative to base address supplied above -->
        <endpoint address="" binding="basicHttpBinding" contract="MyNewsWCFLibrary.IMyNewsService">
          <!-- 
              Upon deployment, the following identity element should be removed or replaced to reflect the 
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
              automatically.
          -->
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <!-- Metadata Endpoints -->
        <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
        <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
تغییرات را ذخیره کنید و پروژه را اجرا کنید. باید پنجره‌ای شبیه به پنجره‌ی زیر نشان داده شود:

در صورت مشاهده پیام خطا، ویژوال استودیو را ببندید و این‌بار به صورت Run as administrator باز کنید.

برای نمونه روی متد AddCategory کلیک کنید. در پنجره نشان داده شده همانند شکل در برابر فیلد CatName مقداری وارد کنید و روی دکمه Invoke کلیک کنید. متد مورد نظر اجرا شده و مقداری که وارد کرده ایم در پایگاه داده‌ها ذخیره می‌شود. مقداری که در قسمت پایین دیده می‌شود خروجی متد است که در اینجا شناسه رکورد درج‌شده است.

بار دیگر برای مشاهده رکورد درج‌شده روی متد GetAllCategory کلیک کنید. به علت این‌که این متد ورودی ندارد در قسمت بالا چیزی نشان داده نمی‌شود. روی دکمه Invoke کلیک کنید. با پیغام خطای زیر روبه‌رو خواهید شد:

افزودن ویژگی Virtual به tblNews و tblCategory در بخش دوم  خواندید؛ باعث می‌شود که Entity Framework در هنگام اجرا کلاس‌هایی با عنوان "پروکسی‌های پویا" به کلاس‌های Address و Customer بیفزاید و بنابراین قابلیت Lazy Loading برای این کلاس‌ها در زمان اجرای برنامه فراهم می‌گردد. 

ولی با افزودن پروکسی‌های پویا به کلاس‌های ما، این کلاس‌ها قابلیت انتقال خود از طریق سرویس‌های WCF را از دست می‌دهند زیرا پروکسی‌های پویا به طور پیش‌گزیده قابلیت سریالایز و دیسریالایز شدن را ندارند!

خوشبختانه می‌توانیم این ویژگی را در کلاس DBContext غیرفعال کنیم. برای این منظور قالب سازنده‌ی آن یا MyNewsModel.Context.tt را از Solution Explorer باز کنید و کد زیر را در آن پیدا کنید:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {

سپس در ادامه‌ی آن کدغیرفعال‌کردن پروکسی پویا را به این شکل بنویسید:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {
      Configuration.ProxyCreationEnabled = false;

اکنون اگر فایل را ذخیره کنیم سپس فایل MyNewsModel.Context.cs را از Solution Explorer باز کنید؛ خواهید دید که این خط کد در جای خود قرارگرفته است.

بار دیگر پروژه را اجرا کنید روی متد GetAllCategory کلیک کنید. این بار اگر دکمه Invoke را بفشارید با همانند شکل زیر را خواهید دید:

در بخش ششم پیرامون ارتباط جدول‌های tblNews و tblCategory و نمایش محتویات وابسته جدول خبر به دسته و تنظیمات آن در t4 و کلاس Service

در بخش هفتم پیرامون میزبانی WCFLibrary در یک Web Application

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

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت هفتم - کار با سرویس‌های متفاوتی با یک امضاء
فرض کنید قرارداد IService را تدارک دیده‌اید و بر اساس آن سرویس‌های A و B را به سیستم تزریق وابستگی‌های برنامه‌های NET Core. تزریق کرده‌اید (برای مثال در برنامه دو DbContext را تعریف کرده‌اید و یک اینترفیس IUnitOfWork را دارید). اکنون اگر از این سیستم، یک پیاده سازی IService را درخواست کنید، چه اتفاقی رخ می‌دهد؟ در حالت معمول آن، آخرین سرویسی را که ثبت کرده‌اید، یعنی وهله‌ای از سرویس B را بازگشت خواهد داد. در ادامه قصد داریم با این قابلیت امکان ثبت چندین سرویس مشتق شده‌ی از یک اینترفیس، بیشتر آشنا شویم.


ثبت پیاده سازی‌های متعدد یک اینترفیس در سیستم تزریق وابستگی‌های NET Core.

داشتن چندین پیاده سازی از یک اینترفیس، شاید یکی از اهداف اصلی وجود آن‌ها باشد. برای نمونه قرار داد ارسال پیامی را در برنامه، مانند IMessageService به صورت زیر طراحی و سپس بر اساس آن، دو پیاده سازی ارسال پیام‌ها را از طریق ایمیل و SMS، اضافه می‌کنیم:
namespace CoreIocServices
{
    public interface IMessageService
    {
        void Send(string message);
    }

    public class EmailService : IMessageService
    {
        public void Send(string message)
        {
            // ...
        }
    }

    public class SmsService : IMessageService
    {
        public void Send(string message)
        {
            //todo: ...
        }
    }
}
در ادامه برای معرفی آن‌ها به سیستم تزریق وابستگی‌های NET Core.، به نحو متداول زیر عمل خواهیم کرد و هر دوی این پیاده سازی‌ها را بر اساس اینترفیس آن‌ها معرفی می‌کنیم:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IMessageService, EmailService>();
            services.AddTransient<IMessageService, SmsService>();
        }
در این حالت اگر این سرویس‌ها را به صورت زیر به یک کنترلر تزریق کنیم، انتظار دریافت کدام سرویس‌ها را خواهید داشت؟
using CoreIocServices;
using Microsoft.AspNetCore.Mvc;

namespace CoreIocSample02.Controllers
{
    public class MessagesController : Controller
    {
        private readonly IMessageService _emailService;
        private readonly IMessageService _smsService;

        public MessagesController(IMessageService emailService, IMessageService smsService)
        {
            _emailService = emailService;
            _smsService = smsService;
        }
    }
}
در این حالت اگر بر روی سازنده‌ی این کنترلر یک break-point را قرار دهیم، پارامترهای تزریق شده‌ی در سازنده‌ی کلاس به صورت زیر خواهند بود:


همانطور که مشاهده می‌کنید، هر دو پارامتر، با وهله‌ای از آخرین سرویس معرفی شده‌ی از نوع IMessageService مقدار دهی شده‌اند؛ یعنی SmsService در اینجا. در ادامه روش‌های مختلفی را برای رفع این مشکل بررسی می‌کنیم.


روش اول: سیستم تزریق وابستگی‌های NET Core. از سازنده‌های IEnumerable پشتیبانی می‌کند

برای مدیریت دریافت سرویس‌هایی که از یک اینترفیس مشتق شده‌اند، خود NET Core. بدون نیاز به تنظیمات اضافه‌تری، روش تزریق IEnumerableها را در سازنده‌های کلاس‌ها پشتیبانی می‌کند:
using System.Collections.Generic;
using CoreIocServices;
using Microsoft.AspNetCore.Mvc;

namespace CoreIocSample02.Controllers
{
    public class MessagesController : Controller
    {
        private readonly IEnumerable<IMessageService> _messageServices;

        public MessagesController(IEnumerable<IMessageService> messageServices)
        {
            _messageServices = messageServices;
        }
در اینجا نیز اگر بر روی سازنده‌ی این کنترلر یک break-point را قرار دهیم، پارامتر تزریق شده‌ی در سازنده‌ی کلاس به صورت زیر خواهد بود:


به این ترتیب لیست وهله‌های تمام سرویس‌هایی از نوع IMessageService در اختیار ما قرار گرفته‌است و برای اهدافی مانند پیاده سازی الگوهایی در رده‌ی chain of responsibility و یا الگوی استراتژی، مفید است. در این حالت اگر نیاز به سرویس از نوع خاصی وجود داشت، می‌توان از روش زیر استفاده کرد:
var emailService = _messageServices.OfType<EmailService>().First();
و یا اگر از روش Service Locator استفاده می‌کنید، serviceProvider به همراه متد ویژه‌ی GetServices که لیست تمام سرویس‌هایی از نوعی خاص را بر می‌گرداند نیز هست:
var messageServices = serviceProvider.GetServices<IMessageService>();


روش دوم: دریافت شرطی وهله‌های مورد نیاز با «دخالت در مراحل وهله سازی اشیاء توسط IoC Container»

روش «دخالت در مراحل وهله سازی اشیاء توسط IoC Container» را در قسمت قبل بررسی کردیم. یکی دیگر از مثال‌هایی را که در این مورد می‌توان ارائه داد، شرطی کردن بازگشت وهله‌ها است که برای سناریوی مطلب جاری بسیار مفید است:
الف) برای شرطی کردن بازگشت وهله‌ها، بهتر است این شرط را بجای رشته‌ها و یا اعدادی خاص، بر اساس یک enum مشخص انجام دهیم:
namespace CoreIocServices
{
    public enum MessageServiceType
    {
        EmailService,
        SmsService
    }
در اینجا یک enum جدید را تعریف کرده‌ایم تا مقایسه‌ها را در زمان بازگشت سرویسی خاص، بر اساس اعضای مشخص آن انجام دهیم.

ب) سپس هر دو سرویس مشتق شده‌ی از یک اینترفیس را به صورت معمولی ثبت می‌کنیم:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<EmailService>();
            services.AddTransient<SmsService>();
اینکار سبب خواهد شد تا بتوانیم در حین بررسی شرط‌های رسیده، برای مثال دستور ()<serviceProvider.GetService<EmailService را صادر کنیم.

ج) اکنون می‌توان مرحله‌ی شرطی کردن بازگشت این وهله‌ها را به صورت زیر، با دخالت در مراحل وهله سازی اشیاء، پیاده سازی کرد:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<EmailService>();
            services.AddTransient<SmsService>();
            services.AddTransient<Func<MessageServiceType, IMessageService>>(serviceProvider => key =>
            {
                switch (key)
                {
                    case MessageServiceType.EmailService:
                        return serviceProvider.GetRequiredService<EmailService>();
                    case MessageServiceType.SmsService:
                        return serviceProvider.GetRequiredService<SmsService>();
                    default:
                        throw new NotImplementedException($"Service of type {key} is not implemented.");
                }
            });
در اینجا پارامتر ارسالی به متد AddTransient را از نوع <Func<MessageServiceType, IMessageService انتخاب کرده‌ایم. این مورد نیز دقیقا مانند «مثال 2: وهله سازی در صورت نیاز وابستگی‌های یک سرویس به کمک Lazy loading» قسمت قبل عمل می‌کند. یعنی تا زمانیکه این Func، در قسمتی از کدهای برنامه فراخوانی نشود، سرویسی را نیز بازگشت نخواهد داد.

د) مرحله‌ی آخر کار، روش استفاده‌ی از این امضایء ویژه‌ی Lazy load شده‌است:
namespace CoreIocSample02.Controllers
{
    public class MessagesController : Controller
    {
        private readonly Func<MessageServiceType, IMessageService> _messageServiceResolver;

        public MessagesController(Func<MessageServiceType, IMessageService> messageServiceResolver)
        {
            _messageServiceResolver = messageServiceResolver;
        }
دقیقا همان امضای Func ای را که در متد AddTransient معرفی تنظیمات تزریق وابستگی‌ها تعریف کردیم، به سازنده‌ی یک کنترلر تزریق می‌کنیم. تا اینجای کار هنوز هیچکدام از سرویس‌های از نوع IMessageService وهله سازی نشده‌اند (روش دیگر پیاده سازی وهله سازی با تاخیر و در صورت نیاز). اکنون برای وهله سازی آن‌ها باید به صورت زیر عمل کرد:
public IActionResult Index()
{
   var emailService = _messageServiceResolver(MessageServiceType.EmailService);
   //use emailService ...
   return View();
}

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-07.zip
مطالب
NoSQL و مایکروسافت
روشی را که مایکروسافت برای پرداختن به مقوله NoSQL تاکنون انتخاب کرده است، قرار دادن ویژگی‌هایی خاصی از دنیای NoSQL مانند امکان تعریف اسکیمای متغیر، داخل مهم‌ترین بانک اطلاعاتی رابطه‌ای آن، یعنی SQL Server است، که در ادامه به آن خواهیم پرداخت. همچنین در سمت محصولات پردازش ابری آن نیز امکان دسترسی به محصولات NoSQL کاملی وجود دارد.

1) Azure table storage
Azure table storage در حقیقت یک Key-value store ابری است و برای کار با آن از اینترفیس پروتکل استاندارد OData استفاده می‌شود. علت استفاده و طراحی یک سیستم Key-value store در اینجا، مناسب بودن اینگونه سیستم‌ها جهت مقاصد عمومی است و به این ترتیب می‌توان به بازه بیشتری از مصرف کنندگان، خدمات ارائه داد.
پیش از ارائه Azure table storage، مایکروسافت سرویس خاصی را به نام SQL Server Data Services که به آن SQL Azure نیز گفته می‌شود، معرفی کرد. این سرویس نیز یک Key-Value store است؛ هرچند از SQL Server به عنوان مخزن نگهداری اطلاعات آن استفاده می‌کند.


2) SQL Azure XML Columns
فیلدهای XML از سال 2005 به امکانات توکار SQL Server اضافه شدند و این نوع فیلدها، بسیاری از مزایای دنیای NoSQL را درون SQL Server رابطه‌ای مهیا می‌سازند. برای مثال با تعریف یک فیلد به صورت XML، می‌توان از هر ردیف به ردیفی دیگر، اطلاعات متفاوتی را ذخیره کرد؛ به این ترتیب امکان کار با یک فیلد که می‌تواند اطلاعات یک شیء را قبول کند و در حقیقت امکان تعریف اسکیمای پویا و متغیر را در کنار امکانات یک بانک اطلاعاتی رابطه‌ای که از اسکیمای ثابت پشتیبانی می‌کند، میسر می‌شود. در این حالت در هر ردیف می‌توان تعدادی ستون ثابت را با یک ستون XML با اسکیمای کاملا پویا ترکیب کرد.
همچنین SQL Server در این حالت قابلیتی را ارائه می‌دهد که در بسیاری از بانک‌های اطلاعاتی NoSQL میسر نیست. در اینجا در صورت نیاز و لزوم می‌توان اسکیمای کاملا مشخصی را به یک فیلد XML نیز انتساب داد؛ هر چند این مورد اختیاری است و می‌توان یک un typed XML را نیز بکار برد. به علاوه امکانات کوئری گرفتن توکار از این اطلاعات را به کمک XPath ترکیب شده با T-SQL، نیز فراموش نکنید.
بنابراین اگر یکی از اهداف اصلی گرایش شما به سمت دنیای NoSQL، استفاده از امکان تعریف اطلاعاتی با اسکیمای متغیر و پویا است، فیلدهای نوع XML اس کیوال سرور را مدنظر داشته باشید.
یک مثال عملی: فناوری Azure Dev Fabric's Table Storage (نسخه Developer ویندوز Azure که روی ویندوزهای معمولی اجرا می‌شود؛ یک شبیه ساز خانگی) به کمک SQL Server و فیلدهای XML آن طراحی شده است.


3) SQL Azure Federations
در اینجا منظور از Federations در حقیقت همان پیاده سازی قابلیت Sharding بانک‌های اطلاعاتی NoSQL توسط SQL Azure است که برای توزیع اطلاعات بر روی سرورهای مختلف طراحی شده است. به این ترتیب دو قابلیت Partitioning و همچنین Replication به صورت خودکار در دسترس خواهند بود. هر Partition در اینجا، یک SQL Azure کامل است. بنابراین چندین بانک اطلاعاتی فیزیکی، یک بانک اطلاعاتی کلی را تشکیل خواهند داد.
هرچند در اینجا Sharding  (که به آن Federation member گفته می‌شود) و در پی آن مفهوم «عاقبت یک دست شدن اطلاعات» وجود دارد، اما درون یک Shard یا یک Federation member، مفهوم ACID پیاده سازی شده است. از این جهت که هر Shard واقعا یک بانک اطلاعاتی رابطه‌ای است. اینجا است که مفهوم برنامه‌های  Multi-tenancy را برای درک آن باید درنظر داشت. برای نمونه یک برنامه وب را درنظر بگیرید که قسمت اصلی اطلاعات کاربران آن بر روی یک Shard قرار دارد و سایر اطلاعات بر روی سایر Shards پراکنده شده‌اند. در این حالت است که یک برنامه وب با وجود مفهوم ACID در یک Shard می‌تواند سریع پاسخ دهد که آیا کاربری پیشتر در سایت ثبت نام کرده است یا خیر و از ثبت نام‌های غیرمجاز جلوگیری به عمل آورد.
در اینجا تنها موردی که پشتیبانی نشده‌است، کوئری‌های Fan-out می‌باشد که پیشتر در مورد آن بحث شد. از این جهت که با نحوه خاصی که Sharding آن طراحی شده است، نیازی به تهیه کوئری‌هایی که به صورت موازی بر روی کلیه Shards برای جمع آوری اطلاعات اجرا می‌شوند، نیست. هر چند از هر shard با استفاده از برنامه‌های دات نت، می‌توان به صورت جداگانه نیز کوئری گرفت.


4) OData
اگر به CouchDB و امکان دسترسی به امکانات آن از طریق وب دقت کنید، در محصولات مایکروسافت نیز این دسترسی REST API پیاده سازی شده‌اند.
OData یک RESTful API است برای دسترسی به اطلاعاتی که به شکل XML یا JSON بازگشت داده می‌شوند. انواع و اقسام کلاینت‌هایی برای کار با آن از جاوا اسکریپت گرفته تا سیستم‌های موبایل، دات نت و جاوا، وجود دارند. از این API نه فقط برای خواندن اطلاعات، بلکه برای ثبت و به روز رسانی داده‌ها نیز استفاده می‌شود. در سیستم‌های جاری مایکروسافت، بسیاری از فناوری‌ها، اطلاعات خود را به صورت OData دراختیار مصرف کنندگان قرار می‌دهند مانند Azure table storage، کار با SQL Azure از طریق WCF Data Services (جایی که OData از آن نشات گرفته شده)، Azure Data Market (برای ارائه فیدهایی از اطلاعات خصوصا رایگان)، ابزارهای گزارشگیری مانند SQL Server reporting services، لیست‌های شیرپوینت و غیره.
به این ترتیب به بسیاری از قابلیت‌های دنیای NoSQL مانند کار با اطلاعات JSON بدون ترک دنیای رابطه‌ای می‌توان دسترسی داشت.


5) امکان اجرای MongoDB و امثال آن روی سکوی کاری Azure
امکان توزیع MongoDB بر روی یک Worker role سکوی کاری Azure وجود دارد. در این حالت بانک‌های اطلاعاتی این سیستم‌ها بر روی Azure Blob Storage قرار می‌گیرند که به آن‌ها Azure drive نیز گفته می‌شود. همین روش برای سایر بانک‌های اطلاعاتی NoSQL نیز قابل اجرا است.
به علاوه امکان اجرای Hadoop نیز بر روی Azure وجود دارد. مایکروسافت به کمک شرکتی به نام HortonWorks نسخه ویندوزی Hadoop را توسعه داده‌اند. HortonWorks را افرادی تشکیل داده‌اند که پیشتر در شرکت یاهو بر روی پروژه Hadoop کار می‌کرده‌اند.


6) قابلیت‌های فرا رابطه‌ای SQL Server
الف) فیلدهای XML (که در ابتدای این مطلب به آن پرداخته شد). به این ترتیب می‌توان به یک اسکیمای انعطاف پذیر، بدون از دست دادن ضمانت ACID رسید.
ب) فیلد HierarchyId برای ذخیره سازی اطلاعات چند سطحی. برای مثال در بانک‌های اطلاعاتی NoSQL سندگرا، یک سند می‌تواند سند دیگری را در خود ذخیره کند و الی آخر.
ج) Sparse columns؛ ستون‌های اسپارس تقریبا شبیه به Key-value stores عمل می‌کنند و یا حتی Wide column stores نیز با آن قابل مقایسه است. در اینجا هنوز اسکیما وجود دارد، اما برای نمونه علت استفاده از Wide column stores این نیست که واقعا نمی‌دانید ساختار داده‌های مورد استفاده چیست، بلکه در این حالت می‌دانیم که در هر ردیف تنها از تعداد معدودی از فیلدها استفاده خواهیم کرد. به همین جهت در هر ردیف تمام فیلدها قرار نمی‌گیرند، چون در اینصورت تعدادی از آن‌ها همواره خالی باقی می‌ماندند. مایکروسافت این مشکل را با ستون‌های اسپارس حل کرده است؛ در اینجا هر چند ساختار کلی مشخص است، اما مواردی که هر بار استفاده می‌شوند، تعداد محدودی می‌باشند. به این صورت SQL Server تنها برای ستون‌های دارای مقدار، فضایی را اختصاص می‌دهد. به این ترتیب از لحاظ فیزیکی و ذخیره سازی نهایی، به همان مزیت Wide column stores خواهیم رسید.
د) FileStreams در اس کیوال سرور بسیار شبیه به پیوست‌های سندهای بانک‌های اطلاعاتی NoSQL سندگرا هستند. در اینجا نیز اطلاعات در فایل سیستم ذخیره می‌شوند اما ارجاعی به آن‌ها در جداول مرتبط وجود خواهند داشت.


7) SQL Server Parallel Data Warehouse Edition
SQL PDW، نگارش خاصی از SQL Server است که در آن یک شبکه از SQL Serverها به صورت یک وهله منطقی SQL Server در اختیار برنامه نویس‌ها قرار می‌گیرد.
این نگارش، از فناوری خاصی به نام MPP یا massively parallel processing برای پردازش کوئری‌ها استفاده می‌کند. در اینجا همانند بانک‌های اطلاعاتی NoSQL، یک کوئری به نود اصلی ارسال شده و به صورت موازی بر روی تمام نودها پردازش گردیده (همان مفهوم Map Reduce که پیشتر در مورد آن بحث شد) و نتیجه در اختیار مصرف کننده قرار خواهد گرفت. نکته مهم آن نیز در عدم نیاز به نوشتن کدی جهت رخ دادن این عملیات از طرف برنامه نویس‌ها است و موتور پردازشی آن جزئی از سیستم اصلی است. تنها کافی است یک کوئری SQL صادر گردد تا نتیجه نهایی از تمام سرورها جمع آوری و بازگردانده شود.
این نگارش ویژه تنها به صورت یک Appliance به فروش می‌رسد (به صورت سخت افزار و نرم افزار باهم) که در آن CPU‌ها، فضاهای ذخیره سازی اطلاعات و جزئیات شبکه به دقت از پیش تنظیم شده‌اند.
مطالب
MongoDb در سی شارپ (بخش دهم)

ابتدا بسته زیر را از طریق  nuget نصب نمایید:

dotnet add package MongoDB.Driver


سپس مدل‌های زیر را ایجاد نمایید:

public class BaseModel
{
    public BaseModel()
    {
        CreationDate=DateTime.Now;
    }
    public string Id { get; set; }
    public DateTime CreationDate { get; set; }
    public bool IsRemoved { get; set; }
    public DateTime? ModificationDate { get; set; }

}


 این مدل شامل یک کلاس پایه برای id,CreationDate,ModificationDate,IsRemoved میباشد که بسیار شبیه مدل‌هایی است که عموما در EntityFramework تعریف می‌کنیم.

برای اینکه فیلد Id به صورت objectId ایجاد شود ولی به صورت رشته‌ای استفاده شود ابتدا ویژگی BsonId را در بالای آن تعریف کرده تا به عنوان شناسه یکتا سند شناخته شود و سپس با استفاده از ویژگی BsonRepresentation  اعلام میکنیم که کار تبدیل به رشته و بلعکس آن به صورت خودکار در پشت صحنه صورت بگیرد:

public class BaseModel
    {
        [BsonId]
        [BsonRepresentation((BsonType.ObjectId))]
        public string Id { get; set; }
    }

 البته این حالت برای زمانی مناسب است که ما در استفاده از ویژگی‌ها محدودیتی نداشته باشیم؛ ولی در بسیاری از نرم افزارها که از معماری‌های چند لایه مانند لایه پیازی استفاده میشود استفاده از این خصوصیت‌ها یعنی اعمال کارکرد کتابخانه بالاتر بر روی لایه‌های زیرین که هسته نرم افزار شناخته میشوند که صحیح نبوده و باید توسط لایه‌های بالاتر این تغییرات اعمال شوند که میتواند از طریق کلاس این کار را انجام دهید. به ازای هر مدل که نیاز به تغییرات دارد، یک حالت جدید تعریف شده و در ابتدای برنامه در فایل Program.cs یا قبل از دات نت 6 در Startup.cs صدا زده می‌شوند.

BsonClassMap.RegisterClassMap<BaseModel>(map =>
{
    map.SetIdMember(map.GetMemberMap(x=>x.Id));
    map.GetMemberMap(x => x.Id)
        .SetSerializer(new StringSerializer(BsonType.ObjectId));
});


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

public class Employee : BaseModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
//=================
BsonClassMap.RegisterClassMap<Employee >(map =>
{
    map.SetIdMember(map.GetMemberMap(x=>x.Id));
    map.GetMemberMap(x => x.Id)
        .SetSerializer(new StringSerializer(BsonType.ObjectId));
});


روش استفاده از مونگو در asp.net core  به صورت زیر بسیار متداول میباشد که در قسمت‌های پیشین هم در این مورد نوشته بودیم:

MongoDbContext

  public interface IMongoDbContext
    {
        IMongoCollection<TEntity> GetCollection<TEntity>();
    }

  public class MongoDbContext : IMongoDbContext
    {

        private readonly IMongoClient _client;
        private readonly IMongoDatabase _database;

        public MongoDbContext(string databaseName,string connectionString)
        {
            var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
            _client = new MongoClient(settings);
            _database = _client.GetDatabase(databaseName);
        }

        public IMongoCollection<TEntity> GetCollection<TEntity>()
        {
            return _database.GetCollection<TEntity>(typeof(TEntity).Name.ToLower() + "s");
        }
    }

سپس از طریق کد زیر IMongoDbContext را به سیستم تزریق وابستگی‌ها معرفی میکنیم. الگوی استفاده شده‌ی در اینجا بر خلاف نسخه‌های sql که عموما به صورت AddScoped تعریف میشدند، در اینجا به صورت AddSingleton تعریف کردیم و نحوه پیاده سازی آن را نیز در طرف سمت راست به صورت صریح اعلام کردیم:

public static class MongoDbContextService
{
    public static void AddMongoDbContext(this IServiceCollection services,string databaseName,string connectionString)
    {
        services.AddSingleton<IMongoDbContext>(serviceProvider => new MongoDbContext(databaseName, connectionString));
    }
}

//===============
Program.cs

builder.Services.AddMongoDbContext("bookstore", "mongodb://localhost:27017");


پیاده سازی SoftDelete در مونگو

در مونگو چیزی تحت عنوان Global Query Filter نداریم که تمام کوئری هایی که به سمت دیتابیس ارسال میشوند، توسط کانتکس اطلاح شوند؛ بدین جهت برای پیاده سازی این خصوصیت میتوان اینترفیسی با نام <IRepository<T را به شکل زیر طراحی نماییم:

public interface IRepository<T> where T : BaseModel
{

    IMongoCollection<T> GetCollection();
    IMongoQueryable<T> GetFilteredCollection();
}

public class Repository<T> : IRepository<T> where T:BaseModel
{
    private IMongoDbContext _mongoDbContext;

    public Repository(IMongoDbContext mongoDbContext)
    {
        _mongoDbContext = mongoDbContext;
    }

    public IMongoCollection<T> GetCollection()
    {
        return _mongoDbContext.GetCollection<T>();
    }
    
    public IMongoQueryable<T> GetFilteredCollection()
    {
        var query= _mongoDbContext.GetCollection<T>().AsQueryable();
        
        //================= Global Query Filters ====================
        
        //Filter 1
        query=query.Where(x => x.RemovedAt.HasValue == false);
        
        //==============================================================
        
        return query;
    }
}

این کلاس یا اینترفیس شامل دو متد هستند که کلاس جنریک آنها باید از BaseModel ارث بری کرده باشد و اولین متد، تنها یک کالکشن بدون هیچگونه فیلتری است که میتواند نقش متد IgnoreQueryFilters  را بازی کند و دیگری GetFilteredCollection است که در این متد ابتدا کالکشنی دریافت شده و سپس آن را به حالت کوئری تغییر داده و فیلترهای مورد نظر، مانند حذف منطقی را پیاده سازی میکنیم:

public interface IRepository<T> where T : BaseModel
{

    IMongoCollection<T> GetCollection();
    IMongoQueryable<T> GetFilteredCollection();
}

public class Repository<T> : IRepository<T> where T:BaseModel
{
    private IMongoDbContext _mongoDbContext;

    public Repository(IMongoDbContext mongoDbContext)
    {
        _mongoDbContext = mongoDbContext;
    }

    public IMongoCollection<T> GetCollection()
    {
        return _mongoDbContext.GetCollection<T>();
    }
    
    public IMongoQueryable<T> GetFilteredCollection()
    {
        var query= _mongoDbContext.GetCollection<T>().AsQueryable();
        
        //================= Global Query Filters ====================
        
        //Filter 1
        query=query.Where(x => x.RemovedAt.HasValue == false);
        
        //==============================================================
        
        return query;
    }
}


اصلاح تاریخ ویرایش در مدل

در EF به لطف dbset و همچنین ChangeTracking امکان شناسایی حالت‌ها وجود دارد و میتوانید در متدی مانند saveChanges مقدار تاریخ ویرایش را تنظیم نمود. برای مدل‌های منگو چنین چیزی وجود ندارد و به همین دلیل چند روش زیر پیشنهاد میگردد:

یک. استفاده از اینترفیس INotifyPropertyChanged یا جهت حذف کدهای تکراری نیز از الگوی AOP بهره بگیرید.

دو. استفاده از یک <Repository<T همانند بالا که شامل متدهای داخلی Update و Delete هستند که در آنجا میتوانید این مقادیر را به صورت مستقیم تغییر دهید.

نظرات مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت دوم - سرویس اعتبارسنجی
با تغییر کلاس سرویس AppConfigService به شکل زیر :

import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";

@Injectable()
export class AppConfigService {

    private config: IAppConfig;

    constructor(private http: HttpClient) { }

    loadClientConfig(): Promise<any> {
        return this.http.get<IAppConfig>("assets/client-config.json")
            .toPromise()
            .then(config => {
                this.config = config;
                console.log("Config", this.config);
            })
            .catch(err => {
                return Promise.reject(err);
            });
    }

    get configuration(): IAppConfig {
        if (!this.config) {
            throw new Error("Attempted to access configuration property before configuration data was loaded.");
        }
        return this.config;
    }
}

export interface IAppConfig {
    apiEndpoint: string;
    loginPath: string;
    logoutPath: string;
    refreshTokenPath: string;
    accessTokenObjectKey: string;
    refreshTokenObjectKey: string;
    adminRoleName: string;
}

و تغییر ماژول CoreModule به شکل زیر :

import { NgModule, Optional, SkipSelf, APP_INITIALIZER } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterModule } from "@angular/router";
import { HTTP_INTERCEPTORS } from "@angular/common/http";

// import RxJs needed operators only once
import "./services/rxjs-operators";

import { HeaderComponent } from "./component/header/header.component";
import { AuthGuard } from "./services/auth.guard";
import { AuthInterceptor } from "./services/auth.interceptor";
import { AuthService } from "./services/auth.service";
import { AppConfigService } from "./services/app-config.service";
import { BrowserStorageService } from "./services/browser-storage.service";


@NgModule({
    imports: [CommonModule, RouterModule],
    exports: [
        // components that are used in app.component.ts will be listed here.
        HeaderComponent
    ],
    declarations: [
        // components that are used in app.component.ts will be listed here.
        HeaderComponent
    ],
    providers: [
        // global singleton services of the whole app will be listed here.
        BrowserStorageService,
        AppConfigService,
        AuthService,
        AuthGuard,
        {
            provide: HTTP_INTERCEPTORS,
            useClass: AuthInterceptor,
            multi: true
        },
        {
            provide: APP_INITIALIZER,
            useFactory: (config: AppConfigService) => () => config.loadClientConfig(),
            deps: [AppConfigService ],
            multi: true
        }
    ]
})
export class CoreModule {
    constructor( @Optional() @SkipSelf() core: CoreModule) {
        if (core) {
            throw new Error("CoreModule should be imported ONLY in AppModule.");
        }
    }
}
با خطای زیر مواجه شدم لطفا راهنمایی بفرمائید :

Error: Provider parse errors:
Cannot instantiate cyclic dependency! ApplicationRef ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1 
مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
ممکن است تعدادی از اکشن متدهای API طراحی شده، محافظت شده باشند. بنابراین OpenAPI Specification تولیدی نیز باید به همراه مستندات کافی در این مورد باشد تا استفاده کنندگان از آن بدانند چگونه باید با آن کار کنند. نگارش سوم OpenAPI Specification از اعتبارسنجی و احراز هویت مبتنی بر هدرها مانند basic و یا bearer، همچنین حالت کار با API Keys مانند هدرها، کوئری استرینگ‌ها و کوکی‌ها و یا حالت OAuth2 و OpenID Connect پشتیبانی می‌کند و این موارد ذیل خواص securitySchemes و security در OpenAPI Specification ظاهر می‌شوند:
"securitySchemes":
{ "basicAuth":
       {
             "type":"http",
             "description":"Input your username and password to access this API",
             "scheme":"basic"
       }
}
…
"security":[
  {"basicAuth":[]}
]
- خاصیت securitySchemes انواع حالت‌های اعتبارسنجی پشتیبانی شده را لیست می‌کند.
- خاصیت security کار اعمال Scheme تعریف شده را به کل API یا صرفا قسمت‌های خاصی از آن، انجام می‌دهد.

در ادامه مثالی را بررسی خواهیم کرد که مبتنی بر basic authentication کار می‌کند و در این حالت به ازای هر درخواست به API، نیاز است یک نام کاربری و کلمه‌ی عبور نیز ارسال شوند. البته روش توصیه شده، کار با JWT و یا OpenID Connect است؛ اما جهت تکمیل ساده‌تر این قسمت، بدون نیاز به برپایی مقدماتی پیچیده، کار با basic authentication را بررسی می‌کنیم و اصول کلی آن از دیدگاه مستندات OpenAPI Specification تفاوتی نمی‌کند.


افزودن Basic Authentication به API برنامه

برای پیاده سازی Basic Authentication نیاز به یک AuthenticationHandler سفارشی داریم:
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace OpenAPISwaggerDoc.Web.Authentication
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
            {
                return Task.FromResult(AuthenticateResult.Fail("Missing Authorization header"));
            }

            try
            {
                var authenticationHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authenticationHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
                var username = credentials[0];
                var password = credentials[1];

                if (username == "DNT" && password == "123")
                {
                    var claims = new[] { new Claim(ClaimTypes.NameIdentifier, username) };
                    var identity = new ClaimsIdentity(claims, Scheme.Name);
                    var principal = new ClaimsPrincipal(identity);
                    var ticket = new AuthenticationTicket(principal, Scheme.Name);
                    return Task.FromResult(AuthenticateResult.Success(ticket));
                }
                return Task.FromResult(AuthenticateResult.Fail("Invalid username or password"));
            }
            catch
            {
                return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization header"));
            }
        }
    }
}
کار این AuthenticationHandler سفارشی، با بازنویسی متد HandleAuthenticateAsync شروع می‌شود. در اینجا به دنبال هدر ویژه‌ای با کلید Authorization می‌گردد. این هدر باید به همراه نام کاربری و کلمه‌ی عبوری با حالت base64 encoded باشد. اگر این هدر وجود نداشت و یا مقدار هدر Authorization، با فرمتی که مدنظر ما است قابل decode و همچنین جداسازی نبود، شکست اعتبارسنجی اعلام می‌شود.
پس از دریافت مقدار هدر Authorization، ابتدا مقدار آن‌را از base64 به حالت معمولی تبدیل کرده و سپس بر اساس حرف ":"، دو قسمت را از آن جداسازی می‌کنیم. قسمت اول را به عنوان نام کاربری و قسمت دوم را به عنوان کلمه‌ی عبور پردازش خواهیم کرد. در این مثال جهت سادگی، این دو باید مساوی DNT و 123 باشند. اگر اینچنین بود، یک AuthenticationTicket دارای Claim ای حاوی نام کاربری را ایجاد کرده و آن‌را به عنوان حاصل موفقیت آمیز بودن عملیات بازگشت می‌دهیم.

مرحله‌ی بعد، استفاده و معرفی این BasicAuthenticationHandler تهیه شده به برنامه است:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(defaultScheme: "Basic")
                    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("Basic", null);
در اینجا توسط متد services.AddAuthentication، این scheme جدید که نام رسمی آن Basic است، به همراه Handler آن، به برنامه معرفی می‌شود.
همچنین نیاز است میان‌افزار اعتبارسنجی را نیز با فراخوانی متد app.UseAuthentication، به برنامه اضافه کرد که باید پیش از فراخوانی app.UseMvc صورت گیرد تا به آن اعمال شود:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
   // ...
            app.UseStaticFiles();

            app.UseAuthentication();

            app.UseMvc();
        }
    }
}

همچنین برای اینکه تمام اکشن متدهای موجود را نیز محافظت کنیم، می‌توان فیلتر Authorize را به صورت سراسری اعمال کرد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
     // ...
   
            services.AddMvc(setupAction =>
            {
                setupAction.Filters.Add(new AuthorizeFilter());
        // ...


تکمیل مستندات API جهت انعکاس تنظیمات محافظت از اکشن متدهای آن

پس از تنظیم محافظت دسترسی به اکشن متدهای برنامه، اکنون نوبت به مستند کردن آن است و همانطور که در ابتدای بحث نیز عنوان شد، برای این منظور نیاز به تعریف خواص securitySchemes و security در OpenAPI Specification است:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
   // ...

                setupAction.AddSecurityDefinition("basicAuth", new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "basic",
                    Description = "Input your username and password to access this API"
                });
            });
در اینجا توسط متد setupAction.AddSecurityDefinition، ابتدا یک نام تعریف می‌شود (از این نام، در قسمت بعدی تنظیمات استفاده خواهد شد). پارامتر دوم آن همان SecurityScheme است که توضیح داده شد. برای حالت basic auth، نوع آن Http است و اسکیمای آن basic. باید دقت داشت که مقدار خاصیت Scheme در اینجا، حساس به بزرگی و کوچکی حروف است.

پس از این تنظیم اگر برنامه را اجرا کنیم، یک دکمه‌ی authorize اضافه شده‌است:


با کلیک بر روی آن، صفحه‌ی ورود نام کاربری و کلمه‌ی عبور ظاهر می‌شود:


اگر آن‌را تکمیل کرده و سپس برای مثال لیست نویسندگان را درخواست کنیم (با کلیک بر روی دکمه‌ی try it out آن و سپس کلیک بر روی دکمه‌ی execute ذیل آن)، تنها خروجی 401 یا unauthorized را دریافت می‌کنیم:


- بنابراین برای تکمیل آن، مطابق نکات قسمت چهارم، ابتدا باید status code مساوی 401 را به صورت سراسری، مستند کنیم:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(setupAction =>
            {
                setupAction.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status401Unauthorized));

- همچنین هرچند با کلیک بر روی دکمه‌ی Authorize در Swagger UI و ورود نام کاربری و کلمه‌ی عبور توسط آن، در همانجا پیام Authorized را دریافت کردیم، اما اطلاعات آن به ازای هر درخواست، به سمت سرور ارسال نمی‌شود. به همین جهت در حین درخواست لیست نویسندگان، پیام unauthorized را دریافت کردیم. برای رفع این مشکل نیاز است به OpenAPI Spec اعلام کنیم که تعامل با API، نیاز به Authentication دارد:
namespace OpenAPISwaggerDoc.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSwaggerGen(setupAction =>
            {
    // ...

                setupAction.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "basicAuth"
                            }
                        },
                        new List<string>()
                    }
                });
            });
در اینجا OpenApiSecurityRequirement یک دیکشنری از نوع <<Dictionary<OpenApiSecurityScheme, IList<string است که کلید آن از نوع OpenApiSecurityScheme تعریف می‌شود و باید آن‌را به نمونه‌ای که توسط setupAction.AddSecurityDefinition پیشتر اضافه کردیم، متصل کنیم. این اتصال توسط خاصیت Reference آن و Id ای که به نام تعریف شده‌ی توسط آن اشاره می‌کند، صورت می‌گیرد. مقدار این دیکشنری نیز لیستی از رشته‌ها می‌تواند باشد (مانند توکن‌ها و scopes در OpenID Connect) که در اینجا با یک لیست خالی مقدار دهی شده‌است.

پس از این تنظیمات، Swagger UI با افزودن یک آیکن قفل به مداخل APIهای محافظت شده، به صورت زیر تغییر می‌کند:


در این حالت اگر بر روی آیکن قفل کلیک کنیم، همان صفحه‌ی دیالوگ ورود نام کاربری و کلمه‌ی عبوری که پیشتر با کلیک بر روی دکمه‌ی Authorize ظاهر شد، نمایش داده می‌شود. با تکمیل آن و کلیک مجدد بر روی آیکن قفل، جهت گشوده شدن پنل API و سپس کلیک بر روی try it out  آن، برای مثال می‌توان به API محافظت شده‌ی دریافت لیست نویسندگان، بدون مشکلی، دسترسی یافت:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: OpenAPISwaggerDoc-06.zip
مطالب
شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
در مورد «امکانات توکار تزریق وابستگی‌ها در ASP.NET Core» پیشتر بحث شد. همچنین «نحوه‌ی تعریف Context، تزریق سرویس‌های EF Core و تنظیمات رشته‌ی اتصالی آن» را نیز بررسی کردیم. به علاوه مباحث «به روز رسانی ساختار بانک اطلاعاتی» و «انتقال مهاجرت‌ها به یک اسمبلی دیگر» نیز مرور شدند. بنابراین در این قسمت برای لایه بندی برنامه‌های EF Core، صرفا یک مثال را مرور خواهیم کرد که این قسمت‌ها را در کنار هم قرار می‌دهد و عملا نکته‌ی اضافه‌تری را ندارد.


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

در همان قسمت اول سری شروع به کار با EF Core 1.0، مشاهده کردیم که پس از انجام تنظیمات اولیه‌ی آن در کلاس آغازین برنامه:
public void ConfigureServices(IServiceCollection services)
{    
   services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
Context برنامه را در تمام قسمت‌های آن می‌توان تزریق کرد و کار می‌کند:
    public class TestDBController : Controller
    {
        private readonly ApplicationDbContext _ctx;

        public TestDBController(ApplicationDbContext ctx)
        {
            _ctx = ctx;
        }

        public IActionResult Index()
        {
            var name = _ctx.Persons.First().FirstName;
            return Json(new { firstName = name });
        }
    }
این روشی است که در بسیاری از مثال‌های گوشه و کنار اینترنت قابل مشاهده‌است. یا کلاس Context را مستقیما در سازنده‌ی کنترلرها تزریق می‌کنند و از آن استفاده می‌کنند (روش فوق) و یا لایه‌ی سرویسی را ایجاد کرده و مجددا همین تزریق مستقیم را در آنجا انجام می‌دهند و سپس اینترفیس‌های آن سرویس را در کنترلرهای برنامه تزریق کرده و استفاده می‌کنند. به این نوع تزریق وابستگی‌ها، تزریق concrete types و یا concrete classes می‌گویند.
مشکلاتی را که تزریق مستقیم کلاس‌ها و نوع‌ها به همراه دارند به شرح زیر است:
- اگر نام این کلاس تغییر کند، باید این نام، در تمام کلاس‌هایی که به صورت مستقیم از آن استفاده می‌کنند نیز تغییر داده شود.
- اگر سازنده‌ای به آن اضافه شد و یا امضای سازنده‌ی موجود آن، تغییر کرد، باید نحوه‌ی وهله سازی این کلاس را در تمام کلاس‌های وابسته نیز اصلاح کرد.
- یکی از مهم‌ترین دلایل استفاده‌ی از تزریق وابستگی‌ها، بالابردن قابلیت تست پذیری برنامه است. زمانیکه از اینترفیس‌ها استفاده می‌شود، می‌توان در مورد نحوه‌ی تقلید (mocking) رفتار کلاسی خاص، مستقلا تصمیم گیری کرد. اما هنگامیکه یک کلاس را به همان شکل اولیه‌ی آن تزریق می‌کنیم، به این معنا است که همواره دقیقا همین پیاده سازی خاص مدنظر ما است و این مساله، نوشتن آزمون‌های واحد را با مشکل کردن mocking آن‌ها، گاهی از اوقات غیرممکن می‌کند. هرچند تعدادی از فریم ورک‌های پیشرفته‌ی mocking گاهی از اوقات امکان تقلید رفتار کلاس‌ها و نوع‌ها را نیز فراهم می‌کنند، اما با این شرط که تمام خواص و متدهای آن‌ها را virtual تعریف کنید؛ تا بتوانند متدهای اصلی را با نمونه‌های مدنظر شما بازنویسی (override) کنند.

به همین جهت در ادامه، به همان طراحی EF Code First #12 با نوشتن اینترفیس IUnitOfWork خواهیم رسید. یعنی کلاس Context برنامه را با این اینترفیس نشانه گذاری می‌کنیم (در انتهای لیست تمام اینترفیس‌های دیگری که ممکن است در اینجا ذکر شده باشند):
 public class ApplicationDbContext :  IUnitOfWork
و سپس اینترفیس IUnitOfWork را به لایه سرویس برنامه و یا هر لایه‌ی دیگری که به Context آن نیاز دارد، تزریق خواهیم کرد.


طراحی اینترفیس IUnitOfWork

برای اینکه دیگر با کلاس ApplicationDbContext مستقیما کار نکرده و وابستگی به آن‌را در تمام قسمت‌های برنامه پخش نکنیم، اینترفیسی را ایجاد می‌کنیم که تنها قسمت‌های مشخصی از DbContext را عمومی کند:
public interface IUnitOfWork : IDisposable
{
    DbSet<TEntity> Set<TEntity>() where TEntity : class;
 
    void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
    void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class;
 
    EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
    void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
 
    void ExecuteSqlCommand(string query);
    void ExecuteSqlCommand(string query, params object[] parameters);
 
    int SaveAllChanges();
    Task<int> SaveAllChangesAsync();
}
توضیحات
- در این طراحی شاید عنوان کنید که DbSet، اینترفیس نیست. تعریف DbSet در EF Core به صورت زیر است و در حقیقت همانند اینترفیس‌ها یک abstraction به حساب می‌آید:
 public abstract class DbSet<TEntity> : IQueryable<TEntity>, IEnumerable<TEntity>, IEnumerable, IQueryable, IAsyncEnumerableAccessor<TEntity>, IInfrastructure<IServiceProvider> where TEntity : class
علت اینکه در پروژه‌های بزرگی مانند EF، تمایل زیادی به استفاده‌ی از کلاس‌های abstract وجود دارد (بجای اینترفیس‌ها) این است که اگر این نوع پرکاربرد را به صورت اینترفیس تعریف کنند، با تغییر متدی در آن، باید تمام کدهای خود را به اجبار بازنویسی کنید. اما در حالت استفاده‌ی از کلاس‌های abstract، می‌توان پیاده سازی پیش فرضی را برای متدهایی که قرار است در آینده اضافه شوند، ارائه داد (یکی از تفاوت‌های مهم آن‌ها با اینترفیس‌ها)، بدون اینکه تمام استفاده کنندگان از این کتابخانه، با ارتقاء نگارش EF خود، دیگر نتوانند برنامه‌ی خود را کامپایل کنند.
- این اینترفیس به عمد به صورت IDisposable تعریف شده‌است. این مساله به IoC Containers کمک خواهد کرد که بتوانند پاکسازی خودکار نوع‌های IDisposable را در انتهای هر درخواست انجام دهند و برنامه مشکلی نشتی حافظه را پیدا نکند.
- اصل کار این اینترفیس، تعریف DbSet و متدهای SaveChanges است. سایر متدهایی را که مشاهده می‌کنید، صرفا جهت بیان اینکه چگونه می‌توان قابلیتی از DbContext را بدون عمومی کردن خود کلاس DbContext، در کلاس‌هایی که از اینترفیس IUnitOfWork استفاده می‌کنند، میسر کرد.

پس از اینکه این اینترفیس تعریف شد، اعمال آن به کلاس Context برنامه به صورت ذیل خواهد بود:
public class ApplicationDbContext : DbContext, IUnitOfWork
{
    private readonly IConfigurationRoot _configuration;
 
    public ApplicationDbContext(IConfigurationRoot configuration)
    {
        _configuration = configuration;
    }
 
    //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    //{
    //}
 
    public virtual DbSet<Blog> Blog { get; set; }

 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
            _configuration["ConnectionStrings:ApplicationDbContextConnection"]
            , serverDbContextOptionsBuilder =>
             {
                 var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                 serverDbContextOptionsBuilder.CommandTimeout(minutes);
             }
            );
    }
 
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
 
        base.OnModelCreating(modelBuilder);
    }
 
    public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        base.Set<TEntity>().AddRange(entities);
    }
 
    public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class
    {
        base.Set<TEntity>().RemoveRange(entities);
    }
 
    public void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class
    {
        base.Entry(entity).State = EntityState.Modified; // Or use ---> this.Update(entity);
    }
 
    public void ExecuteSqlCommand(string query)
    {
        base.Database.ExecuteSqlCommand(query);
    }
 
    public void ExecuteSqlCommand(string query, params object[] parameters)
    {
        base.Database.ExecuteSqlCommand(query, parameters);
    }
 
    public int SaveAllChanges()
    {
        return base.SaveChanges();
    }
 
    public Task<int> SaveAllChangesAsync()
    {
        return base.SaveChangesAsync();
    }
}
در ابتدا اینترفیس IUnitOfWork به کلاس Context برنامه اعمال شده‌است:
 public class ApplicationDbContext : DbContext, IUnitOfWork
و سپس متدهای آن منهای پیاده سازی اینترفیس IDisposable اعمالی به IUnitOfWork :
 public interface IUnitOfWork : IDisposable
پیاده سازی شده‌اند. علت اینجا است که چون کلاس پایه DbContext از همین اینترفیس مشتق می‌شود، دیگر نیاز به پیاده سازی اینترفیس IDisposable نیست.
در مورد تزریق IConfigurationRoot به سازنده‌ی کلاس Context برنامه، در مطلب اول این سری در قسمت «یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه» پیشتر بحث شده‌است.


ثبت تنظیمات تزریق وابستگی‌های IUnitOfWork

پس از تعریف و پیاده سازی اینترفیس IUnitOfWork، اکنون نوبت به معرفی آن به سیستم تزریق وابستگی‌های ASP.NET Core است:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
  services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
  services.AddScoped<IUnitOfWork, ApplicationDbContext>();
در اینجا هم ApplicationDbContext و هم IUnitOfWork با طول عمر Scoped به تنظیمات IoC Container مربوط به ASP.NET Core اضافه شده‌اند. به این ترتیب هر زمانیکه وهله‌ای از نوع IUnitOfWork درخواست شود، تنها یک وهله از ApplicationDbContext در طول درخواست وب جاری، در اختیار مصرف کننده قرار می‌گیرد و همچنین مدیریت Dispose این وهله‌ها نیز خودکار است. به همین جهت اینترفیس IUnitOfWork را با IDisposable علامتگذاری کردیم.


استفاده از IUnitOfWork در لایه سرویس‌های برنامه

اکنون لایه سرویس برنامه و فایل project.json آن چنین شکلی را پیدا می‌کند:
{
  "version": "1.0.0-*",
 
    "dependencies": {
        "Core1RtmEmptyTest.DataLayer": "1.0.0-*",
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
        "Microsoft.Extensions.Options": "1.0.0",
        "NETStandard.Library": "1.6.0"
    },
 
  "frameworks": {
    "netstandard1.6": {
      "imports": "dnxcore50"
    }
  }
}
در اینجا ارجاعاتی را به اسمبلی‌های موجودیت‌ها و DataLayer برنامه مشاهده می‌کنید. در مورد این اسمبلی‌ها در مطلب «شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر» پیشتر بحث شد.
پس از تنظیم وابستگی‌های این اسمبلی، اکنون یک کلاس نمونه از لایه سرویس برنامه، به شکل زیر خواهد بود: 
namespace Core1RtmEmptyTest.Services
{
    public interface IBlogService
    {
        IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage);
    }
 
    public class BlogService : IBlogService
    {
        private readonly IUnitOfWork _uow;
        private readonly DbSet<Blog> _blogs;
 
        public BlogService(IUnitOfWork uow)
        {
            _uow = uow;
            _blogs = _uow.Set<Blog>();
        }
 
        public IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage)
        {
            var skipRecords = pageNumber * recordsPerPage;
            return _blogs
                        .AsNoTracking()
                        .Skip(skipRecords)
                        .Take(recordsPerPage)
                        .ToList();
        }
    }
}
در اینجا اکنون می‌توان IUnitOfWork را به سازنده‌ی کلاس سرویس Blog تنظیم کرد و سپس به نحو متداولی از امکانات EF Core استفاده نمود.


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

خروجی لایه سرویس، توسط اینترفیس‌هایی مانند IBlogService در قسمت‌های دیگر برنامه قابل استفاده و دسترسی می‌شود.
به همین جهت نیاز است مشخص کنیم، این اینترفیس را کدام کلاس ویژه قرار است پیاده سازی کند. برای این منظور همانند قبل در متد ConfigureServices کلاس آغازین برنامه این تنظیم را اضافه خواهیم کرد:
public void ConfigureServices(IServiceCollection services)
{
  services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
  services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
  services.AddScoped<IUnitOfWork, ApplicationDbContext>();
  services.AddScoped<IBlogService, BlogService>();
پس از آن، امضای سازنده‌ی کلاس کنترلری که در ابتدای بحث عنوان شد، به شکل زیر تغییر پیدا می‌کند:
public class TestDBController : Controller
{
    private readonly IBlogService _blogService;
    private readonly IUnitOfWork _uow;
 
    public TestDBController(IBlogService blogService, IUnitOfWork uow)
    {
        _blogService = blogService;
        _uow = uow;
    }
در اینجا کنترلر برنامه تنها با اینترفیس‌های IUnitOfWork و IBlogService کار می‌کند و دیگر ارجاع مستقیمی را به کلاس ApplicationDbContext ندارد.