مطالب
ایجاد ابزارهای سراسری ویژه NET Core.
از زمان ارائه نگارش net core 2.1.، ابزارهای سراسری (Global tools) نیز معرفی شدند. استفاده از این ابزارها در محیط cli در جهت آسان‌تر شدن و سریعتر شدن وظایف، صورت می‌پذیرد. net core sdk. مربوطه، تمامی امکانات لازم از جهت ایجاد، حذف و به روزرسانی ابزارها را از طریق nuget شامل می‌گردد. تعداد بسیار زیادی از این ابزارها در حال حاضر ایجاد شده‌اند که در لیست زیر، تعدادی از آن‌ها را معرفی میکنیم و سپس به نحوه‌ی ایجاد این نوع ابزارها میپردازیم.
  • dotnet-ignore : این ابزار جهت دریافت فایل‌های gitignore. کاربرد داشته و از یک مخزن عمومی گیت هاب جهت دریافت این فایل‌ها استفاده میکند. این مخزن شامل انواع قالب‌های gitignore در پروژه‌های متفاوت میباشد. با استفاده از این ابزار، ایجاد فایل gitignore راحت‌تر و سریعتر امکانپذیر میباشد.
  • dotnet-serve : میزبانی و نمایش لیست فایل‌های استاتیک محلی و اجرای آن‌ها را در بستر http، فراهم مینماید.
  • dotnet-cleanup : جهت پاکسازی محیط بیلد مانند دایرکتوری‌های bin و obj میباشد. همان کار گزینه clean در منوی بیلد را بازی میکند.
  • dotnet-warp : این ابزار در واقع پروژه Warp است که برای ایجاد یک تک فایل اجرایی جهت انتقال راحت‌تر فایل پروژه صورت میگیرد که همه وابستگی‌های آن در همان تک فایل قرار میگیرد.
  • Amazon.ECS.Tools Amazon.ElasticBeanstalk.Tools  و  Amazon.Lambda.Tools  : این ابزارها که به صورت رسمی از طرف آمازون ارائه شده‌اند که جهت deploy شدن راحت‌تر پروژه به محیط‌های توسعه وب آمازون مورد استفاده قرار میگیرند.
جهت مشاهده لیست کامل این ابزارها، به این مخزن گیت هاب مراجعه نمایید. نام ابزار و همچنین لینک‌ها و توضیحات هر کدام، در این مخزن موجود است. همچنین جهت اضافه شدن ابزاری که در لیست نیست، از طریق ایجاد issue یا pull request لیست را به روزرسانی نمایید.

نحوه‌ی نصب، حذف و به روزرسانی ابزارهای سراسری
جهت نصب یک ابزار، از دستور زیر استفاده میکنیم:
dotnet tool install -g dotnet-ignore
سوییچ g به معنای نصب سراسری ابزار و افزوده شدن آن به متغیرهای محیطی PATH میباشد که به راحتی در هر مسیری از محیط کنسول در دسترس خواهد بود و به مسیر dotnet/tools/. محدود نخواهد بود.
جهت مشاهده لیست تمامی ابزاهای سراسری نصب شده بر روی سیستم میتوانید از کامند زیر استفاده نمایید:
dotnet tool list -g
نحوه به روزرسانی ابزار و ارتقا آن به آخرین نسخه پایدار، با دستور زیر میباشد:
dotnet tool update -g dotnet-ignore
دستور حذف:
dotnet tool uninstall -g dotnet-ignore

ایجاد یک ابزار سراسری
جهت ساخت یک ابزار سراسری نیاز است تا یک پروژه را از نوع کنسول ایجاد نمایید و سپس به فایل csproj، خطوط زیر را اضافه کنید:
<PropertyGroup>
    <PackAsTool>true</PackAsTool>
    <ToolCommandName>dotnet-mytool</ToolCommandName>
    <PackageOutputPath>./nupkg</PackageOutputPath>
</PropertyGroup>
گزینه PackAsTool، امکان تبدیل فایل اجرایی شما را به یک ابزار سراسری فراهم میکند. دو گزینه بعدی که اختیاری است، به ترتیب شامل نام ابزار سراسری است که در صورت ذکر نشدن نام فایل پروژه، بدون پسوند csproj. میباشد و سومین مورد نیز مسیر قرارگیری فایل ابزار سراسری به عنوان یک بسته nuget میباشد.
جهت ساخته شدن فایل، ابتدا یکبار پروژه را بیلد کرده و پس از اجرای دستور dotnet pack، فایل پکیج در مسیر ذکر شده ساخته میشود و آماده انتقال به مخازن nuget میباشد. جهت تست و اجرای ابزار بر روی سیستم خود قبل از عرضه نهایی نیاز است تا با دستور زیر آن را بر روی سیستم خود نصب و آزمایش نمایید:
dotnet tool install --global --add-source ./nupkg globaltools
سوییچ global که در بالاتر نیز توضیح داده شد، باعث نصب سراسری ابزار میگردد و سوییچ add-source که بعد از آن مسیر فایل ابزار، آمده است، به این معنا است که به صورت موقت، این دایرکتوری یا مسیر را به عنوان مخزن nuget  شناسایی کرده تا امکان یافتن بسته در آن مسیر مهیا گردد و سپس نام پروژه در پایان ذکر میگردد. در آخر جهت اطمینان از نصب میتوانید ابزار را صدا بزنید:
dotnet-mytool
با توجه به اینکه اصل مطلب گفته در رابطه با ایجاد یک ابزار سراسری در اینجا به پایان میرسد، ولی ایجاد یک ابزار خط فرمانی نیازمند یک سری کدنویسی‌ها جهت ایجاد کامندها و سوییچ‌ها و راهنمای مربوط به آن نیز میباشد. بدین جهت کتابخانه زیر را نصب نمایید:
https://www.nuget.org/packages/McMaster.Extensions.CommandLineUtils
این کتابخانه شامل کلاس هایی جهت ایجاد یک ابزار خط فرمانی راحت‌تر میباشد.

ایجاد یک ابزار عمومی جهت یادداشت نویسی
برای استفاده از این کتابخانه، یک پروژه از نوع کنسول را با نام globaltools ایجاد نمایید و کتابخانه‌ی بالا را نصب نمایید. سپس به ازای هر کامند، یک کلاس را ایجاد میکنیم. ابتدا جهت ایجاد کامندی با نام NewNote یک کلاس را به همین نام میسازیم:
[Command(Description="Add a new note")]
    public class NewNote
    {
        [Required]
        [Option(Description="title of note")]
        public string Title{ get; set; }

        [Option(Description="content of note")]
        public string Body{ get; set; }
    }
با مزین کردن کلاس به ویژگی command، این کلاس را یک کامند معرفی کرده و شرحی از کاری که این کامند را انجام میدهد، نیز وارد می‌کنیم. این شرح بعدا در ابزار تولید شده به عنوان متن راهنما به کار می‌رود. سپس پراپرتی‌هایی را که با ویژگی option مزین گشته‌اند، به عنوان سوییچ معرفی میکنیم. همچنین میتوان از DataAnotation‌ها نیز جهت اعتبار سنجی نیز استفاده نمود. 
بعد از ایجاد موارد بالا، نیاز است که اکشنی که باید این کامند را اجرا کند، به آن اضافه کرد. جهت افزودن این اکشن، یک متد را با نام OnExecute، به بدنه این کلاس اضافه می‌کنیم:
[Command(Description="Add a new note")]
    public class NewNote:BaseClass
    {
        [Required]
        [Option(Description="title of note")]
        public string Title{ get; set; }

        [Option(Description="content of note")]
        public string Body{ get; set; }

        public void OnExecute(IConsole console)
        {

            var dir = GetBaseDirectory();
            if(!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            var filePath = Path.Combine(dir, Title + ".txt");
            File.WriteAllText(filePath, Body);
            console.WriteLine("the note is saved");
        }
    }
در پارامتر این متد، یک اینترفیس با نام IConsole جهت ارتباط با محیط کنسول دیده میشود که در پایان عملیات، پیام «یادداشت ذخیره شد» توسط آن چاپ میگردد. کار این متد به طور خلاصه این است که مسیر اجرایی ابزار جاری را دریافت کرده و سپس در یک دایرکتوری با نام notes، برای هر یادداشت یک فایل ایجاد شده و محتوای دریافتی از کاربر داخل آن قرار میگرد و نام هر فایل، موضوع یادداشتی است که کاربر وارد کرده‌است. متد GetBaseDirectory که مسیر ذخیره یادداشت‌ها را بر میگرداند، در کلاس BaseClass با محتوای زیر قرار گرفته است:
public class BaseClass
    {
        protected string GetBaseDirectory(){
            var baseDirectory = Environment.CurrentDirectory;
            return (Path.Combine(baseDirectory, "notes"));
        }
    }

کامند بعدی، لیست یادداشت‌های ثبت شده‌است:
public class List:BaseClass
    {
        [Option(Description="search a phrase in notes title")]
        public string Grep{ get; set; }
        public void OnExecute(IConsole console)
        {
            try
            {
                var baseDirectory = GetBaseDirectory();

                var dir = new DirectoryInfo(baseDirectory);
                var files = dir.GetFiles();
                foreach(var file in files)
                {
                    if(!String.IsNullOrEmpty(Grep) && !file.Name.Contains(Grep))
                        continue;

                    console.WriteLine(Path.GetFileNameWithoutExtension(file.Name));
                }
            }
            catch (Exception e)
            {
                console.WriteLine(e.Message);
            }
        }
    }
کار این کلاس، بازگردانی لیستی از یادداشت‌های ثبت شده است که حاوی سوییچ grep برای فیلتر کردن اسامی یادداشت هاست.
کلاس بعدی show نیز جهت نمایش کلاس بر اساس عنوان یادداشت است:
[Command(Description="show contnet of note")]
    public class Show:BaseClass
    {
        [Required]
        [Option(Description="title of note")]
        public string Title{ get; set; }


        public void OnExecute(IConsole console){
            var baseDirectory = GetBaseDirectory();
            var file = Path.Combine(baseDirectory, Title+".txt");

            if(!File.Exists(file))
            {
                console.WriteLine("The Note NotFound...");
                return;
            }
            console.WriteLine(File.ReadAllText(file));

        }
    }
در صورتیکه یادداشت مورد نظر وجود نداشته باشد، با پیام The Note NotFound کار به پایان میرسد.
بعد از اتمام کامندهای مربوطه، به کلاس program رفته و برای آن نیز ویژگی command را اضافه می‌کنیم و همچنین ویژگی subCommand را جهت معرفی کامندهایی که در برنامه در دسترس کاربر قرار میگیرند، اضافه میکنیم:
    [Command(Description="An Immediate Note Saver")]
    [Subcommand(typeof(NewNote),typeof(List),typeof(Show))]
    class Program
    {
        static int Main(string[] args)
        {
            return CommandLineApplication.Execute<Program>(args);
        }

        public int OnExecute(CommandLineApplication app, IConsole console)
        {
            console.WriteLine("You must specify a subcommand.");
            console.WriteLine();
            app.ShowHelp();
            return 1;
        }
    }
از آنجا که کلاس Program نیز به ویژگی command مزین شده‌است، متد OnExecute را اضافه می‌کنیم. تنها تفاوت این متد با متدهای قبلی، در نوع خروجی آن است که هر مقدار غیر از صفر، به منزله خطا میباشد. در این حالت چون کاربر کامندی را صادر نکرده است، ابتدا به کاربر اجباری بودن کامند را گوشزد کرده و سپس از طریق متد ShowHelp، راهنمای کار با ابزار را به او نشان داده و سپس کد یک را به منزله رخ دادن خطا یا اعلام شرایط غیرعادی بازمیگردانیم. نوع خروجی متد OnExecute در صورتی که void باشد، به معنای مقدار 0 میباشد که در کلاس‌های قبلی از آن استفاده کرده‌ایم.
در نهایت متد Main را نیز به شکل زیر تغییر می‌دهیم:
        static int Main(string[] args)
        {
            return CommandLineApplication.Execute<Program>(args);
        }
تکه کد CommandLineApplication.Execute آرگومان‌های ورودی را دریافت کرده و کامند مورد نظر را شناسایی میکند و همچنین مقدار عددی که از آن جهت return شدن استفاده می‌کند، همان عددهای صفر و غیر صفر میباشد که در بالا توضیح داده شده است.

نمونه استفاده از ابزار نهایی
PS D:\projects\Samples\globaltools> dotnet-notes new-note -t "sample1" -b "this is body"
the note is saved
PS D:\projects\Samples\globaltools> dotnet-notes new-note -t "test1" -b "this is body of another note"
the note is saved
PS D:\projects\Samples\globaltools> dotnet-notes list
sample1
test1
PS D:\projects\Samples\globaltools> dotnet-notes list -g sa
sample1
PS D:\projects\Samples\globaltools> dotnet-notes show -t sample1
this is body
در ابزار بالا کامند new-note به صورت جدا از هم با خط تیره مشخص شده‌است. دلیل این امر نیز جداشدن این کلمات در نام کلاس با حروف بزرگ است. در صورتیکه قصد ندارید نام کامندها با خط تیره از هم جدا شوند، باید نام کلاس را از NewNote به Newnote تغییر دهید.
اشتراک‌ها
کتاب Getting the Most from LINQPad Succinctly

LINQPad is a powerful testing tool for all .NET developers that can help them deliver solutions in less time. In Getting the Most from LINQPad Succinctly, returning Succinctly series author José Roberto Olivas Mendoza lays out different ways to extend the functionality built into LINQPad. In this ebook, you’ll learn how to use LINQPad to query Entity Framework models in Visual Studio, how to work with the LINQPad command-line utility, how to write your own extensions and visualizers, and how to write custom data context drivers.

TABLE OF CONTENTS
  • A Quick Tour of LINQPad

  • LINQPad and Entity Framework

  • LINQPad Scripting

  • LINQPad Extensibility

  • Custom Data Context Drivers

کتاب Getting the Most from LINQPad Succinctly
اشتراک‌ها
تغییرات ASP.NET Core در NET 7 Preview 6.

Here’s a summary of what’s new in this preview release:

  • Request decompression middleware
  • Output caching middleware
  • Updates to rate limiting middleware
  • Kestrel support for WebSockets over HTTP/2
  • Kestrel performance improvements on high core machines
  • Support for logging additional request headers in W3CLogger
  • Empty Blazor project templates
  • System.Security.Cryptography support on WebAssembly
  • Blazor custom elements no longer experimental
  • Experimental QuickGrid component for Blazor
  • gRPC JSON transcoding multi-segment parameters
  • MapGroup support for more extension methods 
تغییرات ASP.NET Core در NET 7 Preview 6.
اشتراک‌ها
ضبط و پخش مجدد درخواست های Http توسط کتابخانه داتنتی VCR Sharp

Vcr-sharp lets you record and playback HTTP requests! In this example, WithCassette is an extension method on HttpClientFactory. That extension method sets up a DelgatingHandler to a ReplayingHandler. That ReplayingHandler "loads the cassette" and returns it as a cached response. 

  using (var httpClient = HttpClientFactory.WithCassette( "my-test-scenario" ))      
  {        
    var request =  new HttpRequestMessage(HttpMethod.Get,  "http://www.iana.org/domains/reserved" );   
    var response = await httpClient.SendAsync(request);     
    var body = await response.Content.ReadAsStringAsync();      
    body.ShouldContain( "Example domains" );     
  }
ضبط و پخش مجدد درخواست های Http توسط کتابخانه داتنتی VCR Sharp
اشتراک‌ها
کتاب 100 تمرین برای فراگیری Rust
Welcome to "100 Exercises To Learn Rust"!
This course will teach you Rust's core concepts, one exercise at a time. You'll learn about Rust's syntax, its type system, its standard library, and its ecosystem.
کتاب 100 تمرین برای فراگیری Rust
اشتراک‌ها
ویدیوی آموزشی Angular 2.0 با Typescript

Angular 2.0 will be built using the TypeScript language. It will embrace TypeScript's idioms for working with immersive web experiences in larger applications.

You can get those same benefits by working with TypeScript and Angular together. In this session, you'll learn how Angular and TypeScript work together to create single page applications. You'll see how you can leverage the features of ECMAScript 6, and still support today's browsers. You'll see how adopting TypeScript can be as easy as changing the extensions on your .js files. How you use the TypeScript features is completely in your control. 

ویدیوی آموزشی Angular 2.0 با Typescript
نظرات مطالب
استفاده از چند Routing در یک پروژه ASP.NET MVC بدون درد و خونریزی
با سلام 
یه سوالی داشتم، میخواستم بدونم با این روش میشه توی MVC چنین ادرسی ساخت؟ اگر نمیشه با چه روشی میشه درست کرد؟
http://www.sitename.com/books/gifting-store/box-sets/children/pr?sid=bks,cwa,0b9,hj7&otracker=ch_vn_giftingsto 
مطالب
RadioButtonList در ASP.NET MVC

برای تهیه یک RadioButtonList نیز می‌توان از همان نکته‌ی CheckBoxList استفاده کرد: نام عناصر radio button اضافه شده به صفحه را یکسان وارد می‌کنیم. به این ترتیب یک گروه تشکیل خواهد شد و زمانیکه اطلاعات این عناصر به سرور ارسال می‌شود، اینبار بجای یک آرایه، تنها مقدار کنترل انتخاب شده، ارسال می‌گردد. یک مثال:
یک پروژه جدید و خالی ASP.NET MVC را آغاز کنید. سپس کنترلر Home و View خالی Index را نیز ایجاد نمائید. محتویات این دو را به نحو زیر تغییر دهید:

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm1 (Normal)</legend>
@using (Html.BeginForm(actionName: "HandleForm1", controllerName: "Home"))
{
@:your favorite tech: <br />
@Html.RadioButton(name: "tech", value: ".NET", isChecked: true) @:DOTNET <br />
@Html.RadioButton(name: "tech", value: "JAVA", isChecked: false) @:JAVA <br />
@Html.RadioButton(name: "tech", value: "PHP", isChecked: false) @:PHP <br />
<input type="submit" value="Submit" />
}
</fieldset>

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}

[HttpPost]
public ActionResult HandleForm1(string tech)
{
return RedirectToAction("Index");
}
}
}

در اینجا سه RadioButton با نامی یکسان در صفحه اضافه شده‌اند. سپس داخل متد HandleForm1 یک breakpoint قرار دهید. اکنون برنامه را اجرا کنید و فرم را به سرور ارسال نمائید. پارامتر tech با value عنصر انتخابی مقدار دهی خواهد شد.

تهیه یک RadioButtonList عمومی

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

@helper RadioButtonList(string groupName, IEnumerable<System.Web.Mvc.SelectListItem> items)
{
<div class="RadioButtonList">
@foreach (var item in items)
{
@item.Text
<input type="radio" name="@groupName"
value="@item.Value"
@if (item.Selected) { <text>checked="checked"</text> }
/>
<br />
}
</div>
}

برای مثال یک فایل را در مسیر app_code\Helpers.cshtml ایجاد کرده و اطلاعات فوق را به آن اضافه نمائید.
اینبار برای استفاده از آن خواهیم داشت:

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Tags = new[]
{
new SelectListItem { Text = ".NET", Value = "Val1", Selected = true },
new SelectListItem { Text = "JAVA", Value = "Val2", Selected = false },
new SelectListItem { Text = "PHP", Value = "Val3", Selected = false }
};
return View();
}

[HttpPost]
public ActionResult HandleForm2(string preferredTechnology)
{
return RedirectToAction("Index");
}
}
}

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>

<fieldset>
<legend>HandleForm2 (Helper)</legend>
@using (Html.BeginForm(actionName: "HandleForm2", controllerName: "Home"))
{
@:your favorite tech: <br />
@Helpers.RadioButtonList("preferredTechnology", (SelectListItem[])ViewBag.Tags)
<input type="submit" value="Submit" />
}
</fieldset>

متد سفارشی تهیه شده، یک آرایه از SelectListItem ها را دریافت کرده و به صورت خودکار تبدیل به RadioButtonList می‌کند. بر اساس نام آن می‌توان به مقدار انتخاب شده ارسالی به سرور در کنترلر مرتبط، دسترسی یافت.


تهیه یک Templated helper سفارشی

در عمل زمانیکه با مدل‌ها کار می‌کنیم و اطلاعات برنامه قرار است Strongly typed باشند، مرسوم است لیستی از انتخاب‌ها را به صورت یک enum تعریف کنند. برای مثال مدل زیر را به برنامه اضافه کنید:

using System.ComponentModel.DataAnnotations;

namespace MvcApplication23.Models
{
public enum Gender
{
[Display(Name = "مرد")]
Male,
[Display(Name = "زن")]
Female,
}

public class User
{
[ScaffoldColumn(false)]
public int Id { set; get; }

[Display(Name = "نام")]
public string Name { set; get; }

[Display(Name = "جنسیت")]
[UIHint("EnumRadioButtonList")]
public Gender Gender { set; get; }
}
}

قصد داریم یک Templated helper سفارشی را به نام EnumRadioButtonList، ایجاد کنیم تا در زمان فراخوانی متد Html.EditorForModel، به صورت خودکار enum تعریف شده را به صورت یک RadioButtonList نمایش دهد.
برای این منظور فایل جدید Views\Shared\EditorTemplates\EnumRadioButtonList.cshtml را به برنامه اضافه کنید. محتوای آن‌را به نحو زیر تغییر دهید:

@using System.ComponentModel.DataAnnotations
@using System.Globalization
@model Enum
@{
Func<Enum, string> getDescription = enumItem =>
{
var type = enumItem.GetType();
var memInfo = type.GetMember(enumItem.ToString());
if (memInfo != null && memInfo.Any())
{
var attrs = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
if (attrs != null && attrs.Any())
return ((DisplayAttribute)attrs[0]).GetName();
}
return enumItem.ToString();
};

var listItems = Enum.GetValues(Model.GetType())
.OfType<Enum>()
.Select(enumItem =>
new SelectListItem()
{
Text = getDescription(enumItem),
Value = enumItem.ToString(),
Selected = enumItem.Equals(Model)
});

string prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;

int index = 0;
foreach (var li in listItems)
{
string fieldName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", prefix, index++);
<div class="editor-radio">
@Html.RadioButton(prefix, li.Value, li.Selected, new { @id = fieldName })
@Html.Label(fieldName, li.Text)
</div>
}

ViewData.TemplateInfo.HtmlFieldPrefix = prefix;
}

در اینجا به کمک Reflection به اطلاعات enum دریافتی دسترسی خواهیم داشت. بر این اساس می‌توان نام عناصر آن‌را یافت و تبدیل به یک RadioButtonList کرد. البته کار به همینجا ختم نمی‌شود. در این بین باید دقت داشت که ممکن است از ویژگی Display (مانند مدل نمونه فوق) بر روی تک تک عناصر یک enum نیز استفاده شود. به همین جهت این مورد نیز باید پردازش گردد.
نهایتا برای استفاده از این Templated helper سفارشی، کنترلر و View برنامه را به نحو زیر می‌توان تغییر داد:

using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication23.Models;

namespace MvcApplication23.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var user = new User { Id = 1, Name = "name 1", Gender = Gender.Male };
return View(user);
}

[HttpPost]
public ActionResult HandleForm3(User user)
{
return RedirectToAction("Index");
}
}
}

@model MvcApplication23.Models.User
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<fieldset>
<legend>HandleForm3 (EditorForModel)</legend>
@using (Html.BeginForm(actionName: "HandleForm3", controllerName: "Home"))
{
@Html.EditorForModel()
<input type="submit" value="Submit" />
}
</fieldset>

برای استفاده از یک templated helper سفارشی چندین روش وجود دارد:
الف) همانند مثال فوق از ویژگی UIHint استفاده شود.
ب) نام فایل را به enum.cshtml تغییر دهیم. به این ترتیب از این پس کلیه enumها در صورت استفاده از متد Html.EditorForModel، به صورت خودکار تبدیل به یک RadioButtonList می‌شوند.
ج) متد زیر نیز همین کار را انجام می‌دهد:
@Html.EditorFor(model => model.EnumProperty, "EnumRadioButtonList")


مطالب
آموزش 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، کش را به شیوه‌ای متفاوت در لایه‌های مختلفی از برنامه قرار داده‌ام تا شما همراهان بتوانید طبق نیازتان از روشی مناسب و بهینه در پروژه‌های واقعی خود از آن استفاده کنید.
نظرات مطالب
Globalization در ASP.NET MVC
سلام مهندس یوسف نژاد (ابتدا ممنونم از پست خوب شما)
با پیروی از پست شما
ابتدا فایل‌های ریسورس در پروژه جاری فولدر App_GlobalResources گذاشتم و پروژه در صفحات aspx با قالب زیر به راحتی تغییر زبان داده میشد:

<asp:Literal ID="Literal1" Text='<%$ Resources:resource, Title %>' runat="server" />
اما بعدش فایل هارو توی یک پروژه کتابخانه ای جدید گذاشتم و Build Action فایل‌های ریسورس روی Embedded Resource  تنظیم کردم، پروژه با موفقیت اجرا شد و در سمت سرور با کد زیر راحت به مقادیر دسترسی دارم:
Literal1.Text=ResourcesManager.Resource.Title;
اما در سمت صفحات aspx با کد قبلی به شکل زیر نمایش نمیده و خطا صادر میشه:
<asp:Literal ID="Literal1"  runat="server" Text='<%$ ResourcesManager.Resource:resource, Title %>'/>

و خطای زیر صادر میشه:
 Parser Error
Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately.

Parser Error Message: The expression prefix 'ResourcesManager.Resource' was not recognized.  Please correct the prefix or register the prefix in the <expressionBuilders> section of configuration.

Source Error:
مراحل این پست روی هم دنبال کردم اما بازم نشد.
چه تنظیماتی ست نکردم ؟