پاسخ به بازخورد‌های پروژه‌ها
استفاده از Fluent Query در دیتا سورس PDFReporter
- با هاست دار تماس بگیرید و عنوان کنید که برنامه شما نیاز است دسترسی write روی پوشه pdf داشته باشد.
- یا از روش ذیل استفاده کنید:
.Generate(data =>
                {
                    fileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
                    data.FlushInBrowser(fileName, FlushType.Inline);
                }); // creating an in-memory PDF file
مطالب
نگاهی به هویت سنجی کاربران در ASP.NET MVC 5
در مقاله پیش رو، سعی شده‌است به شکلی تقریبا عملی، کلیاتی در مورد Authentication در MVC5 توضیح داده شود. هدف روشن شدن ابهامات اولیه در هویت سنجی MVC5 و حل شدن مشکلات اولیه برای ایجاد یک پروژه است.
در MVC 4 برای دسترسی به جداول مرتبط با اعتبار سنجی (مثلا لیست کاربران) مجبور به استفاده از متدهای از پیش تعریف شده‌ی رفرنس‌هایی که برای آن نوع اعتبار سنجی وجود داشت، بودیم. راه حلی نیز برای دسترسی بهتر وجود داشت و آن هم ساختن مدل‌های مشابه آن جدول‌ها و اضافه کردن چند خط کد به برنامه بود. با اینکار دسترسی ساده به Roles و Users برای تغییر و اضافه کردن محتوای آنها ممکن می‌شد. در لینک زیر توضیحاتی در مورد روش اینکار وجود دارد.
 در MVC5 داستان کمی فرق کرده است. برای درک موضوع پروژه ای بسازید و حالت پیش فرض آن را تغییر ندهید و آن را اجرا کنید و ثبت نام را انجام دهید، بلافاصله تصویر زیر در دیتابیس نمایان خواهد شد.

دقت کنید بعد از ایجاد پروژه در MVC5 دو پکیج بصورت اتوماتیک از طریق Nuget به پروژه شما اضافه میشود:
 Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.EntityFrameWork
عامل اصلی تغییرات جدید، همین دو پکیج فوق است.
 اولین پکیج شامل اینترفیس‌های IUser و IRole است که شامل فیلدهای مرتبط با این دو می‌باشد. همچنین اینترفیسی به نام IUserStore وجود دارد که چندین متد داشته و وظیفه اصلی هر نوع اضافه و حذف کردن یا تغییر در کاربران، بر دوش آن است.
 دومین پکیج هم وظیفه پیاده سازی آن‌چیزی را دارد که در پکیج اول معرفی شده است. کلاس‌های موجود در این پکیج ابزارهایی برای ارتباط EntityFramework با دیتابیس هستند.
اما از مقدمات فوق که بگذریم برای درک بهتر رفتار با دیتابیس یک مثال را پیاده سازی خواهیم کرد.

 فرض کنید میخواهیم چنین ارتباطی را بین سه جدول در دیتابیس برقرار کنیم، فقط به منظور یادآوری، توجه کنید که جدول ASPNetUsers جدولی است که به شکل اتوماتیک پیش از این تولید شد و ما قرار است به کمک یک جدول واسط (AuthorProduct) آن را به جدول Product مرتبط سازیم تا مشخص شود هر کتاب (به عنوان محصول) به کدام کاربر (به عنوان نویسنده) مرتبط است.
 بعد از اینکه مدل‌های مربوط به برنامه خود را ساختیم، اولا نیاز به ساخت کلاس کانتکست نداریم چون خود MVC5 کلاس کانتکست را دارد؛ ثانیا نیاز به ایجاد مدل برای جداول اعتبارسنجی نیست، چون کلاسی برای فیلدهای اضافی ما که علاقمندیم به جدول Users اضافه شود، از پیش تعیین گردیده است.

دو کلاسی که با فلش علامت گذاری شده اند، تنها فایل‌های موجود در پوشه مدل، بعد از ایجاد یک پروژه هستند. فایل IdentityModel را به عنوان فایل کانتکست خواهیم شناخت (چون یکی از کلاسهایش Context است). همانطور که پیش از این گفتیم با وجود این فایل نیازی به ایجاد یک کلاس مشتق شده از DbContext نیست. همانطور که در کد زیر میبینید این فایل دارای دو کلاس است:
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
      
}
کلاس اول همان کلاسی است که اگر به آن پراپرتی اضافه کنیم، بطور اتوماتیک آن پراپرتی به جدول ASPNetUsers در دیتابیس اضافه می‌شود و دیگر نگران فیلدهای نداشته‌ی جدول کاربران ASP.NET نخواهیم بود. مثلا در کد زیر چند عنوان به این جدول اضافه کرده ایم.
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Display(Name = "نام انگلیسی")]
        public string EnglishName { get; set; }

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

        [Display(Name = "نام فارسی")]
        public string PersianName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
     }
}
کلاس دوم نیز محل معرفی مدلها به منظور ایجاد در دیتابیس است. به ازای هر مدل یک جدول در دیتابیس خواهیم داشت. مثلا در شکل فوق سه پراپرتی به جدول کاربران اضافه میشود. دقت داشته باشید با اینکه هیچ مدلی برای جدول کاربران نساخته ایم اما کلاس ApplicatioUsers کلاسی است که به ما امکان دسترسی به مقادیر این جدول را می‌دهد(دسترسی به معنای اضافه و حذف وتغییر مقادیر این جدول است) (در MVC4 به کمک کلاس membership کارهای مشابهی انجام میدادیم)
 در ساختن مدل هایمان نیز اگر نیاز به ارتباط با جدول کاربران باشد، از همین کلاس فوق استفاده میکنیم. کلاس واسط(مدل واسط) بین AspNetUsers و Product در کد زیر زیر نشان داده شده است :
namespace MyShop.Models
{
    public class AuthorProduct
    {
        [Key]
        public int AuthorProductId { get; set; }
       /* public int UserId { get; set; }*/

        [Display(Name = "User")]
        public string ApplicationUserId { get; set; }

        public int ProductID { get; set; }

        public virtual Product Product { get; set; }
    
        public virtual ApplicationUser ApplicationUser { get; set; }
    }
}
همانطور که مشاهده میکنید، به راحتی ارتباط را برقرار کردیم و برای برقراری این ارتباط از کلاس ApplicationUser استفاده کردیم. پراپرتی ApplicationUserId نیز فیلد ارتباطی ما با جدول کاربران است. جدول product هم نکته خاصی ندارد و به شکل زیر مدل خواهد شد.
namespace MyShop.Models
{
    [DisplayName("محصول")]
    [DisplayPluralName("محصولات")]
    public class Product
    {
        [Key]
        public int ProductID { get; set; }

        [Display(Name = "گروه محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductGroupID { get; set; }

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

   
        [Display(Name = "نام تهیه کننده")]
        public string Producer { get; set; }

        [Display(Name = "عنوان محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string ProductTitle { get; set; }

        [StringLength(200)]
        [Display(Name = "کلید واژه")]
        public string MetaKeyword { get; set; }

        [StringLength(200)]
        [Display(Name = "توضیح")]
        public string MetaDescription { get; set; }

        [Display(Name = "شرح محصول")]
        [UIHint("RichText")]
        [AllowHtml]
        public string ProductDescription { get; set; }

        [Display(Name = "قیمت محصول")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")]
        [UIHint("Integer")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductPrice { get; set; }
        [Display(Name = "تاریخ ثبت محصول")]

        public DateTime? RegisterDate { get; set; }

    }
}
به این ترتیب هم ارتباطات را برقرار کرده‌ایم و هم از ساختن یک UserProfile اضافی خلاص شدیم.
برای پر کردن مقادیر اولیه نیز به راحتی از seed موجود در Configuration.cs مربوط به migration استفاده میکنیم. نمونه‌ی اینکار در کد زیر موجود است:
protected override void Seed(MyShop.Models.ApplicationDbContext context)
        {
            context.Users.AddOrUpdate(u => u.Id,
                      new ApplicationUser() {  Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }
                     
                        );


            context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId,
              new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" },
              new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" },
              new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" }

          );
 می‌توانیم از کلاس‌های خود Identity برای انجام روش فوق استفاده کنیم؛ فرض کنید بخواهیم یک کاربر به نام admin و با نقش admin به سیستم اضافه کنیم.
            if (!context.Users.Where(u => u.UserName == "Admin").Any())
            {
                var roleStore = new RoleStore<IdentityRole>(context);
                var rolemanager = new RoleManager<IdentityRole>(roleStore);

                var userstore = new UserStore<ApplicationUser>(context);
                var usermanager = new UserManager<ApplicationUser>(userstore);
                
                var user = new ApplicationUser {UserName = "Admin"};
                
                usermanager.Create(user, "121212");
                rolemanager.Create(new IdentityRole {Name = "admin"});
                
                usermanager.AddToRole(user.Id, "admin");
            }
   در عبارت شرطی موجود کد فوق، ابتدا چک کردیم که چنین یوزری در دیتابیس نباشد، سپس از کلاس RoleStore که پیاده سازی شده‌ی اینترفیس IRoleStore است استفاده کردیم. سازنده این کلاس به کانتکست نیاز دارد؛ پس به آن context را به عنوان ورودی می‌دهیم. در خط بعد، کلاس rolemanager را داریم که بخشی از پکیج Core است و پیش از این درباره اش توضیح دادیم ( یکی از دو رفرنسی که خوبخود به پروژه اضافه میشوند) و از ویژگی‌های Identity است. به آن آبجکتی که از RoleStore ساختیم را پاس میدهیم و خود کلاس میداند چه چیز را کجا ذخیره کند.
برای ایجاد کاربر نیز همین روند را انجام می‌دهیم. سپس یک آبجکت به نام user را از روی کلاس ApplicationUser میسازیم. برای آن پسورد 121212 سِت میکنیم و نقش ادمین را به آن نسبت میدهیم. این روش قابل تسری به تمامی بخش‌های برنامه شماست. میتوانید عملیات کنترل و مدیریت اکانت را نیز به همین شکل انجام دهید. ساخت کاربر و لاگین کردن یا مدیریت پسورد نیز به همین شکل قابل انجام است.
 بعد از آپدیت دیتابیس تغییرات را مشاهده خواهیم کرد. 
اشتراک‌ها
50 نکته برای توسعه‌ی بهتر برنامه‌های مبتنی بر Angular

The Top 50 Tips for Better Angular Development

This article dives into essential tips and best practices that will help you enhance your Angular development skills. Whether you're a seasoned Angular developer or a beginner, these insights will help optimize your applications, improve code quality, and leverage Angular’s features effectively.

50 نکته برای توسعه‌ی بهتر برنامه‌های مبتنی بر Angular
اشتراک‌ها
PSScriptAnalyzer (PSSA) 1.21.0 منتشر شد

PSScriptAnalyzer is a static code checker for PowerShell modules and scripts. PSScriptAnalyzer checks the quality of PowerShell code by running a set of rules. The rules are based on PowerShell best practices identified by PowerShell Team and the community. It generates DiagnosticResults (errors and warnings) to inform users about potential code defects and suggests possible solutions for improvements. 

PSScriptAnalyzer (PSSA) 1.21.0 منتشر شد
اشتراک‌ها
SQL Server Management Studio 18.7 منتشر شد

The 18.7 release is the third major release of SSMS in 2020 and expands on our commitment to providing quality tooling for database administration. Key changes include:

  • Fix for Database diagrams add table dialog.
  • Installation of Azure Data Studio.
  • Extended Events script with wait type name. 
SQL Server Management Studio 18.7 منتشر شد
اشتراک‌ها
Bosque زبان برنامه نویسی جدید مایکروسافت بر پایه تایپ اسکریپت

The Bosque programming language is designed for writing code that is simple, obvious, and easy to reason about for both humans and machines. The key design features of the language provide ways to avoidaccidental complexity in the development and coding process. The goal is improved developer productivity, increased software quality, and enabling a range of new compilers and developer tooling experiences. 

Bosque زبان برنامه نویسی جدید مایکروسافت بر پایه تایپ اسکریپت
اشتراک‌ها
MASVS یک استاندارد امنیتی مبتنی بر OWASP برای اپلیکیشن‌های موبایل

The Mobile Application Security Verification Standard (MASVS) is a standard for mobile app security


استاندارد امنیتی MASVS شامل موضوعات زیر می‌باشد :

  1. Architecture, Design & Threat Modeling Requirements
  2. Data Storage & Privacy Requirements
  3. Cryptography Requirements
  4. Authentication & Session Management Requirements
  5. Network Communication Requirements
  6. Environmental Interaction Requirements
  7. Code Quality & Build Setting Requirements
  8. Resiliency Against Reverse Engineering Requirements
MASVS یک استاندارد امنیتی مبتنی بر OWASP برای اپلیکیشن‌های موبایل
اشتراک‌ها
دانلود Microsoft Visual Studio Team Foundation Server 2013 with Update 3

Download Link: ISO File

Visual Studio Team Foundation Server 2013 is the source-code-control, project-management, and team-collaboration platform at the core of the Microsoft suite of Application Lifecycle Management (ALM) tools, which help teams be more agile, collaborate more effectively, and deliver quality software more consistently. 

دانلود Microsoft Visual Studio Team Foundation Server 2013 with Update 3
مطالب
کش خروجی API در ASP.NET Core با Redis
در این مقاله نمی‌خواهیم به طور عمیقی وارد جزییاتی مثل توضیح Redis یا کش بشویم؛ فرض شده‌است که کاربر با این مفاهیم آشناست. به طور خلاصه کش کردن یعنی همیشه به دیتابیس یا هارددیسک برای گرفتن اطلاعاتی که می‌خواهیم و گرفتنش هم کند است، وصل نشویم و بجای آن، اطلاعات را در یک محل موقتی که گرفتنش خیلی سریعتر بوده قرار دهیم و برای استفاده به آنجا برویم و اطلاعات را با سرعت بالا بخوانیم. کش کردن هم دسته بندی‌های مختلفی دارد که بر حسب سناریوهای مختلفی که وجود دارد، کاربرد خود را دارند. مثلا ساده‌ترین کش در ASP.NET Core، کش محلی (In-Memory Cache) می‌باشد که اینترفیس IMemoryCache را اعمال می‌کند و نیازی به هیچ پکیجی ندارد و به صورت درونی در ASP.NET Core در دسترس است که برای حالت توسعه، یا حالتیکه فقط یک سرور داشته باشیم، مناسب است؛ ولی برای برنامه‌های چند سروری، نوع دیگری از کش که به اصطلاح به آن Distributed Cache می‌گویند، بهتر است استفاده شود. چند روش برای پیاده‌سازی با این ساختار وجود دارد که نکته مشترکشان اعمال اینترفیس واحد IDistributedCache می‌باشد. در نتیجه‌ی آن، تغییر ساختار کش به روش‌های دیگر، که اینترفیس مشابهی را اعمال می‌کنند، با کمترین زحمت صورت می‌گیرد. این روش‌ها به طور خیلی خلاصه شامل موارد زیر می‌باشند: 

1- Distributed Memory Cache: در واقع Distributed نیست و کش معمولی است؛ فقط برای اعمال اینترفیس IDistributedCache که امکان تغییر آن در ادامه‌ی توسعه نرم‌افزار میسر باشد، این روش توسط مایکروسافت اضافه شده‌است. نیاز به نصب پکیجی را ندارد و به صورت توکار در ASP.NET Core در دسترس است.
2- Distributed SQL Server Cache: کاربرد چندانی ندارد. با توجه به اینکه هدف اصلی از کش کردن، افزایش سرعت و عدم اتصال به دیتابیس است، استفاده از حافظه‌ی رم، بجای دیتابیس ترجیح داده می‌شود.
3- Distributed Redis Cache: استفاده از Redis که به طور خلاصه یک دیتابیس Key/Value در حافظه است. سرعت بالایی دارد و محبوب‌ترین روش بین برنامه‌نویسان است. برای اعمال آن در ASP.NET Core نیاز به نصب پکیج می‌باشد.

موارد بالا انواع زیرساخت و ساختار (Cache Provider) برای پیاده‌سازی کش می‌باشند. روش‌های مختلفی برای استفاده از این Cache Providerها وجود دارد. مثلا یک روش، استفاده مستقیم در کدهای درونی متد یا کلاسمان می‌باشد و یا در روش دیگر می‌توانیم به صورت یک Middleware این پروسه را مدیریت کنیم، یا در روش دیگر (که موضوع این مقاله است) از ActionFilterAttribute استفاده می‌کنیم. یکی از روش‌های جالب دیگر کش کردن، اگر از Entity Framework به عنوان ORM استفاده می‌کنیم، استفاده از سطح دوم کش آن (EF Second Level Cache) می‌باشد. EF دو سطح کش دارد که سطح اول آن توسط خود Context به صورت درونی استفاده می‌شود و ما می‌توانیم از سطح دوم آن استفاده کنیم. مزیت آن به نسبت روش‌های قبلی این است که نتیجه‌ی کوئری ما (که با عبارات لامبدا نوشته می‌شود) را کش می‌کند و علاوه بر امکان تنظیم زمان انقضا برای این کش، در صورت تغییر یک entity خاص (انجام عملیات Update/Insert/Delete) خود به خود، کش کوئری مربوط به آن entity پاک می‌شود تا با مقدار جدید آن جایگزین شود که روش‌های دیگر این مزیت را ندارند. در این مقاله قرار نیست در مورد این روش کش صحبت کنیم. استفاده از این روش کش به صورت توکار در EF Core وجود ندارد و برای استفاده از آن در صورتی که از EF Core قبل از ورژن 3 استفاده می‌کنید می‌توانید از پکیج  EFSecondLevelCache.Core  و در صورت استفاده از EF Core 3 از پکیج  EF Core Second Level Cache Interceptor  استفاده نمایید که در هر دو حالت می‌توان هم از Memory Cache Provider و هم از Redis Cache Provider استفاده نمود.

در این مقاله می‌خواهیم Responseهای APIهایمان را در یک پروژه‌ی Web API، به ساده‌ترین حالت ممکن کش کنیم. زیرساخت این کش می‌تواند هر کدام از موارد ذکر شده‌ی بالا باشد. در این مقاله از Redis برای پیاده‌سازی آن استفاده می‌کنیم که با نصب پکیج Microsoft.Extensions.Caching.StackExchangeRedis انجام می‌گیرد. این بسته‌ی نیوگت که متعلق به مایکروسافت بوده و روش پایه‌ی استفاده از Redis در ASP.NET Core است، اینترفیس IDistributedCache را اعمال می‌کند:
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis

سپس اینترفیس IResponseCacheService را می‌سازیم تا از این اینترفیس به جای IDistributedCache استفاده کنیم. البته می‌توان از IDistributedCache به طور مستقیم استفاده کرد؛ ولی چون همه‌ی ویژگی‌های این اینترفیس را نمی‌خواهیم و هم اینکه می‌خواهیم serialize کردن نتایج API را در کلاسی که از این اینترفیس ارث‌بری می‌کند (ResponseCacheService) بیاوریم (تا آن را کپسوله‌سازی (Encapsulation) کرده باشیم تا بعدا بتوانیم مثلا بجای پکیج Newtonsoft.Json، از System.Text.Json برای serialize کردن‌ها استفاده کنیم):
public interface IResponseCacheService
    {
        Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive);
        Task<string> GetCachedResponseAsync(string cacheKey);
    }
یادآوری: Redis قابلیت ذخیره‌ی داده‌هایی از نوع آرایه‌ی بایت‌ها را دارد (و نه هر نوع دلخواهی را). بنابراین اینجا ما بجای ذخیره‌ی مستقیم نتایج APIهایمان (که ممکن نیست)، می‌خواهیم ابتدا آن‌ها را با serialize کردن به نوع رشته‌ای (که فرمت json دارد) تبدیل کنیم و سپس آن را ذخیره نماییم.

حالا کلاس ResponseCacheService که این اینترفیس را اعمال می‌کند می‌سازیم: 
    public class ResponseCacheService : IResponseCacheService, ISingletonDependency
    {
        private readonly IDistributedCache _distributedCache;

        public ResponseCacheService(IDistributedCache distributedCache)
        {
            _distributedCache = distributedCache;
        }

        public async Task CacheResponseAsync(string cacheKey, object response, TimeSpan timeToLive)
        {
            if (response == null) return;
            var serializedResponse = JsonConvert.SerializeObject(response);
            await _distributedCache.SetStringAsync(cacheKey, serializedResponse, new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = timeToLive
            });
        }

        public async Task<string> GetCachedResponseAsync(string cacheKey)
        {
            var cachedResponse = await _distributedCache.GetStringAsync(cacheKey);
            return string.IsNullOrWhiteSpace(cachedResponse) ? null : cachedResponse;
        }
    }
دقت کنید که اینترفیس IDistributedCache در این کلاس استفاده شده است. اینترفیس ISingletonDependency صرفا یک اینترفیس نشان گذاری برای اعمال خودکار ثبت سرویس به صورت Singleton می‌باشد (اینترفیس را خودمان ساخته‌ایم و آن را برای رجیستر راحت سرویس‌هایمان تنظیم کرده‌ایم). اگر نمی‌خواهید از این روش برای ثبت این سرویس استفاده کنید، می‌توانید به صورت عادی این سرویس را رجیستر کنید که در ادامه، در قسمت مربوطه به صورت کامنت شده آمده است.

حالا کدهای لازم برای رجیستر کردن Redis و تنظیمات آن را در برنامه اضافه می‌کنیم. قدم اول ایجاد یک کلاس POCO به نام RedisCacheSettings است که به فیلدی به همین نام در appsettings.json نگاشت می‌شود:
public class RedisCacheSettings
    {
        public bool Enabled { get; set; }
        public string ConnectionString { get; set; }
        public int DefaultSecondsToCache { get; set; }
    }

این فیلد را در appsettings.json هم اضافه می‌کنیم تا در استارتاپ برنامه، با مپ شدن به کلاس RedisCacheSettings، قابلیت استفاده شدن در تنظیمات Redis را داشته باشد. 
"RedisCacheSettings": {
      "Enabled": true,
      "ConnectionString": "192.168.1.107:6379,ssl=False,allowAdmin=True,abortConnect=False,defaultDatabase=0,connectTimeout=500,connectRetry=3",
      "DefaultSecondsToCache": 600
    },

  حالا باید سرویس Redis را در متد ConfigureServices، به همراه تنظیمات آن رجیستر کنیم. می‌توانیم کدهای مربوطه را مستقیم در متد ConfigureServices بنویسیم و یا به صورت یک متد الحاقی در کلاس جداگانه بنویسیم و از آن در ConfigureServices استفاده کنیم و یا اینکه از روش Installer برای ثبت خودکار سرویس و تنظیماتش استفاده کنیم. اینجا از روش آخر استفاده می‌کنیم. برای این منظور کلاس CacheInstaller را می‌سازیم: 
    public class CacheInstaller : IServiceInstaller
    {
        public void InstallServices(IServiceCollection services, AppSettings appSettings, Assembly startupProjectAssembly)
        {
            var redisCacheService = appSettings.RedisCacheSettings;
            services.AddSingleton(redisCacheService);

            if (!appSettings.RedisCacheSettings.Enabled) return;

            services.AddStackExchangeRedisCache(options =>
                options.Configuration = appSettings.RedisCacheSettings.ConnectionString);

            // Below code applied with ISingletonDependency Interface
            // services.AddSingleton<IResponseCacheService, ResponseCacheService>();
        }
    }

خب تا اینجا اینترفیس اختصاصی خودمان را ساختیم و Redis را به همراه تنظیمات آن، رجیستر کردیم. برای اعمال کش، چند روش وجود دارد که همانطور که گفته شد، اینجا از روش ActionFilterAttribute استفاده می‌کنیم که یکی از راحت‌ترین راه‌های اعمال کش در APIهای ماست. کلاس CachedAttribute را ایجاد می‌کنیم:
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class CachedAttribute : Attribute, IAsyncActionFilter
    {
        private readonly int _secondsToCache;
        private readonly bool _useDefaultCacheSeconds;
        public CachedAttribute()
        {
            _useDefaultCacheSeconds = true;
        }
        public CachedAttribute(int secondsToCache)
        {
            _secondsToCache = secondsToCache;
            _useDefaultCacheSeconds = false;
        }

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var cacheSettings = context.HttpContext.RequestServices.GetRequiredService<RedisCacheSettings>();

            if (!cacheSettings.Enabled)
            {
                await next();
                return;
            }

            var cacheService = context.HttpContext.RequestServices.GetRequiredService<IResponseCacheService>();

            // Check if request has Cache
            var cacheKey = GenerateCacheKeyFromRequest(context.HttpContext.Request);
            var cachedResponse = await cacheService.GetCachedResponseAsync(cacheKey);

            // If Yes => return Value
            if (!string.IsNullOrWhiteSpace(cachedResponse))
            {
                var contentResult = new ContentResult
                {
                    Content = cachedResponse,
                    ContentType = "application/json",
                    StatusCode = 200
                };
                context.Result = contentResult;
                return;
            }

            // If No => Go to method => Cache Value
            var actionExecutedContext = await next();

            if (actionExecutedContext.Result is OkObjectResult okObjectResult)
            {
                var secondsToCache = _useDefaultCacheSeconds ? cacheSettings.DefaultSecondsToCache : _secondsToCache;
                await cacheService.CacheResponseAsync(cacheKey, okObjectResult.Value,
                    TimeSpan.FromSeconds(secondsToCache));
            }
        }

        private static string GenerateCacheKeyFromRequest(HttpRequest httpRequest)
        {
            var keyBuilder = new StringBuilder();
            keyBuilder.Append($"{httpRequest.Path}");
            foreach (var (key, value) in httpRequest.Query.OrderBy(x => x.Key))
            {
                keyBuilder.Append($"|{key}-{value}");
            }

            return keyBuilder.ToString();
        }
    }
در این کلاس، تزریق وابستگی‌های IResponseCacheService و RedisCacheSettings به روش خاصی انجام شده است و نمی‌توانستیم از روش Constructor Dependency Injection استفاده کنیم چون در این حالت می‌بایستی این ورودی در Controller مورد استفاده هم تزریق شود و سپس در اتریبیوت [Cached] بیاید که مجاز به اینکار نیستیم؛ بنابراین از این روش خاص استفاده کردیم. مورد دیگر فرمول ساخت کلید کش می‌باشد تا بتواند کش بودن یک Endpoint خاص را به طور خودکار تشخیص دهد که این متد در همین کلاس آمده است. 
 
حالا ما می‌توانیم با استفاده از attributeی به نام  [Cached]  که روی APIهای از نوع HttpGet قرار می‌گیرد آن‌ها را براحتی کش کنیم. کلاس بالا هم طوری طراحی شده (با دو سازنده متفاوت) که در حالت استفاده به صورت [Cached] از مقدار زمان پیشفرضی استفاده می‌کند که در فایل appsettings.json تنظیم شده است و یا اگر زمان خاصی را مد نظر داشتیم (مثال 1000 ثانیه) می‌توانیم آن را به صورت  [(Cached(1000]  بیاوریم. کلاس زیر نمونه‌ی استفاده‌ی از آن می‌باشد:
[Cached]
[HttpGet]
public IActionResult Get()
  {
    var rng = new Random();
    var weatherForecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
      Date = DateTime.Now.AddDays(index),
      TemperatureC = rng.Next(-20, 55),
      Summary = Summaries[rng.Next(Summaries.Length)]
    })
      .ToArray();
    return Ok(weatherForecasts);
  }
بنابراین وقتی تنظیمات اولیه، برای پیاده‌سازی این کش انجام شود، اعمال کردن آن به سادگی قرار دادن یک اتریبیوت ساده‌ی [Cached] روی هر apiی است که بخواهیم خروجی آن را کش کنیم. فقط توجه نمایید که این روش فقط برای اکشن‌هایی که کد 200 را بر می‌گردانند، یعنی متد Ok را return می‌کنند (OkObjectResult) کار می‌کند. بعلاوه اگر از اتریبیوت ApiResultFilter یا مفهوم مشابه آن برای تغییر خروجی API به فرمت خاص استفاده می‌کنید، باید در آن تغییرات کوچکی را انجام دهید تا با این حالت هماهنگ شود.