مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت سوم - تنظیم مقادیر خواص اشیاء
در قسمت قبل، چون متد Validate سرویس تصدیق هویت استفاده شده، همواره مقدار false را بر می‌گرداند:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
     application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
شیء Mock آن‌را طوری تنظیم کردیم که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد. اما در این بین، کدهای بررسی سرویس creditScorer را کامنت کردیم:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
if (_creditScorer.Score < MinimumCreditScore)
{
    return application.IsAccepted;
}
تا آزمایش واحد ما با موفقیت به پایان برسد. در این قسمت، کار تنظیم مقادیر خواص آن‌را در آزمون واحد، به کمک Mocked objects انجام می‌دهیم تا این قسمت از کد نیز پوشش داده شود. برای این منظور به کلاس LoanApplicationProcessor مراجعه کرده و در متد Process آن، ابتدا مجددا از همان overload ساده‌ی فوق متد Validate بجای نمونه‌ی ref دار استفاده کرده و سپس کدهای creditScorer را نیز از حالت کامنت خارج می‌کنیم.


تنظیم مقدار خاصیت Score شیء Mock شده

اینترفیس ICreditScorer به صورت زیر تعریف شده‌است و دارای خاصیت Score می‌باشد که مقدار عددی آن با مقدار حداقل اعتبار تنظیم شده‌ی در کلاس LoanApplicationProcessor مقایسه خواهد شد (MinimumCreditScore = 100_000):
namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
    }
}
برای تنظیم مقدار خاصیت Score، در متد Accept آزمون‌های واحد تهیه شده، می‌توان به صورت زیر عمل کرد:
var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.Score).Returns(110_000);
که بسیار شبیه به نحوه‌ی تنظیم مقادیر بازگشتی متدها است. در متد Setup می‌توان به صورت strongly typed به تمام خواص اینترفیس ICreditScorer دسترسی یافت و سپس توسط متد Returns، مقدار بازگشتی آن‌ها را تنظیم نمود.
اکنون اگر متد آزمایش واحد Accept را بررسی کنیم، چون شخص درخواست دهنده، دارای اعتبار بیشتری از حداقل اعتبار مورد نیاز است، این آزمایش با موفقیت به پایان خواهد رسید. اگر این تنظیم صورت نمی‌گرفت، شیء mockCreditScorer، مقدار پیش‌فرض int یا همان صفر را به عنوان مقدار Score بازگشت می‌داد.


تنظیم مقادیر خواص تو در تو و سلسله مراتبی اشیاء Mock شده

برای کار با خواص تو در تو، ابتدا دو مدل زیر را ایجاد می‌کنیم:
namespace Loans.Models
{
    public class ScoreResult
    {
        public ScoreValue ScoreValue { get; }
    }

    public class ScoreValue
    {
        public int Score { get; }
    }
}
اکنون بجای مقدار ساده‌ی int Score { get; }، از نمونه‌ی ScoreResult فوق، در اینترفیس ICreditScorer استفاده خواهیم کرد:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
        
        ScoreResult ScoreResult { get; }
    }
}
در ادامه برای استفاده‌ی از ScoreResult، به کلاس LoanApplicationProcessor مراجعه کرده و در انتهای متد Process آن، این تغییر را ایجاد می‌کنیم:
//if (_creditScorer.Score < MinimumCreditScore)
if (_creditScorer.ScoreResult.ScoreValue.Score < MinimumCreditScore)
اینبار اگر متد آزمون واحد Accept را اجرا کنیم، با یک null reference exception به پایان می‌رسد؛ چون اولین سطح این شیء تو در تو، یعنی ScoreResult، مساوی نال است.
برای رفع این مشکل در متد آزمون واحد Accept، باید به صورت زیر عمل کرد:
var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.Score).Returns(110_000);

var mockScoreValue = new Mock<ScoreValue>();
mockScoreValue.Setup(x => x.Score).Returns(110_000);

var mockScoreResult = new Mock<ScoreResult>();
mockScoreResult.Setup(x => x.ScoreValue).Returns(mockScoreValue.Object);

mockCreditScorer.Setup(x => x.ScoreResult).Returns(mockScoreResult.Object);
ابتدا از پایین‌ترین سطح یعنی ScoreValue شروع و مقدار خاصیت Score آن‌را تنظیم می‌کنیم.
سپس یک سطح بالاتر را یعنی ScoreResult را تنظیم خواهیم کرد. در اینجا نیاز است خاصیت ScoreValue آن به mock object قبلی تنظیم شود. به همین جهت Returns آن به خاصیت Object شیء mockScoreValue، تنظیم شده‌است.
در آخر برای تنظیم خاصیت ScoreResult شیء mockCreditScorer اصلی، از شیء mockScoreResult استفاده خواهیم کرد.

در این حالت اگر متد آزمون واحد Accept را اجرا کنیم، اینبار به خطای زیر برخواهیم خورد:
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception:
System.NotSupportedException: Unsupported expression: x => x.Score
Non-overridable members (here: ScoreValue.get_Score) may not be used in setup / verification expressions.
عنوان می‌کند که خاصیت Score شیء ScoreValue، قابل بازنویسی نیست (Non-overridable). منظورش این است که برای mocking آن خاصیت، باید آن‌را virtual تعریف کنیم تا کتابخانه‌ی Moq بتواند آن‌را بازنویسی کند. به همین جهت، هر دو خاصیتی را که در اینجا قصد بازنویسی آن‌ها را داریم، به صورت virtual تعریف می‌کنیم:
namespace Loans.Models
{
    public class ScoreResult
    {
        public virtual ScoreValue ScoreValue { get; }
    }

    public class ScoreValue
    {
        public virtual int Score { get; }
    }
}
اکنون اگر متد آزمایش واحد Accept را بررسی کنیم با موفقیت به پایان خواهد رسید.


ساده سازی روش تنظیم مقادیر خواص تو در تو و سلسله مراتبی اشیاء Mock شده

روش دیگری نیز برای تنظیم مقادیر خواص تو در تو در کتابخانه‌ی Moq وجود دارد:
mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);
کتابخانه‌ی Moq قادر است به نحوی که مشاهده می‌کنید، سلسله مراتب اشیاء را به صورت strongly typed ایجاد کرده و در نهایت خاصیت Score آن‌را به 110_000 تنظیم کند.
بدیهی است در این حالت نیز باید شرط virtual بودن این خواص، برقرار باشد؛ در غیراینصورت همان استثنای NotSupportedException را دریافت خواهیم کرد.

یک نکته: اگر در زمان تشکیل یک Mock object، مقدار خاصیت DefaultValue آن‌را به صورت زیر تنظیم کنیم:
var mockCreditScorer = new Mock<ICreditScorer> { DefaultValue = DefaultValue.Mock };
تمام خواص تو در توی موجود در ICreditScorer، به صورت خودکار با نمونه‌های پیش‌فرض آن‌ها مقدار دهی و آماده‌ی استفاده خواهند شد. اگر بجای مقدار DefaultValue.Mock از DefaultValue.Empty استفاده شود، این مقادیر پیش‌فرض، نال خواهد بود (که همان حالت پیش‌فرض new Mock است).


بررسی تغییرات مقادیر خواص اشیاء Mock شده

کتابخانه‌ی Moq، امکان ردیابی تغییرات مقادیر خواص اشیاء Mock شده را نیز داراست. برای نمایش آن، فرض کنید خاصیت جدید Count را به اینترفیس ICreditScorer اضافه کرده‌ایم:
using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
        
        ScoreResult ScoreResult { get; }
        
        int Count { get; set; }
    }
}
سپس در کلاس LoanApplicationProcessor و متد Process آن، هربار که CalculateScore فراخوانی می‌شود، یکبار مقدار Count را افزایش می‌دهیم:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
_creditScorer.Count++;
اکنون در متد آزمون واحد Accept، بررسی می‌کنیم که آیا پس از یکبار فراخوانی متد CalculateScore، مقدار Count برای مثال 1 شده‌است یا خیر؟
Assert.AreEqual(1, mockCreditScorer.Object.Count);
تا اینجا اگر آزمون واحد را اجرا کنیم، با شکست مواجه خواهد شد. چون کتابخانه‌ی Moq تغییرات مقادیر خواص شیء mockCreditScorer.Object را ردیابی نمی‌کند و مقدار mockCreditScorer.Object.Count، همان مقدار پیش‌فرض نوع int، یعنی صفر می‌باشد.
برای فعال سازی ردیابی تغییرات مقادیر خاصیت Count، تنها کافی است آن‌را توسط متد SetupProperty، معرفی کنیم:
mockCreditScorer.SetupProperty(x => x.Count);
پس از این تغییر، بررسی متد آزمون واحد Accept با موفقیت به پایان می‌رسد.

در اینجا می‌توان یک مقدار اولیه را هم درنظر گرفت:
mockCreditScorer.SetupProperty(x => x.Count, 10);
بدیهی است در این صورت Assert.AreEqual ما با شکست مواجه می‌شود؛ چون اینبار مقدار Count نهایی، بر اساس این مقدار اولیه، 11 خواهد بود.


فعالسازی بررسی تغییرات تمام مقادیر خواص اشیاء Mock شده

اگر تعداد خواصی که قرار است مورد ردیابی قرارگیرند زیاد است، بجای فراخوانی متد SetupProperty بر روی تک تک آن‌ها، می‌توان تمام آن‌ها را به صورت زیر تحت کنترل قرار داد:
mockCreditScorer.SetupAllProperties();

نکته‌ی مهم: محل قرارگیری SetupAllProperties مهم است. برای مثال اگر این سطر را پس از سطر تنظیم مقدار پیش‌فرض x.ScoreResult.ScoreValue.Score قرار دهید، آزمایش با شکست مواجه می‌شود؛ چون تنظیمات بازگشت مقادیر پیش‌فرض خواص را به طور کامل بازنویسی می‌کند. بنابراین این سطر باید پیش از سطر تنظیم مقادیر پیش‌فرض خواص Mock شده، فراخوانی شود تا بر روی این مقادیر تنظیمی، تاثیری نداشته باشد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-03.zip
نظرات مطالب
Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
یک نکته‌ی تکمیلی: روش تعریف data binding دو طرفه در کامپوننت‌ها
در مطلب جاری، binding دو طرفه بررسی شد؛ که نکته‌ی مورد بحث آن، به ویژگی‌های استاندارد HTML مانند ویژگی value یک input استاندارد اختصاص داشت. اما اگر بخواهیم در کامپوننت‌های سفارشی خود، این binding دو طرفه را تعریف کنیم تا قابل اعمال به خواص و ویژگی‌های #C باشد (مانند bind-ProprtyName@)، روش کار به نحو دیگری است. نمونه‌ی آن کامپوننت استاندارد InputText خود Blazor است که در اینجا هم دارای bind-Value@ است؛ اما این Value (شروع شده‌ی با حروف بزرگ) یک خاصیت #C تعریف شده‌ی در کلاس InputText است و نه یک ویژگی استاندارد HTML که عموما با حروف کوچک شروع می‌شوند:
<InputText @bind-Value="employee.FirstName" />
برای رسیدن به bind-Value@ فوق، سه مرحله باید طی شود:
الف) یک پارامتر عمومی به نام Value باید در کلاس کامپوننت جاری تعریف شود تا بتوان از طریق والد آن، مقداری را دریافت کرد (یک طرف binding به این نحو تشکیل می‌شود):
[Parameter] public string Value { set; get; }
ب) یک رویداد خاص Blazor، به نام EventCallback نیز باید اضافه شود تا به کامپوننت استفاده کننده‌ی از کامپوننت جاری، تغییرات را اطلاع رسانی کند (به این نحو است که این binding، دو طرفه می‌شود و تغییرات رخ‌داده‌ی در اینجا، به کامپوننت والد در برگیرنده‌ی آن، اطلاع رسانی می‌شود):
[Parameter] public EventCallback<string> ValueChanged { get; set; }
نام آن هم ویژه‌است. یعنی حتما باید با نام پارامتر Value شروع شود (نام پارامتری که قرار است binding دو طرفه روی آن اعمال گردد) و حتما باید ختم به واژه‌ی Changed باشد. این الگوی استاندارد از این جهت مورد استفاده قرار می‌گیرد که در تعریف InputText فوق، چنین پارامتر و مقدار دهی را مشاهده نمی‌کنیم، اما ... در پشت صحنه توسط Blazor به صورت خودکار تشکیل شده و مدیریت می‌شود.

نکته‌ی مهم: در اینجا بجای EventCallback، از Action هم می‌توان استفاده کرد:
[Parameter] public Action<string> ValueChanged { get; set; }
تفاوت اصلی و مهم آن با EventCallback، در فراخوانی نشدن خودکار متد StateHasChanged، در پایان کار آن است. زمانیکه EventCallback ای فراخوانی می‌شود، Blazor به صورت خودکار در پایان کار آن، متد StateHasChanged را نیز فراخوانی می‌کند تا والد دربرگیرنده‌ی کامپوننت جاری، مجددا رندر شود (به همراه تمام کامپوننت‌های فرزند آن). اما <Action<T فاقد این درخواست خودکار رندر و به روز رسانی مجدد UI است.

ج) برای فعالسازی اعتبارسنجی استاندارد فرم‌های Blazor، نیاز به خاصیت ویژه‌ی سومی نیز هست (که اختیاری است):
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
این خاصیت ویژه نیز باید با نام Value یا همان پارامتر تعریف شده، شروع شود و حتما باید ختم به واژه‌ی Expression شود. در مورد اعتبارسنجی‌ها در قسمت‌های بعدی بیشتر بحث خواهد شد. این پارامتر و مدیریت آن توسط خود Blazor صورت می‌گیرد و به ندرت توسط ما به صورت مستقیمی مقدار دهی خواهد شد؛ مگر اینکه بخواهیم کامپوننتی مانند InputText را سفارشی سازی کنیم.

مرحله‌ی آخر این طراحی، فراخوانی پارامتر ValueChanged است تا به کامپوننت والد این تغییرات را اطلاع رسانی کنیم. روش استاندارد آن به صورت زیر است:
private string _value;

[Parameter]
public string Value
{
   get => _value;
   set
   {
       var hasChanged = string.Equals(_value, value, StringComparison.Ordinal);
       if (hasChanged)
       {
           _value = value;

           if (ValueChanged.HasDelegate)
           {
              _ = ValueChanged.InvokeAsync(value);
           }
         }
    }
}
در اینجا در قسمت set همان پارامتر Value ای که در قسمت الف تعریف کردیم، در صورت بروز تغییری نسبت به قبل، متد InvokeAsync پارامتر ValueChanged را فراخوانی می‌کنیم. تا همین اندازه برای اطلاع رسانی به والد کافی است؛ همچنین وجود مقایسه‌ی بین مقدار جدید و مقدار قبلی، برای کاهش تعداد بار به روز رسانی UI ضروری است. هر بار که ValueChanged.InvokeAsync فراخوانی می‌شود، والد کامپوننت جاری، یکبار دیگر UI را مجددا رندر خواهد کرد. بنابراین هر چقدر تعداد این رندرها کمتر باشد، کارآیی برنامه بهبود خواهد یافت.
در این قطعه کد، بررسی ValueChanged.HasDelegate را هم مشاهده می‌کنید. زمانیکه پارامتر Value ای با طی سه مرحله‌ی فوق تعریف شد، قرار نیست حتما توسط bind-Value@ مورد استفاده قرار گیرد. می‌توان Value را به صورت یک طرفه هم مورد استفاده قرار داد. در این حالت دو پارامتر ب و ج دیگر توسط Blazor ایجاد و مقدار دهی نشده و رهگیری نخواهند شد. یعنی تعریف bind-Value@ در سمت والد، معادل سیم کشی خودکار به ValueChanged و ValueExpression از طرف Blazor است و تعریف دستی آن‌ها ضرورتی ندارد. اما می‌توان bind-Value@ را هم تعریف نکرد و فقط نوشت Value. در این حالت از تنظیمات ب و ج صرفنظر می‌شود. بنابراین ضروری است که بررسی کنیم آیا پارامتر ValueChanged واقعا متصل به روال رویدادگردانی شده‌است یا خیر. اگر خیر، نیازی به اطلاع رسانی و فراخوانی متد ValueChanged.InvokeAsync نیست.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification
در مستندات خود مایکروسافت نیز پیکیج WebOptimizer معرفی شده و با توجه به اینکه asp.net core به صورت native  مبحث bundling و minification را پوشش نمی‌دهد، بهترین گزینه همین بسته میباشد. اما چند نکته در این بسته وجود داره:

- وقتی شما از تنظیمات پیش فرض (بدون اعمال هیچ کانفیگی) استفاده کنید، تمام فایل‌های استاتیکی که در صفحه در خواست شده را minify خواهد کرد.
برای مثال دو فایل bootstrap.min.css و index.css را در یک صفحه رفرنس داده اید. در هر ریکوئست (اگر تنظیمات را اعمال نکرده باشید) تمامی فایل‌ها را minify خواهد کرد که این امر سبب کاهش مدت زمان بارگذاری صفحه و خطا در نمایش و چیدمان خواهد شد.
اما میتوانید یک مسیر را به صورت سراسری مشخص کنید و تعدادی فایل دیگر را هم مشخص کنید تا فایل‌های از پیش minify شده را نیز به اشتباه فشرده سازی نکند.
برای مثال میتوان گفت فقط تمام فایل‌های مسیر wwwroot/myJsFiles را فشرده سازی کن و  در این صورت کاری به فولدر wwwroot/myLibraries  نخواهد داشت. یا فقط فایل‌های b.js، a.js و c.js فشرده شوند.
- minify در زمان runtime: هر بار که ریکوئست به یک فایل استاتیک ارسال شود، این پکیج در همان لحظه عملیات minify را شروع خواهد کرد.
(در بارگذاری اولیه و قبل از cache مرورگر) البته که اگر نکته اول را رعایت کرده باشید، پرفرمنس را نه تنها کاهش میدهد، بلکه باعث افزایش هم میشود. ولی قطعا در حالتی که از قبل فایل‌ها minify شده باشند، پرفرمنس بیشتر خواهد بود.
- متاسفانه این پکیج فشرده سازی html را ندارد و نمونه خوبی در حد این بسته را پیدا نکردم.
مطالب
API Versioning in ASP.NET Core
در مطالب گذشته، درباره‌ی پیاده سازی API Versioning در ASP.NET Web API و الزامات استفاده‌ی از آن، صحبت شده‌است. اگر مطلب ذکر شده را مطالعه کنید، می‌بینید که پیاده سازی Versioning در ASP.NET Web API کاری دشوار و زمانبر بود؛ اما در ASP.NET Core انجام تمامی آن مراحل، در 1 خط صورت می‌گیرد که در ادامه آن را بررسی میکنیم.

برای شروع با اجرای این دستور در Package Manager Console، پکیج Microsoft.AspNetCore.Mvc.Versioning را داخل پروژه نصب می‌کنیم:
Install-Package Microsoft.AspNetCore.Mvc.Versioning
بعد از نصب، کافیست کد زیر را داخل متد ConfigureServices در فایل Startup.cs پروژه‌ی خود اضافه کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddApiVersioning();
    // ...
}
در ابتدا بعد از نصب این پکیج، ممکن است شما API هایی داشته باشید که برای آن‌ها از قبل ورژنی مشخص نکرده باشید (بصورت explicit ). می‌توانید یک Version پیش‌فرض را به برنامه اضافه کرده و برای Endpoint هایی که ورژن ندارند، از آن استفاده کنید :  
services.AddApiVersioning(opt =>
{
    opt.AssumeDefaultVersionWhenUnspecified = true;
    opt.DefaultApiVersion = new ApiVersion(1, 0);
});
در این صورت، API شما به شکل زیر قابل دسترسی خواهد بود:
  • api/foo?api-version=1.0/

پارامتر DefaultApiVersion را برابر با یک ApiVersion قرار داده‌ایم. کلاس ApiVersion دارای Overload‌های مختلفی است. Overload ای را که ما در اینجا از آن استفاده کرده‌ایم، بعنوان پارامتر اول Major Version و برای پارامتر دوم، Minor Version را میگیرد. همچنین بجای Major و Minor میتوان از یک DateTime بعنوان ورژن استفاده کرد:
opt.DefaultApiVersion = new ApiVersion(new DateTime(2018, 10, 22));
و در این صورت API شما به شکل زیر قابل دسترسی می‌باشد: 
  • api/foo?api-version=2018-10-22/

URL Path Segment Versioning

تا به اینجا API Versioning ما بر اساس Query String Parameters انجام می‌شود؛ اما اگر بخواهیم بجای آن به شکل مقابل به API‌‌های خود دسترسی داشته باشیم چطور؟ : api/v1/foo/
برای پیاده سازی به این صورت، کافیست Route کنترلر خود را به این شکل تغییر دهید:
[Route("api/v{version:apiVersion}/[controller]")]
public class FooController : ControllerBase
{
    public ActionResult<IEnumerable<string>> Get()
    {
        return new[] { "value1", "value2" };
    }
}

Header Versioning
 روش سوم انجام Versioning، استفاده از Header است. برای فعال کردن Header Versioning، داخل Startup، کد خود را به شکل زیر تغییر دهید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddApiVersioning(opt => opt.ApiVersionReader = new HeaderApiVersionReader("api-version"));
}
با انجام این تغییر، برای تست API خود دیگر نمی‌توانید از Browser استفاده کنید که این یکی از مشکلات این روش است. برای تست کردن یک درخواست GET ساده مجبور به استفاده از ابزارهایی همچون Postman, CURL و ... هستید. ما در اینجا برای تست از Postman استفاده می‌کنیم:


Deprecating
ممکن است بخواهید یک ورژن را منسوخ دانسته و آن را Deprecate کنید. دقت کنید که Deprecate کردن یک API، به معنی از کار افتادن آن نیست. به این صورت میتوانید یک Endpoint از برنامه خود را Deprecate شده «معرفی» کنید:
[ApiVersion("2")]
[ApiVersion("1", Deprecated = true)]
[Route("api/v{version:apiVersion}/[controller]")]
public class FooController : ControllerBase
{
    [HttpGet]
    public string Get() => "I'm deprecated, Bye bye :(";

    [HttpGet, MapToApiVersion("2.0")]
    public string GetV2() => "Hello world ! :D";
}  
برای برگرداندن نام API‌ها و وضعیت Support شان داخل Response Header، باید ReportApiVersions فعال شود:
services.AddApiVersioning(opt =>
{
    opt.DefaultApiVersion = new ApiVersion(1, 0);
    opt.AssumeDefaultVersionWhenUnspecified = true;
    opt.ReportApiVersions = true;
});
که در نتیجه‌ی آن، Response Header برگشتی به این شکل خواهد بود :


Ignoring Versioning
اگر داخل برنامه‌ی خود، کنترلری را دارید که در طی زمان آپدیت نشده و تغییر نخواهد کرد، می‌توانید از Version زدن آن با استفاده از ApiVersionNeutral جلوگیری کنید:
[ApiVersionNeutral]
[Route("api/[controller]")]
public class BarController : ControllerBase
{
    public string Get() => HttpContext.GetRequestedApiVersion().ToString();
}
اجرای این متد در صورت غیرفعال بودن AssumeDefaultVersionWhenUnspecified باعث وقوع خطای NullReferenceException می‌شود و بدین معناست که همانطور که انتظار داشتیم، Version ای برای این Endpoint تنظیم نشده است.

مطلب تکمیلی:
برای آپدیت کردن و یا معرفی نسخه‌ی جدیدی از یک کنترلر با ورژنی متفاوت، نیازی به Rename کردن کلاس قبلی برای رفع Conflict با نام فایل جدید نیست؛ با استفاده از namespace‌ها میتوانید کنترلری همنام، اما با ورژن و عملکردی متفاوت داشته باشید:
namespace TestVersioning.Controllers.V1
{
    [ApiVersion("1", Deprecated = true)]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class FooController : ControllerBase
    {
        public string Get() => "I'm deprecated, Bye bye :(";
    }
}

namespace TestVersioning.Controllers.V2
{
    [ApiVersion("2")]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class FooController : ControllerBase
    {
        public string GetV2() => "Hello world ! :D";
    }
}

مطالب
کوئری نویسی در EF Core - قسمت هفتم - کار با رشته‌‌ها
هدف از این سری مثال‌ها، آشنایی با متدها و توابعی است که در حین کار با خواص رشته‌ای در LINQ to Entities، مجاز به استفاده‌ی از آن‌ها هستیم و همچنین اگر تابعی در EF-Core هنوز تعریف نشده بود، راه حل چیست.


مثال 1: نام تمام کاربران را با قالب 'Surname, Firstname'  نمایش دهید.

var members = context.Members
                                    .Select(member => new { Name = member.Surname + ", " + member.FirstName })
                                    .ToList();
متد Select می‌تواند به همراه اعمال محاسباتی ساده‌ای نیز باشد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.
با این خروجی:



مثال 2: تمام امکاناتی را که با Tennis شروع می‌شوند، لیست کنید.
این گزارش به همراه تمام ستون‌های جدول است.

var facilities = context.Facilities
                                        .Where(facility => facility.Name.StartsWith("Tennis"))
                                        .ToList();
متدهای استانداردی مانند StartsWith، EndsWith و Contains را می‌توان بر روی خواص رشته‌ای بکار برد.
با این خروجی:



مثال 3: تمام امکاناتی را که با tennis شروع می‌شوند، لیست کنید. این جستجو باید غیرحساس به بزرگی و کوچکی حروف باشد.
این گزارش به همراه تمام ستون‌های جدول است.

نیازی به انجام مجزای این تمرین نیست؛ چون پاسخ آن همان پاسخ مثال 2 است. Collation پیش‌فرض در SQL Server، غیرحساس به بزرگی و کوچکی حروف است. بنابراین چه tennis را جستجو کنیم و یا TeNnis را، تفاوتی نمی‌کند.


مثال 4: شماره تلفن‌های دارای پرانتز را لیست کنید.
این گزارش باید به همراه ستون‌های memid, telephone باشد.

روش اول: در اینجا دوبار از متد Contains استفاده شده‌است:
var members = context.Members
                                    .Select(member => new { member.MemId, member.Telephone })
                                    .Where(member => member.Telephone.Contains("(")
                                                    && member.Telephone.Contains(")"))
                                    .ToList();
با این خروجی:


روش دوم: اگر می‌خواهیم کنترل بیشتری را بر روی خروجی نهایی LIKE تولیدی داشته باشیم، می‌توان از متد سفارشی استاندارد EF.Functions.Like استفاده کرد که از حروف wild cards نیز پشتیبانی می‌کند:
members = context.Members
                                    .Select(member => new { member.MemId, member.Telephone })
                                    .Where(member => EF.Functions.Like(member.Telephone, "%[()]%"))
                                    .ToList();
با این خروجی:



مثال 5: کد پستی‌ها 5 رقمی هستند. گزارشی را تهیه کنید که در آن اگر کدپستی کمتر از 5 رقم بود، ابتدای آن با صفر شروع شود.
هدف اصلی از این مثال، اعمال متد PadLeft(5, '0') به خاصیت member.ZipCode است.

روش اول: EF-Core فعلا قابلیت ترجمه‌ی PadLeft(5, '0') را به معادل SQL آن‌را ندارد. به همین جهت مجبور هستیم ابتدا ZipCode‌ها را به صورت رشته‌ای بازگشت دهیم که در اینجا استفاده‌ی از Convert.ToString مجاز است.
با این خروجی:
SELECT   CONVERT (NVARCHAR (MAX), [m].[ZipCode]) AS [Zip]
FROM     [Members] AS [m]
ORDER BY CONVERT (NVARCHAR (MAX), [m].[ZipCode]);
 سپس می‌توان بر روی لیست آماده‌ی موجود در حافظه، از LINQ to Objects استفاده کرد و در این حالت دسترسی کاملی به تمام امکانات زبان #C وجود دارد:
var members = context.Members
                                    .Select(member => new { ZipCode = Convert.ToString(member.ZipCode) })
                                    .OrderBy(m => m.ZipCode)
                                    .ToList();
// Now using LINQ to Objects
members = members.Select(member => new { ZipCode = member.ZipCode.PadLeft(5, '0') })
                                                    .OrderBy(m => m.ZipCode)
                                                    .ToList();

روش دوم: SQL Server به همراه تابع استانداردی به نام Replicate است که از آن می‌توان برای شبیه سازی PadLeft، بدون متوسل شدن به LINQ to Objects، استفاده کرد. اما چون این تابع هنوز به EF-Core معرفی نشده‌است، نیاز است خودمان اینکار را انجام دهیم. در این روش، از متد SqlDbFunctionsExtensions.SqlReplicate استفاده می‌شود. روش تعریف این نوع متدها را در مطلب «امکان تعریف توابع خاص بانک‌های اطلاعاتی در EF Core» پیشتر بررسی کرده‌ایم که برای مثال در اینجا چنین شکلی را پیدا می‌کند:
namespace EFCorePgExercises.Utils
{
    public static class SqlDbFunctionsExtensions
    {
        public static string SqlReplicate(string expression, int count)
            => throw new InvalidOperationException($"{nameof(SqlReplicate)} method cannot be called from the client side.");

        private static readonly MethodInfo _sqlReplicateMethodInfo = typeof(SqlDbFunctionsExtensions)
            .GetRuntimeMethod(
                nameof(SqlDbFunctionsExtensions.SqlReplicate),
                new[] { typeof(string), typeof(int) }
            );


        public static void AddCustomSqlFunctions(this ModelBuilder modelBuilder)
        {
            modelBuilder.HasDbFunction(_sqlReplicateMethodInfo)
                .HasTranslation(args =>
                {
                    return SqlFunctionExpression.Create("REPLICATE",
                        args,
                        _sqlReplicateMethodInfo.ReturnType,
                        typeMapping: null);
                });
        }
    }
}
پس از آن فقط کافی است متد AddCustomSqlFunctions را به Context برنامه معرفی کنیم:
namespace EFCorePgExercises.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
         // ...

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
         // ...
            modelBuilder.AddCustomSqlFunctions();
         // ...
        }
    }
}
اکنون می‌توان از تابع SqlDbFunctionsExtensions.SqlReplicate جهت شبیه سازی PadLeft به صورت زیر استفاده کرد:
var newMembers = context.Members
                                        .Select(member => new
                                        {
                                            ZipCode =
                                                SqlDbFunctionsExtensions.SqlReplicate(
                                                    "0", 5 - Convert.ToString(member.ZipCode).Length)
                                                + member.ZipCode
                                        })
                        .OrderBy(m => m.ZipCode)
                        .ToList();
با این خروجی:



مثال 6: اولین حرف نام خانوادگی کاربران در کل ردیف‌های جدول چندبار تکرار شده‌است؟
این گزارش باید به همراه ستون‌های letter,  count باشد.

var members = context.Members
                                    .Select(member => new { Letter = member.Surname.Substring(0, 1) })
                                    .GroupBy(m => m.Letter)
                                    .Select(g => new
                                    {
                                        Letter = g.Key,
                                        Count = g.Count()
                                    })
                                    .OrderBy(r => r.Letter)
                                    .ToList();
هدف از این مثال بیان مجاز بودن استفاده‌ی از متد Substring بر روی خواص رشته‌ای است که EF-Core امکان ترجمه‌ی آن‌ها را به کدهای SQL دارد.
با این خروجی:



مثال 7: حروف '-','(',')', ' ' را از شماره تلفن‌ها حذف کنید.
این گزارش باید به همراه ستون‌های memid, telephone باشد.

بانک اطلاعاتی PostgreSQL به همراه تابع استاندارد regexp_replace است و می‌توان از آن برای حل یک چنین مسایلی استفاده کرد:
select memid, regexp_replace(telephone, '[^0-9]', '', 'g') as telephone
from members
order by memid;
اما SQL Server هنوز هم به همراه یک چنین تابعی نیست. بنابراین از روش زیر نیز می‌توان مثال جاری را حل کرد:
var members = context.Members
                                .Select(member => new
                                {
                                    member.MemId,
                                    Telephone = member.Telephone.Replace("-", "")
                                                        .Replace("(", "")
                                                        .Replace(")", "")
                                                        .Replace(" ", "")
                                })
                                .OrderBy(r => r.MemId)
                                .ToList();
با این خروجی:



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت چهارم - انجام اعمال async با Redux
در قسمت اول این سری، با مفاهیم توابع خالص و ناخالص آشنا شدیم و همچنین عنوان شد که هرگونه تعامل با یک API خارجی به عنوان یک اثر جانبی و یا side effect در نظر گرفته شده و توابع را ناخالص می‌کند. به علاوه Redux تنها امکان کار با توابع خالص قابل پیش بینی را دارد و همچنین dispatch تمام اکشن‌ها توسط آن، به صورت پیش‌فرض synchronous است و نه asynchronous. اما در برنامه‌های واقعی نیاز است بتوان با یک API خارجی کارکرد و اطلاعاتی را از آن دریافت نمود و یا به آن ارسال کرد. برای رفع این مشکل، یک کتابخانه‌ی کمکی به نام redux-thunk ایجاد شده‌است که جزئیات کار با آن‌را در این قسمت بررسی می‌کنیم.


معرفی کتابخانه‌ی Redux Thunk

thunk، تابعی است که خروجی تابعی دیگر است؛ مانند مثال زیر:
function definitelyNotAThunk() {
   return function aThunk() {
     console.log('Hello, I am a thunk.');
   }
}
هدف اصلی از انجام یک چنین کاری، فراهم آوردن روشی برای اجرای به تاخیر افتاده‌است. برای مثال زمانیکه برای اجرای آن می‌نویسیم ()definitelyNotAThunk، تابع درونی آن هنوز اجرا نشده‌است. اجرای کامل آن چنین شکلی را دارد: ()()definitelyNotAThunk. حالا فرض کنید میان‌افزاری پیش از اجرای reducer قرار گرفته‌است که به تمام اشیاء رسیده‌ی به آن (یا همان اکشن‌ها در اینجا) گوش فرا می‌دهد. اگر اکشنی بجای یک شیء، یک تابع را بازگرداند، این میان‌افزار، آن‌را اجرا می‌کند یا همان ()() که عنوان شد. این کل کاری است که میان‌افزار 14 سطری redux-thunk انجام می‌دهد. زمانیکه از این میان‌افزار استفاده می‌شود، تابع درونی، دو پارامتر dispatch و getState را دریافت می‌کند. پارامتر dispatch که در حقیقت یک متد است، امکان اجرای اعمال synchronous و ارسال آن‌ها را به سمت reducer متناظر، میسر می‌کند.
برای مثال فرض کنید که نیاز است یک فراخوانی Ajax ای صورت گیرد و پس از پایان آن، جهت به روز رسانی state، یک شیء اکشن، به سمت reducer متناظری dispatch شود. مشکل اینجا است که نمی‌توان به Redux، یک callback حاصل از دریافت نتیجه‌ی عملیات Ajax ای و یا یک Promise را ارسال کرد. تمام این‌ها یک اثر جانبی یا side effect هستند که با توابع خالص Redux ای سازگاری ندارند. برای مدیریت یک چنین مواردی، یک میان‌افزار را به نام redux-thunk ایجاد کرده‌اند که اجازه‌ی dispatch تابعی را می‌دهد (همان thunk در اینجا) که قرار است action اصلی را در زمانی دیگر dispatch کند. به این ترتیب Redux اطلاعاتی را در مورد یک عمل async نخواهد داشت؛ میان‌افزاری در این بین آن‌را دریافت می‌کند و زمانیکه آن‌را dispatch می‌کنیم، آنگاه اکشن متناظر با آن، به redux منتقل می‌شود. به این ترتیب امکان منتظر ماندن تا زمان رسیدن پاسخ از شبکه، میسر می‌شود.
فرض کنید یک action creator متداول به صورت زیر ایجاد شده‌است:
export const getAllItems = () => ({
   type: UPDATE_ALL_ITEMS,
   items,
});
اکنون این سؤال مطرح می‌شود که چگونه می‌توان متوجه شد، پاسخی از سمت API دریافت شده‌است؟
برای پاسخ به این سؤال، اینبار action creator فوق را بر اساس الگوی redux-thunk به صورت زیر بازنویسی می‌کنیم:
export const getAllItems = () => {
  return dispatch => {
    Api.getAll().then(items => {
      dispatch({
        type: UPDATE_ALL_ITEMS,
        items,
      });
    });
  };
};
اینبار action creator ای را داریم که بجای بازگشت یک شیء، یک تابع را بازگشت داده‌است که به آن thunk گفته می‌شود و پارامتر dispatch را دریافت می‌کند. در این حالت زمانیکه یک Promise بازگشت داده می‌شود (امکان منتظر نتیجه شدن را فراهم می‌کند)، کار dispatch اکشن اصلی مدنظر (برای مثال ارسال آرایه‌ای از اشیاء)، صورت می‌گیرد.


برپایی پیش‌نیازها

در اینجا برای افزودن کامپوننتی که اطلاعات خودش را از یک API خارجی تامین می‌کند، از همان برنامه‌ی به همراه کامپوننت شمارشگر که در قسمت قبل آن‌را تکمیل کردیم، استفاده می‌کنیم. فقط در آن کتابخانه‌ها‌ی Axios و همچنین redux thunk را نیز نصب می‌کنید. به همین جهت در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios redux-thunk
برنامه‌ی backend مورد استفاده هم همان برنامه‌ای است که از قسمت 22 شروع به توسعه‌ی آن کردیم و کدهای کامل آن‌را از پیوست‌های انتهای بحث، می‌توانید دریافت کنید. این برنامه که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است. برای راه اندازی آن، به پوشه‌ی این برنامه، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts آن در مرورگر دسترسی یافت.

پس از نصب پیش‌نیازها و راه اندازی برنامه‌ی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api، ایجاد می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
در ادامه می‌خواهیم در برنامه‌ی React خود، لیست مطالب برنامه‌ی backend را از سرور دریافت کرده و نمایش دهیم.


افزودن میان‌افزار redux-thunk به برنامه

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


چون نیاز به عملیات async وجود دارد، باید از میان‌افزار مخصوص thunk برای انجام آن استفاده کرد. برای این منظور به فایل src\index.js مراجعه کرده و میان‌افزار thunk را توسط تابع applyMiddleware، به متد createStore، معرفی می‌کنیم:
import { applyMiddleware, compose, createStore } from "redux";
import thunk from "redux-thunk";

//...

const store = createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

//...
در اینجا چون نیاز است چندین تابع را به متد createStore ارسال کرد، باید از متد compose برای اعمال دسته جمعی آن‌ها کمک گرفت.


دریافت اطلاعات از یک API خارجی به کمک redux-thunk

پس از آشنایی با روش کلی برقراری اتصالات سیستم react-redux در قسمت قبل، پیاده سازی دریافت اطلاعات را بر اساس همان الگو، پیاده سازی می‌کنیم:
1)  ایجاد نام نوع اکشن متناظر با دکمه‌ی افزودن مقدار
به فایل src\constants\ActionTypes.js، نوع جدید دریافت مطالب را اضافه می‌کنیم:
export const GetPostsSuccess = "GetPostsSuccess";
export const GetPostsStarted = "GetPostsStarted";
export const GetPostsFailure = "GetPostsFailure";
در حین دریافت اطلاعات از API، حداقل سه اکشن نمایش loading (و یا GetPostsStarted در اینجا)، نمایش نهایی اطلاعات دریافت شده‌ی از سرور (و یا GetPostsSuccess در اینجا) و یا نمایش خطاهای حاصل (با نوع GetPostsFailure در اینجا) باید مدنظر باشند. به همین جهت سه نوع مختلف در اینجا تعریف شده‌اند.

2) ایجاد متد Action Creator
در فایل src\actions\index.js، متد ایجاد کننده‌ی شیء اکشن ارسالی به reducer متناظری را تعریف می‌کنیم تا بتوان بر اساس نوع آن در reducer دریافت اطلاعات، منطق نهایی را پیاده سازی کرد:
import axios from "axios";

import { apiUrl } from "../config.json";
import * as types from "../constants/ActionTypes";

export const fetchPosts = () => {
  return (dispatch, getState) => {
    dispatch(getPostsStarted());
    axios.get(apiUrl + "/posts").then(response => {
      dispatch(getPostsSuccess(response.data)).catch(err => {
        dispatch(getPostsFailure(err));
      });
    });
  };
};

export const fetchPostsAsync = () => {
  return async (dispatch, getState) => {
    dispatch(getPostsStarted());
    try {
      const { data } = await axios.get(apiUrl + "/posts");
      console.log(data);
      dispatch(getPostsSuccess(data));
    } catch (error) {
      dispatch(getPostsFailure(error));
    }
  };
};

const getPostsSuccess = posts => ({
  type: types.GetPostsSuccess,
  payload: { posts }
});

const getPostsStarted = () => ({
  type: types.GetPostsStarted
});

const getPostsFailure = error => ({
  type: types.GetPostsFailure,
  payload: {
    error
  }
});
- در اینجا همان الگوی بازگشت یک تابع را بجای یک شیء، در توابع action creator، مشاهده می‌کنید.
- تابع fetchPosts، از همان روش قدیمی callback، برای مدیریت اطلاعات دریافتی از سرور استفاده می‌کند. زمانیکه اطلاعاتی دریافت شد، آن‌را با فراخوانی dispatch و با قالبی که تابع getPostsSuccess ارائه می‌دهد، به reducer متناظر، ارسال می‌کند.
- تابع fetchPostsAsync، نمونه‌ی به همراه async/await کار با کتابخانه‌ی axios است. هر دو روش callback و یا async/await در اینجا پشتیبانی می‌شوند.

به صورت پیش‌فرض action creators کتابخانه‌ی redux از اعمال async پشتیبانی نمی‌کنند. برای رفع این مشکل پس از ثبت میان‌افزار thunk، اینبار متدهای action creator، بجای بازگشت یک شیء، یک تابع را بازگشت می‌دهند که این تابع درونی در زمانی دیگر توسط میان‌افزار thunk و پیش از رسیدن به reducer، فراخوانی خواهد شد. این تابع درونی، دو پارامتر dispatch و  getState را دریافت می‌کند. هر دوی این‌ها نیز متد هستند. برای مثال اگر نیاز به دریافت وضعیت فعلی state در اینجا وجود داشت، می‌توان متد ()getState رسیده را فراخوانی کرد و حاصل آن‌را بررسی نمود. برای مثال شاید تصمیم گرفته شود که بر اساس وضعیت فعلی state، نیازی نیست تا اطلاعاتی از سرور دریافت شود و بهتر است همان اطلاعات کش شده‌ی موجود در state را بازگشت دهیم. البته در این مثال فقط از متد dispatch ارسالی، برای بازگشت نتیجه‌ی نهایی به reducer متناظر، استفاده شده‌است.

- در نهایت آرایه‌ی اشیاء مطلب دریافتی از سرور، به عنوان مقدار خاصیت posts شیء منتسب به خاصیت payload شیء ارسالی به reducer، در متد getPostsSuccess تعریف شده‌است. یعنی reducer متناظر، اطلاعات را از طریق خاصیت action.payload.posts شیء رسیده، دریافت می‌کند.
- همچنین دو اکشن شروع به دریافت اطلاعات (getPostsStarted) و بروز خطا (getPostsFailure) نیز در ابتدا و در قسمت catch عملیات async، به سمت reducer متناظر، dispatch خواهند شد.

3) ایجاد تابع reducer مخصوص دریافت اطلاعات از سرور
اکنون در فایل جدید src\reducers\posts.js، بر اساس نوع شیء رسیده و مقدار action.payload.posts آن، کار تامین آرایه‌ی posts موجود در state انجام می‌شود:
import * as types from "../constants/ActionTypes";

const initialState = { loading: false, posts: [], error: null };

export default function postsReducer(state = initialState, action) {
  switch (action.type) {
    case types.GetPostsStarted:
      return {
        loading: true,
        posts: [],
        error: null
      };
    case types.GetPostsSuccess:
      return {
        loading: false,
        posts: action.payload.posts,
        error: null
      };
    case types.GetPostsFailure:
      return {
        loading: false,
        posts: [],
        error: action.payload.error
      };
    default:
      return state;
  }
}
در این reducer با استفاده از یک switch، سه حالت ممکنی را که اکشن‌های رسیده‌ی به آن می‌توانند داشته باشند، مدیریت کرده‌ایم:
- در حالت آغاز کار و یا GetPostsStarted، با تنظیم خاصیت loading به true، سبب نمایش یک div «لطفا منتظر بمانید» خواهیم شد.
- در حالت دریافت نهایی اطلاعات از سرور، خاصیت loading به false تنظیم می‌شود تا div «لطفا منتظر بمانید» را مخفی کند. همچنین آرایه‌ی posts را نیز از payload رسیده استخراج کرده و به سمت کامپوننت‌ها ارسال می‌کند.
- در حالت بروز خطا و یا GetPostsFailure، خاصیت error شیء action.payload استخراج شده و جهت نمایش div متناظری، بازگشت داده می‌شود.

پس از تعریف این reducer باید آن‌را در فایل src\reducers\index.js به کمک combineReducers، با سایر reducer‌های موجود، ترکیب و یکی کرد تا در نهایت این rootReducer در فایل index.js اصلی برنامه، جهت ایجاد store اصلی redux، مورد استفاده قرار گیرد:
import { combineReducers } from "redux";

import counterReducer from "./counter";
import postsReducer from "./posts";

const rootReducer = combineReducers({
  counterReducer,
  postsReducer
});

export default rootReducer;


تشکیل کامپوننت‌های دکمه‌ی دریافت اطلاعات و نمایش لیست مطالب

UI این قسمت از سه کامپوننت تشکیل شده‌است که کدهای کامل آن‌ها را در ادامه مشاهده می‌کنید:

الف) کامپوننت src\components\FetchPosts.jsx
import React from "react";

const FetchPosts = ({ fetchPostsAsync }) => {
  return (
    <section className="card mt-5">
      <div className="card-header text-center">
        <button className="btn btn-primary" onClick={fetchPostsAsync}>
          Fetch Posts
        </button>
      </div>
    </section>
  );
};

export default FetchPosts;
کار این کامپوننت، نمایش دکمه‌ی Fetch Posts است. با کلیک بر روی آن قرار است action creator ای به نام fetchPostsAsync اجرا شود که کدهای آن‌را پیشتر مرور کردیم.
همانطور که مشاهده می‌کنید، این کامپوننت هیچ اطلاعاتی از وجود کامپوننت دومی که قرار است لیست مطالب را نمایش دهد، ندارد. کارش تنها dispatch یک اکشن است.
بنابراین این کامپوننت از طریق props فقط یک اشاره‌گر به متد رویدادگردانی را دریافت می‌کند و اطلاعات دیگری را نیاز ندارد.

ب) کامپوننت src\components\Posts.jsx
import React from "react";

import Post from "./Post";

const Posts = ({ posts, loading, error }) => {
  return (
    <>
      <section className="card mt-5">
        <div className="card-header">
          <h2>Posts</h2>
        </div>
        <div className="card-body">
          {loading ? (
            <div className="alert alert-info">Loading ...</div>
          ) : (
            <div className="list-group list-group-flush">
              {posts.map(post => (
                <Post key={post.id} post={post} />
              ))}
            </div>
          )}
          {error && <div className="alert alert-warning">{error.message}</div>}
        </div>
      </section>
    </>
  );
};

export default Posts;
این کامپوننت، آرایه‌ای از اشیاء مطالب را دریافت کرده و با  ایجاد حلقه‌ای بر روی آن‌ها، توسط کامپوننت Post، هر کدام را در صفحه درج می‌کند. بنابراین این کامپوننت اکشنی را dispatch نمی‌کند. فقط از طریق props، یک آرایه‌ی posts، وضعیت جاری عملیات و خطاهای حاصل را باید دریافت کند.
در این کامپوننت اگر loading رسیده به true تنظیم شده باشد، یک div با عبارت loading نمایش داده می‌شود. در غیراینصورت، لیست مطالب را درج می‌کند. همچنین اگر خطایی نیز رخ داده باشد، آن‌را نیز درون یک div در صفحه نمایش می‌دهد.

ج) کامپوننت src\components\Post.jsx
import React from "react";

const Post = ({ post }) => {
  return (
    <article className="list-group-item">
      <header>
        <h2>{post.title}</h2>
      </header>
      <p>{post.body}</p>
    </article>
  );
};

export default Post;
این کامپوننت کار نمایش جزئیات هر رکورد مطلب را به عهده دارد؛ مانند نمایش عنوان و بدنه‌ی یک مطلب.


اتصال کامپوننت‌های FetchPosts و Posts به مخزن redux

مرحله‌ی آخر کار، تامین state کامپوننت‌های FetchPosts و Posts از طریق props است. به همین جهت باید دو دربرگیرنده را برای این دو کامپوننت ایجاد کنیم.
الف) ایجاد دربرگیرنده‌ی کامپوننت FetchPosts
برای این منظور فایل جدید src\containers\FetchPosts.js را با محتوای زیر ایجاد می‌کنیم:
import { connect } from "react-redux";

import { fetchPostsAsync } from "../actions";
import FetchPosts from "../components/FetchPosts";

const mapDispatchToProps = {
  fetchPostsAsync
};

export default connect(null, mapDispatchToProps)(FetchPosts);
- کار این تامین کننده، اتصال action creator ای به نام fetchPostsAsync به props کامپوننت FetchPosts است.
- چون اطلاعات state ای قرار نیست به این کامپوننت ارسال شود، تابع mapStateToProps را در اینجا مشاهده نمی‌کنید و با نال مقدار دهی شده‌است.

ب) ایجاد دربرگیرنده‌ی کامپوننت Posts
برای این منظور فایل جدید src\containers\Posts.js را با محتوای زیر ایجاد می‌کنیم:
import { connect } from "react-redux";

import Posts from "../components/Posts";

const mapStateToProps = state => {
  console.log("PostsContainer->mapStateToProps", state);
  return {
    ...state.postsReducer
  };
};

export default connect(mapStateToProps)(Posts);
- کار این تامین کننده، اتصال خاصیت posts بازگشت داده شده‌ی از طریق postsReducer، به props کامپوننت Posts است. البته چون کامپوننت Posts سه خاصیت { posts, loading, error } را از طریق props دریافت می‌کند، با استفاده از spread operator، هر سه خاصیت دریافتی از reducer را به صورت یک شیء جدید بازگشت داده‌ایم.
- کامپوننت Posts رویدادی را سبب نخواهد شد. به همین جهت تابع mapDispatchToProps را در اینجا تعریف و ذکر نکرده‌ایم.


استفاده از کامپوننت‌های دربرگیرنده جهت نمایش نهایی کامپوننت‌های تحت کنترل Redux

اکنون به فایل src\App.js مراجعه کرده و دو تامین کننده‌ی فوق را درج می‌کنیم:
import "./App.css";

import React from "react";

import CounterContainer from "./containers/Counter";
import FetchPostsContainer from "./containers/FetchPosts";
import PostsContainer from "./containers/Posts";

function App() {
  const prop1 = 123;
  return (
    <main className="container">
      <div className="row">
        <div className="col">
          <CounterContainer prop1={prop1} />
        </div>
        <div className="col">
          <FetchPostsContainer />
        </div>
        <div className="col">
          <PostsContainer />
        </div>
      </div>
    </main>
  );
}

export default App;
در اینجا FetchPostsContainer و PostsContainer سبب خواهند شد تا اتصالات مخزن اصلی redux، به کامپوننت‌هایی که توسط آن‌ها دربرگرفته شده‌اند، برقرار شود و کار تامین props آن‌ها صورت گیرد.

یک نکته: برای مثال در انتهای کامپوننت FetchPosts، سطر export default FetchPosts را داریم. اگر این سطر را حذف کنیم و بجای آن export default connect فوق را قرار دهیم، دیگر نیازی نخواهد بود تا FetchPostsContainer را از دربرگیرنده‌ها، import کرد و سپس بجای درج المان </FetchPosts> نوشت </FetchPostsContainer>. می‌توان همانند قبل از همان نام متداول </FetchPosts> استفاده کرد و import انجام شده نیز همانند سابق از همان فایل ماژول کامپوننت صورت می‌گیرد. یعنی می‌توان پوشه‌ی containers را حذف کرد و کدهای آن را دقیقا ذیل کلاس کامپوننت درج نمود.


کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-redux-mobx-part04-backend.zip و state-management-redux-mobx-part04-frontend.zip
نظرات مطالب
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
- فلسفه‌ی کار با Blazor server، امکان دسترسی مستقیم به لایه سرویس‌های برنامه، بدون نیاز به طراحی یک Web API خاص آن‌ها است (کار با آن ساده‌تر است). اگر قرار است با Web API کار کنید، شاید بهتر باشد از WASM استفاده کنید.
- سرویس‌های Blazor Server، طول عمر خاصی دارند و تا زمانیکه اتصال برقرار است، از دست نمی‌روند. بنابراین خیلی از اطلاعات را می‌توان به صورت متداولی در آن‌ها ذخیره کرد و نیازی به تمهید خاصی نیست؛ هرچند encrypted local storage هم دارند.
- امکان طراحی interceptor برای HTTP Client هم وجود دارد تا هربار نیازی به مقدار دهی هدر Authorization نباشد.
- بله. در این سری اگر از Identity استفاده شده، بیشتر هدف مدیریت کاربران بوده و یا برای Blazor Server، دسترسی به کوکی خودکار پس از لاگین. نکات تهیه‌ی authentication provider سفارشی مطرح شده‌ی در قسمت wasm این سری برای کار با JWT، همه جا یکسان است و وابستگی به Identity و یا حتی Identity server پیش‌فرض مطرح شده‌ی توسط مایکروسافت، ندارد.
نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت دهم- ذخیره سازی اطلاعات کاربران IDP در بانک اطلاعاتی
- Identity server یک محصول کاملا مجزای از ASP.NET Core Identity است و توسط تیم دیگری خارج از مایکروسافت توسعه داده می‌شود و این دو ارتباطی به هم ندارند.
- هرچند امکان استفاده‌ی از ASP.NET Core Identity برای مدیریت کاربران Identity server هم وجود دارد.
- کاری که در این سری انجام شده، ابتدا در «قسمت چهارم - نصب و راه اندازی IdentityServer» فایل‌های Quick Start UI را به پروژه‌ی IDP اضافه کردیم (یعنی دقیقا از کدهای اصلی تیم Identity server استفاده شده). بعد در این قسمت، سایر کدهای اصلی بسته‌ی EF Core آن‌را (^ و ^) به پروژه اضافه و سفارشی سازی کرده و در قسمت‌های دیگر، این کدها را تکمیل کرده‌ایم. بنابراین در این سری از کدهای استاندارد خود  Identity server استفاده شده و سپس توسعه‌ی بیشتری پیدا کرده‌اند. برای نمونه کلاس‌های موجودیت‌های مثال این سری، از اینجا تامین شده‌اند.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
- لطفا خارج از موضوع بحث نکنید. اینجا یک انجمن عمومی و همه منظوره نیست.
- مراجعه کنید به مطالب «استفاده‌ی گسترده از DateTimeOffset در NET Core.» و کتابخانه‌ی «DNTPersianUtils.Core» برای تبدیل تاریخ.
نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
Type Load Exception فقط به معنای binary-mismatch است. یعنی کتابخانه‌ی مدنظر شما از وابستگی‌های قدیمی استفاده می‌کند که با نگارش ASP.NET Core مورد استفاده‌ی در پروژه‌ی شما یکی نیست. فقط یک راه حل هم دارد: به روز رسانی وابستگی‌های کتابخانه و کامپایل مجدد آن.