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

throw new InvalidOperationException();

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


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

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

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

throw new InvalidOperationException();

return RedirectToAction("Index");
خواهید دید که عملیات roll back شده و تغییرات در دیتابیس (در اینجا ذخیره سازی گروه محصولات) اعمال نخواهد شد.
مطالب
بررسی متد های یک طرفه در WCF
در WCF به صورت پیش فرض متد‌ها به صورت Request-Response هستند. این بدین معنی است که هر زمان درخواستی از سمت کلاینت به سرور ارسال شود تا زمانی که پاسخی از سمت سرور به کلاینت برگشت داده نشود، کلاینت منتظر خواهد ماند. برای مثال:
پروژه ای از نوع Wcf Service App می‌سازیم و یک سرویس با یک متد که خروجی آن نیز void است خواهیم داشت. به صورت زیر:
    [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        void Wait();
    }
پیاده سازی Contract بالا:
public class SampleService : ISampleService
    {
        public void Wait()
        {
            Thread.Sleep( new TimeSpan( 0, 1, 0 ) );
        }
    }
  در متد Wait، به مدت یک دقیقه اجرای برنامه سمت سرور را متوقف می‌کنیم. حال در یک پروژه از نوع Console App، سرویس مورد نظر را اضافه کرده و متد Wait آن را فراخوانی می‌کنیم. به صورت زیر:
class Program
    {
        static void Main( string[] args )
        {
            SampleService.SampleServiceClient client = new SampleService.SampleServiceClient();

            Console.WriteLine( DateTime.Now );

            client.Wait();

            Console.WriteLine( DateTime.Now );

            Console.ReadKey();
        }
    }
همان طور که می‌بینید قبل از فراخوانی متد Wait زمان جاری سیستم را نمایش داده و سپس بعد از فراخوانی دوباره زمان مورد را نمایش می‌دهیم. در مرحله اول با خطای زیر مواجه خواهیم شد:


دلیل اینکه Timeout Exception پرتاب شد این است که به صورت پیش فرض مقدار خاصیت sendTimeout برابر 59  ثانیه است، در نتیجه قبل از اینکه پاسخی از سمت سرور به کلاینت برگشت داه شود این Exception رخ می‌دهد. برای حل این مشکل کافیست در فایل app.config کلاینت در قسمت تنظیمات Binding ، تغییر زیر را اعمال کنیم:
 <basicHttpBinding>
     <binding name="BasicHttpBinding_ISampleService" sendTimeout="0:2:0"/>
 </basicHttpBinding>
حال خروجی به صورت زیر است:


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

در این مواقع زمانی که باید متدی سمت سرور فراخوانی شود و قرار نیست که خروجی نیز در اختیار کلاینت قرار دهد بهتر است که از متد‌های یک طرفه استفاده نماییم. متد‌های یک طرفه یا یه اصطلاح OneWay، هیچ پاسخی را به کلاینت برگشت نمی‌دهند و بلافاصله بعد از فراخوانی، کنترل اجرای برنامه را در اختیار کلاینت قرار خواهند داد. برای تعریف یک متد به صورت یک طرفه کافیست به صورت زیر عمل نماییم(مقدار خاصیت IsOneWay را در OperationContractAttribute برابر true خواهیم کرد):
    [ServiceContract]
    public interface ISampleService
    {
        [OperationContract( IsOneWay = true )]
        void Wait();
    }
حال اگر سرویس سمت کلاینت را به روز کرده و برنامه را اجرا کنیم خروجی به صورت زیر تغییر می‌کند:

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

مطالب
سفارشی سازی ASP.NET Core Identity - قسمت دوم - سرویس‌های پایه
در قسمت قبل کلاس‌های متناظر با جداول پایه‌ی ASP.NET Core Identity را تغییر دادیم. اما هنوز سرویس‌های پایه‌ی این فریم ورک مانند مدیریت و ذخیره‌ی کاربران و مدیریت و ذخیره‌ی نقش‌ها، اطلاعی از وجود آن‌ها ندارند. در ادامه این سرویس‌ها را نیز سفارشی سازی کرده و سپس به ASP.NET Core Identity معرفی می‌کنیم.

سفارشی سازی RoleStore

ASP.NET Core Identity دو سرویس را جهت کار با نقش‌های کاربران پیاده سازی کرده‌است:
- سرویس RoleStore: کار آن دسترسی به ApplicationDbContext ایی است که در قسمت قبل سفارشی سازی کردیم و سپس ارائه‌ی زیر ساختی به سرویس RoleManager جهت استفاده‌ی از آن برای ذخیره سازی و یا تغییر و حذف نقش‌های سیستم.
وجود Storeها از این جهت است که اگر علاقمند بودید، بتوانید از سایر ORMها و یا زیرساخت‌های ذخیره سازی اطلاعات برای کار با بانک‌های اطلاعاتی استفاده کنید. در اینجا از همان پیاده سازی پیش‌فرض آن که مبتنی بر EF Core است استفاده می‌کنیم. به همین جهت است که وابستگی ذیل را در فایل project.json مشاهده می‌کنید:
   "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0",
- سرویس RoleManager: این سرویس از سرویس RoleStore و تعدادی سرویس دیگر مانند اعتبارسنج نام نقش‌ها و نرمال ساز نام نقش‌ها، جهت ارائه‌ی متدهایی برای یافتن، افزودن و هر نوع عملیاتی بر روی نقش‌ها استفاده می‌کند.
برای سفارشی سازی سرویس‌های پایه‌ی ASP.NET Core Identity‌، ابتدا باید سورس این مجموعه را جهت یافتن نگارشی از سرویس مدنظر که کلاس‌های موجودیت را به صورت آرگومان‌های جنریک دریافت می‌کند، پیدا کرده و سپس از آن ارث بری کنیم:
public class ApplicationRoleStore :
     RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>,
     IApplicationRoleStore
تا اینجا مرحله‌ی اول تشکیل کلاس ApplicationRoleStore سفارشی به پایان می‌رسد. نگارش جنریک RoleStore پایه را یافته و سپس موجودیت‌های سفارشی سازی شده‌ی خود را به آن معرفی می‌کنیم.
این ارث بری جهت تکمیل، نیاز به بازنویسی سازنده‌ی RoleStore پایه را نیز دارد:
public ApplicationRoleStore(
   IUnitOfWork uow,
   IdentityErrorDescriber describer = null)
   : base((ApplicationDbContext)uow, describer)
در اینجا پارامتر اول آن‌را به IUnitOfWork بجای DbContext متداول تغییر داده‌ایم؛ چون IUnitOfWork دقیقا از همین نوع است و همچنین امکان دسترسی به متدهای ویژه‌ی آن‌را نیز فراهم می‌کند.
در نگارش 1.1 این کتابخانه، بازنویسی متد CreateRoleClaim نیز اجباری است تا در آن مشخص کنیم، نحوه‌ی تشکیل کلید خارجی و اجزای یک RoleClaim به چه نحوی است:
        protected override RoleClaim CreateRoleClaim(Role role, Claim claim)
        {
            return new RoleClaim
            {
                RoleId = role.Id,
                ClaimType = claim.Type,
                ClaimValue = claim.Value
            };
        }
در نگارش 2.0 آن، این new RoleClaim به صورت خودکار توسط کتابخانه صورت خواهد گرفت و سفارشی کردن آن ساده‌تر می‌شود.

در ادامه اگر به انتهای تعریف امضای کلاس دقت کنید، یک اینترفیس IApplicationRoleStore را نیز مشاهده می‌کنید. برای تشکیل آن بر روی نام کلاس سفارشی خود کلیک راست کرده و با استفاده از ابزارهای Refactoring کار Extract interface را انجام می‌دهیم؛ از این جهت که در سایر لایه‌های برنامه نمی‌خواهیم از تزریق مستقیم کلاس ApplicationRoleStore استفاده کنیم. بلکه می‌خواهیم اینترفیس IApplicationRoleStore را در موارد ضروری، به سازنده‌های کلاس‌های تزریق نمائیم.
پس از تشکیل این اینترفیس، مرحله‌ی بعد، معرفی آن‌ها به سیستم تزریق وابستگی‌های ASP.NET Core است. چون تعداد تنظیمات مورد نیاز ما زیاد هستند، یک کلاس ویژه به نام IdentityServicesRegistry تشکیل شده‌است تا به عنوان رجیستری تمام سرویس‌های سفارشی سازی شده‌ی ما عمل کند و تنها با فراخوانی متد AddCustomIdentityServices آن در کلاس آغازین برنامه، کار ثبت یکجای تمام سرویس‌های ASP.NET Core Identity انجام شود و بی‌جهت کلاس آغازین برنامه شلوغ نگردد.
ثبت ApplicationDbContext طبق روش متداول آن در کلاس آغازین برنامه انجام شده‌است. سپس معرفی سرویس IUnitOfWork را که از ApplicationDbContext تامین می‌شود، در کلاس IdentityServicesRegistry مشاهده می‌کنید.
 services.AddScoped<IUnitOfWork, ApplicationDbContext>();
در ادامه RoleStore سفارشی ما نیاز به دو تنظیم جدید را خواهد داشت:
services.AddScoped<IApplicationRoleStore, ApplicationRoleStore>();
services.AddScoped<RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>, ApplicationRoleStore>();
ابتدا مشخص کرده‌ایم که اینترفیس IApplicationRoleStore، از طریق کلاس سفارشی ApplicationRoleStore تامین می‌شود.
سپس RoleStore توکار ASP.NET Core Identity را نیز به ApplicationRoleStore خود هدایت کرده‌ایم. به این ترتیب هر زمانیکه در کدهای داخلی این فریم ورک وهله‌ای از RoleStore جنریک آن درخواست می‌شود، دیگر از همان پیاده سازی پیش‌فرض خود استفاده نخواهد کرد و به پیاده سازی ما هدایت می‌شود.

این روشی است که جهت سفارشی سازی تمام سرویس‌های پایه‌ی ASP.NET Core Identity بکار می‌گیریم:
1) ارث بری از نگارش جنریک سرویس پایه‌ی موجود و معرفی موجودیت‌های سفارشی سازی شده‌ی خود به آن
2) سفارشی سازی سازنده‌ی این کلاس‌ها با سرویس‌هایی که تهیه کرده‌ایم (بجای سرویس‌های پیش فرض).
3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
5) معرفی اینترفیس و همچنین نمونه‌ی توکار این سرویس به سیستم تزریق وابستگی‌های ASP.NET Core جهت استفاده‌ی از این سرویس جدید سفارشی سازی شده.


سفارشی سازی RoleManager

در اینجا نیز همان 5 مرحله‌ای را که عنوان کردیم باید جهت تشکیل کلاس جدید ApplicationRoleManager پیگیری کنیم.
1) ارث بری از نگارش جنریک سرویس پایه‌ی موجود و معرفی موجودیت‌های سفارشی سازی شده‌ی خود به آن
public class ApplicationRoleManager :
    RoleManager<Role>,
    IApplicationRoleManager
در اینجا نگارشی از RoleManager را انتخاب کرده‌ایم که بتواند Role سفارشی خود را به سیستم معرفی کند.

2) سفارشی سازی سازنده‌ی این کلاس با سرویسی که تهیه کرده‌ایم (بجای سرویس پیش فرض).
public ApplicationRoleManager(
            IApplicationRoleStore store,
            IEnumerable<IRoleValidator<Role>> roleValidators,
            ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors,
            ILogger<ApplicationRoleManager> logger,
            IHttpContextAccessor contextAccessor,
            IUnitOfWork uow) :
            base((RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>)store, roleValidators, keyNormalizer, errors, logger, contextAccessor)
در این سفارشی سازی دو مورد را تغییر داده‌ایم:
الف) ذکر IApplicationRoleStore بجای RoleStore آن
ب) ذکر IUnitOfWork بجای ApplicationDbContext

3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
RoleManager پایه نیازی به پیاده سازی اجباری متدی ندارد.

4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در IApplicationRoleManager‌ مشاهده می‌کنید.

5) معرفی اینترفیس و همچنین نمونه‌ی توکار این سرویس به سیستم تزریق وابستگی‌های ASP.NET Core جهت استفاده‌ی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationRoleManager, ApplicationRoleManager>();
services.AddScoped<RoleManager<Role>, ApplicationRoleManager>();
در کلاس IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار RoleManager را به سرویس جدید و سفارشی سازی شده‌ی ApplicationRoleManager خود هدایت کرده‌ایم.


سفارشی سازی UserStore

در مورد مدیریت کاربران نیز دو سرویس Store و Manager را مشاهده می‌کنید. کار کلاس Store، کپسوله سازی لایه‌ی دسترسی به داده‌ها است تا کتابخانه‌های ثالث، مانند وابستگی Microsoft.AspNetCore.Identity.EntityFrameworkCore بتوانند آن‌را پیاده سازی کنند و کار کلاس Manager، استفاده‌ی از این Store است جهت کار با بانک اطلاعاتی.

5 مرحله‌ای را که باید جهت تشکیل کلاس جدید ApplicationUserStore پیگیری کنیم، به شرح زیر هستند:
1) ارث بری از نگارش جنریک سرویس پایه‌ی موجود و معرفی موجودیت‌های سفارشی سازی شده‌ی خود به آن
public class ApplicationUserStore :
   UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>,
   IApplicationUserStore
از بین نگارش‌های مختلف UserStore، نگارشی را انتخاب کرده‌ایم که بتوان در آن موجودیت‌های سفارشی سازی شده‌ی خود را معرفی کنیم.

2) سفارشی سازی سازنده‌ی این کلاس با سرویسی که تهیه کرده‌ایم (بجای سرویس پیش فرض).
public ApplicationUserStore(
   IUnitOfWork uow,
   IdentityErrorDescriber describer = null)
   : base((ApplicationDbContext)uow, describer)
در این سفارشی سازی یک مورد را تغییر داده‌ایم:
الف) ذکر IUnitOfWork بجای ApplicationDbContext

3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
در اینجا نیز تکمیل 4 متد از کلاس پایه UserStore جنریک انتخاب شده، جهت مشخص سازی نحوه‌ی انتخاب کلیدهای خارجی جداول سفارشی سازی شده‌ی مرتبط با جدول کاربران ضروری است:
        protected override UserClaim CreateUserClaim(User user, Claim claim)
        {
            var userClaim = new UserClaim { UserId = user.Id };
            userClaim.InitializeFromClaim(claim);
            return userClaim;
        }

        protected override UserLogin CreateUserLogin(User user, UserLoginInfo login)
        {
            return new UserLogin
            {
                UserId = user.Id,
                ProviderKey = login.ProviderKey,
                LoginProvider = login.LoginProvider,
                ProviderDisplayName = login.ProviderDisplayName
            };
        }

        protected override UserRole CreateUserRole(User user, Role role)
        {
            return new UserRole
            {
                UserId = user.Id,
                RoleId = role.Id
            };
        }

        protected override UserToken CreateUserToken(User user, string loginProvider, string name, string value)
        {
            return new UserToken
            {
                UserId = user.Id,
                LoginProvider = loginProvider,
                Name = name,
                Value = value
            };
        }
در نگارش 2.0 آن، این new‌ها به صورت خودکار توسط خود فریم ورک صورت خواهد گرفت و سفارشی کردن آن ساده‌تر می‌شود.

4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در IApplicationUserStore‌ مشاهده می‌کنید.

5) معرفی اینترفیس و همچنین نمونه‌ی توکار این سرویس به سیستم تزریق وابستگی‌های ASP.NET Core جهت استفاده‌ی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationUserStore, ApplicationUserStore>();
services.AddScoped<UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>, ApplicationUserStore>();
در کلاس IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار UserStore را به سرویس جدید و سفارشی سازی شده‌ی ApplicationUserStore خود هدایت کرده‌ایم.


سفارشی سازی UserManager

5 مرحله‌ای را که باید جهت تشکیل کلاس جدید ApplicationUserManager پیگیری کنیم، به شرح زیر هستند:
1) ارث بری از نگارش جنریک سرویس پایه‌ی موجود و معرفی موجودیت‌های سفارشی سازی شده‌ی خود به آن
public class ApplicationUserManager :
   UserManager<User>,
   IApplicationUserManager
از بین نگارش‌های مختلف UserManager، نگارشی را انتخاب کرده‌ایم که بتوان در آن موجودیت‌های سفارشی سازی شده‌ی خود را معرفی کنیم.

2) سفارشی سازی سازنده‌ی این کلاس با سرویسی که تهیه کرده‌ایم (بجای سرویس پیش فرض).
public ApplicationUserManager(
            IApplicationUserStore store,
            IOptions<IdentityOptions> optionsAccessor,
            IPasswordHasher<User> passwordHasher,
            IEnumerable<IUserValidator<User>> userValidators,
            IEnumerable<IPasswordValidator<User>> passwordValidators,
            ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors,
            IServiceProvider services,
            ILogger<ApplicationUserManager> logger,
            IHttpContextAccessor contextAccessor,
            IUnitOfWork uow,
            IUsedPasswordsService usedPasswordsService)
            : base((UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>)store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
در این سفارشی سازی چند مورد را تغییر داده‌ایم:
الف) ذکر IUnitOfWork بجای ApplicationDbContext (البته این مورد، یک پارامتر اضافی است که بر اساس نیاز این سرویس سفارشی، اضافه شده‌است)
تمام پارامترهای پس از logger به دلیل نیاز این سرویس اضافه شده‌اند و جزو پارامترهای سازنده‌ی کلاس پایه نیستند.
ب) استفاده‌ی از IApplicationUserStore بجای UserStore پیش‌فرض

3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
UserManager پایه نیازی به پیاده سازی اجباری متدی ندارد.

4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در IApplicationUserManager‌ مشاهده می‌کنید.

5) معرفی اینترفیس و همچنین نمونه‌ی توکار این سرویس به سیستم تزریق وابستگی‌های ASP.NET Core جهت استفاده‌ی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationUserManager, ApplicationUserManager>();
services.AddScoped<UserManager<User>, ApplicationUserManager>();
در کلاس IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار UserManager را به سرویس جدید و سفارشی سازی شده‌ی ApplicationUserManager خود هدایت کرده‌ایم.


سفارشی سازی SignInManager

سرویس پایه SignInManager از سرویس UserManager جهت فراهم آوردن زیرساخت لاگین کاربران استفاده می‌کند.
5 مرحله‌ای را که باید جهت تشکیل کلاس جدید ApplicationSignInManager پیگیری کنیم، به شرح زیر هستند:
1) ارث بری از نگارش جنریک سرویس پایه‌ی موجود و معرفی موجودیت‌های سفارشی سازی شده‌ی خود به آن
public class ApplicationSignInManager :
    SignInManager<User>,
    IApplicationSignInManager
از بین نگارش‌های مختلف SignInManager، نگارشی را انتخاب کرده‌ایم که بتوان در آن موجودیت‌های سفارشی سازی شده‌ی خود را معرفی کنیم.

2) سفارشی سازی سازنده‌ی این کلاس با سرویسی که تهیه کرده‌ایم (بجای سرویس پیش فرض).
public ApplicationSignInManager(
            IApplicationUserManager userManager,
            IHttpContextAccessor contextAccessor,
            IUserClaimsPrincipalFactory<User> claimsFactory,
            IOptions<IdentityOptions> optionsAccessor,
            ILogger<ApplicationSignInManager> logger)
            : base((UserManager<User>)userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
در این سفارشی سازی یک مورد را تغییر داده‌ایم:
الف) استفاده‌ی از IApplicationUserManager بجای UserManager پیش‌فرض

3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
SignInManager پایه نیازی به پیاده سازی اجباری متدی ندارد.

4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در IApplicationSignInManager‌ مشاهده می‌کنید.

5) معرفی اینترفیس و همچنین نمونه‌ی توکار این سرویس به سیستم تزریق وابستگی‌های ASP.NET Core جهت استفاده‌ی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationSignInManager, ApplicationSignInManager>();
services.AddScoped<SignInManager<User>, ApplicationSignInManager>();
در کلاس IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار مدیریت لاگین را به سرویس جدید و سفارشی سازی شده‌ی ApplicationSignInManager خود هدایت کرده‌ایم.


معرفی نهایی سرویس‌های سفارشی سازی شده به ASP.NET Identity Core

تا اینجا سرویس‌های پایه‌ی این فریم ورک را جهت معرفی موجودیت‌های سفارشی سازی شده‌ی خود سفارشی سازی کردیم و همچنین آن‌ها را به سیستم تزریق وابستگی‌های ASP.NET Core نیز معرفی نمودیم. مرحله‌ی آخر، ثبت این سرویس‌ها در رجیستری ASP.NET Core Identity است:
 services.AddIdentity<User, Role>(identityOptions =>
{
}).AddUserStore<ApplicationUserStore>()
  .AddUserManager<ApplicationUserManager>()
  .AddRoleStore<ApplicationRoleStore>()
  .AddRoleManager<ApplicationRoleManager>()
  .AddSignInManager<ApplicationSignInManager>()
  // You **cannot** use .AddEntityFrameworkStores() when you customize everything
  //.AddEntityFrameworkStores<ApplicationDbContext, int>()
  .AddDefaultTokenProviders();
اگر منابع را مطالعه کنید، تمام آن‌ها از AddEntityFrameworkStores و سپس معرفی ApplicationDbContext به آن استفاده می‌کنند. با توجه به اینکه ما همه چیز را در اینجا سفارشی سازی کرده‌ایم، فراخوانی متد افزودن سرویس‌های EF این فریم ورک، تمام آن‌ها را بازنویسی کرده و به حالت اول و پیش فرض آن بر می‌گرداند. بنابراین نباید از آن استفاده شود.
در اینجا متد AddIdentity یک سری  تنظیم‌های پیش فرض‌ها این فریم ورک مانند اندازه‌ی طول کلمه‌ی عبور، نام کوکی و غیره را در اختیار ما قرار می‌دهد به همراه ثبت تعدادی سرویس پایه مانند نرمال ساز نام‌ها و ایمیل‌ها. سپس توسط متدهای AddUserStore، AddUserManager و ... ایی که مشاهده می‌کنید، سبب بازنویسی سرویس‌های پیش فرض این فریم ورک به سرویس‌های سفارشی خود خواهیم شد.

در این مرحله‌است که اگر Migration را اجرا کنید، کار می‌کند و خطای تبدیل نشدن کلاس‌ها به یکدیگر را دریافت نخواهید کرد.


تشکیل مرحله استفاده‌ی از ASP.NET Core Identity و ثبت اولین کاربر در بانک اطلاعاتی به صورت خودکار

روال متداول کار با امکانات کتابخانه‌های نوشته شده‌ی برای ASP.NET Core، ثبت سرویس‌های پایه‌ی آن‌ها توسط متدهای Add است که نمونه‌ی services.AddIdentity فوق دقیقا همین کار را انجام می‌دهد. مرحله‌ی بعد به app.UseIdentity می‌رسیم که کار ثبت میان‌افزارهای این فریم ورک را انجام می‌دهد. متد UseCustomIdentityServices کلاس IdentityServicesRegistry این‌کار را انجام می‌دهد که از آن در کلاس آغازین برنامه استفاده شده‌است.
        public static void UseCustomIdentityServices(this IApplicationBuilder app)
        {
            app.UseIdentity();

            var identityDbInitialize = app.ApplicationServices.GetService<IIdentityDbInitializer>();
            identityDbInitialize.Initialize();
            identityDbInitialize.SeedData();
        }
در اینجا یک مرحله‌ی استفاده‌ی از سرویس IIdentityDbInitializer را نیز مشاهده می‌کنید. کلاس IdentityDbInitializer‌ این اهداف را برآورده می‌کند:
الف) متد Initialize آن، متد context.Database.Migrate را فراخوانی می‌کند. به همین جهت دیگر نیاز به اعمال دستی حاصل Migrations، به بانک اطلاعاتی نخواهد بود. متد Database.Migrate هر مرحله‌ی اعمال نشده‌ای را که باقی مانده باشد، به صورت خودکار اعمال می‌کند.
ب) متد SeedData آن، به نحو صحیحی یک Scope جدید را ایجاد کرده و توسط آن به ApplicationDbContext دسترسی پیدا می‌کند تا نقش Admin و کاربر Admin را به سیستم اضافه کند. همچنین به کاربر Admin، نقش Admin را نیز انتساب می‌دهد. تنظیمات این کاربران را نیز از فایل appsettings.json و مدخل AdminUserSeed آن دریافت می‌کند.


کدهای کامل این سری را در مخزن کد DNT Identity می‌توانید ملاحظه کنید.
مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
در این مطلب مثالی را در مورد نحوه‌ی تنظیمات یک پروژه‌ی خالی ASP.NET Core، جهت استفاده‌ی از یک پروژه‌ی Angular CLI قرار گرفته‌ی در پوشه‌ی آن‌را بررسی خواهیم کرد.

پیشنیازها

 - مطالعه‌ی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامه‌های آن، پیش از مطالعه‌ی این مطلب ضروری است.
 - همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کرده‌اید و با نحوه‌ی برپایی یک برنامه‌ی MVC آن و ارائه‌ی فایل‌های استاتیک توسط یک پروژه‌ی ASP.NET Core آشنایی دارید.


ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017

در ابتدا یک پروژه‌ی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
 - ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینه‌ی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
 - در صفحه‌ی بعدی آن هم گزینه‌ی «empty template» را انتخاب نمائید.


تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI

برای اجرای یک برنامه‌ی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامه‌ی ASP.NET Core کلیک راست کرده و گزینه‌ی Edit آن‌را انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:

الف) درخواست عدم کامپایل فایل‌های TypeScript
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
  </PropertyGroup>
چون نحوه‌ی کامپایل پروژه‌های Angular CLI صرفا مبتنی بر کامپایل مستقیم فایل‌های TypeScript آن نیست و در اینجا از یک گردش کاری توکار مبتنی بر webpack، به صورت خودکار استفاده می‌کند، کامپایل فایل‌های TypeScript توسط ویژوال استودیو، مفید نبوده و صرفا سبب دریافت گزارش‌های خطای بیشماری به همراه کند کردن پروسه‌ی Build آن خواهد شد. بنابراین با افزودن تنظیم TypeScriptCompileBlocked به true، از VS 2017 خواهیم خواست تا در این زمینه دخالت نکند.

ب) مشخص کردن پوشه‌هایی که باید الحاق و یا حذف شوند
  <ItemGroup>
    <Folder Include="Controllers\" />
    <Folder Include="wwwroot\" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="node_modules\**" />
    <Content Remove="node_modules\**" />
    <EmbeddedResource Remove="node_modules\**" />
    <None Remove="node_modules\**" />
  </ItemGroup>
 
  <ItemGroup>
    <Compile Remove="src\**" />
    <Content Remove="src\**" />
    <EmbeddedResource Remove="src\**" />    
  </ItemGroup>
در اینجا پوشه‌های کنترلرها و wwwroot به پروژه الحاق شده‌اند. پوشه‌ی wwwroot جایی است که فایل‌هایی خروجی را Angular CLI ارائه خواهد کرد.
سپس دو پوشه‌ی node_modules و src واقع در ریشه‌ی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کرده‌ایم. پوشه‌ی node_modules وابستگی‌های Angular را به همراه دارد و src همان پوشه‌ی برنامه‌ی Angular ما خواهد بود.

ج) افزودن وابستگی‌های سمت سرور مورد نیاز
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
  </ItemGroup>
 
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
  </ItemGroup>
 
  <ItemGroup>
    <!-- extends watching group to include *.js files -->
    <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" />
  </ItemGroup>
برای اجرای یک برنامه‌ی تک صفحه‌ای وب Angular، صرفا به وابستگی MVC و StaticFiles آن نیاز است.
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شده‌اند که در ادامه‌ی بحث به آن اشاره خواهد شد.


افزودن یک کنترلر Web API جدید

با توجه به اینکه دیگر در اینجا قرار نیست با فایل‌های cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائه‌ی Web API و همچنین فایل‌های Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفته‌است.
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
 
namespace ASPNETCoreIntegrationWithAngularCLI.Controllers
{
  [Route("api/[controller]")]
  public class ValuesController : Controller
  {
    // GET: api/values
    [HttpGet]
    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
    public IEnumerable<string> Get()
    {
      return new string[] { "Hello", "DNT" };
    }
  }
}
در اینجا کدهای یک کنترلر نمونه را جهت بازگشت یک خروجی JSON ساده مشاهده می‌کنید که در ادامه، در برنامه‌ی Angular CLI تهیه شده از آن استفاده خواهیم کرد.


تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular

در ادامه به فایل Startup.cs برنامه‌ی خالی جاری، مراجعه کرده و آن‌را به نحو ذیل تغییر دهید:
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace ASPNETCoreIntegrationWithAngularCLI
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
 
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.Use(async (context, next) => {
                await next();
                if (context.Response.StatusCode == 404 &&
                    !Path.HasExtension(context.Request.Path.Value) &&
                    !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
                {
                    context.Request.Path = "/index.html";
                    await next();
                }
            });
 
            app.UseMvcWithDefaultRoute();
            app.UseDefaultFiles();
            app.UseStaticFiles();
        }
    }
}
در اینجا برای ارائه‌ی کنترلر Web API، نیاز به ثبت سرویس‌های MVC است. همچنین ارائه‌ی فایل‌های پیش فرض و فایل‌های استاتیک (همان پوشه‌ی wwwroot برنامه) نیز فعال شده‌اند.
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامه‌های Angular را مشاهده می‌کنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال می‌شود و چون چنین صفحه‌ای در سمت سرور وجود ندارد، خطای 404 بازگشت داده می‌شود و کار به پردازش برنامه‌ی Angular نخواهد رسید. اینجا است که تنظیم میان‌افزار فوق، کار مدیریت خروجی‌های 404 را بر عهده گرفته و کاربر را به فایل index.html برنامه‌ی تک صفحه‌ای وب، هدایت می‌کند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کننده‌ی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحه‌ی index.html معاف خواهد شد.


ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری

اکنون از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core‌، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
 >ng new ClientApp --routing --skip-install --skip-git --skip-commit
به این ترتیب پوشه‌ی جدید ClientApp، در داخل پوشه‌ی برنامه اضافه خواهد شد که در آن تنظیمات اولیه‌ی مسیریابی Angular نیز انجام شده‌است؛ از دریافت وابستگی‌های npm آن صرفنظر شده و همچنین کار تنظیمات git آن نیز صورت نگرفته‌است (تا از تنظیمات git پروژه‌ی اصلی استفاده شود).
پس از تولید ساختار برنامه‌ی Angular CLI، به پوشه‌ی آن وارد شده و تمام فایل‌های آن را Cut کنید. سپس به پوشه‌ی ریشه‌ی برنامه‌ی ASP.NET Core جاری، وارد شده و این فایل‌ها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژه‌های Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایل‌های تنظیمات آن‌را می‌شناسد و قابلیت پردازش آن‌ها را دارد.
پس از این cut/paste، پوشه‌ی خالی ClientApp را نیز حذف کنید.


تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot

برای اینکه سیستم Build پروژه‌ی Angular CLI جاری، خروجی خود را در پوشه‌ی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آن‌را به wwwroot تنظیم کنیم:
"apps": [
    {
      "root": "src",
      "outDir": "wwwroot",
به این ترتیب پس از هر بار build آن، فایل‌های index.html و تمام فایل‌های js نهایی، در پوشه‌ی wwwroot که در فایل Startup.cs‌، کار عمومی کردن آن انجام شد، تولید می‌شوند.


فراخوانی کنترلر Web API برنامه در برنامه‌ی Angular CLI

در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; 
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private _httpService: Http) { }
 
  apiValues: string[] = [];
 
  ngOnInit() {
    this._httpService.get('/api/values').subscribe(values => {
      this.apiValues = values.json() as string[];
    });
  }
}
در اینجا خروجی JSON کنترلر Web API برنامه دریافت شده و به آرایه‌ی apiValues انتساب داده می‌شود.

سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1>
<ul *ngFor="let value of apiValues">
  <li>{{value}}</li>
</ul>
<router-outlet></router-outlet>
در اینجا یک حلقه ایجاد شده و عناصر آرایه‌ی apiValues به صورت یک لیست نمایش داده می‌شوند.


نصب وابستگی‌های برنامه‌ی Angular CLI

در ابتدای ایجاد پوشه‌ی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمام‌تر، ایجاد شود. اکنون برای نصب وابستگی‌های آن یا می‌توان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینه‌ی restore packages ظاهر می‌شود. و یا می‌توان به روش متداول این نوع پروژه‌ها، از طریق خط فرمان به پوشه‌ی ریشه‌ی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارش‌های این ابزار که بر روی سیستم نصب شده‌است، استفاده می‌کنید.


روش اول اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشده‌است.
بنابراین روش اول اجرای این نوع برنامه‌ها، شامل مراحل ذیل است:
الف) ساخت پروژه‌ی Angular CLI در حالت watch
 > ng build --watch
برای اجرای آن از طریق خط فرمان، به پوشه‌ی ریشه‌ی پروژه وارد شده و دستور فوق را وارد کنید. به این ترتیب کار build پروژه انجام شده و همچنین فایل‌های نهایی آن در پوشه‌ی wwwroot قرار می‌گیرند. به علاوه چون از پرچم watch استفاده شده‌است، با هر تغییری در پوشه‌ی src برنامه، این فایل‌ها به صورت خودکار به روز رسانی می‌شوند. بنابراین این پنجره‌ی خط فرمان را باید باز نگه داشت تا watcher آن بتواند کارش را به صورت مداوم انجام دهد.

ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشه‌ی wwwroot برنامه انجام شده‌است، می‌توان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:


یک نکته: می‌توان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build">
    <Exec Command="ng build" />
</Target>
به این ترتیب با هربار Build پروژه در VS 2017، کار تولید مجدد محتوای پوشه‌ی wwwroot نیز انجام خواهد شد.
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده می‌شود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.


روش دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامه‌های NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آن‌ها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگی‌های برنامه اضافه کردیم.

الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشه‌ی پروژه‌، صادر کنید:
> dotnet watch run
این دستور پروژه‌ی ASP.NET Core را کامپایل کرده و بر روی پورت 5000 ارائه می‌دهد:
>dotnet watch run
[90mwatch : [39mStarted
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
به علاوه پارامتر watch آن سبب خواهد شد تا هر تغییری که در کدهای پروژه‌ی ASP.NET Core صورت گیرند، بلافاصله کامپایل شده و قابل استفاده شوند.

ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شده‌است، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو ساده‌تری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامه‌ی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده می‌شود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامه‌ی Angular ما، یک چنین درخواست‌هایی را به سمت سرور، جهت دریافت اطلاعات ارسال می‌کند: localhost:4200/api
برای رفع این مشکل می‌توان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{
  "/api": {
    "target": "http://localhost:5000",
    "secure": false
  }
}
سپس دستور ng server صادر شده، اندکی متفاوت خواهد شد:
 >ng serve --proxy-config proxy.config.json -o
در اینجا به ng serve اعلام کرده‌ایم که تمامی درخواست‌های ارسالی به مسیر api/  (و یا همان localhost:4200/api جاری) را به سرور وب ASP.NET Core، بر روی پورت 5000 ارسال کن و نتیجه را در همینجا بازگشت بده. به این ترتیب مشکل عدم دسترسی به سرور وب، جهت تامین اطلاعات برطرف خواهد شد.
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.

نکته 1: بدیهی است می‌توان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژه‌ی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمی‌شود که آن‌را نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمه‌ی refresh پس از هر بار پایان کار ng build).
نکته 2: می‌توان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی می‌خواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه می‌خواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شده‌است. عدد این پورت را می‌توان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شده‌ی در این مطلب، می‌توان از آن‌ها استفاده کرد:
 - فایل restore.bat کار بازیابی و نصب وابستگی‌های پروژه‌ی دات نتی و همچنین Angular CLI را انجام می‌دهد.
 - دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائه‌ی نهایی.
 - دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کننده‌ی قسمت‌های «الف» و «ب» روش دوم هستند.
مطالب
ذخیره‌ی سوابق کامل تغییرات یک رکورد در یک فیلد توسط Entity framework Core
در این مقاله، نوشته‌ی ایمان محمدی، ذخیره‌ی اطلاعات نظارتی هر Entity توسط دو فیلد CreatedSources و ModifiedSources به صورت JSON انجام می‌شود که در هر کدام از این فیلدها، اطلاعات مختلفی مانند ip کاربر، شناسه دستگاه، HostName، ClientName و یک سری اطلاعات دیگر ذخیره می‌شوند. بیایید به این اطلاعات متادیتا بگوییم. در این حالت اگر رکورد، چندین بار تغییر کند، متادیتای آخرین تغییرات در فیلد ModifiedSources ذخیره می‌شود. حالا اگر ما بخواهیم اطلاعات متادیتای همه‌ی تغییرات را داشته باشیم چه؟ اگر بخواهیم علاوه بر اطلاعات بالا، اینکه چه کسی و در چه زمانی این تغییرات را انجام داده است، نیز داشته باشیم چطور؟ اگر بخواهیم حتی اطلاعات متادیتای حذف یک رکورد را داشته باشیم چطور (در حالت soft-delete که رکورد واقعا پاک نمی‌شود)؟ سوال جالبتر اینکه اگر بخواهیم تمام تاریخچه‌ی مقادیر مختلف یک رکورد را از ابتدای ایجاد شدن داشته باشیم چطور؟ در این مقاله قصد داریم به همه‌ی این موارد اضافی برسیم؛ آن هم فقط با یک ستون در Entityهایمان، به اسم Audit!

ابتدا کلاس پایه موجودیت‌هایمان را تعریف می‌کنیم؛ تا بر روی Entityهایمان بتوانیم فیلد نظارتی Audit را اعمال کنیم:
public class BaseEntity : IBaseEntity
{
   [JsonIgnore]
   int Id { get; set; } 

   [JsonIgnore] 
    string? Audit { get; set; }
}
ویژگی [JsonIgnore]  به این منظور استفاده شده است تا از serialize کردن این فیلدها هنگام ایجاد Audit، جلوگیری شود؛ تا در نهایت حجم جیسن Audit کاهش یابد. با مطالعه‌ی ادامه‌ی مقاله، متوجه این قضیه خواهید شد.

دقیقا مانند مقاله‌ی اشاره شده (که خواندن آن توصیه می‌شود)، کلاس AuditSourceValues را ایجاد می‌کنیم:
public class AuditSourceValues
{
    [JsonProperty("hn")]
    public string? HostName { get; set; }

    [JsonProperty("mn")]
    public string? MachineName { get; set; }

    [JsonProperty("rip")]
    public string? RemoteIpAddress { get; set; }

    [JsonProperty("lip")]
    public string? LocalIpAddress { get; set; }

    [JsonProperty("ua")]
    public string? UserAgent { get; set; }

    [JsonProperty("an")]
    public string? ApplicationName { get; set; }

    [JsonProperty("av")]
    public string? ApplicationVersion { get; set; }

    [JsonProperty("cn")]
    public string? ClientName { get; set; }

    [JsonProperty("cv")]
    public string? ClientVersion { get; set; }

    [JsonProperty("o")]
    public string? Other { get; set; }
}
با تعریف کردن نام برای فیلد‌های JSON و نادیده گرفتن مقادیر نال، سعی کردیم حجم خروجی JSON پایین باشد.

اکنون کلاس EntityAudit را ایجاد می‌کنیم که شامل تمامی اطلاعات مورد نیاز ما برای ثبت تاریخچه‌ی کامل هر موجودیت است:
public class EntityAudit<TEntity>
{
    [JsonProperty("type")]
    [JsonConverter(typeof(StringEnumConverter))]
    public EntityEventType EventType { get; set; }

    [JsonProperty("user", NullValueHandling = NullValueHandling.Include)]
    public int? ActorUserId { get; set; }

    [JsonProperty("at")]
    public DateTime ActDateTime { get; set; }

    [JsonProperty("sources")]
    public AuditSourceValues? AuditSourceValues { get; set; }

    [JsonProperty("newValues", NullValueHandling = NullValueHandling.Include)]
    public TEntity NewEntity { get; set; } = default!;

    public string? SerializeJson()
    {
        return JsonSerializer.Serialize(this, 
            options: new JsonSerializerOptions { WriteIndented = false, IgnoreNullValues = true }); 
    }
}

دقت کنید که این کلاس به صورت جنریک تعریف شده است تا اگر بعدا بخواهیم آن را Deserialize کنیم و مثلا از آن API بسازیم، یا استفاده‌ی خاصی را از آن داشته باشیم، به‌راحتی به Entity مد نظر تبدیل شود. در این مقاله فقط به ذخیره‌ی آن پرداخته می‌شود و استفاده از این فیلد که به راحتی و با کمک DbFunctionها در Entity Framework قابل انجام است به خواننده واگذار می‌شود. 

همچنین اینام EntityEventType که تعریف آن در زیر می‌آید دارای ویژگی [JsonConverter(typeof(StringEnumConverter))]  می‌باشد تا مقدار رشته‌ای آن را بجای مقدار عددی، در خروجی جیسن داشته باشیم. این اینام، شامل  تمامی عملیاتی است که بر روی یک رکورد قابل انجام است و به این صورت تعریف می‌شود:
public enum EntityEventType
{
    Create = 0,
    Update = 1,
    Delete = 2
}

تامین اطلاعات کلاس AuditSourceValues به همان صورت است که در مقاله‌ی اشاره شده آمده‌است؛ ابتدا تعریف اینترفیس IAuditSourcesProvider و سپس ایجاد کلاس AuditSourcesProvider:
public interface IAuditSourcesProvider
{
    AuditSourceValues GetAuditSourceValues();
}
public class AuditSourcesProvider : IAuditSourcesProvider
{
    protected readonly IHttpContextAccessor HttpContextAccessor;

    public AuditSourcesProvider(IHttpContextAccessor httpContextAccessor)
    {
        HttpContextAccessor = httpContextAccessor;
    }

    public virtual AuditSourceValues GetAuditSourceValues()
    {
        var httpContext = HttpContextAccessor.HttpContext;

        return new AuditSourceValues
        {
            HostName = GetHostName(httpContext),
            MachineName = GetComputerName(httpContext),
            LocalIpAddress = GetLocalIpAddress(httpContext),
            RemoteIpAddress = GetRemoteIpAddress(httpContext),
            UserAgent = GetUserAgent(httpContext),
            ApplicationName = GetApplicationName(httpContext),
            ClientName = GetClientName(httpContext),
            ClientVersion = GetClientVersion(httpContext),
            ApplicationVersion = GetApplicationVersion(httpContext),
            Other = GetOther(httpContext)
        };
    }

    protected virtual string? GetUserAgent(HttpContext httpContext)
    {
        return httpContext.Request?.Headers["User-Agent"].ToString();
    }

    protected virtual string? GetRemoteIpAddress(HttpContext httpContext)
    {
        return httpContext.Connection?.RemoteIpAddress?.ToString();
    }

    protected virtual string? GetLocalIpAddress(HttpContext httpContext)
    {
        return httpContext.Connection?.LocalIpAddress?.ToString();
    }

    protected virtual string GetHostName(HttpContext httpContext)
    {
        return httpContext.Request.Host.ToString();
    }

    protected virtual string GetComputerName(HttpContext httpContext)
    {
        return Environment.MachineName;
    }
    protected virtual string? GetApplicationName(HttpContext httpContext)
    {
        return Assembly.GetEntryAssembly()?.GetName().Name;
    }

    protected virtual string? GetApplicationVersion(HttpContext httpContext)
    {
        return Assembly.GetEntryAssembly()?.GetName().Version.ToString();
    }

    protected virtual string? GetClientVersion(HttpContext httpContext)
    {
        return httpContext.Request?.Headers["client-version"];
    }
    protected virtual string? GetClientName(HttpContext httpContext)
    {
        return httpContext.Request?.Headers["client-name"];
    }

    protected virtual string? GetOther(HttpContext httpContext)
    {
        return null;
    }
}

حالا برای تامین اطلاعات کلاس EntityAudit کار مشابهی می‌کنیم. ابتدا اینترفیس IEntityAuditProvider را به صورت زیر تعریف می‌کنیم: 
public interface IEntityAuditProvider
{
    string? GetAuditValues(EntityEventType eventType, object? entity, string? previousJsonAudit = null);
}

  و سپس کلاس EntityAuditProvider را ایجاد می‌کنیم:
public class EntityAuditProvider : IEntityAuditProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IAuditSourcesProvider _auditSourcesProvider;

    #region Constructor Injections

    public EntityAuditProvider(IHttpContextAccessor httpContextAccessor, IAuditSourcesProvider auditSourcesProvider)
    {
        _httpContextAccessor = httpContextAccessor;
        _auditSourcesProvider = auditSourcesProvider;
    }

    #endregion

    public virtual string? GetAuditValues(EntityEventType eventType, object? newEntity, string? previousJsonAudit = null)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        int? userId;

        var user = httpContext.User;

        if (!user.Identity.IsAuthenticated)
            userId = null;
        else
            userId = user.Claims.Where(x => x.Type == "UserID").Select(x => x.Value).First().ToInt();

        var auditSourceValues = _auditSourcesProvider.GetAuditSourceValues();

        var auditJArray = new JArray();

        // Update & Delete
        if (eventType == EntityEventType.Update || eventType == EntityEventType.Delete)
        {
            auditJArray = JArray.Parse(previousJsonAudit!);
        }

        // Delete => No NewValues
        if (eventType == EntityEventType.Delete)
        {
            newEntity = null;
        }

        JObject newAuditJObject = JObject.FromObject(new EntityAudit<object?>
        {
            EventType = eventType,
            ActorUserId = userId,
            ActDateTime = DateTime.Now,
            AuditSourceValues = auditSourceValues,
            NewEntity = newEntity
        }, new JsonSerializer
        {
            NullValueHandling = NullValueHandling.Ignore,
            Formatting = Formatting.None
        });

        auditJArray.Add(newAuditJObject);

        return auditJArray.SerializeToJson(true);
    }
}
در این کلاس برای اینکه به جیسن قبلی Audit که تاریخچه‌ی قبلی رکورد می‌باشد یک آیتم را اضافه کنیم، از JArray و JObject پکیج Newtonsoft استفاده کرد‌ه‌ایم.

حالا همه چیز آماده است. مانند مقاله‌ی اشاره شده، از مفهوم Interceptor استفاده می‌کنیم. کلاس AuditSaveChangesInterceptor را که از کلاس SaveChangesInterceptor مشتق می‌شود، به صورت زیر ایجاد می‌کنیم: 
public class AuditSaveChangesInterceptor : SaveChangesInterceptor
{
    private readonly IEntityAuditProvider _entityAuditProvider;

    #region Constructor Injections

    public AuditSaveChangesInterceptor(IEntityAuditProvider entityAuditProvider)
    {
        _entityAuditProvider = entityAuditProvider;
    }

    #endregion

    public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
    {
        ApplyAudits(eventData.Context.ChangeTracker);
        return base.SavingChanges(eventData, result);
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result,
        CancellationToken cancellationToken = new CancellationToken())
    {
        ApplyAudits(eventData.Context.ChangeTracker);
        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }

    private void ApplyAudits(ChangeTracker changeTracker)
    {
        ApplyCreateAudits(changeTracker);
        ApplyUpdateAudits(changeTracker);
        ApplyDeleteAudits(changeTracker);
    }

    private void ApplyCreateAudits(ChangeTracker changeTracker)
    {
        var addedEntries = changeTracker.Entries()
            .Where(x => x.State == EntityState.Added);

        foreach (var addedEntry in addedEntries)
        {
            if (addedEntry.Entity is IBaseEntity entity)
            {              
                entity.Audit = _entityAuditProvider.GetAuditValues(EntityEventType.Create, entity);
            }
        }
    }

    private void ApplyUpdateAudits(ChangeTracker changeTracker)
    {
        var modifiedEntries = changeTracker.Entries()
            .Where(x => x.State == EntityState.Modified);

        foreach (var modifiedEntry in modifiedEntries)
        {
            if (modifiedEntry.Entity is IBaseEntity entity)
            {
                var eventType = entity.IsArchived ? EntityEventType.Delete : EntityEventType.Update; // Maybe Soft Delete
                entity.Audit = _entityAuditProvider.GetAuditValues(eventType, entity, entity.Audit);
            }
        }
    }

    private void ApplyDeleteAudits(ChangeTracker changeTracker)
    {
        var deletedEntries = changeTracker.Entries()
            .Where(x => x.State == EntityState.Deleted);

        foreach (var modifiedEntry in deletedEntries)
        {
            if (modifiedEntry.Entity is IBaseEntity entity)
            {
                entity.Audit = _entityAuditProvider.GetAuditValues(EntityEventType.Delete, entity, entity.Audit);
            }
        }
    }

}


و سپس آن را به سیستم معرفی می‌کنیم:

services.AddDbContext<ATADbContext>((serviceProvider, options) =>
{
    options
        .UseSqlServer(...)

    // Interceptors
    var entityAuditProvider = serviceProvider.GetRequiredService<IEntityAuditProvider>();
    options.AddInterceptors(new AuditSaveChangesInterceptor(entityAuditProvider));

});

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

نمونه‌ی نتیجه‌ای را که از این روش بدست می‌آید، در این تصویر می‌بینید. اگر بخواهید به صورت نرم‌افزاری یا کدی از این دیتا استفاده کنید، باید آن را Deserialize کنید که همانطور که گفته شد با امکاناتی که SQL Server برای خواندن فیلدهای JSON دارد و معرفی آن به EF، قابل انجام است. در غیر اینصورت استفاده از این دیتا به صورت چشمی یا استفاده از Json Formatterها به‌راحتی امکان پذیر است. 

 
نمونه‌ی کامل فیلد Audit که در JsonFormatter قرار داده شده است، بعد از ایجاد شدن و یکبار آپدیت و سپس حذف نرم رکورد:
[
   {
      "type":"Create",
      "user":1,
      "at":"2020-11-24T23:05:54.2692711+03:30",
      "sources":{
         "hn":"localhost:44398",
         "mn":"DESKTOP-N1GAV2U",
         "rip":"::1",
         "lip":"::1",
         "ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
         "an":"Server.Api",
         "av":"1.0.0.0"
      },
      "newValues":{               
         "Name":"Farshad"
      }
   },
   {
      "type":"Update",
      "user":1,
      "at":"2020-11-24T23:06:20.0838188+03:30",
      "sources":{
         "hn":"localhost:44398",
         "mn":"DESKTOP-N1GAV2U",
         "rip":"::1",
         "lip":"::1",
         "ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
         "an":"Server.Api",
         "av":"1.0.0.0"
      },
      "newValues":{                 
         "Name":"Edited Farshad"
      }
   },
   {
      "type":"Delete",
      "user":null,
      "at":"2020-11-24T23:06:28.601837+03:30",
      "sources":{
         "hn":"localhost:44398",
         "mn":"DESKTOP-N1GAV2U",
         "rip":"::1",
         "lip":"::1",
         "ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
         "an":"Server.Api",
         "av":"1.0.0.0"
      },
      "newValues":null
   }
]

یک روش مرسوم داشتن تاریخچه‌ی تغییرات رکورد که با جستجو در اینترنت نیز می‌توان به آن رسید، داشتن یک جدول جداگانه به اسم Audit است که با هر بار تغییر هر Entity، یک رکورد در آن ایجاد می‌شود. ساختار آن مانند تصاویر زیر است:


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

مطالب دوره‌ها
ایندکس‌ها در RavenDB
RavenDB یک Document database است و در این نوع بانک‌های اطلاعاتی، اسکیما و ساختار مشخصی وجود ندارد. شاید اینطور به نظر برسد، زمانیکه با دات نت کلاینت RavenDB کار می‌کنیم، یک سری کلاس مشخص دات نتی داشته و این‌ها ساختار اصلی کار را مشخص می‌کنند. اما در عمل RavenDB چیزی از این کلاس‌ها و خواص نمی‌داند و این کلاس‌های دات نتی صرفا کمکی هستند جهت سهولت اعمال Serialization و Deserialization اطلاعات. زمانیکه اطلاعاتی را در RavenDB ذخیره می‌کنیم، هیچ نوع قیدی در مورد ساختار نوع سندی که در حال ذخیره است، اعمال نمی‌شود.
خوب؛ اکنون این سؤال مطرح می‌شود که RavenDB چگونه اطلاعاتی را در این اسناد بدون اسکیما جستجو می‌کند؟ اینجا است که مفهوم و کاربرد ایندکس‌ها مطرح می‌شوند. ما در قسمت قبل که کوئری نویسی مقدماتی را بررسی کردیم، عملا ایندکس خاصی را به صورت دستی جهت انجام جستجو‌ها ایجاد نکردیم؛ از این جهت که خود RavenDB به کمک امکانات dynamic indexing آن، پیشتر اینکار را انجام داده است. برای نمونه به سطر ارسال کوئری به سرور، که در قسمت قبل ارائه شد، دقت کنید. در اینجا ارسال کوئری به indexes/dynamic کاملا مشخص است:
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128

Dynamic Indexes یا ایندکس‌های پویا

ایندکس‌های پویا زمانی ایجاد خواهند شد که ایندکس صریحی توسط برنامه نویس تعریف نگردد. برای مثال زمانیکه یک کوئری LINQ را صادر می‌کنیم، RavenDB بر این اساس و برای مثال فیلدهای قسمت Where آن، ایندکس پویایی را تولید خواهد کرد. ایجاد ایندکس‌ها در RavenDB از اصل عاقبت یک دست شدن پیروی می‌کنند. یعنی مدتی طول خواهد کشید تا کل اطلاعات بر اساس ایندکس جدیدی که در حال تهیه است، ایندکس شوند. بنابراین تولید ایندکس‌های پویا در زمان اولین بار اجرای کوئری، کوئری اول را اندکی کند جلوه خواهند داد؛ اما کوئری‌های بعدی که بر روی یک ایندکس آماده اجرا می‌شوند، بسیار سریع خواهند بود.


Static indexes یا ایندکس‌های ایستا

ایندکس‌های پویا به دلیل وقفه ابتدایی که برای تولید آن‌ها وجود خواهد داشت، شاید آنچنان مطلوب به نظر نرسند. اینجا است که مفهوم ایندکس‌های ایستا مطرح می‌شوند. در این حالت ما به RavenDB خواهیم گفت که چه چیزی را ایندکس کند. برای تولید ایندکس‌های ایستا، از مفاهیم Map/Reduce که در پیشنیازهای دوره جاری در مورد آن بحث شد، استفاده می‌گردد. خوشبختانه تهیه Map/Reduceها در RavenDB پیچیده نبوده و کل عملیات آن توسط کوئری‌های LINQ قابل پیاده سازی است.
تهیه ایندکس‌های پویا نیز در تردهای پس‌زمینه انجام می‌شوند. از آنجائیکه RavenDB برای اعمال Read، بهینه سازی شده است، با ارسال یک کوئری به آن، این بانک اطلاعاتی، کلیه اطلاعات آماده را در اختیار شما قرار خواهد داد؛ صرفنظر از اینکه کار تهیه ایندکس تمام شده است یا خیر.


چگونه یک ایندکس ایستا را ایجاد کنیم؟

اگر به کنسول مدیریتی سیلورلایت RavenDB مراجعه کنیم، حاصل کوئری‌های LINQ قسمت قبل را در برگه‌ی ایندکس‌های آن می‌توان مشاهده کرد:


در اینجا بر روی دکمه Edit کلیک نمائید، تا با نحوه تهیه این ایندکس پویا آشنا شویم:


این ایندکس، یک نام داشته به همراه قسمت Map از پروسه Map/Reduce که توسط یک کوئری LINQ تهیه شده است. کاری که در اینجا انجام شده، ایندکس کردن کلیه سؤالات، بر اساس خاصیت عنوان آن‌ها است.
اکنون اگر بخواهیم همین کار را با کدنویسی انجام دهیم، به صورت زیر می‌توان عمل کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;
using Raven.Client.Linq;
using Raven.Client.Indexes;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                store.DatabaseCommands.PutIndex(
                name: "Questions/ByTitle",
                indexDef: new IndexDefinitionBuilder<Question>
                {
                    Map = questions => questions.Select(question => new { Title = question.Title } )
                });
            }
        }
    }
}
کار با شیء DatabaseCommands یک DocumentStore شروع می‌شود. سپس توسط متد PutIndex آن می‌توان یک ایندکس جدید را تعریف کرد. این متد نیاز به نام ایندکس ایجاد شده و همچنین حداقل، متد Map آن‌را دارد. برای این منظور از شیء IndexDefinitionBuilder برای تعریف نحوه جمع آوری اطلاعات ایندکس کمک خواهیم گرفت. در اینجا خاصیت Map آن‌را باید توسط یک کوئری LINQ که فیلدهای مدنظر را بازگشت می‌دهد، مقدار دهی کنیم.
برنامه را اجرا کرده و سپس به کنسول مدیریتی تحت وب RavenDB، قسمت ایندکس‌های آن مراجعه کنید. در اینجا می‌توان ایندکس جدید ایجاد شده را مشاهده کرد:


هرچند همین اعمال را در کنسول مدیریتی نیز می‌توان انجام داد، اما مزیت آن در سمت کدها، دسترسی به intellisense و نوشتن کوئری‌های strongly typed است.

روش استفاده از store.DatabaseCommands.PutIndex اولین روش تولید Index در RavenDB با کدنویسی است. روش دوم، بر اساس ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود و مناسب است برای حالتیکه نمی‌خواهید کدهای تولید ایندکس، با کدهای سایر قسمت‌های برنامه مخلوط شوند:
    public class QuestionsByTitle : AbstractIndexCreationTask<Question>
    {
        public QuestionsByTitle()
        {
            Map = questions => questions.Select(question => new { Title = question.Title });
        }
    }
در اینجا با ایجاد یک کلاس جدید و ارث بری از کلاس AbstractIndexCreationTask کار شروع می‌شود. سپس در سازنده این کلاس، خاصیت Map را مقدار دهی می‌کنیم. مقدار آن نیز یک کوئری LINQ است که کار Select فیلدهای شرکت دهنده در کار تهیه ایندکس را انجام می‌دهد.
اکنون برای معرفی آن به برنامه باید از متد IndexCreation.CreateIndexes استفاده کرد. این متد، نیاز به دریافت اسمبلی محل تعریف کلاس‌های تولید ایندکس را دارد. به این ترتیب تمام کلاس‌های مشتق شده از AbstractIndexCreationTask را یافته و ایندکس‌های متناظری را تولید می‌کند.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsByTitle).Assembly, store);
            }
این روش، قابلیت نگهداری و نظم بهتری دارد.


استفاده از ایندکس‌های ایستای ایجاد شده

تا اینجا موفق شدیم ایندکس‌های ایستای خود را با کد نویسی ایجاد کنیم. در ادامه قصد داریم از این ایندکس‌ها در کوئری‌های خود استفاده نمائیم.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>(indexName: "QuestionsByTitle")
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
استفاده از ایندکس تعریف شده نیز بسیار ساده می‌باشد. تنها کافی است نام آن‌را به متد Query ارسال نمائیم. اینبار اگر به خروجی کنسول سرور RavenDB دقت کنیم، از ایندکس indexes/QuestionsByTitle بجای ایندکس‌های پویا استفاده کرده است:
Request # 147: GET     -    58 ms - <system>   - 200 - /indexes/QuestionsByTitle?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 7 ms
        Index: QuestionsByTitle
        Results: 2 returned out of 2 total.
روش مشخص سازی نام ایندکس با استفاده از رشته‌ها، با هر دو روش store.DatabaseCommands.PutIndex و استفاده از AbstractIndexCreationTask سازگار است. اما اگر ایندکس‌های خود را با ارث بری از AbstractIndexCreationTask ایجاد کرده‌ایم، می‌توان نام کلاس مشتق شده را به صورت یک آرگومان جنریک دوم به متد Query به شکل زیر ارسال کرد تا از مزایای تعریف strongly typed آن نیز بهره‌مند شویم:
                    var questions = session.Query<Question, QuestionsByTitle>()
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);

ایجاد ایندکس‌های پیشرفته با پیاده سازی Map/Reduce

حالتی را در نظر بگیرید که در آن قصد داریم تعداد عنوان‌های سؤالات مانند هم را بیابیم (یا تعداد مطالب گروه‌های مختلف یک وبلاگ را محاسبه کنیم). برای انجام اینکار با سرعت بسیار بالا، می‌توانیم از ایندکس‌هایی با قابلیت محاسباتی در RavenDB استفاده کنیم. کار با ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود. آرگومان جنریک اول آن، نام کلاسی است که در تهیه ایندکس شرکت خواهد داشت و آرگومان دوم (و اختیاری) ذکر شده، نتیجه عملیات Reduce است:
    public class QuestionsCountByTitleReduceResult
    {
        public string Title { set; get; }
        public int Count { set; get; }
    }

    public class QuestionsCountByTitle : AbstractIndexCreationTask<Question, QuestionsCountByTitleReduceResult>
    {
        public QuestionsCountByTitle()
        {
            Map = questions => questions.Select(question =>
                                                    new
                                                    {
                                                        Title = question.Title,
                                                        Count = 1
                                                    });
            Reduce = results => results.GroupBy(x => x.Title)
                                       .Select(g =>
                                                   new
                                                   {
                                                       Title = g.Key,
                                                       Count = g.Sum(x => x.Count)
                                                   });
        }
    }
در اینجا یک ایندکس پیشرفته را تعریف کرده‌ایم که در آن در قسمت Map، کار ایندکس کردن تک تک عنوان‌ها انجام خواهد شد. به همین جهت مقدار Count در این حالت، عدد یک است. در قسمت Reduce، بر روی نتیجه قسمت Map کوئری LINQ دیگری نوشته شده و تعداد عنوان‌های همانند، با گروه بندی اطلاعات، شمارش گردیده است.
اکنون برای استفاده از این ایندکس، ابتدا توسط متد IndexCreation.CreateIndexes، کار معرفی آن به RavenDB صورت گرفته و سپس متد Query سشن باز شده، دو آرگومان جنریگ را خواهد پذیرفت. اولین آرگومان، همان نتیجه Map/Reduce است و دومین آرگومان نام کلاس ایندکس جدید تعریف شده می‌باشد:
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsCountByTitle).Assembly, store);

                using (var session = store.OpenSession())
                {
                    var result = session.Query<QuestionsCountByTitleReduceResult, QuestionsCountByTitle>()
                                         .FirstOrDefault(x => x.Title == "Raven") ?? new QuestionsCountByTitleReduceResult();
                    Console.WriteLine(result.Count);
                }
            }
در کوئری فوق چون عملیات بر روی نتیجه نهایی باید صورت گیرد از FirstOrDefault استفاده شده است. این کوئری در حقیقت بر روی قسمت Reduce پیشتر محاسبه شده، اجرا می‌شود.
بازخوردهای دوره
به روز رسانی غیرهمزمان قسمتی از صفحه به کمک jQuery در ASP.NET MVC
با سلام
اگر یک کلاس product داشته باشیم که تعداد زیاد قسمت مانند نظرات و تصاویر و ... داشته باشد.اگر بخواهیم از روش بالا استفاده کنیم اطلاعات را از دیتا بیس  (code first)دریافت کنیم برای هر قسمت یک درخواست باید به دیتا بیس ارسال شود در حالی که می‌توان همه را با یک کوئری و با یک بار رفت آمد به دیتابیس دریافت کرد. 
در این حالت پرفورمنس و کارایی کدوم حالت بیشتره؟
ممنون
نظرات مطالب
ASP.NET MVC #3
من خوب متوجه نشدم که این قسمت دوم باید در پوشه کنترلر باشه یا ویوو؟

در پوشه Views، ...همچنین مطابق قرارداد دیگری، اگر نام کنترلر ما مثلا ProductController باشد (با توجه به اینکه نام کلاس آن هم مطابق قرارداد، مختوم به کلمه Controller است)، فایل‌های Viewهای مرتبط با آن در پوشه Views/Product قرار خواهند گرفت.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 15 - نوشتن آزمون‌های واحد
قسمت using‌ها را می‌توانیم درون یک متد جنریک به اینصورت نیز کپسوله کنیم:
private void ScopeContext<T>(Action<IUnitOfWork, T> callback)
        {
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
                    callback(context, serviceScope.ServiceProvider.GetRequiredService<T>());
                }
            }
        }

در اینحالت خواهیم داشت:
ScopeContext<IBlogService>(async (context, scope) =>
{
                Assert.AreEqual(3, context.Set<Blog>().Count());
                Assert.AreEqual("http://sample.com/cats", context.Set<Blog>().First().Url);
});