اشتراک‌ها
بررسی imageهای مختلف NET. در Docker

.NET and .NET Core (and Windows!) have been getting better and better with Docker. I run Docker for Windows as it supports both Linux Containers and Windows Containers. They have both a Stable and Edge channel. The Edge (Beta) channel is regularly updated and, as a rule, gets better and better in the year I've been running it. 

بررسی imageهای مختلف NET. در Docker
اشتراک‌ها
نگاهی به Docker ها در ویندوز سرور 2016
With the release of Technical Preview 3 (TP3) for Windows Server 2016, Microsoft for the first time have enabled native Containers under the Windows platform. Integrated with this is the Docker support for Windows Server, meaning you can run Docker containers in a Windows environment 
نگاهی به Docker ها در ویندوز سرور 2016
مطالب
PowerShell 7.x - قسمت هشتم - ماژول‌ها
توسط ماژول‌ها میتوانیم یک مجموعه از دستورات را گروه‌بندی کنیم و تحت عنوان یک پکیج ارائه دهیم که برای دیگران نیز قابل استفاده باشند. برای ایجاد یک ماژول کافی است اسکریپت‌های خود را درون یک فایل با پسوند psm1 قرار دهیم؛ به این فایل اصطلاحاً root module گفته میشود. در واقع میتوان گفت ماژول‌ها یک روش مناسب برای به اشتراک‌گذاری اسکریپت‌ها میباشند. تا اینجا با کمک پروفایل‌ها توانستیم امکان استفاده مجدد از توابع و اسکریپت‌ها را داشته باشیم؛ ماژول‌ها نیز یک روش دیگر برای بارگذاری اسکریپت‌ها درون شل هستند. زمانیکه شل را باز میکنیم PowerShell به صورت خودکار یکسری مسیر را برای بارگذاری ماژول‌ها اسکن میکند. توسط متغیر env:PSModulePath$ میتوانیم لیست این مسیرها را ببینیم:  
PS /> $env:PSModulePath -Split ":"

/Users/sirwanafifi/.local/share/powershell/Modules
/usr/local/share/powershell/Modules
/usr/local/microsoft/powershell/7/Modules
همانطور که عنوان شد برای ایجاد یک ماژول کافی است اسکریپت‌های خود را داخل یک فایل با پسوند psm1 ذخیره کنیم. به عنوان مثال میتوانیم تابع Get-PingReply را درون یک فایل با نام PingModule.psm1 ذخیره و سپس توسط دستور Import-Module ماژول را ایمپورت کنیم:  
PS /> Import-Module ./PingModule.psm1
سپس توسط دستور Get-Module PingModule میتوانیم جزئیات ماژول ایمپورت شده را مشاهده نمائیم: 
PS /> Get-Module PingModule

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     0.0                   PingModule                          Get-PingReply
به صورت پیش‌فرض تمام توابع درون اسکریپت export خواهند شد. اگر ExportedCommands خالی باشد به این معنا است که ماژول به درستی ایمپورت نشده‌است. به عنوان مثال اگر سعی کنید فایل قبل را با پسوند ps1 به عنوان ماژول ایمپورت کنید. خطایی هنگام ایمپورت کردن مشاهده نخواهید کرد و قسمت ExportedCommands خالی خواهد بود. در این‌حالت نیز امکان استفاده از تابع درون اسکریپت را خواهیم داشت؛ اما هیچ تضمینی نیست که به صورت یک ماژول به درستی عمل کند. بنابراین بهتر است ماژول‌هایی که ایجاد میکنیم حتماً پسوند psm1 داشته باشند.
PS /> Import-Module ./PingModule.ps1
PS /> Get-Module PingModule

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     0.0                   PingModule
ممکن است بخواهیم یکسری توابع را به صورت private تعریف کنیم و فقط تعداد محدودی از توابع به صورت public باشند. در این حالت میتوانیم درون فایل psm1 با کمک دستور Export-ModuleMember اینکار را انجام دهیم: 
Function Get-PingReply {
    // as before
}

Function Get-PrivateFunction {
    Write-Debug 'This is a private function'
}

Export-ModuleMember -Function @(
    'Get-PingReply'
)
اضافه کردن متادیتا به ماژول‌ها
برای هر ماژول میتوانیم با کمک module manifest یکسری متادیتا به ماژول اضافه کنیم. این متادیتا درون یک فایل با پسوند psd1 که محتویات آن در واقع یک Hashtable است ذخیره میشود. برای ایجاد فایل manifest میتوانیم از دستور New-ModuleManifest استفاده کنیم. به عنوان مثال برای ایجاد فایل manifest برای ماژول PingModule.psm1 اینگونه عمل خواهیم کرد:  
$moduleSettings = @{
    Path                 = './PingModule.psd1'
    Description          = 'A module to ping a remote system'
    RootModule           = 'PingModule.psm1'
    ModuleVersion        = '1.0.0'
    FunctionsToExport    = @(
        'Get-PingReply'
    )
    PowerShellVersion    = '5.1'
    CompatiblePSEditions = @(
        'Core'
        'Desktop'
    )
}
New-ModuleManifest @moduleSettings
تنها پراپرتی موردنیاز برای ایجاد module manifest پراپرتی Path میباشد. این پراپرتی به فایلی که متادیتا قرار است درون آن ایجاد شود اشاره دارد. همانطور که مشاهده میکنید یکسری پراپرتی دیگر نیز اضافه کرده‌ایم و توسط splatted hash table (با کمک @) به دستور New-ModuleManifest ارسال کرده‌ایم. به این معنا که کلیدها و مقادیر درون hash table به جای اینکه یکجا به عنوان یک آبجکت به دستور موردنظر ارسال شوند، به صورت جدا پاس داده شده‌اند. اما اگر از $ استفاده میکردیم: 
$moduleSettings = @{
    Author = 'John Doe'
    Description = 'This is a sample module'
}

New-ModuleManifest $moduleSettings
با خطای زیر مواجه میشدیم: 
New-ModuleManifest : A parameter cannot be found that matches parameter name 'Author'.
در نهایت فایل manifest در مسیر تعیین شده ایجاد خواهد شد: 
#
# Module manifest for module 'PingModule'
#
# Generated by: sirwanafifi
#
# Generated on: 01/01/2023
#

@{

    # Script module or binary module file associated with this manifest.
    RootModule           = './PingModule.psm1'

    # Version number of this module.
    ModuleVersion        = '1.0.0'

    # Supported PSEditions
    CompatiblePSEditions = 'Core', 'Desktop'

    # ID used to uniquely identify this module
    GUID                 = '3f8561fc-c004-4c8e-b2fc-4a4191504131'

    # Author of this module
    Author               = 'sirwanafifi'

    # Company or vendor of this module
    CompanyName          = 'Unknown'

    # Copyright statement for this module
    Copyright            = '(c) sirwanafifi. All rights reserved.'

    # Description of the functionality provided by this module
    Description          = 'A module to ping a remote system'

    # Minimum version of the PowerShell engine required by this module
    PowerShellVersion    = '5.1'

    # Name of the PowerShell host required by this module
    # PowerShellHostName = ''

    # Minimum version of the PowerShell host required by this module
    # PowerShellHostVersion = ''

    # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
    # DotNetFrameworkVersion = ''

    # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
    # ClrVersion = ''

    # Processor architecture (None, X86, Amd64) required by this module
    # ProcessorArchitecture = ''

    # Modules that must be imported into the global environment prior to importing this module
    # RequiredModules = @()

    # Assemblies that must be loaded prior to importing this module
    # RequiredAssemblies = @()

    # Script files (.ps1) that are run in the caller's environment prior to importing this module.
    # ScriptsToProcess = @()

    # Type files (.ps1xml) to be loaded when importing this module
    # TypesToProcess = @()

    # Format files (.ps1xml) to be loaded when importing this module
    # FormatsToProcess = @()

    # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
    # NestedModules = @()

    # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
    FunctionsToExport    = 'Get-PingReply'

    # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
    CmdletsToExport      = '*'

    # Variables to export from this module
    VariablesToExport    = '*'

    # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
    AliasesToExport      = '*'

    # DSC resources to export from this module
    # DscResourcesToExport = @()

    # List of all modules packaged with this module
    # ModuleList = @()

    # List of all files packaged with this module
    # FileList = @()

    # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
    PrivateData          = @{

        PSData = @{

            # Tags applied to this module. These help with module discovery in online galleries.
            # Tags = @()

            # A URL to the license for this module.
            # LicenseUri = ''

            # A URL to the main website for this project.
            # ProjectUri = ''

            # A URL to an icon representing this module.
            # IconUri = ''

            # ReleaseNotes of this module
            # ReleaseNotes = ''

            # Prerelease string of this module
            # Prerelease = ''

            # Flag to indicate whether the module requires explicit user acceptance for install/update/save
            # RequireLicenseAcceptance = $false

            # External dependent modules of this module
            # ExternalModuleDependencies = @()

        } # End of PSData hashtable

    } # End of PrivateData hashtable

    # HelpInfo URI of this module
    # HelpInfoURI = ''

    # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
    # DefaultCommandPrefix = ''

}
هر ماژول باید یک آی‌دی منحصر به فرد داشته باشد که به صورت Guid توسط یک پراپرتی با همین نام تعیین میشود. برای هر پراپرتی درون این فایل توضیحی به صورت کامنت نوشته شده است؛ اما برای دیدن جزئیات کامل میتوانید به اینجا مراجعه نمائید. در اینجا RootModule به PingModule.psm1 تنظیم شده است. تنظیم این پراپرتی نحوه نمایش module type را در خروجی Get-Module مشخص میکند. این مقدار اگر به فایل ماژول (psm1) اشاره کند، نوع ماژول script در نظر گرفته میشود، اگر به یک DLL اشاره کند به binary و در نهایت اگر به یک فایلی با پسوند دیگری اشاره کند manifest در نظر گرفته میشود. همچنین درون فایل فوق یکسری پراپرتی مانند CmdletsToExport, VariablesToExport, AliasesToExport به صورت خودکار تنظیم شده‌اند. در نهایت برای تست صحت فایل میتوانیم از دستور Test-ModuleManifest استفاده کنیم: 
PS /> Test-ModuleManifest ./PingModule.psd1   

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     1.0.0                 PingModule                          Get-PingReply
پابلیش ماژول
در نهایت توسط Publish-Module میتوانیم ماژول موردنظرمان را درون یک مخزن PowerShell پابلیش کنیم. رایج‌ترین مخزن برای انتشار پکیج‌های PowerShell یک فید نیوگت (مانند PowerShell Gallery) است. همچنین میتوانیم یک مخزن لوکال نیز برای پابلیش پکیج‌ها نیز ایجاد کنیم و خروجی نهایی را به صورت یک فایل با پسوند nupkg با دیگران به اشتراک بگذاریم. برای اینکار ابتدا نیاز است یک مخزن لوکال را به PowerShell معرفی کنیم: 
PS /> Register-PSRepository -Name 'PSLocal' `                       
>>     -SourceLocation "$(Resolve-Path $RepoPath)" `
>>     -PublishLocation "$(Resolve-Path $RepoPath)" `
>>     -InstallationPolicy 'Trusted'
در کد فوق میتوانید مقدار متغیر RepoPath را به محل مدنظر روی سیستم‌تان تنظیم کنید. ساختار ماژولی که میخواهیم پابلیش کنیم نیز اینچنین خواهد بود: 
ProjectRoot 
| -- PingModule
     | -- PingModule.psd1 
     | -- PingModule.psm1
در ادامه برای پابلیش ماژول فوق درون ProjectRoot دستور Publish-Module را اینگونه اجرا خواهیم کرد: 
PS /> Publish-Module -Path ./PingModule/ -Repository PSLocal
خروجی دستور فوق یک فایل با پسوند nupkg در مسیر مخزنی است که معرفی کردیم. همچنین با کمک دستور Find-Module میتوانیم ماژول موردنظر را لیست کنیم: 
PS /> Find-Module -Name PingModule -Repository PSLocal

Version              Name                                Repository           Description
-------              ----                                ----------           -----------
0.0.1                PingModule                          PSLocal              Get-PingReply is a.
برای نصب ماژول روی یک سیستم دیگر نیز ابتدا باید مطمئن شوید که سیستم مقصد، مخزن لوکال را تعریف کرده باشد و در نهایت توسط دستور Install-Module میتوانیم ماژول موردنظر را نصب کنیم: 
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
همچنین امکان پابلیش کردن ورژن‌های متفاوت را نیز خواهیم داشت: 
ProjectRoot 
| -- PingModule
     | -- 1.0.0
          | -- PingModule.psd1 
          | -- PingModule.psm1
     | -- 1.0.1
          | -- PingModule.psd1 
          | -- PingModule.psm1
برای پابلیش کردن هر کدام از ماژول‌های فوق باید به ازای هر کدام یکبار دستور Publish-Module را اجرا کنیم: 
PS /> Publish-Module -Path ./PingModule/1.0.0 -Repository PSLocal
PS /> Publish-Module -Path ./PingModule/1.0.1 -Repository PSLocal
اکنون حین نصب ماژول میتوانیم ورژن را نیز تعیین کنیم؛ در غیراینصورت آخرین ورژن یعنی 1.0.1 نصب خواهد شد: 
PS /> Install-Module -Name PingModule -Repository PSLocal -Scope CurrentUser
PS /> Get-InstalledModule -Name PingModule                                                                            
Version              Name                                Repository           Description
-------              ----                                ----------           -----------
1.0.1                PingModule                          PSLocal              Get-PingReply is a.
ساختار مناسب برای ایجاد ماژول‌ها
برای توسعه ماژول‌های PowerShell توصیه میشود که از ساختار Multi-file layout استفاده شود به این معنا که بخش‌های مختلف پروژه به قسمت‌های کوچک‌تر و قابل‌نگهداری تقسیم شوند: 
ProjectRoot 
| -- PingModule
     | -- 1.0.0
          | -- Public/
          | -- Private/
          | -- PingModule.psd1
          | -- PingModule.psm1
در اینحالت باید اسکریپت‌ها و فایل‌های موردنیاز را توسط dot sourcing درون فایل ماژول بارگذاری کنیم: 
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}

$ScriptList = Get-ChildItem -Path $PSScriptRoot/Private/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}
یک مثال عملی
در ادامه میخواهیم یک ماژول تهیه کنیم که قابلیت امضاء روی PDF را با کمک کتابخانه iTextSharp.LGPLv2.Core انجام دهیم و به شکل زیر برای کاربر قابل استفاده باشد: 
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
بنابراین ساختار پروژه را اینگونه ایجاد خواهیم کرد: 
ProjectRoot 
| -- SignPdf
     | -- 1.0.0
          | -- Public/
               | -- dependencies/
                    | -- BouncyCastle.Crypto.dll
                    | -- System.Drawing.Common.dll
                    | -- Microsoft.Win32.SystemEvents.dll
                    | -- iTextSharp.LGPLv2.Core.dll
               | -- Set-PDFSingature.ps1
          | -- SignPdf.psd1
          | -- SignPdf.psm1
برای استفاده از پکیج ذکر شده نیاز خواهد بود که Dllهای موردنیاز را نیز به عنوان وابستگی به پروژه اضافه کنیم (محتویات پوشه dependencies) سپس درون فایل ماژول (SignPdf.psm1) همانطور که عنوان شد میبایست اسکریپت‌ها درون پوشه Public را بارگذاری کنیم و همچنین درون تابعی که قرار است Export شود را نیز تعیین کرده‌ایم (Set-PDFSingature) 
$ScriptList = Get-ChildItem -Path $PSScriptRoot/Public/*.ps1 -Filter *.ps1

foreach ($Script in $ScriptList) {
    . $Script.FullName
}

Export-ModuleMember -Function Set-PDFSingature
در ادامه درون فایل Set-PDFSignature پیاده‌سازی را انجام خواهیم داد: 
using namespace iTextSharp.text
using namespace iTextSharp.text.pdf
using namespace System.IO

Function Set-PDFSingature {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                if (Test-Path ([Path]::Join($(Get-Location), $_))) {
                    return $true
                }
                else {
                    throw "Signature image not found"
                }
                if ($_.EndsWith('.pdf')) {
                    return $true
                }
                else {
                    throw "File extension must be .pdf"
                }
            })]
        [string]$PdfToSign,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript({
                if (Test-Path ([Path]::Join($(Get-Location), $_))) {
                    return $true
                }
                else {
                    throw "Signature image not found"
                }
                if ($_.EndsWith('.png') -or $_.EndsWith('.jpg')) {
                    return $true
                }
                else {
                    throw "File extension must be .png or .jpg"
                }
            })]
        [string]$SignatureImage,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [int]$XPos = 130,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [int]$YPos = 50
    )
    Try {
        Add-Type -Path "$PSScriptRoot/dependencies/*.dll"
        $pdf = [PdfReader]::new("$(Get-Location)/$PdfToSign")
        $fs = [FileStream]::new("$(Get-Location)/$PdfToSign-signed.pdf", 
            [FileMode]::Create)
        $stamper = [PdfStamper]::new($pdf, $fs)
        $stamper.AcroFields.AddSubstitutionFont([BaseFont]::CreateFont())
        $content = $stamper.GetOverContent(1)
        $width = $pdf.GetPageSize(1).Width
        $image = [Image]::GetInstance("$(Get-Location)/$SignatureImage")
        $image.SetAbsolutePosition($width - $XPos, $YPos)
        $image.ScaleAbsolute(100, 30)
        $content.AddImage($image)
        $stamper.Close()
        $pdf.Close()
        $fs.Dispose()
    }
    Catch {
        Write-Host "Error: $($_.Exception.Message)"
    }
}
روال قرار دادن یک امضاء بر روی یک فایل PDF قبلاً در سایت توضیح داده شده است. کدهای فوق در واقع معادل PowerShell همان کدهای موجود در سایت هستند و نکته خاصی ندارند. در نهایت میتوانیم ماژول تهیه شده را روی مخزن موردنظر پابلیش کنیم: 
PS /> Publish-Module -Path ./SignPdf/1.0.0 -Repository PSLocal
برای نصب ماژول میتوانیم از دستور Install-Module استفاده کنیم: 
PS /> Install-Module -Name SignPdf -Repository PSLocal -Scope CurrentUser
در نهایت برای استفاده از ماژول ایجاد شده میتوانیم اینگونه عمل کنیم: 
PS /> Set-PDFSingature -PdfToSign "./sample_invoice.pdf" -SignatureImage "./sample_signature.jpg"
خروجی نیز فایل امضاء شده خواهد بود: 

کدهای ماژول را میتوانید از اینجا دانلود کنید. 

مطالب
آموزش Cache در ASP.NET Core - (قسمت دوم : EasyCaching)
در قسمت اول، درمورد سیستم Cache پیش‌فرض موجود در Asp.Net Core و مزیت‌ها و معایب آن گفتیم. اگر قسمت اول را نخواندید، قسمت اول مقاله را میتوانید از این لینک بخوانید. 
 در این قسمت میخواهیم یک پکیج محبوب و کاربردی را برای پیاده سازی کش، در Asp.Net Core را بررسی کنیم.
در دنیای امروز، برنامه نویسی پکیج‌ها و فریمورک‌ها، نقش بسیار مهمی را ایفا میکنند؛ بطوریکه در بسیاری از این موارد، استفاده از این پکیج‌ها، عمل عاقلانه‌تری نسبت به دوباره نویسی فیچر‌های مربوطه است. برای عمل کشینگ در Asp.Net Core نیز پکیج‌های فوق‌العاده‌ای وجود دارند که در این مقاله، به بررسی و استفاده پکیج این میپردازیم.
در این پکیج، هر یک از متد‌های موجود در عملیات کشینگ، بصورت بهینه‌ای تعریف شده و قابل استفاده‌اند. سیستمی که این پکیج برای کش کردن داده‌ها استفاده میکند، همان سیستم کش Asp.Net Core هست و به‌نوعی، سوار بر این سیستم، قابلیت‌های بیشتر و بهتری را ارائه میدهد و این متد‌ها شامل موارد زیر هستند:
  1.  Get/GetAsync(with data retriever)
  2.  Get/GetAsync(without data retriever)
  3.  Set/SetAsync
  4.  Remove/RemoveAsync
  5.  ~~Refresh/RefreshAsync (was removed)~~
  6.  RemoveByPrefix/RemoveByPrefixAsync
  7.  SetAll/SetAllAsync
  8.  GetAll/GetAllAsync
  9.  GetByPrefix/GetByPrefixAsync
  10.  RemoveAll/RemoveAllAsync
  11.  GetCount
  12.  Flush/FlushAsync
  13.  TrySet/TrySetAsync
  14.  GetExpiration/GetExpirationAsync

مفهوم استفاده از این متد‌ها، با همان مفهوم متد‌های کش در core، برابری میکند که در قسمت اول این مقاله به آن پرداختیم. همانطور که می‌بینید، این پکیج از Async Method‌‌ها هم پشتیبانی میکند و میتوانید کش‌های خود را بصورت Async بنویسید.
یکی از قابلیت‌های دیگر این پکیج، سازگاری آن با انواع Cache Provider‌های موجود است. بطور خلاصه Cache Provider‌ها، همان ارائه دهندگان حافظه‌ی Ram، در قالب‌ها و ابزارهای مختلف هستند. برخی از این‌ها با داشتن الگوریتم‌های بهینه‌تر، سرعت بالاتری از رد و بدل کردن اطلاعات در Ram را در اختیار ما قرار میدهند و Local بودن یا Distributed بودن را کنترل میکنند. Cache provider‌های گوناگونی وجود دارند که هریک به شکلی کار میکند؛ برای مثال شما میتوانید با Provider ای مستقیما با خود Ram، برای Get و Set کردن کش‌های خود در ارتباط باشید و یا در روشی دیگر، از یک دیتابیس(Redis)، جدا از دیتابیس اصلی برنامه که حافظه مصرفی آن Ram هست و منابع حافظه شما را نیز مدیریت میکند، برای کش‌های خود استفاده کنید و اطلاعات را بصورت ایندکس گذاری شده در Ram ذخیره کنید که به سرعت واکشی آن می‌افزاید.

بطور کل Cache Provider هایی که پکیج EasyCaching با آن‌ها سازگار است شامل موارد زیر است:
  1. In-Memory
  2. Memcached
  3. Redis(Based on StackExchange.Redis)
  4. Redis(Based on csredis)
  5. SQLite
  6. Hybrid
  7. Disk
  8. LiteDb

یکی دیگر از مزیت‌های این پکیج، سازگاری آن با Serializer‌های مختلف است. همانطور که میدانید دیتا‌های ورودی و خروجی در برنامه، نیاز به Serialize شدن دارند. وقتی میخواهید دیتایی را در دیتابیس ذخیره کنید، آن را در قالب یک شی (Model) از کاربر دریافت میکنید و شما باید برای ذخیره این دیتا، اطلاعات درون شیء را به قالبی که قابل ذخیره شدن باشد، در آورید که این عمل Serialize نام دارد. دقیقا برعکس این روند، بعد از واکشی اطلاعات از دیتابیس، اطلاعات را در قالب اشیایی که قابل نمایش به کاربر باشد (DeSerialize) در میاوریم.
در کش کردن هم چیزی که شما با آن سروکار دارید، دیتا است؛ پس برای ذخیره و واکشی این دیتا، از هر حافظه‌ای، چه دیتابیس و چه Ram، باید از یک Serializer استفاده کنید تا عملیات Serialize و DeSerialize را برایتان انجام دهد. Serializer‌های مختلفی وجود دارند که بصورت پکیج‌هایی ارائه شده‌اند و اما Serializer هایی که سیستم EasyCaching آن‌هارا پشتیبانی میکند، شامل موارد ذیل هستند:
  1. BinaryFormatter
  2. MessagePack
  3. Newtonsoft.Json
  4. Protobuf
  5. System.Text.Json

در ادامه به پیاده سازی کش، با استفاده از EasyCaching در سه Provider مختلف از این پکیج می‌پردازیم.

 1_ پروایدر InMemory :
پروایدر InMemory، یک سیستم Local Caching را برای ما به وجود میاورد. در قسمت قبلی مقاله سیستم‌های Local(InMemory) و Distributed را بررسی کردیم و تفاوت‌های میان آن‌ها را گفتیم.

برای استفاده از پروایدر InMemory در EasyCaching باید پکیج زیر را نصب کنید: 
Install-Package EasyCaching.InMemory
در مرحله بعد، کانفیگ‌های مربوط به این پکیج را در کلاس Startup برنامه خود میاوریم. راحت‌ترین روش افزودن این پکیج به Startup، صرفا افزودن حالت پیشفرض آن به متد ConfigureServices است که به شرح زیر عمل میکنیم: 
  services.AddEasyCaching(options =>
 {
       // use memory cache with a simple way
        options.UseInMemory();
 }
این حالت از کانفیگ، پکیج تنظیمات پیش‌فرض خود پکیج را برای برنامه قرار میدهد؛ شما میتوانید با استفاده از option‌های دیگری که در متد ()UseInMemory وجود دارند، تنظیمات شخصی سازی شده از سیستم کشینگ خود را اعمال کنید. 
و تمام. هم اکنون میتوان با استفاده از اینترفیس IEasyCachingProvider که این سرویس در اختیارمان قرار داده و عمل تزریق وابستگی آن در کلاس‌ها و کنترلر‌های مان دیتای در حال عبور را کش کنیم. متد‌های موجود در این اینترفیس به شرح زیر میباشد : 
// تنظیم یک کش با کلید - مقدار - زمان انقضا
void Set<T>(string cacheKey, T cacheValue, TimeSpan expiration);
Task SetAsync<T>(string cacheKey, T cacheValue, TimeSpan expiration);

// تنظیم یک کش با مقدار و زمان انقضا که تایپ مقدار از نوع دیکشنری هست و کلید دیکشنری بعنوان کلید کش قرار میگیرد
void SetAll<T>(IDictionary<string, T> value, TimeSpan expiration);
Task SetAllAsync<T>(IDictionary<string, T> value, TimeSpan expiration);

// تنظیم یک کش با کلید - مقدار - زمان انقضا
// اگر کلیدی همنام وجود داشته باشد مقدار نادرست و در غیر اینصورت مقدار نادرست را برمیگرداند
bool TrySet<T>(string cacheKey, T cacheValue, TimeSpan expiration);
Task<bool> TrySetAsync<T>(string cacheKey, T cacheValue, TimeSpan expiration);
 
// گرفتن یک کش با کلید
CacheValue<T> Get<T>(string cacheKey);
Task<CacheValue<T>> GetAsync<T>(string cacheKey);

// 
CacheValue<T> Get<T>(string cacheKey, Func<T> dataRetriever, TimeSpan expiration);
Task<CacheValue<T>> GetAsync<T>(string cacheKey, Func<Task<T>> dataRetriever, TimeSpan expiration);
 
// گرفتن یک کش با چند کاراکتر پیشین کلید آن
// برای مثال یک کلید با نام
// MyKey
// تنها با داشتن چند حرف اول 
// MyK
// میتوانیم این کش را دریافت کنیم
IDictionary<string, CacheValue<T>> GetByPrefix<T>(string prefix);
Task<IDictionary<string, CacheValue<T>>> GetByPrefixAsync<T>(string prefix);

// 
IDictionary<string, CacheValue<T>> GetAll<T>(IEnumerable<string> cacheKeys);
Task<IDictionary<string, CacheValue<T>>> GetAllAsync<T>(IEnumerable<string> cacheKeys);

// گرفتن تعداد کش‌های با کاراکتر‌های پیشین کلید که میان چند کلید یکسان است 
int GetCount(string prefix = "");
Task<int> GetCountAsync(string prefix = "");

// گرفتن زمان انقضا باقیمانده از یک کش با کلید آن
TimeSpan GetExpiration(string cacheKey);
Task<TimeSpan> GetExpirationAsync(string cacheKey);

// حذف کردن یک کش با کلید
void Remove(string cacheKey);
Task RemoveAsync(string cacheKey);

// حذف کردن یک کش با چند کاراکتر پیشین کلید
void RemoveByPrefix(string prefix);
Task RemoveByPrefixAsync(string prefix);
 
// حذف کردن چند کش با لیستی از کلید‌ها void RemoveAll(IEnumerable<string> cacheKeys);
Task RemoveAllAsync(IEnumerable<string> cacheKeys);

// بررسی وجود یا عدم وجود یک کش با کلید
bool Exists(string cacheKey);
Task<bool> ExistsAsync(string cacheKey);

// حذف کردن همه کش‌ها void Flush();
Task FlushAsync();

همانطور که قبلا گفته شد، سیستم کش، با دیتا مرتبط است و نیازمند یک Object Serializer جهت Serialize کردن اطلاعات ورودی و ذخیره آن در Target Storage مشخص شده است. پکیج EasyCaching برای Provider‌های خود، یک Object Serializer پیش‌فرض قرار داده‌است و تا وقتی که شما آن را طبق نیازی خاص، بصورت سفارشی تغییر نداده باشید، از آن استفاده میکند.
در میان پنج Serializer معرفی شده که EasyCaching آن‌ها را پشتیبانی میکند، BinaryFormatter بصورت پیش‌فرض در همه‌ی Provider‌ها برقرار است و تا وقتی یک Serializer انتخابی به EasyCaching معرفی نکنید، این پکیج از این Serializer استفاده میکند.
برای استفاده از Serializer‌های دیگری که معرفی شده میتوانید از لینک‌های زیر کمک بگیرید :

2 - پروایدر Redis :
ردیس، یک دیتابیس Key Value محور هست که محل ذخیره سازی آن Ram است و اطلاعات، بصورت موقت در آن ذخیره میشوند. بطور خلاصه، Key Value یعنی یکبار کلید و مقداری برای آن کلید تعریف میشود و هر وقت نام کلید تعریف شده، صدا زده شد، مقدار نسبت داده شده به آن، در اختیار ما قرار میگیرد. برای مثال کلید "Name" و مقدار "James". با این انتساب، هروقت "Name" فراخوانده شود، مقدار "James" را خواهیم داشت. سیستم Key Value بخاطر عدم پیچیدگی و سادگی‌ای که دارد، بسیار سریع عمل میکند و همچنین ایندکس گذاری‌هایی که ردیس روی دیتا‌ها انجام میدهد، باعث افزایش سرعت آن نیز خواهد شد که ردیس را به سریع‌ترین دیتابیس Key Value دنیا تبدیل کرده.
در اینجا با توجه به قابلیت هایی که ردیس داراست، یکی از بهترین گزینه‌ها برای انتخاب بعنوان فضای ذخیره سازی کش‌ها بصورت Distributed است.
برای استفاده از این دیتابیس قدرتمند ابتدا باید از طریق یکی از روش‌های معمول اقدام به نصب آن کنید. میتوانید فایل نصبی را از وبسایت رسمی آن دانلود کنید و یا یا با استفاده از Docker اقدام به نصب آن نمایید.
پس از نصب این دیتابیس روی سیستم خود ، برای استفاده از آن در EasyCaching ابتدا باید پکیج مورد نیاز را نصب کنید. 
Install-Package EasyCaching.Redis
ادامه کار به همان سادگی پروایدر قبلی هست و فقط کافیست EasyCaching و option ردیس را به کلاس Startup اضافه کنید. 
 services.AddEasyCaching(option =>
{
       option.UseRedis(config =>
      {
             config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379));
      });
});
با استفاده از متد UseRedis شما قابلیت استفاده از ردیس را در EasyCaching فعال میکنید و سپس باید اطلاعات Host و Port ردیس نصب شده‌ی روی سیستم خود را به این متد معرفی کنید.
اگر ردیس را بدون تنظیمات شخصی سازی شده و در همان حالت پیش‌فرض خودش نصب کرده باشید، Host و Port شما مانند نمونه بالا 127.0.0.1 و 6379 خواهد بود و نیازی به تغییر نیست.
در مرحله بعد برای استفاده از پروایدر ردیس ، اینترفیس IRedisCachingProvider در سرتاسر برنامه در دسترس خواهد بود. این اینترفیس علاوه بر اینکه متد‌های اصلی موجود در EasyCaching را ساپورت کرده ، بخاطر ساختار دیتابیسی که خود ردیس در اختیار ما قرار میدهد قابلیت‌های بیشتری نیز اراعه خواهد داد. این قابلیت‌ها خصیصه‌های ردیس هست چرا که این دیتابیس هم دقیقا شبیه به ساختار سیستم کش Key , Value را پشتیبانی میکند و در پی آن قابلیت هایی برای مدیریت بهتر کلید‌ها و مقادیر اراعه میدهد.
اینترفیس IRedisCachingProvider شامل تعداد زیادی از متد‌ها برای پشتیبانی از قابلیت‌های ردیس است که در ادامه همه آنهارا نام برده و برخی را توضیح مختصری خواهیم داد:
  • متد‌های Keys 
// حذف کردن یک کلید در صورت وجود
bool KeyDel(string cacheKey);
Task<bool> KeyDelAsync(string cacheKey);

// تنظیم تاریخ انتضا به یک کلید موجود بر حسب ثانیه
bool KeyExpire(string cacheKey, int second);
Task<bool> KeyExpireAsync(string cacheKey, int second);

// بررسی وجود یا عدم وجود یک کلید
bool KeyExists(string cacheKey);
Task<bool> KeyExistsAsync(string cacheKey);

// گرفتن زمان انتقضا باقیمانده یک کلید
long TTL(string cacheKey);
Task<long> TTLAsync(string cacheKey);

// جستجو بین همه کلید‌ها براساس فیلتر شامل بودن نام کلید از مقدار ورودی
List<string> SearchKeys(string cacheKey, int? count = null);
  • متد‌های String 
// افزودن یک عدد (پیشقرض 1) به مقدار نوع عددی یک کلید
long IncrBy(string cacheKey, long value = 1);
Task<long> IncrByAsync(string cacheKey, long value = 1);

// افزودن یک عدد (پیشقرض 1) به مقدار نوع عددی یک کلید
double IncrByFloat(string cacheKey, double value = 1);
Task<double> IncrByFloatAsync(string cacheKey, double value = 1);

// تنظیم یک کلید و مقدار وقتی مقدار از نوع رشته باشد
bool StringSet(string cacheKey, string cacheValue, TimeSpan? expiration = null, string when = "");
Task<bool> StringSetAsync(string cacheKey, string cacheValue, TimeSpan? expiration = null, string when = "");

// گرفتن کلید و مقدار آن وقتی مقدار از نوع رشته باشد
string StringGet(string cacheKey);
Task<string> StringGetAsync(string cacheKey);

// گرفتن تعداد کاراکتر‌های مقدار یک کلید وقتی مقدار از نوع رشته باشد
long StringLen(string cacheKey);
Task<long> StringLenAsync(string cacheKey);

// جایگزاری یک رشته درون رشته مقدار یک کلید بعد از شماره کاراکتر مشخص شده در ورودی برای مثال 
// "Hello World"
// 6 , jack
// "Hello jack"
long StringSetRange(string cacheKey, long offest, string value);
Task<long> StringSetRangeAsync(string cacheKey, long offest, string value);

// گرفتن یک بازه از رشته مقدار یک کلید با شماره کاراکتر شروع و پایان
string StringGetRange(string cacheKey, long start, long end);
Task<string> StringGetRangeAsync(string cacheKey, long start, long end);
  • متد‌های Hashes
// شما میتوانید دو کلید با نام‌های یکسان داشته باشید که در کلید تایپ دیکشنری مقدار خود باهم متفاوت هستند
bool HMSet(string cacheKey, Dictionary<string, string> vals, TimeSpan? expiration = null);
Task<bool> HMSetAsync(string cacheKey, Dictionary<string, string> vals, TimeSpan? expiration = null);

// شما میتوانید دو کلید با نام‌های یکسان داشته باشید که در ورودی فیلد باهم متفاوت هستند
bool HSet(string cacheKey, string field, string cacheValue);
Task<bool> HSetAsync(string cacheKey, string field, string cacheValue);

// بررسی وجود یا عدم وجود یک کلید و فیلد
bool HExists(string cacheKey, string field);
Task<bool> HExistsAsync(string cacheKey, string field);

// حذف کردن کلید‌های همنام موجود با همه فیلد‌های متفاوت در حالت پیشفرض مگر اینکه کلید و نام فیلد را بهمراه آن مشخص کنید
long HDel(string cacheKey, IList<string> fields = null);
Task<long> HDelAsync(string cacheKey, IList<string> fields = null);

// گرفتن مقدار با نام کلید و نام فیلد
string HGet(string cacheKey, string field);
Task<string> HGetAsync(string cacheKey, string field);

// گرفتن فیلد و مقدار با کلید
Dictionary<string, string> HGetAll(string cacheKey);
Task<Dictionary<string, string>> HGetAllAsync(string cacheKey);

//  افزودن یک عدد (پیشقرض 1) به مقدار نوع عددی یک کلید و فیلد
long HIncrBy(string cacheKey, string field, long val = 1);
Task<long> HIncrByAsync(string cacheKey, string field, long val = 1);

// گرفتن فیلد‌های متفاوت یک کلید
List<string> HKeys(string cacheKey);
Task<List<string>> HKeysAsync(string cacheKey);

// گرفتن تعداد فیلد‌های متفاوت یک کلید
long HLen(string cacheKey);
Task<long> HLenAsync(string cacheKey);

// گرفتن مقادیر یک کلید بدون در نظر گرفتن فیلد‌های متفاوت
List<string> HVals(string cacheKey);
Task<List<string>> HValsAsync(string cacheKey);

// گرفتن مقدار دیکشنری با کلید و نام فیلد‌ها Dictionary<string, string> HMGet(string cacheKey, IList<string> fields);
Task<Dictionary<string, string>> HMGetAsync(string cacheKey, IList<string> fields);
  • متد‌های List
// گرفتن یک مقدار از لیست مقادیر با شماره ایندکس آن
T LIndex<T>(string cacheKey, long index);
Task<T> LIndexAsync<T>(string cacheKey, long index);

// گرفتن تعداد مقادیر در لیست یک کلید
long LLen(string cacheKey);
Task<long> LLenAsync(string cacheKey);

// گرفتن اولین مقدار از مقادیر یک لیست در یک کلید
T LPop<T>(string cacheKey);
Task<T> LPopAsync<T>(string cacheKey);

// ایجاد یک کلید که لیستی از مقادیر را پشتیبانی میکند و میتوانید هر بار مقدار جدید به لیست آن اضافه کنید
long LPush<T>(string cacheKey, IList<T> cacheValues);
Task<long> LPushAsync<T>(string cacheKey, IList<T> cacheValues);

// گرفتن مقادیر یک لیست از داده بر اساس شماره ایندکس شروع و پایان برای مثال مقادیر ۳ تا ۷ از ۱۰ مقدار
List<T> LRange<T>(string cacheKey, long start, long stop);
Task<List<T>> LRangeAsync<T>(string cacheKey, long start, long stop);

// حذف کردن مقادیر یک لیست بر اساس تعداد وارد شده که بعد از مقدار وارد شده شروع به شمارش میشود
long LRem<T>(string cacheKey, long count, T cacheValue);
Task<long> LRemAsync<T>(string cacheKey, long count, T cacheValue);

// افزودن یک مقدار به لیستی از مقادیر یک کلید با گرفتن شماره ایندکس
bool LSet<T>(string cacheKey, long index, T cacheValue);
Task<bool> LSetAsync<T>(string cacheKey, long index, T cacheValue);

// بررسی میکند که لیست مقداری برای شماره ایندکس شروع و پایان درون خودش دارد یا خیر
bool LTrim(string cacheKey, long start, long stop);
Task<bool> LTrimAsync(string cacheKey, long start, long stop);

//  https://redis.io/commands/lpushx
long LPushX<T>(string cacheKey, T cacheValue);
Task<long> LPushXAsync<T>(string cacheKey, T cacheValue);

// https://redis.io/commands/linsert
long LInsertBefore<T>(string cacheKey, T pivot, T cacheValue);
Task<long> LInsertBeforeAsync<T>(string cacheKey, T pivot, T cacheValue);

// https://redis.io/commands/linsert
long LInsertAfter<T>(string cacheKey, T pivot, T cacheValue);
Task<long> LInsertAfterAsync<T>(string cacheKey, T pivot, T cacheValue);

// https://redis.io/commands/rpushx
long RPushX<T>(string cacheKey, T cacheValue);
Task<long> RPushXAsync<T>(string cacheKey, T cacheValue);

// https://redis.io/commands/rpush
long RPush<T>(string cacheKey, IList<T> cacheValues);
Task<long> RPushAsync<T>(string cacheKey, IList<T> cacheValues);

// https://redis.io/commands/rpop
T RPop<T>(string cacheKey);
Task<T> RPopAsync<T>(string cacheKey);
  • متد‌های Set
// https://redis.io/commands/SAdd
long SAdd<T>(string cacheKey, IList<T> cacheValues, TimeSpan? expiration = null);
Task<long> SAddAsync<T>(string cacheKey, IList<T> cacheValues, TimeSpan? expiration = null);
       
// https://redis.io/commands/SCard
long SCard(string cacheKey);
Task<long> SCardAsync(string cacheKey);

// https://redis.io/commands/SIsMember
bool SIsMember<T>(string cacheKey, T cacheValue);
Task<bool> SIsMemberAsync<T>(string cacheKey, T cacheValue);

// https://redis.io/commands/SMembers
List<T> SMembers<T>(string cacheKey);
Task<List<T>> SMembersAsync<T>(string cacheKey);

// https://redis.io/commands/SPop
T SPop<T>(string cacheKey);
Task<T> SPopAsync<T>(string cacheKey);

// https://redis.io/commands/SRandMember
List<T> SRandMember<T>(string cacheKey, int count = 1);
Task<List<T>> SRandMemberAsync<T>(string cacheKey, int count = 1);

// https://redis.io/commands/SRem
long SRem<T>(string cacheKey, IList<T> cacheValues = null);
Task<long> SRemAsync<T>(string cacheKey, IList<T> cacheValues = null);
  • متد‌های Stored Set
// https://redis.io/commands/ZAdd
long ZAdd<T>(string cacheKey, Dictionary<T, double> cacheValues);
Task<long> ZAddAsync<T>(string cacheKey, Dictionary<T, double> cacheValues);
       
// https://redis.io/commands/ZCard       
long ZCard(string cacheKey);
Task<long> ZCardAsync(string cacheKey);

// https://redis.io/commands/ZCount
long ZCount(string cacheKey, double min, double max);
Task<long> ZCountAsync(string cacheKey, double min, double max);

// https://redis.io/commands/ZIncrBy
double ZIncrBy(string cacheKey, string field, double val = 1);
Task<double> ZIncrByAsync(string cacheKey, string field, double val = 1);

// https://redis.io/commands/ZLexCount
long ZLexCount(string cacheKey, string min, string max);
Task<long> ZLexCountAsync(string cacheKey, string min, string max);

// https://redis.io/commands/ZRange
List<T> ZRange<T>(string cacheKey, long start, long stop);
Task<List<T>> ZRangeAsync<T>(string cacheKey, long start, long stop);

// https://redis.io/commands/ZRank
long? ZRank<T>(string cacheKey, T cacheValue);
Task<long?> ZRankAsync<T>(string cacheKey, T cacheValue);

// https://redis.io/commands/ZRem
long ZRem<T>(string cacheKey, IList<T> cacheValues);
Task<long> ZRemAsync<T>(string cacheKey, IList<T> cacheValues);

// https://redis.io/commands/ZScore
double? ZScore<T>(string cacheKey, T cacheValue);
Task<double?> ZScoreAsync<T>(string cacheKey, T cacheValue);
  • متد‌های Hyperloglog
// https://redis.io/commands/PfAdd
bool PfAdd<T>(string cacheKey, List<T> values);
Task<bool> PfAddAsync<T>(string cacheKey, List<T> values);

// https://redis.io/commands/PfCount
long PfCount(List<string> cacheKeys);
Task<long> PfCountAsync(List<string> cacheKeys);

// https://redis.io/commands/PfMerge
bool PfMerge(string destKey, List<string> sourceKeys);
Task<bool> PfMergeAsync(string destKey, List<string> sourceKeys);
  • متد‌های Geo
// https://redis.io/commands/GeoAdd
long GeoAdd(string cacheKey, List<(double longitude, double latitude, string member)> values);
Task<long> GeoAddAsync(string cacheKey, List<(double longitude, double latitude, string member)> values);

// https://redis.io/commands/GeoDist
double? GeoDist(string cacheKey, string member1, string member2, string unit = "m");
Task<double?> GeoDistAsync(string cacheKey, string member1, string member2, string unit = "m");

// https://redis.io/commands/GeoHash
List<string> GeoHash(string cacheKey, List<string> members);
Task<List<string>> GeoHashAsync(string cacheKey, List<string> members);

// https://redis.io/commands/GeoPos
List<(decimal longitude, decimal latitude)?> GeoPos(string cacheKey, List<string> members);
Task<List<(decimal longitude, decimal latitude)?>> GeoPosAsync(string cacheKey, List<string> members);
برای اطلاعات بیشتر از متد‌های دیگر موجود در ردیس میتوانید از این لینک استفاده کنید. 

3 - پروایدر Hybrid :
این پروایدر، روشی از کشینگ را مابین local caching و distributed caching، ارائه میدهد و میتوانید از یک پروایدر Local مثل InMemory و پروایدر Distributed مثل Redis، همزمان باهم استفاده کنید که در یک کانال باهم و در راستای هم کار میکنند.
اما سوال اینجاست که این قابلیت دقیقا چه کاری انجام میدهد؟
همانطور که قبلا گفته شد، کش In-Memory سرعت بالاتری نسبت به کش Distributed دارد؛ اما دچار معایبی در حالت چند سروری هست که این معایب از جمله حذف شدن دیتای یک سرور، در صورت Down شدن آن، Sync نبودن کش سرور‌ها باهم دیگر و دو نسخه، کش کردن دیتا در هر سرور و موارد دیگری که میتوان نام برد. اما از طرفی کش Distributed مشکلات چند سروری را با قرار دادن یک مرکزیت واحد کش در حافظه شبکه شده سرور‌ها برطرف میکند و اطلاعات سرور‌ها، از یک منبع خوانده میشود و طبعا مشکلات In-Memory را نخواهیم داشت؛ اما به دلیل رد و بدل شدن دیتا در محیط شبکه و عمل Serialize , Deserialize که هنگام عبور دیتا روی آن صورت میگیرد، بخشی از سرعت، کاهش خواهد یافت و درنهایت Performance کمتری را نسبت به In-Memory ارائه میدهد.
حالا برای اینکه بتوانیم سیستم کش خودمان را طوری طراحی کنیم که عیب‌های (Local)In-Memory و Distributed را نداشته باشیم و بتوانیم از هریک به شکلی درست استفاده کنیم که هم اطلاعاتمان Sync باشد و هم از سرعت بالای In-Memory برخوردار شویم، میتوانیم از پروایدر Hybrid استفاده کنیم. 

شیوه کار این پروایدر به این صورت است که وقتی برنامه برای بار اول به کش In-Memory درخواستی را ارسال میکند و کش مورد نظر در آن وجود ندارد، برنامه یک درخواست دیگر را به کش Distributed ارسال میکند و دیتای مورد نظر را به کاربر بازگشت میدهد و علاوه بر آن یک کپی از کش آن دیتا، در کش In-Memory هم ایجاد میکند. با این ساختار از دفعات بعد که کاربر درخواستی را ارسال کند، دیتای درخواستی در In-Memory نیز موجود خواهد بود و سریع‌تر از بار اول پاسخ را ارسال خواهد کرد.
از طرفی نیز وقتی کاربر دیتای جدیدی را ذخیره میکند، ابتدا آن دیتا در In-Memory کش شده و سپس با درخواست خود پروایدر، در کش Distributed هم اعمال میشود تا در نهایت دیتابیس نیز آن را ذخیره کند.
وقتی این اتفاق می‌افتد، پروایدر Hybrid با کمک پکیج Bus.Redis به کش In-Memory سرور‌های دیگر دستور Pull کردن دیتا کش‌های جدید را ارسال میکند و در نهایت همه سرور‌ها نیز به کمک Distributed مرکزی باهم Sync خواهند بود.

برای فعال سازی این پروایدر باید پکیج‌های زیر را در برنامه خود نصب کنید: 
Install-Package EasyCaching.HybridCache
Install-Package EasyCaching.InMemory
Install-Package EasyCaching.Redis
Install-Package EasyCaching.Bus.Redis
در این مجموعه از پکیج‌ها، از یک پروایدر Local(InMemory) و یک پروایدر distributed(Redis) استفاده شده و همانطور که گفته شد، مدیریت هماهنگ سازی این دو، توسط پکیج دیگری بنام EasyCaching.Bus.Redis صورت میگیرد.

تنظیمات فعالسازی این پروایدر هم متشکل از تنظیمات دو پروایدر In-Memory و Redis، بعلاوه معرفی این دو به هم در متد UseHybrid خواهد بود. 
   services.AddEasyCaching(option =>
       // local
       option.UseInMemory("c1");

       // distributed
       option.UseRedis(config =>
                config.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379));
       }, "c2");

       // combine local and distributed
        option.UseHybrid(config =>
                 // specify the local cache provider name after v0.5.4
                   config.LocalCacheProviderName = "c1"
                // specify the distributed cache provider name after v0.5.4
                   config.DistributedCacheProviderName = "c2"
        });

          // use redis bus
           .WithRedisBus(busConf =>
                   busConf.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379));
           });
});
برای استفاده از این پروایدر، متفاوت با پروایدر‌های قبلی، باید اینترفیس IHybridCachingProvider را فراخوانی کنیم. متد‌های موجود در این اینترفیس، همان متدهایی است که در اینترفیس IEasyCachingProvider وجود دارند و از نظر نام متد و روش استفاده، تفاوتی میان آن نیست.

 پیشنهاد شخصی در Distributed Cache‌ها 
همانطور که گفته شد Distributed کش‌ها، گزینه مناسب‌تری برای برنامه‌های چند سروری هستند؛ اما در این حالت مواردی مثل Round Trip شبکه و جابجایی اطلاعات در این محیط بعلاوه Serialize , Deserialize هایی که باید انجام شوند دلیلی میشود تا سرعت آن در پاسخ به درخواست‌های برنامه، نسبت به حالت تک سروری(In-Memory) کمتر باشد. Hybrid Provider یکی از روش‌های حل این مشکل بوده که معرفی کردیم. اما برای اینکه تیر خلاص را به پیکره سیستم Distributed Cache خود بزنید و تریک فنی آخر را نیز روی آن اجرا کنید، پیشنهاد میکنم از پکیج EasyCaching.Extensions.EasyCompressor که بر پایه پکیج EasyCaching نوشته شده استفاده کنید. این پکیج، اطلاعات را قبل از کش شدن، فشرده سازی میکند و حجم اطلاعات را به طور محسوسی کاهش میدهد که میزان فضای اشغالی Ram را کم کرده و همچنین عمل جابجایی اطلاعات را نیز تسریع می‌بخشد. میتوانید از این پکیج هم در Redis و هم در Hybrid استفاده کنید. چگونگی استفاده از آن نیز در لینک Github ذکر شده موجود است.

معرفی پروژه
تا اینجا با مفاهیمی که برای شروع استفاده حرفه‌ای از کش در پروژه‌تان نیاز بود، آشنا شدید. در پروژه‌های واقعی، میتوانیم از این سیستم به روش‌های مختلفی در سطوح مختلفی از برنامه استفاده کنیم؛ برای مثال کد‌های مربوط به عملیات کش را میتوان بصورت ساده‌ای در هر کنترلر تزریق و در اکشن‌ها استفاده کرد؛ یا از لایه کنترلر، آن را به لایه سرویس منتقل کرد. در روشی دیگر میتوانیم یک Attribute را برای این عمل در نظر بگیریم و یا اینکه آن را بصورت یک Middleware اختصاصی در برنامه پیاده کنیم. 
در این پروژه علاوه بر اینکه سعی کرده‌ام استفاده از Provider‌های معرفی شده را در محیط واقعی‌تر پیاده سازی کنم، در هر پروژه از این Solution، کش را به شیوه‌ای متفاوت در لایه‌های مختلفی از برنامه قرار داده‌ام تا شما همراهان بتوانید طبق نیازتان از روشی مناسب و بهینه در پروژه‌های واقعی خود از آن استفاده کنید.
مطالب
نصب dotnet core framework روی اوبونتو 16.04
مایکروسافت در چند سال اخیر و به خصوص بعد از روی کار آمدن ساتیا نادلا،  رویکرد خاصی را به مباحث Cross Platform پیدا کرد، تا جایی که dotnet core شکل گرفت. این فناوری جدید به شما این امکان را میدهد تا دات نت فریمورک را بر روی سیستم عامل‌های دیگری چون لینوکس و مک نصب کنید. در سایت اختصاصی این فناوری،  نحوه نصب آن بر روی توزیع‌های مختلف سیستم عامل  لینوکس،  توضیحاتی داده شده است و یکی از این آموزش‌ها مربوط به پرچم دار توزیع‌های لینوکس و به خصوص خانواده دبیان یعنی اوبونتو است. در این راهنما، تنها نسخه خاصی از اوبونتو یعنی نسخه 14.04 آن مدنظر می‌باشد ولی آخرین نسخه اوبونتو 16.04 است که در اینباره توضیحی داده نشده است و با نصب آن به مشکل بر می‌خورد. در این مقاله میخواهیم نصب dotnet core را بر روی آخرین نسخه این سیستم عامل، بررسی کنیم.

با توجه به ابتدای آموزش، کدهای زیر را در ترمینال وارد میکنیم:
sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'

sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893

sudo apt-get update
اولین خط از لینوکس، مخزنی را که فایل‌های مربوط به دات نت، در آن قرار دارند، در لینوکس ثبت می‌کند. دستور sh وظیفه‌ی اجرای اسکریپت را بر عهده دارد که میتوانید یک فایل اسکریپت را به آن معرفی کنید. ولی اگر با سوپیچ c به کار رود، می‌توانید خود دستور ارسالی را به آن معرفی کنید. اجرای این اسکریپت منجر به معرفی مخازن دات نت می‌شود که میتواند برای نصب و به روزرسانی آن به کار رود. در ساختار فایل‌های خانواده‌ی دبیان، فایلی به نام Sources.list وجود دارد که شامل آدرس مخازن رسمی اوبونتو جهت دریافت نرم افزاری‌ها آن می‌شود. ولی اگر قرار باشد مخازنی جدای از مخازن رسمی معرفی گردند، باید داخل دایرکتوری sources.list.d قرار گیرند و با نامی مشخص و با همان فرمت مشخص، ذخیره گردند.

در خط  دوم،  کلید اختصاصی این پکیج جهت اعتبارسنجی معرفی می‌گردد که در سایت جاری قبلا به آن پرداخته شده است. در خط آخر هم مخازن موجود را به روزرسانی می‌کنیم تا آماده استفاده شود.
در سایت مایکروسافت از شما خواسته می‌شود که کد زیر را وارد کنید:
sudo apt-get install dotnet-dev-1.0.0-preview1-002702
ولی بهتر است به جای آن از جدیدترین بسته استفاده کنید که باگ آن رفع شده است و علت باگ قبلا به آن پرداخته شده است. پس کد زیر را به جای کد بالا وارد می‌کنیم:
sudo apt-get install dotnet-dev-1.0.0-preview2-003096
چه بسته قبلی و چه جدید را نصب کنید، به خطای زیر برخورد خواهید کرد:
The following packages have unmet dependencies:
 dotnet-dev-1.0.0-preview1-002702 : Depends: dotnet-sharedframework-microsoft.netcore.app-1.0.0-rc2-3002702 but it is not going to be installed
E: Unable to correct problems, you have held broken packages.
که برای حل آن، باید ابتدا بسته‌ی وابسته نصب گردد و سپس دستور بالا دوباره اجرا شود. برای دریافت این وابستگی، به این لینک مراجعه کنید و فایل مربوطه را که با پسوند deb است، دریافت کنید. بعد از آن باید فایل دریافتی را نصب نمایید که برای نصب این نوع بسته‌ها از دستور زیر استفاده می‌کنیم:
sudo dpkg -i libicu52_52.1-3ubuntu0.4_amd64.deb
بعد از نصب موفقیت آمیز این بسته، دستورات زیر را اجرا میکنیم تا بسته dotnet core نصب شود:
sudo apt-get install dotnet-sharedframework-microsoft.netcore.app-1.0.0-rc2-3002702
sudo apt-get install dotnet-dev-1.0.0-preview2-003096
بعد از اتمام نصب، دستور زیر را جهت اطمینان از صحت نصب وارد کنید، تا نسخه نصب شده جاری به شما نمایش داده شود:
dotnet --version
خروجی:
1.0.0-preview2-003096
البته ممکن است خروجی دفعه اول به خاطر پیام‌های خوش آمدگویی، خطوط بیشتری را داشته باشد، ولی برای دفعات آینده همین یک خط است.
مطالب
PowerShell 7.x - قسمت دوازدهم - آشنایی با GitHub Actions و بررسی یک مثال
GitHub Actions، یک راه‌حل Continuous Integration است که توسط آن می‌توان یکسری trigger workflowهایی را حین push کردن، ارسال PR و … اجرا کرد. برای کارهایی از قبیل اجرای تست‌های خودکار، اجرای یکسری تست و همچنین deploy کردن از آن استفاده میشود. GitHub Actions در واقع یک managed serviceیی است که توسط GitHub ارائه میشود. به این معنا که نیازی نیست خودمان درگیر مدیریت منابع باشیم. همچنین تعداد زیادی اکشن توسط community برای استفاده توسعه داده شده‌اند. در ادامه ابتدا مرور سریعی بر GitHub Actions خواهیم داشت، سپس یک مثال از آن را به همراه PowerShell بررسی خواهیم کرد.

ساختار یک اکشن
  • Workflow: یکی از مفاهیمی که باید با آن آشنا باشیم workflowها هستند. یک workflow مجموعه‌ایی از jobهایی هستند که در رخدادهای خاصی اجرا میشوند. در واقع یک workflow یک CI pipeline است که با کمک YAML آنها را تعریف میکنیم.
  • Runner: اینها به اصطلاح compute machineهایی هستند که workflowها را اجرا میکنند. این runnerها هم میتوانند به صورت سفارشی باشند و هم سرویس‌های ارائه شده توسط GitHub باشند.
  • Job: مجموعه‌ایی از مراحلی که درون یک runner workspace اجرا میشوند.
  • Step: در نهایت stepها هستند که کوچکترین بخش GitHub Actions هستند. stepها میتوانند فایل اسکریپت، Dockerfile یا یک community action باشند.

نمونه‌ی یک Workflow
در ادامه یک workflow را مشاهده میکنید. در اینجا نام آن را به Build Application Code تنظیم کرده‌ایم. سپس با کمک on، تریگر اجرای این workflow را تعیین کرده‌ایم. به این معنا که با push کردن بر روی ریپوزیتوری، workflow اجرا خواهد شد. سپس توسط job، لیست jobهایی را که میخواهیم این workflow اجرا کند، مشخص کرده‌ایم. اولین jobی که اجرا خواهد شد، build است. این job قرار است بر روی یک ماشین با آخرین نگارش ابونتو اجرا شود. مراحل یا stepهای این job نیز به ترتیب، clone کردن سورس‌کد و سپس نصب وابستگی‌های پروژه است. در نهایت job بعدی، test خواهد بود که با کمک needs تعیین کرده‌ایم که ابتدا مرحله‌ی قبل یعنی build اجرا شود و سپس وارد این مرحله شود. 
name: Build Application Code

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v2
      - name: Install Libraries
        uses: pip install -r requirements.txt -t .
    
    test:
      runs-on: ubuntu-latest
      needs: build
      steps:
      ...

مثال PowerShell
هدف، پویا کردن قسمت README یک پروفایل GitHub است. برای این مثال من از پروفایل خودم استفاده خواهم کرد. درون فایل README میخواهم لیست آخرین بلاگ‌پست‌هایی را که منتشر کرده‌ام، به همراه یک کامپوننت، تعداد قدم‌هایی را که در طول روز پیاده‌روی میکنم، نمایش دهم. برای نمایش آخرین دیتای درون پروفایلم، نیاز به دو Action Workflow داشتیم که هر یک در تایم خاصی اجرا شده و اسکریپت‌هایی را که در ادامه توضیح خواهم داد، اجرا کنند. برای اینکار درون دایرکتوری مخصوص github.، ساختار زیر را ایجاد کرده‌ام: 
├── .github
│   ├── scripts
│   └── workflows
├── README.md
├── assets
└── deps
ابتدا workflow اول یعنی نمایش بلاگ‌پست‌های اخیر را بررسی خواهیم کرد: 
name: Update Recent Blog Posts

on:
  schedule:
    - cron: "0 0 * * 0" # Run once a week at 00:00 (midnight) on Sunday
  workflow_dispatch:

jobs:
  update_posts:
    runs-on: ubuntu-latest

    steps:
    - name: Check out repository code
      uses: actions/checkout@v3

    - name: Run the script for fetching latest blog posts
      shell: pwsh
      run: |
        . ./.github/scripts/Get-Posts.ps1
        
    - name: Commit and Push the changes
      uses: mikeal/publish-to-github-action@master
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
workflow فوق یکبار در هفته فایل PowerShell موردنظر را اجرا خواهد کرد. در ادامه محتویات این فایل را مشاهده می‌کنید: 
Function Get-Posts {
    Param (
        [Parameter(Mandatory = $false)]
        [string]$rssUrl
    )
    $posts = @()
    $feed = [xml](Invoke-WebRequest -Uri $rssUrl).Content
    $feed.rss.channel.item | Select-Object -First 3 | ForEach-Object {
        $post = [PSCustomObject]@{
            Title       = $_.title."#cdata-section" ?? $_.title
            Link        = $_.link
            Description = $_.description."#cdata-section" ?? $_.description
            PubDate     = $_.pubDate
        }
        $posts += $post
    }
    $posts
}

Function Get-DntipsPosts {
    $assemblyPath = "$(Get-Location)/deps/CodeHollow.FeedReader.dll"
    [Reflection.Assembly]::LoadFile($assemblyPath)
    $feed = [CodeHollow.FeedReader.FeedReader]::ReadAsync("https://www.dntips.ir/feed/author/%d8%b3%db%8c%d8%b1%d9%88%d8%a7%d9%86%20%d8%b9%d9%81%db%8c%d9%81%db%8c").Result
    $posts = @()
    $feed.Items | Select-Object -First 3 | ForEach-Object {
        $post = [PSCustomObject]@{
            Title       = $_.Title
            Link        = $_.Link
            Description = $_.Description
            PubDate     = $_.PublishingDate
        }
        $posts += $post
    }
    $posts
}

Function Set-Posts {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSCustomObject[]]$posts,
        [Parameter(Mandatory = $false)]
        [string]$marker = "## Recent Blog Posts - English"
    )
    Begin {
        $readMePath = "./README.md"
        $readmeContents = Get-Content -Path $readMePath -Raw
        $markdownTable = "| Link | Published At |`n"
        $markdownTable += "| --- | --- |`n"
    }
    Process {
        if ($null -eq $_.Title) {
            return
        }
        $date = Get-Date -Date $_.PubDate
        $link = "[$($_.Title)]($($_.Link))"
        
        $markdownTable += "| $($link) | $($date.ToString("dd/MM/yy")) |`n"
    }
    End {
        $updatedContent = $readmeContents -replace "$marker\n([\s\S]*?)(?=#| $)", "$marker`n$($markdownTable)`n"
        $updatedContent | Set-Content -Path $readMePath
    }
}

Function Set-Blogs {
    $recentBlogPostsStr = "## Recent blog posts -"
    Get-Posts("https://dev.to/feed/sirwanafifi") | Set-Posts -marker "$recentBlogPostsStr dev.to"
    Get-Posts("https://sirwan.infohttps://www.dntips.ir/rss.xml") | Set-Posts -marker "$recentBlogPostsStr sirwan.info"
    Get-DntipsPosts | Set-Posts -marker "$recentBlogPostsStr dntips.ir"
}

Set-Blogs

در اینجا تابع Set-Blogs فراخوانی خواهد شد. کاری که این تابع انجام میدهد، دریافت آخرین بلاگ‌پست‌هایی که در جاهای مختلف منتشر کرده‌ام و سپس آپدیت کردن فایل README با دیتای جدید است. همانطور که مشاهده میکنید برای خواندن فید سایت جاری، از پکیج FeedReader استفاده کرده‌ام. در PowerShell توسط Invoke-WebRequest میتوانیم یک فید را پارز کنیم؛ اما برای سایت جاری با خطا روبرو شدم و در نهایت تصمیم گرفتم از یک پکیج دات‌نتی استفاده کنم. وابستگی موردنظر، درون دایرکتوری dep به صورت DLL قرار دارد. سپس از طریق PowerShell اسمبلی مربوطه بارگذاری شده و از کتابخانه موردنظر استفاده شده‌است. در نهایت برای آپدیت کردن فایل README.md یکسری marker تعیین کرده‌ام که با یک جایگزینی محتویات موردنظر، آنجا قرار خواهند گرفت.

workflow بعدی نیز به صورت زیر میباشد که در پایان هر روز در یک ساعت مشخص اجرا خواهد شد: 
name: Update Step Component

on:
  schedule:
    - cron: "0 18 * * *"
  workflow_dispatch:

jobs:
  update_steps:
    runs-on: ubuntu-latest

    steps:
    - name: Check out repository code
      uses: actions/checkout@v3

    - name: Run the script for fetching my latest steps
      shell: pwsh
      env:
          STEPS_URI: ${{ secrets.STEPS_URI }}
      run: |
        . ./.github/scripts/Get-Steps.ps1
    
    - name: Commit and Push the changes
      uses: mikeal/publish-to-github-action@master
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
workflow فوق نیز همانند روال قبل فایل اسکریپت موردنظر را توسط dot sourcing اجرا میکند. این روال هر روز، ساعت ۱۸ انجام خواهد شد. اسکریپت مربوطه نیز به صورت زیر پیاده‌سازی شده است: 
Function Set-Steps {
    Param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject]$json
    )
    Write-Host ($json | ConvertTo-Json)
    $SvgPath = "$(Get-Location)/assets/step.svg"
    $SvgContent = Get-Content -Path $SvgPath -Raw
    $TextTags = @"
    <tspan id="step-count" font-weight="bold">$([System.String]::Format("{0:n0}", [int]$json.steps))</tspan>
"@
    $DatetimeTags = "<text id=""datetime"" x=""800"" y=""72"" font-size=""39"" fill="#99989E"">$($json.date)</text>"
    $SvgContent = $SvgContent -Replace '<tspan id="step-count" font-weight="bold">.*?</tspan>', $TextTags
    $SvgContent = $SvgContent -Replace '<text id="datetime" x="800" y="72" font-size="39" fill="#99989E">.*?</text>', $DatetimeTags
    $SvgContent | Set-Content -Path $SvgPath
}

Function Get-LatestSteps {
    Try {
        $Uri = $env:STEPS_URI
        Write-Host "Uri: $Uri"
        $JsonResult = (Invoke-WebRequest -Uri $Uri).Content | ConvertFrom-Json
        Write-Host "Steps: $($JsonResult.steps)"
        Return $JsonResult
    }
    Catch {
        Return @{
            steps = 0
            date  = Get-Date -Format "yyyy-MM-dd"
        }
    }
}

Write-Host "Getting latest steps..."
Get-LatestSteps | Set-Steps
Write-Host "Done!"
اسکریپت فوق نیز همانند منطق اسکریپت قبلی یعنی جایگذاری رشته‌ی موردنظر با کمک عبارات باقاعده انجام شده‌است. در اینجا دیتای مربوط به قدم‌های من از APIایی که از طریق Environment Variable تعیین شده‌است، دریافت میشود و سپس خروجی آن که یک JSON است به تابع Set-Steps برای بروزرسانی فایل README.md ارسال میشود. در دو workflow نشان داده شده بعد از ایجاد تغییرات بر روی فایل‌های README.md و همچنین فایل SVG نیاز است که تغییرات را مجدداً به ریپوزیتوری پوش کنیم. برای اینکار از یک community action با نام  publish-to-github-action استفاده شده‌است. این اکشن نیاز به دسترسی پوش به ریپوزیتوری‌مان دارد که در اینجا ما از یک secret key مخصوص، با نام GITHUB_TOKEN استفاده کرده‌ایم. این توکن به صورت خودکار جنریت میشود و نیازی نیست خودمان آن را تنظیم کنیم.
خروجی در نهایت، اینچنین خواهد بود:

نظرات مطالب
طراحی یک ماژول IpBlocker در ASP.NET MVC
اطلاعات IPهای بسته شده توسط خود این ماژول لاگ می‌شود. چه چیزی را در لاگ مشاهده می‌کنید؟ یک قسمت آن reason هست یا دلیل بسته شدن. این لاگ‌ها را در کنسول می‌توانید مشاهده کنید. برنامه‌ی ASP.NET Core خود را با دستور dotnet run اجرا و لاگ‌های کنسول آن‌را بررسی کنید.
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی
- یکبار دستور dotnet build را در ریشه‌ی پروژه‌ی اصلی و یکبار این دستور را در ریشه‌ی پروژه‌ی لایه دیتا به صورت مجزا اجرا کنید.
- از NET Core CLI. استفاده می‌کنید (از روش ... dotnet ef)؟ از پارامتر v- آن استفاده کنید (verbose-)
- سازنده‌ی کلاس Context شما وابستگی خاصی دارد؟ آیا IDesignTimeDbContextFactory را برای آن مشخص کرده‌اید؟
نظرات مطالب
Gulp #1
مرسی بابت این مقاله. در زیر یک توضیحاتی در مورد اطمینان از نصب nodeJs دادم
در همون مسیری که در مقاله ذکر شده NodeJs رو دانلود و نصب کنید. توجه داشته باشید در هنگام نصب حتما npm package manager رو انتخاب کنید :


برای اینکه بدونیم NodeJs رو سیستم نصب هستش باید دستور زیر رو در خطر فرمان اجرا کنیم :
node -v
خروجی باید معادل آخرین نسخه نصبی باشه یعنی به این صورت :
v4.1.1
و برای اینکه اطمینان داشته باشیم npm هم نصب هستش کافیه دستور زیر رو اجرا کنیم :
npm -v
و خورجی ، مثل دستور قبلی باید نسخه npm رو نمایش بده ، یعنی چیزی مثل خروجی زیر :
2.14.4
برای تست نهایی و اجرای یک فایل js برای اینکه اطمینان داشته باشیم همه چیز خوب پیش رفته ، یک فایل js با نام dotnettips_info.js درست کنید و محتوای اون رو به این صورت بنویسید :
console.log('Hello www.dotnettips.info');
 و دستور زیر رو به خط فرمان صادر کنید :
node dotnettips_info.js

 و خروجی زیر رو باید ببینید :
Hello www.dotnettips.info

و در نهایت چیزی همانند زیر باید داشته باشید :