مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 20 - بررسی تغییرات فیلترها
پیشنیازها

- فیلترها در MVC
- ASP.NET MVC #15


فیلترها در ASP.NET MVC، امکان اجرای کدهایی را پیش و یا پس از مرحله‌ی خاصی از طول اجرای pipeline آن فراهم می‌کنند. کلیات فیلترها در ASP.NET Core با نگارش‌های قبلی ASP.NET MVC (پیشنیازهای فوق) تفاوت چندانی را ندارد و بیشتر تغییراتی مانند نحوه‌ی معرفی سراسری آن‌ها، اکشن فیلترهای Async و یا تزریق وابستگی‌ها در آن‌ها، جدید هستند.


امکان تعریف فیلترهای Async در ASP.NET Core

حالت کلی تعریف یک فیلتر در ASP.NET MVC که در ASP.NET Core نیز همچنان معتبر است، پیاده سازی اینترفیس کلی IActionFilter می‌باشد که توسط آن می‌توان به مراحل پیش و پس از اجرای قطعه‌ای از کدهای برنامه دسترسی پیدا کرد:
namespace FiltersSample.Filters
{
    public class SampleActionFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            // انجام کاری پیش از اجرای اکشن متد
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // انجام کاری پس از اجرای اکشن متد
        }
    }
}
در اینجا اینترفیس IAsyncActionFilter نیز معرفی شده‌است که توسط آن می‌توان فراخوانی‌های غیرهمزمان و async را نیز مدیریت کرد:
namespace FiltersSample.Filters
{
    public class SampleAsyncActionFilter : IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(
            ActionExecutingContext context,
            ActionExecutionDelegate next)
        {
            // انجام کاری پیش از اجرای اکشن متد
            await next();
            // انجام کاری پس از اجرای اکشن متد
        }
    }
}
به کامنت‌های نوشته شده‌ی در بدنه‌ی متد OnActionExecutionAsync دقت کنید. در اینجا کدهای پیش از await next معادل OnActionExecuting و کدهای پس از await next معادل OnActionExecuted حالت همزمان و یا همان حالت متداول هستند. بنابراین جایی که اکشن متد اجرا می‌شود، همان await next است.

یک نکته: توصیه شده‌است که تنها یکی از حالت‌های همزمان و یا غیرهمزمان را پیاده سازی کنید و نه هر دوی آن‌ها را. اگر هر دوی این‌ها را در طی یک کلاس پیاده سازی کنید (تک کلاسی که هر دوی اینترفیس‌های IActionFilter و IAsyncActionFilter را با هم پیاده سازی می‌کند)، تنها نگارش Async آن توسط ASP.NET Core فراخوانی و استفاده خواهد شد. همچنین مهم نیست که اکشن متد شما Async هست یا خیر؛ برای هر دو حالت می‌توان از فیلترهای async نیز استفاده کرد.


ساده سازی تعریف فیلترها

اگر مدتی با ASP.NET MVC کار کرده باشید، می‌دانید که عموما کسی از این اینترفیس‌های کلی برای پیاده سازی فیلترها استفاده نمی‌کند. روش کار با ارث بری از یکی از فیلترهای از پیش تعریف شده‌ی ASP.NET MVC صورت می‌گیرد؛ از این جهت که این فیلترها که در اصل همین اینترفیس‌ها را پیاده سازی کرده‌اند، یک سری جزئیات توکار protected را نیز به همراه دارند که با ارث بری از آن‌ها می‌توان به امکانات بیشتری دسترسی پیدا کرد و کدهای ساده‌تر و کم حجم‌تری را تولید نمود:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute

برای مثال در اینجا فیلتری را مشاهده می‌کنید که با ارث بری از فیلتر توکار ResultFilterAttribute، سعی در تغییر Response برنامه و افزودن هدری به آن کرده‌است:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
    public class AddHeaderAttribute : ResultFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;
        public AddHeaderAttribute(string name, string value)
        {
            _name = name;
            _value = value;
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                _name, new string[] { _value });
            base.OnResultExecuting(context);
        }
    }
}
و برای استفاده‌ی از این فیلتر جدید خواهیم داشت:
[AddHeader("Author", "DNT")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("با فایرباگ هدر خروجی را بررسی کنید");
    }
}


نحوه‌ی تعریف میدان دید فیلترها

نحوه‌ی دید فیلترها در اینجا نیز همانند سابق، سه حالت را می‌تواند داشته باشد:
الف) اعمال شده‌ی به یک اکشن متد.
ب) اعمال شده‌ی به یک کنترلر که به تمام اکشن متدهای آن کنترلر اعمال خواهد شد.
ج) حالت تعریف سراسری و این مورد محل تعریف آن به کلاس آغازین برنامه منتقل شده‌است:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(SampleActionFilter)); // by type
        options.Filters.Add(new SampleGlobalActionFilter()); // an instance
    });
}
در اینجا دو روش معرفی فیلترهای سراسری را در متد ConfigureServices کلاس آغازین برنامه مشاهده می‌کنید:
الف) اگر توسط ارائه‌ی new ClassName معرفی شوند، یعنی وهله سازی را خودتان قرار است مدیریت کنید و در این حالت تزریق وابستگی‌هایی صورت نخواهند گرفت.
ب) اگر توسط typeof معرفی شوند، یعنی این وهله سازی توسط IoC Container توکار ASP.NET Core انجام خواهد شد و طول عمر آن Transient است. یعنی به ازای هربار نیاز به آن، یکبار وهله سازی خواهد شد.


ترتیب اجرای فیلترها

توسط خاصیت Order می‌توان ترتیب اجرای چندین فیلتر اجرا شده‌ی به یک اکشن متد را مشخص کرد. اگر این مقدار منفی وارد شود:
 [MyFilter(Name = "Method Level Attribute", Order=-1)]
این فیلتر پیش از فیلترهای سراسری و همچنین فیلترهای اعمال شده‌ی در سطح کلاس اجرا می‌شود.


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

فیلترهایی که به صورت ویژگی‌ها یا Attributes تعریف می‌شوند و قرار است به کنترلرها و یا اکشن متدها به صورت مستقیم اعمال شوند، نمی‌توانند دارای وابستگی‌های تزریق شده‌ی در سازنده‌ی خود باشند. این محدودیتی است که توسط زبان‌های برنامه نویسی اعمال می‌شود و نه ASP.NET Core. اگر ویژگی قرار است پارامتری در سازنده‌ی خود داشته باشد، هنگام تعریف و اعمال آن، این پارامترها باید مشخص بوده و تعریف شوند. به همین جهت آنچنان با تزریق وابستگی‌های از طریق سازنده‌ی کلاس قابل مدیریت نیستند. برای رفع این نقصیه، راه‌حل‌های متفاوتی در ASP.NET Core پیشنهاد و طراحی شده‌اند:
الف) استفاده‌ی از ServiceFilterAttribute
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
   return View();
}
ویژگی جدید ServiceFilter، نوع کلاس فیلتر را دریافت می‌کند و سپس هر زمانیکه نیاز به اجرای این فیلتر خاص بود، کار وهله سازی‌های وابستگی‌های آن، در پشت صحنه توسط IoC Container توکار ASP.NET Core انجام خواهد شد.
همچنین باید دقت داشت که در این حالت ثبت کلاس فیلتر در متد ConfigureServices کلاس آغازین برنامه الزامی است.
 services.AddScoped<AddHeaderFilterWithDi>();
در غیراینصورت استثنای ذیل را دریافت خواهید کرد:
 System.InvalidOperationException: No service for type 'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.

ب) استفاده از TypeFilterAttribute
[TypeFilter(typeof(AddHeaderAttribute),  Arguments = new object[] { "Author", "DNT" })]
public IActionResult Hi(string name)
{
   return Content($"Hi {name}");
}
فیلتر و ویژگی TypeFilter بسیار شبیه است به عملکرد ServiceFilter، با این تفاوت که:
- نیازی نیست تا وابستگی آن‌را در متد ConfigureServices ثبت کرد (هرچند وابستگی‌های خود را از DI Container دریافت می‌کنند).
- امکان دریافت پارامترهای اضافی سازنده‌ی کلاس مدنظر را نیز دارند.


یک مثال تکمیلی: لاگ کردن تمام استثناءهای مدیریت نشده‌ی یک برنامه‌ی ASP.NET Core 1.0

می‌توان با سفارشی سازی فیلتر توکار ExceptionFilterAttribute، امکان ثبت وقایع را توسط فریم ورک توکار Logging اضافه کرد:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public class CustomExceptionLoggingFilterAttribute : ExceptionFilterAttribute
    {
        private readonly ILogger<CustomExceptionLoggingFilterAttribute> _logger;
        public CustomExceptionLoggingFilterAttribute(ILogger<CustomExceptionLoggingFilterAttribute> logger)
        {
            _logger = logger;
        }
 
        public override void OnException(ExceptionContext context)
        {
            _logger.LogInformation($"OnException: {context.Exception}");
            base.OnException(context);
        }
    }
}
و برای ثبت سراسری آن در کلاس آغازین برنامه خواهیم داشت:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(CustomExceptionLoggingFilterAttribute));
در اینجا از typeof استفاده شده‌است تا کار تزریق وابستگی‌های این فیلتر به صورت خودکار انجام شود.
در ادامه با این فرض که پیشتر تنظیمات ثبت وقایع صورت گرفته‌است:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddDebug(minLevel: LogLevel.Debug);
اکنون اگر یک چنین اکشن متدی فراخوانی شود:
public IActionResult GetData()
{
  throw new Exception("throwing an exception!");
}
در پنجره‌ی دیباگ ویژوال استودیو، این استثناء قابل مشاهده خواهد بود:

اشتراک‌ها
سه فصل از کتاب Pro ASP.NET MVC 5 Platform در خصوص ASP.NET Identity 2.0
انتشارات Apress سه فصل (100 صفحه) از کتاب Pro ASP.NET MVC 5 Platform نوشته Adam Freeman  را بصورت رایگان برای دانلود قرار داده که ASP.NET Identity 2.0 را تقریبا بصورت کامل مورد بررسی قرار می‌دهد
سه فصل از کتاب Pro ASP.NET MVC 5 Platform در خصوص ASP.NET Identity 2.0
اشتراک‌ها
آموزش Multithreaded Debugging
In this episode, Robert is joined by Andrew Hall, who shows tips and tricks for debugging multithreaded applications. Computers with multiple processors, multi-core processors, or hyperthreading processes can run multiple threads at the same time.  
آموزش Multithreaded Debugging
نظرات مطالب
EF Code First #11
از وقتی کە شروع به یاد گرفتن ASP.NET MVC کردم از کتاب Pro ASP.NET MVC 3 Framework گرفتە تا آموزشهای خود سایت asp.net و در بسیاری از وبلاگها همه استفادە از repository رو به عنوان best pratice توصیه کردن. استفاده از Interface + EF + DI و mock در unit test به نظر خوب میاد. حالا این سئوال برام پیش اومده آیا از اشکال از تیم EF هست که در پیاده سازی DbContext از الگوی Unit of Work استفاده کرده و یک لایه Abstraction روی اون کشیده و یا ایراد از بی اطلاعی کسانی هست الگوی Repository و EF Code First رو با هم به کار میبرن. این موضوع برای من مثل این میمونه کە یک اینترفیس رو با پیادسازی اجزاش به آدم بدن و بعد خودت دوباره بیای اجزاش رو پیاده سازی کنی و خبر نداشته که ‌‌قبلا پیاده سازی شدن. من که گیج شدم.
نظرات مطالب
Blazor 5x - قسمت 19 - کار با فرم‌ها - بخش 7 - نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server
خلاصه‌ی بحث جاری به روشی دیگر (مدیریت صحیح طول عمر DbContext در برنامه‌های Blazor Server)

یک روش دیگر مدیریت طول عمر Context در برنامه‌های Blazor Server که تمام نکات این بحث را به نحو ساده‌ای پوشش می‌دهد، به صورت زیر است:
الف) حتما بجای AddDbContext از AddDbContextFactory که در مطلب فوق توضیح داده شد، استفاده کنید تا بتوان هرجائیکه نیاز است یک Context جدید را به صورت دستی ایجاد کرد، تا از به اشتراک گذاری آن و مشکلات مرتبط با طول عمر اینگونه Contextها، مصون ماند:
// Do NOT do this anymore
//services.AddDbContext(o => o.UseSQlite("filename.db"));

// But instead do this:
services.AddDbContextFactory(o => o.UseSQlite("filename.db"));
ب) سپس بجای انواع و اقسام روش‌های مدیریت طول عمر Scoped و سپس Dispose کردن‌ها، آن‌ها را در سرویس‌های برنامه به نحو زیر پیاده سازی و ساده کنید:
public class MyService
{
   private readonly IDbContextFactorey _contextFactory;

   public MyService(IDbContextFactory contextFactory)
   {
      _contextFactory = contextFactory;
   }

   // create a new context for each operation / unit of work
   public async Task DoSomethingAsync()
   {
      using (var ctx = _contextFactory.CreateDbContext())
      {
         // this using clause contains your unit of work
      }
   }
}
در این حالت با استفاده از IDbContextFactory تزریقی، هر جائیکه که نیاز هست، یک Context جدید و همچنین Scoped، ایجاد و همانجا هم در پایان کار، تخریب و Dispose می‌شود و نیازی به پیاده سازی سرویس‌های IDisposable ندارد. این روشی است که در مثال‌های خود مایکروسافت هم مشاهده می‌شود. سپس کار کردن با سرویس فوق، نیازی به نکات مرتبط با inherits OwningComponentBase ندارد و کاملا به همراه injectهای عادی و متداول است. همچنین هم مطمئن هستیم، Context ای که در هر متد استفاده می‌شود، کاملا جدید و غیر اشتراکی است و در پایان کار همان متد، حتما Dispose می‌شود.
مطالب
Identity و مباحث مربوط به آن (قسمت اول) - آشنایی با Identity
Identity یکی از  Attributeهایی که در SQL Server به ازای Columnهای عددی می‌توان در نظر گرفت. به طور خیلی ساده هنگامی که این Attribute به ازای یک فیلد عددی تنظیم گردد. چنانچه رکوردی در جدول مربوط به Identity درج شود فیلد Identity مقداری را به طور اتوماتیک دریافت خواهد نمود. 
نحوه دریافت مقدار به ازای فیلد Identity با توجه به آخرین مقدار آن و گام افزایش است که در هنگام ایجاد identity تعریف می‌گردد.
برای ایجاد یک فیلد از نوع Identity می‌توانید زمانیکه جدول خود را ایجاد می‌کنید این Attribute را به فیلد مورد نظر خود تخصیص دهید. 
 مثال 1 : این مثال نحوه ایجاد یک فیلد از نوع Identity را نمایش می‌دهد. 
USE tempdb
GO
CREATE TABLE Customers1
(
 ID INT IDENTITY,-- ID INT IDENTITY(1,1)
 Name NVARCHAR(100),
 [Address] NVARCHAR(200)
)
GO

همانطور که در مثال 1 مشاهده می‌کنید فیلد ID از نوع Identity تعریف شده است. در این حالت (ID int IDENTITY) مقدار شروع و گام افزایش به ازای این فیلد 1 در نظر گرفته خواهد شد. در این صورت اگر چند رکورد زیر را به ازای این جدول درج کنید. مقدار Identity به صورت زیر خواهد بود. 
INSERT INTO Customers1 (Name,[Address]) VALUES
(N'مسعود',N'میانه'),
(N'فرید',N'میانه'),
(N'احمد',N'میانه')
GO
SELECT * FROM Customers1

مثال 2 : این مثال نحوه ایجاد یک فیلد از نوع Identity به همراه مقدار شروع و گام افزایش را مشخص می‌کند. 
USE tempdb
GO
CREATE TABLE Customers2
(
ID INT IDENTITY(100,2),
Name NVARCHAR(100),
[Address] NVARCHAR(200)
)
GO

همانطور که در مثال 2 مشاهده می‌کنید فیلد ID از نوع Identity تعریف شده است و مقدار شروع آن از 100 و همچنین گام افزایش 2 در نظر گرفته شده است. در این صورت اگر چند رکورد زیر را به ازای این جدول درج کنید. مقدار Identity به صورت زیر خواهد بود. 
INSERT INTO Customers2 (Name,[Address]) VALUES
(N'مسعود',N'میانه'),
(N'فرید',N'میانه'),
(N'احمد',N'میانه')
GO
SELECT * FROM Customers2

مثال 3 : این مثال نحوه تنظیم یک فیلد به صورت Identity را در محیط SQL Server Management Studio (SSMS) آموزش می‌دهد.
1- برای شروع کار همانند تصویر زیر بر روی قسمت Table کلیک راست کنید و گزینه New Table… را انتخاب کنید.


2- پس از نمایش پنجره زیر فیلدی را که می‌خواهید از نوع Identity باشد را انتخاب کرده و در قسمت Column Properties خصیصه Is Identity  را برابر Yes قرار دهید تا فیلد مورد نظر شما از نوع Identity در نظر گرفته شود. لازم به ذکر است که Identity Seed مقدار شروع و Identity Increment گام افزایش را مشخص می‌نماید. 

نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 8 - فعال سازی ASP.NET MVC
- با نگارش 3.1 مشکلی مشاهده نشد (هیچ تغییری نداشته): net-core-31-final-poco-controller.zip
- حذف کردن ارث‌بری از کلاس پایه کنترلر، نیاز به یکسری تغییرات را هم در کدهای شما خواهد داشت که تعدادی از نکات آن، در انتهای بحث عنوان شد‌ه‌اند؛ مانند  تزریق ActionContext و بازگشت new ContentResult بجای return Content، چون این return Content از کلاس پایه کنترلر تامین می‌شود. اگر آن‌را حذف کردید، باید جستجو کنید که return Content، ساده کننده‌ی چه چیزی در پشت صحنه هست و امثال این. این نوع نکات هم با مفاهیم ASP.NET MVC 5x یکی است: « ASP.NET MVC #6»  
نظرات اشتراک‌ها
توسعه Asp.net Core و Asp.net Core Identity
در لایه سرویس، ارث بری‌های دو سرویس ذیل را باید به صورت زیر تغییر دهید:
public class ApplicationRoleStore :
        RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>,
        IApplicationRoleStore


public class ApplicationUserStore :
        UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>,
        IApplicationUserStore
بعد از آن تمام سازنده‌های مورد نیاز کلاس‌های لایه سرویس باید جهت تزریق و استفاده‌ی از اینترفیس‌های همین لایه تغییر کنند.

پ.ن.
این مورد بحث «بسیار» مفصلی هست که اینجا امکانش نیست و حتما نیاز به ارائه‌ی یک solution کامل دارد تا قابل درک باشد: «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه »
اشتراک‌ها
ILSpy 6.0 منتشر شد
  • ILSpy 6.0 sports a multi-tab docking interface
  • ILSpy 6.0 comes with a new Metadata Explorer
  • ILSpy 6.0 has new icons for high-DPI support
  • ILSpy 6.0 requires at least .NET 4.7.2 (v5.0 required .NET 4.6.2) 
ILSpy 6.0 منتشر شد