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


#1 Optimize main bundle with Lazy Loading
وقتی که پروژه را برای حالت ارائه‌ی نهایی (Production ) بدون در نظر گرفتن Lazy Load، بیلد می‌کنیم احتمالا فایل‌های تولید شده زیر را در پوشه‌ی dist خواهیم دید: 

  1. polyfills.js :  برای ساختن برنامه‌های سازگار با انواع مرورگر‌ها می‌باشد. به دلیل اینکه وقتی کد‌ها را با جدیدترین ویژگی‌ها می‌نویسیم، ممکن است که همه‌ی مرورگر‌ها توانایی پشتیبانی از آن ویژگی‌ها را نداشته باشند.
  2. scripts.js : شامل اسکریپت‌هایی می‌باشد که در بخش scripts، در فایل angular.json تعریف کرده‌ایم.
  3.  webpack loader : runtime.js می‌باشد. این فایل شامل webpack utilities‌هایی می‌باشد که برای بارگذاری دیگر فایل‌ها مورد نیاز است.
  4. styles.css : شامل style ‌هایی است که در بخش styles، در فایل angular.json تعریف کرده‌ایم.
  5. main.js : شامل تمامی کد‌ها از قبیل کامپوننت‌ها ( کد‌های مربوط به css ، html ، ts) ، دایرکتیو‌ها، pipes و سرویس‌ها و ماژول‌های ایمپورت شده از جمله third party‌های می‌باشد.

همانطور که متوجه هستید، فایل main.js در طول زمان بزرگتر و بزرگتر خواهد شد که این یک مشکل است. در این حالت برای مشاهده‌ی وب سایت، مرورگر نیاز دارد که فایل main.js را دانلود کرده و سپس در صفحه،  آن را اجرا و رندر کند که این یک چالش برای کاربران موبایل با اینترنت ضعیف و هم چنین کاربران دسکتاپ می‌باشد. 
آسان‌ترین راه برای برطرف کردن این مشکل این است که پروژه را به چندین ماژول lazy load، تقسیم کنیم. وقتی که از lazy module‌ها استفاده می‌کنیم، انگیولار chunk مربوط به آن را تولید می‌کند که در ابتدا بارگذاری نخواهد شد؛ مگر اینکه مورد نیاز باشند (معمولا با فعال سازی یک مسیر اتفاق می‌افتد). 
وقتی که از lazy loading استفاده می‌کنیم، بعد از فرایند build، فایل‌های جدیدی تولید خواهند شد، مثل  4.386205799sfghe4.js که یک  چانک از  یک lazy module می‌باشد و در زمان راه اندازی صفحه اول اجرا نخواهد شد که نتیجه‌ی آن داشتن فایل main.js ای با حجم کم می‌باشد. بنابراین بارگذاری صفحه‌ی اول، خیلی سریع انجام خواهد شد. 

با این حال، بارگذاری هر قسمت می‌تواند بر روی کارآیی تاثیر داشته باشد (بارگذاری ممکن است کند باشد). خوشبختانه انگیولار یک راه را برای برطرف کردن این مشکل فراهم کرده است ( PreloadingStrategy ) . بعد از اینکه فایل main.js  به صورت کامل بارگذاری و اجرا شد، کار پیش واکشی ماژول‌ها را انجام می‌دهد و زمانیکه کاربری مسیری را درخواست می‌دهد، آْن مسیر را بدون درنگ مشاهده خواهد کرد.

#2 Debug bundles with Webpack Bundle Analyzer 
حتی ممکن است بعد از تقسیم کردن منطق برنامه به چند ماژول lazy load، شما یک فایل main.js بزرگ داشته باشید. در این حالت می‌توانید بهینه سازی بیشتری را با استفاده از Webpack Bundle Analyzer انجام دهید. با استفاده از این پکیج می‌توانید آمار‌هایی را در رابطه با هر باندل داشته باشید. در ابتدا با استفاده از دستور زیر پکیج آن‌را نصب کنید:
npm install --save-dev webpack-bundle-analyzer
سپس فایل package.json را باز کرده و در بخش scripts، مدخل زیر را اضافه کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"
توجه کنید که اگر خروجی برنامه شما مستقیما در dist می‌باشد، به صورت بالا عمل کنید؛ ولی اگر خروجی برنامه شما در پوشه‌ی dist/YourApplicationName باشد، آن را به حالت زیر ویرایش کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/YourApplicationName/stats.json"
در نهایت دستور زیر را اجرا کنید:
npm run bundle-report
دستور بالا یک بیلد را برای پروژه برای حالت ارائه‌ی نهایی  (Production) همراه با  آمار‌هایی در رابطه با هر باندل ایجاد می‌کند. در اینجا می‌توانیم ببینیم که چه ماژولها/فایل‌هایی در هر باندل استفاده شده‌است. این مورد فوق العاده کمک می‌کند. هم چنین می‌توانیم به صورت بصری ببینیم که چه چیز‌هایی در هر ماژول شامل شده‌اند که بهتر بود آنجا نباشند:


#3 Use Lazy Loading for images that are not visible in page 
وقتی که صفحه اصلی را در اولین بار، بارگذاری می‌کنیم، می‌توانیم تصاویری را داشته باشیم که در صفحه‌ی نمایش، نمایان نباشند (منظور viewport می‌باشد) و کاربر برای دیدن آن تصاویر باید صفحه را به پایین اسکرول کند. با این وجود وقتی که صفحه بارگذاری می‌شود، تصاویر هم بلافاصله دانلود می‌شوند. اگر تعداد تصاویر زیاد باشند این مورد می‌تواند بر روی performance تاثیر منفی داشته باشد. برای برطرف کردن این مشکل می‌توان از lazy loading تصاویر استفاده کرد. در این حالت تصاویر زمانی بارگذاری می‌شوند که کاربر به آن‌ها می‌رسد. یک JavaScript API به نام Intersection Observer API وجود دارد که باعث می‌شود پیاده سازی lazy load خیلی آسان شود. علاوه بر این می‌توان یک دایرکتیو را با قابلیت استفاده مجدد طراحی کرد ( lazy loading برای تصاویر با استفاده از Intersection Observer).

#4 Use virtual scrolling for large lists  
با استفاده از virtual scrolling  می‌توان المنت‌ها را در Dom بر اساس بخش‌های قابل مشاهده‌ای از یک لیست، Load  یا unload کرد که برنامه را فوق العاده سریع می‌کند.
 
#5 Use FOUT instead of FOIT for fonts  
در بیشتر وب سایت‌ها می‌توان فونت‌های سفارشی زیبایی را به جای فونت‌های معمول دید. با این حال برای استفاده از فونت‌های فراهم شده توسط سرویس‌های دیگر لازم است که مرورگر آن‌ها را دانلود و parse  کند. اگر از فونت‌های سفارشی استفاده کنیم، مثل  Google Fonts، چه اتفاقی می‌افتد؟ در اینجا دو سناریو وجود دارد: 
  • در این حالت مرورگر منتظر می‌ماند تا فونت دانلود شود و آن را parse  کند و تنها بعد از آن متن را بر روی صفحه نمایش می‌دهد. متن روی صفحه تا زمانیکه فونت دانلود و parse  نشده باشد، قابل مشاهده نیست؛ این FOIT است (Flash of invisible text) .
  • مرورگر در ابتدا متن را با استفاده از فونت معمول نمایش می‌دهد و بعد از آن سعی می‌کند که ساختار‌های فونت خارجی را دریافت کند. وقتی که دانلود انجام شد و سپس آن را parse  کرد، فونت سفارشی دانلود شده، با فونت معمول جایگزین خواهد شد؛ این FOUT است ( Flash of unstyled text ).

بیشتر مرورگر‌ها از FOIT  استفاده می‌کنند و تنها Internet Explorer از FOUT استفاده می‌کند. برای برطرف کردن این مشکل می‌توان از توصیف‌گر font-display استفاده کرد و به مرورگر بگوییم که می‌خواهیم در ابتدا متن را با فونت معمول نمایش دهیم و جایگزینی فونت، بعد از دانلود باشد (بیشتر).
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
یک نکته‌ی تکمیلی: خطایابی خودکار فایل‌های Razor در نگارش 2.1

از نگارش ASP.NET Core 2.1 preview 2 به بعد، کامپایلر ASP.NET Core، فایل‌های Razor را هم در حین کامپایل برنامه به صورت خودکار بررسی می‌کند (Razor compilation on build). برای نمونه اگر model@ تعریف شده‌ی در یک فایل razor حاوی خاصیت مورد استفاده نباشد، در حین کامپایل برنامه، خطای مرتبط با آن فایل cshtml را می‌توان مشاهده کرد:
Views\UploadFileExtensions\Index.cshtml(25,135): 
error CS1061: 'UserFileViewModel' does not contain a definition for 'UserFile' 
and no extension method 'UserFile' accepting a first argument of type 'UserFileViewModel' could be found 
(are you missing a using directive or an assembly reference?)
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت چهارم - پرهیز از الگوی Service Locator در برنامه‌های وب
الگوی Service locator را در قسمت دوم بررسی کردیم. همانطور که عنوان شد، بهتر است تا جائیکه امکان دارد از بکارگیری آن به علت ضدالگو بودن پرهیز کرد. در ادامه قسمت‌های مختلف یک برنامه‌ی ASP.NET Core را که می‌توان بدون نیاز به استفاده‌ی الگوی Service locator، تزریق وابستگی‌ها را در آن‌ها انجام داد، مرور می‌کنیم.


در کلاس آغازین برنامه

در اینجا در متد Configure آن تنها کافی است اینترفیس سرویس مدنظر خود را مانند IAmACustomService، به صورت یک پارامتر جدید اضافه کنید. کار وهله سازی آن توسط Service Provider برنامه به صورت خودکار صورت می‌گیرد:
public class Startup 
{ 
    public void ConfigureServices(IServiceCollection services) { } 
  
    public void Configure(IApplicationBuilder app, IAmACustomService customService) 
    { 
        // ....    
    }         
}

یک نکته‌ی مهم: اگر طول عمر IAmACustomService را Scoped تعریف کرده‌اید و این سرویس از نوع IDisposable نیز می‌باشد، این روش کارآیی نداشته و باید از نکته‌ی «روش صحیح Dispose اشیایی با طول عمر Scoped، در خارج از طول عمر یک درخواست ASP.NET Core» که در قسمت قبل معرفی شد استفاده کنید.


در میان افزارها

هم سازنده‌ی یک میان افزار و هم متد Invoke آن قابلیت تزریق وابستگی‌ها را دارند:
public class TestMiddleware 
{ 
    public TestMiddleware(RequestDelegate next, IAmACustomService service) 
    { 
        // ... 
    } 
 
    public async Task Invoke(HttpContext context, IAmACustomService service) 
    { 
        // ... 
    }     
}
از سازنده‌ی آن برای تزریق وابستگی سرویس‌هایی با طول عمر Singleton استفاده کنید. ServiceProvider به همراه ویژگی است به نام Scope Validation. در این حالت اگر طول عمر سرویسی Singleton باشد (مانند طول عمر یک میان‌افزار) و در سازنده‌ی آن یک سرویس با طول عمر Scoped تزریق شود، در زمان اجرا یک استثناء را صادر می‌کند؛ چون در این حالت رفتار این سرویس Scoped نیز Singleton می‌شود که احتمالا مدنظر شما نیست. در این حالت از پارامترهای اضافی متد Invoke می‌توان برای تزریق وابستگی‌هایی با طول عمر Transient و یا Scoped استفاده کرد.
البته می‌توان این Scope Validation را در فایل program.cs به نحو زیر غیرفعال کرد، ولی بهتر است اینکار را انجام ندهید و همان مقدار پیش‌فرض آن بسیار مناسب است:
public static IWebHostBuilder CreateDefaultBuilder(string[] args) 
{ 
            var builder = new WebHostBuilder() 
//...
                .UseDefaultServiceProvider((context, options) => 
                { 
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); 
                }) 
//...

در کنترلرها

سازنده‌های کنترلرهای برنامه‌های ASP.NET Core قابلیت تزریق وابستگی‌ها را دارند:
public class HelloController : Controller 
{ 
    private readonly IAmACustomService _customService; 
 
    public HelloController(IAmACustomService customService) 
    { 
        _customService = customService; 
    } 
 
    public IActionResult Get() 
    { 
        // ... 
    } 
}
در اینجا حتی می‌توان با استفاده از ویژگی FromServices، یک سرویس را توسط پارامترهای یک اکشن متد نیز درخواست کرد:
[HttpGet("[action]")]
public IActionResult Index([FromServices] IAmACustomService service)
{  
   // ...
}
در این حالت بجای model binding، کار دریافت این سرویس درخواستی صورت می‌گیرد.


در مدل‌ها

ویژگی FromServices بر روی مدل‌ها نیز کار می‌کند.
public IActionResult Index(TestModel model)
{
  // ...
}
در اینجا نحوه‌ی تعریف TestModel را به همراه ویژگی FromServices مشاهده می‌کنید:
public class TestModel 
{        
    public string Name { get; set; } 
 
    [FromServices] 
    public IAmACustomService CustomService { get; set; } 
}
این حالت که property injection نیز نام دارد، نیاز به خاصیتی با یک public setter را دارد.


در Viewها

در Razor Views نیز می‌توان توسط inject directive@ کار تزریق وابستگی‌ها را انجام داد:
 @inject IAmACustomService CustomService
 

در ویژگی‌ها و فیلترها

در ASP.NET Core تزریق وابستگی‌های در سازنده‌های فیلترها نیز کار می‌کند:
public class ApiExceptionFilter : ExceptionFilterAttribute  
{  
    private ILogger<ApiExceptionFilter> _logger;  
    private IHostingEnvironment _environment;  
    private IConfiguration _configuration;  
  
    public ApiExceptionFilter(IHostingEnvironment environment, IConfiguration configuration, ILogger<ApiExceptionFilter> logger)  
    {  
        _environment = environment;  
         _configuration = configuration;  
         _logger = logger;  
    }
در این حالت چون سازنده‌ی این ویژگی، پارامتر دار شده‌است و این پارامترها نیز یک مقدار ثابت قابل کامپایل نیستند، برای معرفی یک چنین فیلتری باید از ServiceFilterها به صورت زیر استفاده کرد:
[Route("api/[controller]")]  
[ApiController]  
[ServiceFilter(typeof(ApiExceptionFilter))]  
public class ValuesController : ControllerBase  
{
مطالب
تبدیل تاریخ میلادی به شمسی در SSIS به کمک سی شارپ
برای تبدیل تاریخ میلادی به تاریخ شمسی در package‌های SSIS می‌توان از زبان سی شارپ استفاده کرد . بدین طریق می‌توان در طی عملیات ETL و هنگام transform کردن داده‌ها ، عملیات تبدیل از میلادی به شمسی را انجام داد . عملیات تبدیل داده در این مثال به کمک Script Component انجام می‌شود. 

برای این کار از داده‌های موجود در پایگاه داده [AdventureWorksLT2008R2].[SalesLT].[Address] استفاده می‌کنم . 
برای این کار یک package تعریف می‌کنم و برای استخراج داده‌ها یک OLEDB تعریف می‌کنم . برای تبدیل داده‌ها از Script Component و برای گرفتن خروجی آزمایشی از union استفاده می‌کنم : 

دایره‌های قرمز رنگ مشخص شده در تصویر ، بیانگر data viewerهای تعریف شده است. 

در تنظیمات dataSource مانند زیر عمل می‌کنیم :
دستور زیر فقط برای نمایش یک نمونه از کارکرد عملیات مورد نظر در این مثال می‌باشد 


نمونه خروجی مانند زیر می‌باشد : 




سپس برای تنظیمات Script Component به ترتیب زیر عمل می‌کنیم :

در قسمت Input column ستون هایی را که به عنوان پارامتر می‌توانیم با آنها کار کنیم ، تعریف می‌کنیم : (دقت شود که تغییر نام متغیر‌ها ، در کد‌ها اعمال می‌شوند .) 




حال می‌خواهم ستون‌های تاریخ میلادی و شمسی را برای خروجی تعریف کنم :

 


همانطور که مشاهده می‌کنید ، نوع داده ای برای خروجی را رشته تعریف کردم.

سپس به قسمت script بر می‌گردیم (سمت چپ پنجره ) و روی Edit Script کلیک می‌کنیم : (در صورتی که تمایل به کد نویسی با VB را دارید در همین قسمت می‌توانید آن را تنظیم کنید) 



پنجره ای مانند زیر برای ویرایش کد‌ها ، باز می‌شود 


همانطور که مشاهده می‌کنید درون این کلاس 4 متد تبدیل تاریخ را پیاده کردم و از آنها در متد input0_processInputRow استفاده کردم . این کار نیازی به پیاده سازی حلقه ندارد و به راحتی می‌توان آنها را روی سطر‌های دلخواه تعریف کرد . خروجی نمایش داده شده در data viewer‌ها مانند زیر می‌باشند : 


قبل از اعمال تبدیلات : 



بعد از اعمال تبدیلات : 


موفق باشید 

مطالب
نحوه کار Expression و ایجاد یک DynamicFilter
ساختار Expression‌ها شبیه به ساختار یک درخت است. به عنوان مثال زمانیکه شما یک فیلتر ساده را مانند دستور زیر اجرا میکنید:
Expression<Func<string, bool>> f = s => s.Length < 5;
Expression ایجاد شده از فیلتر شما به صورت زیر میباشد:

منبع : کتاب C# 8 in a Nutshell 

ParameterCollection به پارامترهای استفاده شده در فیلتر اشاره دارد که در فیلتر بالا فقط s استفاده شده‌است و از نوع string است.

BinaryExpression شامل سه قسمت مهم Left , Right و NodeType میباشد. برای فیلتر بالا، مقدار پراپرتی Left برابر s.Length می‌باشد و پراپرتی Right شامل مقدار 5 و مقدار NodeType هم برابر LessThan میباشد. یعنی فیلتر بالا به یک درخت تبدیل شده که نود اصلی آن LessThan است و دو مقدار Left و Right را باهم مقایسه میکند. اما اگر یک شرط دیگر را به فیلتر بالا اعمال کنیم، ساختار Expression کمی تغییر میکند. برای مثال: 

Expression<Func<string, bool>> filter = s => s.Length > 5 && s.Length < 45;

Expression ایجاد شده برای این فیلتر شامل همان ساختار قبلی است؛ اما با این تغییر که هر کدام از پراپرتی‌های Right و Left، خود یک BinaryExpression شده‌اند و مقدار NodeType اصلی از LessThan به AndAlso تغییر پیدا کرده‌است. Expression ایجاد شده از فیلتر بالا ( filter.Body ) به این صورت است که پراپرتی Left آن برابر است با یک BinaryExpression که مقدار NodeType آن برابر است با GreaterThan و پراپرتی Left آن شامل s.Length میباشد و پراپرتی Right آن برابر 5 میباشد. همچنین پراپرتی Right مربوط به filter.Body برابر یک ExpressionBinary است که مقدار NodeType آن برابر است با LessThan و پراپرتی Left آن برابر s.Length است و پراپرتی Right آن برابر 45 میباشد.

filter.Body شبیه به تصویر زیر میباشد :

اگر بخواهیم خودمان یک Expression tree را ایجاد کنیم، باید از پایین‌ترین نود آن شروع کنیم. یعنی ابتدا باید پراپرتی Left و Right را ایجاد کنیم و سپس این دو پراپرتی را با هم مقایسه کنیم (NodeType). در کد زیر Expression مربوط به فیلتر بالا را نوشته‌ایم:

ParameterExpression parameterExpression = Expression.Parameter(typeof(string));
MemberExpression memberExpression = Expression.Property(parameterExpression, "Length");

ConstantExpression greaterThanConstantExpression = Expression.Constant(5);
BinaryExpression greaterThanComparison = Expression.GreaterThan(memberExpression, greaterThanConstantExpression);
var greaterThan = Expression.Lambda<Func<string, bool>>(greaterThanComparison, parameterExpression);

ConstantExpression lessThanConstantExpression = Expression.Constant(45);
BinaryExpression lessThanComparsion = Expression.LessThan(memberExpression, lessThanConstantExpression);
var lessThan = Expression.Lambda<Func<string, bool>>(lessThanComparsion, parameterExpression);

var param = Expression.Parameter(typeof(string), "x");
var body = Expression.AndAlso(
            Expression.Invoke(greaterThan, param),
            Expression.Invoke(lessThan, param)
        );
Expression<Func<string, bool>> filter = Expression.Lambda<Func<string, bool>>(body, param);

ParameterExpression : نوع پارامتری را که میخواهیم روی آن شرط را روی آن اعمال کنیم، مشخص کرده‌ایم.

MemberExpression  : پراپرتی Length را معرفی کرده‌ایم که قرار است شرطی بر روی این پراپرتی اعمال شود.

ConstantExpression   : مقدار ثابتی که پراپرتی MemeberExpression قرار است با آن مقایسه شود.

BinaryExpression   : نود تایپ را مشخص کرده‌ایم که برابر است با GreaterThan.

سپس Expression مربوط به هرکدام را در greaterThan و lessThan ایجاد کرده‌ایم و این دو را باهم And کرده و در متغییر body قرار داده‌ایم و در نهایت filter را با دستور Expression.Lambda ایجاد کرده‌ایم که برابر است با :

Expression<Func<string, bool>> filter = s => s.Length > 5 && s.Length < 45;


ساخت یک داینامیک فیلتر

در ادامه میخواهیم یک داینامیک فیلتر را ایجاد کنیم که به طور مثال برنامه نویس از سمت فرانت‌اند بتواند فیلتر‌های ساده‌ای را اعمال کند. برای این کار یک کلاس برای فیلتر ایجاد میکنیم :

public class DynamicModel
{
    public string Name { get; set; }
    public string Comparison { get; set; }
    public object Data { get; set; }
}

پراپرتی Data مقداری است که باید با آن مقایسه انجام شود.

Comparison نوع عملیات را مشخص میکند مانند : Equal, LessThan, GreaterThan و... .

پراپرتی Name نام پراپرتی است که باید شرط روی آن اعمال شود.

کلاس ثابت ها:

public static class ComparisonConstant
{
    public const string LessThan = "LesThan";
    public const string LessThanEqual = "LesThanEqual";
    public const string GreaterThan = "GreaterThan";
    public const string GreaterThanEqual = "GreaterThanEqual";
    public const string Equal = "Equal";
    public const string NotEqual = "NotEqual";
}

ساخت اکستنشن متد:

public static IQueryable<TModel> DynamicFilter<TModel>(this IQueryable<TModel> iqueryable, IEnumerable<DynamicModel> dynamicModel)
{
    return iqueryable.Where(Filter<TModel>(dynamicModel));
}  
public static Expression<Func<TModel, bool>> Filter<TModel>(IEnumerable<DynamicModel> dynamicModel)
{
    Expression<Func<TModel, bool>> result = a => true;
    foreach (var item in dynamicModel)
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(TModel));
        MemberExpression memberExpression = Expression.Property(parameterExpression, item.Name);
        ConstantExpression constantExpression = Expression.Constant(item.Data);
        BinaryExpression comparison = GetBinaryExpression(item.Comparison, memberExpression, constantExpression);
        var expression = Expression.Lambda<Func<TModel, bool>>(comparison, parameterExpression);
        var param = Expression.Parameter(typeof(TModel), "x");
        var body = Expression.AndAlso(
                    Expression.Invoke(result, param),
                    Expression.Invoke(expression, param)
                );
        result = Expression.Lambda<Func<TModel, bool>>(body, param);
    }
    return result;
}

ورودی این مدل، لیستی از DynamicModel میباشد که به ازای هر کدام از آیتم‌ها، یک BinaryExpression ایجاد میکند و آن را با result تعریف شده And میکند. یعنی تمامی آیتم‌های ارسال شده باهم And میشوند.

متد GetBinaryExpression بر اساس مقدار فیلد Comparison که از سمت فرانت ارسال میشود، کار میکند:

private static BinaryExpression GetBinaryExpression(string comparison, MemberExpression memberExpression, ConstantExpression constantExpression)
{
    switch (comparison)
    {
        case ComparisonConstant.Equal:
            return Expression.Equal(memberExpression, constantExpression);
        case ComparisonConstant.LessThan:
            return Expression.LessThan(memberExpression, constantExpression);
        case ComparisonConstant.GreaterThan:
            return Expression.GreaterThan(memberExpression, constantExpression);
        case ComparisonConstant.NotEqual:
            return Expression.NotEqual(memberExpression, constantExpression);
        case ComparisonConstant.GreaterThanEqual:
            return Expression.GreaterThanOrEqual(memberExpression, constantExpression);
        case ComparisonConstant.LessThanEqual:
            return Expression.LessThanOrEqual(memberExpression, constantExpression);
        default:
            return null;
    }
}

 کلاس Category را در نظر بگیرید که شامل دو پراپرتی Title و Id میباشد و میخواهیم از این داینامیک فیلتر، برای فیلتر کردن دیتاها استفاده کنیم از سمت فرانت‌اند. اگر از سمت فرانت‌اند چنین دیتایی ارسال شود:

[
   {
      "Name":"Title",
      "Comparison":"Equal",
      "Data":"Hi"
   },
   {
      "Name":"Id",
      "Comparison":"LesThanEqual",
      "Data": 100
   }
]

تمامی رکوردهایی که مقدار پراپرتی Title آنها برابر Hi باشد و Id آن کوچکتر مساوی 100 باشد، از دیتابیس خوانده میشود.

var categories = _dbContext.Categories
                         .DynamicFilter(filter)//filter => IEnumerable<DynamicModel>
                         .ToList();

گیت هاب داینامیک فیلتر 

نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیت‌های پایه و DbContext برنامه
- زمانیکه در GitHub آیکن سبز رنگ مشاهده می‌شود، یعنی این پروژه بدون مشکل کامپایل می‌شود. اطلاعات بیشتر
+ این متد در فضای نام using ASPNETCoreIdentitySample.Common.GuardToolkit تعریف شده که import آن در سطر اول همان Context موجود است.
- برای کار با این پروژه، قبل از هر کاری، حتما Readme آن‌را مطالعه کنید و یکسری مواردی را که ذکر کرده انجام دهید.
نظرات مطالب
پیاده سازی Remote Validation در Blazor
یک نکته‌ی تکمیلی: امکان اجرای ساده‌تر اعمال async پس از رخ‌داد onchange در Blazor 7x

پیشنیاز: برای اجرای نکات زیر، نیاز به حداقل NET SDK 7.0.101. است و اگر از ویژوال استودیو استفاده می‌کنید، باید شماره نگارش آن حداقل 17.4.3 باشد؛ در غیراینصورت با خطای «'cannot convert from 'method group' to 'Action» مواجه خواهید شد.


همانطور که در مطلب فوق هم مشاهده کردید، در جهت انجام اعتبارسنجی از راه دور async پس از ورود اطلاعات، تنها رخ‌دادی که در اینجا در اختیار ما قرار می‌گیرد، رخ‌داد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخ‌دادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم تا بتوانیم برای مثال به کمک رویداد OnFieldChanged آن، این عملیات async را انجام دهیم. در دات نت 7.0.1، این وضعیت با معرفی modifier جدیدی به نام bind:after@ تغییر کرده‌است که در ادامه توضیحات آن‌را ملاحظه خواهید کرد.


تعاریف زیر را جهت پیاده سازی یک انقیاد دوطرفه (two-way data-binding) درنظر بگیرید:
<input @bind="username" />

<InputText @bind-Value="Model.Name" />
که در اولی با درج bind@ بر روی یک المان استاندارد HTML و در دومی با ذکر bind-Value@ میسر شده‌است. در این حالت هر تغییری در مقدار کنترل قرار گرفته‌ی بر روی صفحه، به خاصیت متصل به آن منعکس می‌شود (با پیاده سازی خودکار یک رویدادگردان onchange توسط Blazor در پشت صحنه) و برعکس.
مشکل! اگر در اینجا نیاز باشد تا در حین ورود اطلاعات، کدی نیز اجرا شود چه باید کرد؟
متاسفانه در این حالت نمی‌توانیم رویدادگردان onchange را به صورت دستی، به تعاریف فوق اضافه کنیم و اگر چنین کاری را انجام دهیم، با خطای زیر مواجه خواهیم شد:
RZ10008 The attribute 'onchange' is used two or more times for this element.
Attributes must be unique (case-insensitive). 
The attribute 'onchange' is used by the '@bind' directive attribute.
عنوان می‌کند که چون ما خودمان onchange را راسا پیاده سازی کرده‌ایم، شما دیگر نمی‌توانید اینکار را مجددا انجام دهید!

راه حل‌های ممکن انجام اعمال async پس از بروز تغییرات تا پیش از دات نت 7

الف) username متصل را تبدیل به یک خاصیت get و set دار کرده و اکنون در قسمت set آن می‌توان عملیات synchronous ای را انجام داد که متاسفانه در این حالت، امکان انجام اعمال async میسر نیست.
ب) چون می‌خواهیم عملیات async ای را پس از تغییرات انجام دهیم، باید از انقیاد دوطرفه صرفنظر کنیم و مدیریت رویداد onchange را خودمان به‌دست بگیریم؛ برای نمونه در مثال زیر می‌توان با پیاده سازی async متد CheckUsername به هدف خود رسید؛ اما همانطور که مشاهده می‌کنید، این عملیات اکنون one-way binding است:
<input value="@username" @onchange="CheckUsername" />
ج) اگر از EditForm و کنترل‌های آن استفاده می‌کنیم، می‌توان همانند مثال مطلب جاری از رویداد OnFieldChanged استفاده کرد یا راه دیگر آن شکستن bind-Value@ به اجزای تشکیل دهنده‌ی آن است که سه جزء Value ،ValueExpression و ValueChanged را تشکیل می‌دهد و اینبار می‌توان رویداد ValueChanged آن‌را دستی پیاده سازی کرد:
<InputText
  Value="@Model.Name"
  ValueExpression="()=>Model.Name"
  ValueChanged="(string s)=>CheckUsername(s)" />  
<ValidationMessage For="() => Model.Name" />

راه حل جدید انجام اعمال async پس از بروز تغییرات در دات نت 7

Blazor در دات نت 7، به همراه یک bind:after modifier@ است که امکان اجرای متدی را (چه همزمان یا غیرهمزمان) پس از بروز تغییرات، میسر می‌کند و مزیت آن عدم نیاز به بازنویسی متد onchange و از دست دادن انقیاد دوطرفه است:
<input @bind="username" @bind:after="CheckUsername" />
همانطور که مشاهده می‌کنید هنوز در این حالت bind@ وجود دارد (یعنی two-way data-binding هنوز هم برقرار است) و توسط bind:after@، متدی را که قرار است پس از تغییرات اجرا شود، مشخص کرده‌ایم.

این modifier را حتی می‌توان به کنترل‌های EditForm نیز اعمال کرد؛ بدون اینکه نیازی به استفاده از راه‌حل‌های پیشین (حالت ج عنوان شده) باشد:
<InputText
  @bind-Value="Model.Name"
  @bind-Value:after="CheckUsername" />   
<ValidationMessage For="() => Model.Name" />
در اینجا نیز هنوز از مزایای two-way data-binding برخورداریم و همچنین می‌توانیم پس از تغییری، یک متد sync و یا async را فراخوانی کنیم. برای نمونه پیاده سازی اعتبارسنجی از راه دور مطلب جاری، اینبار به صورت زیر ساده می‌شود:
async Task CheckUsername()
{
    if (!string.IsNullOrWhiteSpace(Model.Name))
    {
        _messageStore?.Clear(EditContext.Field(nameof(UserDto.Name)));


        var response = await HttpClient.PostAsJsonAsync(
                    UserValidationUrl,
                    new UserDto { Name = Model.Name });
        var responseContent = await response.Content.ReadAsStringAsync();
        if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase))
        {
            _messageStore?.Add(EditContext.Field(nameof(UserDto.Name)), 
                      $"`{Model.Name}` is in use. Please choose another name.");        
        }

        EditContext.NotifyValidationStateChanged();
    }
}
مطالب
آشنایی با جنریک‌ها #3
متدهای جنریک
متدهای جنریک، دارای پارامترهایی از نوع جنریک هستند و بوسیله‌ی آنها می‌توانیم نوع‌های (type) متفاوتی را به متد ارسال نمائیم. در واقع از متد، یک نمونه پیاده سازی کرده‌ایم، در حالیکه این متد را برای انواع دیگر هم می‌توانیم فراخوانی کنیم.

تعریف ساده دیگر
جنریک متدها اجازه می‌دهند متدهایی با نوع هایی که در زمان فراخوانی مشخص کرده ایم، داشته باشیم. 

نحوه تعریف یک متد جنریک بشکل زیر است:
return-type method-name<type-parameters>(parameters)
قسمت مهم syntax بالا، type-parameters  است. در آن قسمت می‌توانید یک یا چند نوع که بوسیله کاما از هم جدا می‌شوند را تعریف کنید. این typeها در return-value و نوع برخی یا همه پارامترهای ورودی جنریک متد، قابل استفاده هستند. به کد زیر توجه کنید:
public T1 PrintValue<T1, T2>(T1 param1, T2 param2)
{
    Console.WriteLine("values are: parameter 1 = " + param1 + " and parameter 2 = " + param2);

    return param1;
}
در کد بالا، دو پارامتر ورودی بترتیب از نوع T1 و T2 و پارامتر خروجی (return-type) از نوع T1 تعریف کرده‌ایم.

اعمال محدودیت بر روی جنریک متدها
در زمان تعریف یک جنریک کلاس یا جنریک متد، امکان اعمال محدودیت بر روی typeهایی را که قرار است به آن‌ها ارسال شود، داریم. یعنی می‌توانیم تعیین کنیم جنریک متد چه typeهایی را در زمان ایجاد یک وهله‌ی از آن بپذیرد یا نپذیرد. اگر نوعی که به جنریک متد ارسال می‌کنیم جزء محدودیت‌های جنریک باشد با خطای کامپایلر روبرو خواهیم شد. این محدودیت‌ها با کلمه کلیدی where اعمال می‌شوند.
public void MyMethod< T >()
       where T : struct
{
  ...
}

محدودیت‌های قابل اعمال بر روی جنریک ها
  • struct: نوع آرگومان ارسالی باید value-type باشد؛ بجز مقادیر غیر NULL.
class C<T> where T : struct {} // value type
  • class: نوع آرگومان ارسالی باید reference-type (کلاس، اینترفیس، عامل، آرایه) باشد.
class D<T> where T : class {} // reference type
  • ()new: آرگومان ارسالی باید یک سازنده عمومی بدون پارامتر باشد. وقتی این محدوده کننده را با سایر محدود کننده‌ها به صورت همزمان استفاده می‌کنید، این محدوده کننده باید در آخر ذکر شود.
class H<T> where T : new() {} // no parameter constructor
public void MyMethod< T >()
       where T : IComparable, MyBaseClass, new ()
{
  ...
}
  • <base class name>: نوع آرگومان ارسالی باید از کلاس ذکر شده یا کلاس مشتق شده آن باشد.
class B {}
class E<T> where T : B {} // be/derive from base class
  • <interface name>: نوع آرگومان ارسالی باید اینترفیس ذکر شده یا پیاده ساز آن اینترفیس باشد.
interface I {}
class G<T> where T : I {} // be/implement interface
  • U: نوع آرگومان ارسالی باید از نوع یا مشتق شده U باشد.
class F<T, U> where T : U {} // be/derive from U
توجه: در مثال‌های بالا، محدوده کننده‌ها را برای جنریک کلاس‌ها اعمال کردیم که روش تعریف این محدودیت‌ها برای جنریک متدها هم یکسان است.

اعمال چندین محدودیت همزمان
برای اعمال چندین محدودیت همزمان بر روی یک آرگومان فقط کافی است محدودیت‌ها را پشت سرهم نوشته و آنها را بوسیله کاما از یکدیگر جدا نمایید.
interface I {}
class J<T>
  where T : class, I
در کلاس J بالا، برای آرگومان محدودیت class و اینترفیس I را اعمال کرده‌ایم.
این روش قابل تعمیم است:
interface I {}
class J<T, U>
  where T : class, I
  where U : I, new() {}
در کلاس J، آرگومان T با محدودیت‌های class و اینترفیس I و آرگومان U با محدودیت اینترفیس I و ()new تعریف شده است و البته تعداد آرگومان‌ها قابل گسترش است.
حال سوال این است: چرا از محدود کننده‌ها استفاده می‌کنیم؟
کد زیر را در نظر بگیرید:
//this method returns if both the parameters are equal 
public static bool Equals< T > (T t1, Tt2) 
{ 
  return (t1 == t2); 
}
متد بالا برای مقایسه دو نوع یکسان استفاده می‌شود. در مثال بالا در صورتیکه دو مقدار از نوع int با هم مقایسه نماییم جنریک متد بدرستی کار خواهد کرد ولی اگر بخواهیم دو مقدار از نوع string را مقایسه کنیم با خطای کامپایلر مواجه خواهیم شد. عمل مقایسه دو مقدار از نوع string که مقادیر در heap نگهداری می‌شوند بسادگی مقایسه دو مقدار int نیست. چون همانطور که می‌دانید int یک value-type و string یک reference-type است و برای مقایسه دو reference-type با استفاده از عملگر ==  تمهیداتی باید در نظر گرفته شود.
برای حل مشکل بالا 2 راه حل وجود دارد:
  1. Runtime casting
  2. استفاده از محدود کننده‌ها
casting در زمان اجرا، بعضی اوقات شاید مناسب باشد. در این مورد، CLR نوع‌ها را در زمان اجرا بدلیل کارکرد صحیح بصورت اتوماتیک cast خواهد کرد اما مطمئناً این روش همیشه مناسب نیست مخصوصاً زمانی که نوع‌های مورد استفاده در حال تحریف رفتار طبیعی عملگرها باشند (مانند آخرین نمونه بالا).
مطالب
وظیفه app.UseExceptionHandler("/Error") در Blazor Server
علیرغم اینکه در Program.cs یا Startup.cs کد زیر وجود دارد، اما بازهم استثناءها در Blazor Server در قالب یک نوار زرد رنگ، پایین مرورگر نشان داده می‌شوند؛ حال در محیط توسعه باشد و یا در محیط تولید و پابلیش نهایی محصول!
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

برای آزمایش آن، کد فوق را به شکل زیر کامنت می‌کنیم تا در محیط توسعه نیز در صورتیکه استثنایی رخ داد، ما را به صفحه‌ی Error.cshtml پیش فرض هدایت کند:
            //if (env.IsDevelopment())
            //{
                //app.UseDeveloperExceptionPage();
            //}
            //else
            //{
                app.UseExceptionHandler("/Error");
            //}
حال کامپوننت counter را به شکل زیر ویرایش می‌کنیم تا استثنایی به عمد رخ دهد:
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
        throw new Exception("This is my Exception !!");
    }
}
با اجرای برنامه ملاحظه خواهید کرد که به صفحه‌ی Error.cshtml هدایت نخواهید شد! حتی با اینکه استثنایی رخ داد، خط app.UseExceptionHandler("/Error")   اصلا کاری به مدیریت استثناء نداشت. حال سؤالی اینجا پیش می‌آید: پس چرا مایکروسافت در visual studio به تولید کدهای پیش فرض صفحه Error.cshtml و صدا زدن میان افزار فوق در یک اپلیکیشن Blazor Server می‌پردازد؟
در واقع بسیاری از میان افزارهای Asp.Net Core مانند UseExceptionHandler  در تمام فازهای یک اپلیکیشن Blazor Server به درستی کار نمی‌کنند؛ زیرا Blazor Server با SignalR و هابش کار می‌کند. 
هنگام راه‌اندازی یک برنامه‌ی Blazor Server، ابتدا چند درخواست HTTP وجود دارد که از خط لوله‌ی Asp .Net Core عبور می‌کنند؛ در واقع دقیقا قبل از تشکیل هاب و عملیات websocket. در این فاز اگر استثنایی رخ دهد، آنگاه میان افزار  UseExceptionHandler  وارد عمل خواهد شد و صفحه را به Error.cshtml هدایت خواهد نمود و به این دلیل است که این کدها به صورت پیش فرض وجود دارند. بنابراین باید صفحه‌ی Error.cshtml را نیز در اپلیکیشن‌های تولید شده‌ی خود، به زبان‌های موردنظر پروژه‌تان ترجمه کرده و پیام‌های مناسبی را به کاربر نشان دهید.
باید دقت نمود که دیگر پس از این فاز نمی‌توان به این میان افزار متکی بود. برای مدیریت استثناءها در فازهای بعد از این فاز، می‌توان از ErrorBoundary و یا مدیریت دستی استثناءها استفاده نمود.
مطالب
تبدیل خودکار استثنای HttpRequestValidationException به یک ModelError در ASP.NET MVC
فرم هایی که اطلاعاتی را از یک کاربر دریافت کرده و به سمت سرور Post می‌کنند، از مهمترین اجزای لاینفک یک وب سایت می‌باشند. بی شک همه‌ی ما از چنین فرمهایی حتی در یک پروژه‌ی هرچند کوچک استفاده کرده‌ایم. ممکن است هنگام ارسال این فرمها، کاربری شیطنت به خرج داده و درون یکی از فیلدهای فرم، از عبارت‌های HTML و یا یک اسکریپت استفاده کرده باشد. در ASP.Net + MVC از مکانیزم ValidationRequest برای مقابله با چنین حملاتی (XSS) استفاده شده است.
یک فرم با یک Textbox در یک صفحه قرار دهید و درون Textbox یک تگ HTML بنویسید و آنرا به سرور ارسال کنید، در حالت پیش فرض اتفاقی که خواهد افتاد اینست که با یک صفحه‌ی خطا روبرو خواهید شد که به شما هشدار می‌دهد داده‌های ارسال شده، دارای مقادیر غیرمجازی می‌باشند. شما می‌توانید این مکانیزم اعتبارسنجی-امنیتی را غیرفعال کنید. برای غیرفعال کردن آن هم در لینکی که در فوق معرفی گردید توضیحات کافی داده شده است. ولی توصیه میشود برای جلوگیری از حملات XSS هیچگاه این مکانیزم را غیرفعال نکنید، مگر اینکه هیچ داده‌ای را بدون Encode کردن در صفحه نمایش ندهید.
راه‌های زیادی برای هندل کردن این خطا و مطلع کردن کاربر وجود دارند و معمولا از صفحه‌ی خطای سفارشی برای اینکار استفاده میشود. اما ایده‌ی بهتری نیز برای مقابله با این خطا وجود دارد!
فرض کنید اطلاعات فرم شما از طریق Ajax به سرور ارسال می‌شود و نتیجه بصورت Json به مروگر برگشت داده می‌شود. در حالت معمول در صورت رخ دادن خطای فوق، سرور کد 500 را برگشت می‌دهد و تنها راه هندل کردن آن در رویداد error شئ Ajax شما می‌باشد؛ آن‌هم با یک پیغام ساده. ولی من ترجیح می‌دهم به جای صادر کردن خطای 500، در صورت وقوع این خطا آن‌را بصورت یک خطای ModelState به کاربر نمایش دهم. به نظر من این‌کار وجهه‌ی بهتری دارد و ضمنا لازم نیست در هر رویدادی این خطا را هندل کنید. برای رسیدن به این هدف هم چندین راه وجود دارد که ساده‌ترین آن‌ها اینست که یک ModelBinder سفارشی ایجاد کنید و با هندل کردن این خطا، یک پیام خطا را به ModelState اضافه کنید و بعد به MVC بگویید که از این ModelBinder برای پروژه‌ی من استفاده کن. با این روش هرگاه کاربر داده‌ای حاوی کدهای HTML درون فیلدهای فرم وارد کرده باشد، بدون هیچ کار اضافه‌ای وضعیت ModelState شما Invalid می‌شود و خطای مدل به کاربر نمایش داده خواهد شد.
ابتدا کلاسی برای ModelBinder سفارشی خود ایجاد کنید و از کلاس DefaultModelBinder ارث بری کنید. سپس با بازنویسی متد BindModel آن منطق خود را پیاده سازی کنید:
using System.Web;
using System.Web.Mvc;
using System.Web.Helpers;
using System.Globalization;

namespace Parsnet
{
    public class ParsnetModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            try
            {
                return base.BindModel(controllerContext, bindingContext);
            }
            catch (HttpRequestValidationException ex)
            {
                var modelState = new ModelState();
                modelState.Errors.Add("اطلاعات ارسالی شما دارای کدهای HTML می‌باشند.");
                var key = bindingContext.ModelName;
                var value = controllerContext.RequestContext.HttpContext.Request.Unvalidated().Form[key];
                modelState.Value = new ValueProviderResult(value, value, CultureInfo.InvariantCulture);
                bindingContext.ModelState.Add(key, modelState);
            }

            return null;
        }
    }
}

در این کلاس ابتدا سعی میکنیم بطور عادی کار را به متد BindModel کلاس پایه بسپاریم. اگر داده‌های ارسال شده حاوی کد HTML نباشد، بدون هیچ خطایی Model Binding صورت می‌گیرد. ولی در صورتیکه کاربر در فیلدی، از کدهای HTML استفاده کرده باشد، یک خطای HttpRequestValidationException  رخ خواهد داد که با هندل کردن آن هدف خود را تامین می‌کنیم.
برای اینکار در قسمت catch بلوک مدیریت خطا، ابتدا یک نمونه از کلاس ModelState را میسازیم. بعد پیام خطای مورد نظر خود را به Errors‌های آن اضافه میکنیم. حال باید یک زوج کلید/مقدار برای این ModelState تعریف کنیم و به bindingContext اضافه کنیم. کلید ما در اینجا نام مدل جاری و مقدار آن هم نام فیلدی از فرم است که سبب بروز این خطا شده است.
حالا نهایت کاری که باید انجام دهیم اینست که در رویداد Application_Start این مدل بایندیگ سفارشی را جایگزین مدل بایندیگ پیش فرض کنیم:
    ModelBinders.Binders.DefaultBinder = new ParsnetModelBinder();

کار تمام است. از حالا به بعد در صورتیکه کاربر در فیلدهای هر فرمی از سایت شما از کدهای HTML استفاده کند دیگر خطایی رخ نمی‌دهد و فقط ModelState در وضعیت Invalid قرار میگیرد که میتوانید با قرار دادن یک ValidationMessage یا ValidationSummary به راحتی خطا را به کاربر نشان دهید.