نظرات مطالب
بررسی معادل‌های LINQ to Objects در TypeScript
jQuery خودش به صورت توکار بسیاری از قابلیت‌های LINQ را دارد. برای مثال معادل Where در آن grep.$ است و یا معادل Select، متد map.$ آن است. برای مثال کد #C زیر:
var maleNames = people
.Where(p => p.Sex == "M")
.Select(p => p.Name)
به صورت زیر توسط jQuery ترجمه می‌شود:
var maleNames = $.grep(people, function (p) { return p.Sex == 'M'; })
            .map(function (p) { return p.Name; });
بازخوردهای دوره
استفاده از Async و Await در برنامه‌های ASP.NET MVC
سلام؛ اگر 
var model = await db.Books.ToListAsync();
همزمان اجرا میشه  ولی بازم برای return باید منتظر پاسخ از db بمونه! پس اینجا فایده ای نداره؟
مشکل من اینجاست که فکر میکنم این روش تنها برای قسمت هایی بدرد میخوره که به هم وابسته نیستن. برای مثال وقتی یه فایل رو آپلود میکنی و بعد آدرس فایل رو ذخیره کنیم فایده نداره. چون تا فایل آپلود نشه ذخیره آدرس تو db  بی معنیه؟
نظرات مطالب
بارگزاری PartialView با استفاده از jQuery در زمان اجرا
باسلام و خسته نباشید. 
من مثال فوق را خط به خط اجرا کردم ولی partial view  نمایش داده نمیشه. فکر کنم مکان  قطعه کد Ajax  را اشتباه جایگذاری کردم اگه ممکنه راهنمائی می‌کنید که قطعه:
 $( function () {
$.ajax({
//مشخص کردن  اکشنی که باید فراخوانی شود
url: '/Home/Details' ,
contentType: 'application/html; charset=utf-8' ,
type: 'GET' ,
//نوع نتیجه بازگشتی
dataType: 'html'
 
})
.success( function (result) {
  //زمانی که کدهای سمت سرور بدون خطا اجرا شده اند
  //این قسمت فراخوانی می‌شود و نتیجه اکشن درون متغیر
  //result
  //قرار می‌گیرد
  $( '#sectionContents' ).html(result);
})
.error( function (xhr, status) {
  alert(xhr.responseText);
});
  });
دقیقا کجای Index باید قرارداده بشه؟ سورس پروژه را هم ارسال می‌کردید خیلی خوب می‌شد .
ممنون. 
مسیرراه‌ها
ASP.NET MVC
              مطالب
              آشنایی با NHibernate - قسمت ششم

              آشنایی با Automapping در فریم ورک Fluent NHibernate

              اگر قسمت‌های قبل را دنبال کرده باشید، احتمالا به پروسه طولانی ساخت نگاشت‌ها توجه کرده‌اید. با کمک فریم ورک Fluent NHibernate می‌توان پروسه نگاشت domain model خود را به data model متناظر آن به صورت خودکار نیز انجام داد و قسمت عمده‌ای از کار به این صورت حذف خواهد شد. (این مورد یکی از تفاوت‌های مهم NHibernate با نمونه‌های مشابهی است که مایکروسافت تا تاریخ نگارش این مقاله ارائه داده است. برای مثال در نگار‌ش‌های فعلی LINQ to SQL یا Entity framework ، اول دیتابیس مطرح است و بعد ساخت کد از روی آن، در حالیکه در اینجا ابتدا کد و طراحی سیستم مطرح است و بعد نگاشت آن به سیستم داده‌ای و دیتابیس)

              امروز قصد داریم یک سیستم ساده ثبت خبر را از صفر با NHibernate پیاده سازی کنیم و همچنین مروری داشته باشیم بر قسمت‌های قبلی.

              مطابق کلاس دیاگرام فوق، این سیستم از سه کلاس خبر، کاربر ثبت کننده‌ی خبر و گروه خبری مربوطه تشکیل شده است.

              ابتدا یک پروژه کنسول جدید را به نام NHSample2 آغاز کنید. سپس ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
              FluentNHibernate.dll
              NHibernate.dll
              NHibernate.ByteCode.Castle.dll
              NHibernate.Linq.dll
              و ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

              سپس پوشه‌ای را به نام Domain به این پروژه اضافه نمائید (کلیک راست روی نام پروژه در VS.Net و سپس مراجعه به منوی Add->New folder). در این پوشه تعاریف موجودیت‌های برنامه را قرار خواهیم داد. سه کلاس جدید Category ، User و News را در این پوشه ایجاد نمائید. محتویات این سه کلاس به شرح زیر هستند:

              namespace NHSample2.Domain
              {
              public class User
              {
              public virtual int Id { get; set; }
              public virtual string UserName { get; set; }
              public virtual string Password { get; set; }
              }
              }


              namespace NHSample2.Domain
              {
              public class Category
              {
              public virtual int Id { get; set; }
              public virtual string CategoryName { get; set; }
              }
              }


              using System;

              namespace NHSample2.Domain
              {
              public class News
              {
              public virtual Guid Id { get; set; }
              public virtual string Subject { get; set; }
              public virtual string NewsText { get; set; }
              public virtual DateTime DateEntered { get; set; }
              public virtual Category Category { get; set; }
              public virtual User User { get; set; }
              }
              }
              همانطور که در قسمت‌های قبل نیز ذکر شد، تمام خواص پابلیک کلاس‌های Domain ما به صورت virtual تعریف شده‌اند تا lazy loading را در NHibernate فعال سازیم. در حالت lazy loading ، اطلاعات تنها زمانیکه به آن‌ها نیاز باشد بارگذاری خواهند شد. این مورد در حالتیکه نیاز به نمایش اطلاعات تنها یک شیء وجود داشته باشد بسیار مطلوب می‌باشد، یا هنگام ثبت و به روز رسانی اطلاعات نیز یکی از بهترین روش‌ها است. اما زمانیکه با لیستی از اطلاعات سروکار داشته باشیم باعث کاهش افت کارآیی خواهد شد زیرا برای مثال نمایش آن‌ها سبب خواهد شد که 100 ها کوئری دیگر جهت دریافت اطلاعات هر رکورد در حال نمایش اجرا شود (مفهوم دسترسی به اطلاعات تنها در صورت نیاز به آن‌ها). Lazy loading و eager loading (همانند مثال‌های قبلی) هر دو در NHibernate به سادگی قابل تنظیم هستند (برای مثال LINQ to SQL به صورت پیش فرض همواره lazy load است و تا این تاریخ راه استانداردی برای امکان تغییر و تنظیم این مورد پیش بینی نشده است).

              اکنون کلاس جدید Config را به برنامه اضافه نمائید:

              using FluentNHibernate.Automapping;
              using FluentNHibernate.Cfg;
              using FluentNHibernate.Cfg.Db;
              using NHibernate;
              using NHibernate.Cfg;
              using NHibernate.Tool.hbm2ddl;

              namespace NHSample2
              {
              class Config
              {
              public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
              {
              var cfg = dbType.ConfigureProperties(new Configuration());

              new AutoPersistenceModel()
              .Where(x => x.Namespace.EndsWith("Domain"))
              .AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly).Configure(cfg);

              return cfg;
              }

              public static void GenerateDbScript(Configuration config, string filePath)
              {
              bool script = true;//فقط اسکریپت دیتابیس تولید گردد
              bool export = false;//نیازی نیست بر روی دیتابیس هم اجرا شود
              new SchemaExport(config).SetOutputFile(filePath).Create(script, export);
              }

              public static void BuildDbSchema(Configuration config)
              {
              bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
              bool export = true;//آیا بر روی دیتابیس هم اجرا شود
              bool drop = false;//آیا اطلاعات موجود دراپ شوند
              new SchemaExport(config).Execute(script, export, drop);
              }

              public static void CreateSQL2008DbPlusScript(string connectionString, string filePath)
              {
              Configuration cfg =
              GenerateMapping(
              MsSqlConfiguration
              .MsSql2008
              .ConnectionString(connectionString)
              .ShowSql()
              );
              GenerateDbScript(cfg, filePath);
              BuildDbSchema(cfg);
              }

              public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbType)
              {
              return
              Fluently.Configure().Database(dbType)
              .Mappings(m => m.AutoMappings
              .Add(
              new AutoPersistenceModel()
              .Where(x => x.Namespace.EndsWith("Domain"))
              .AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly))
              )
              .BuildSessionFactory();
              }
              }
              }

              در متد GenerateMapping از قابلیت Automapping موجود در فریم ورک Fluent Nhibernate استفاده شده است (بدون نوشتن حتی یک سطر جهت تعریف این نگاشت‌ها). این متد نوع دیتابیس مورد نظر را جهت ساخت تنظیمات خود دریافت می‌کند. سپس با کمک کلاس AutoPersistenceModel این فریم ورک، به صورت خودکار از اسمبلی برنامه نگاشت‌های لازم را به کلاس‌های موجود در پوشه Domain ما اضافه می‌کند (مرسوم است که این پوشه در یک پروژه Class library مجزا تعریف شود که در این برنامه جهت سهولت کار در خود برنامه قرار گرفته است). قسمت Where ذکر شده به این جهت معرفی گردیده است تا Fluent Nhibernate برای تمامی کلاس‌های موجود در اسمبلی جاری، سعی در تعریف نگاشت‌های لازم نکند. این نگاشت‌ها تنها به کلاس‌های موجود در پوشه دومین ما محدود شده‌اند.
              سه متد بعدی آن، جهت ایجاد اسکریپت دیتابیس از روی این نگاشت‌های تعریف شده و سپس اجرای این اسکریپت بر روی دیتابیس جاری معرفی شده، تهیه شده‌اند. برای مثال CreateSQL2008DbPlusScript یک مثال ساده از استفاده دو متد قبلی جهت ایجاد اسکریپت و دیتابیس متناظر اس کیوال سرور 2008 بر اساس نگاشت‌های برنامه است.
              با متد CreateSessionFactory در قسمت‌های قبل آشنا شده‌اید. تنها تفاوت آن در این قسمت، استفاده از کلاس AutoPersistenceModel جهت تولید خودکار نگاشت‌ها است.

              در ادامه دیتابیس متناظر با موجودیت‌های برنامه را ایجاد خواهیم کرد:

              using System;

              namespace NHSample2
              {
              class Program
              {
              static void Main(string[] args)
              {
              Config.CreateSQL2008DbPlusScript(
              "Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true",
              "db.sql");

              Console.WriteLine("Press a key...");
              Console.ReadKey();
              }
              }
              }

              پس از اجرای برنامه، ابتدا فایل اسکریپت دیتابیس به نام db.sql در پوشه اجرایی برنامه تشکیل خواهد شد و سپس این اسکریپت به صورت خودکار بر روی دیتابیس معرفی شده اجرا می‌گردد. دیتابیس دیاگرام حاصل را در شکل زیر می‌توانید ملاحظه نمائید:



              همچنین اسکریپت تولید شده آن، صرفنظر از عبارات drop اولیه، به صورت زیر است:

              create table [Category] (
              Id INT IDENTITY NOT NULL,
              CategoryName NVARCHAR(255) null,
              primary key (Id)
              )

              create table [User] (
              Id INT IDENTITY NOT NULL,
              UserName NVARCHAR(255) null,
              Password NVARCHAR(255) null,
              primary key (Id)
              )

              create table [News] (
              Id UNIQUEIDENTIFIER not null,
              Subject NVARCHAR(255) null,
              NewsText NVARCHAR(255) null,
              DateEntered DATETIME null,
              Category_id INT null,
              User_id INT null,
              primary key (Id)
              )

              alter table [News]
              add constraint FKE660F9E1C9CF79
              foreign key (Category_id)
              references [Category]

              alter table [News]
              add constraint FKE660F95C1A3C92
              foreign key (User_id)

              references [User]

              اکنون یک سری گروه خبری، کاربر و خبر را به دیتابیس خواهیم افزود:

              using System;
              using FluentNHibernate.Cfg.Db;
              using NHibernate;
              using NHSample2.Domain;

              namespace NHSample2
              {
              class Program
              {
              static void Main(string[] args)
              {
              using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
              MsSqlConfiguration
              .MsSql2008
              .ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
              .ShowSql()
              ))
              {
              using (ISession session = sessionFactory.OpenSession())
              {
              using (ITransaction transaction = session.BeginTransaction())
              {
              //با توجه به کلیدهای خارجی تعریف شده ابتدا باید گروه‌ها را اضافه کرد
              Category ca = new Category() { CategoryName = "Sport" };
              session.Save(ca);
              Category ca2 = new Category() { CategoryName = "IT" };
              session.Save(ca2);
              Category ca3 = new Category() { CategoryName = "Business" };
              session.Save(ca3);

              //سپس یک کاربر را به دیتابیس اضافه می‌کنیم
              User u = new User() { Password = "123$5@1", UserName = "VahidNasiri" };
              session.Save(u);

              //اکنون می‌توان یک خبر جدید را ثبت کرد

              News news = new News()
              {
              Category = ca,
              User = u,
              DateEntered = DateTime.Now,
              Id = Guid.NewGuid(),
              NewsText = "متن خبر جدید",
              Subject = "عنوانی دلخواه"
              };
              session.Save(news);

              transaction.Commit(); //پایان تراکنش
              }
              }
              }

              Console.WriteLine("Press a key...");
              Console.ReadKey();
              }
              }
              }
              جهت بررسی انجام عملیات ثبت هم می‌توان به دیتابیس مراجعه کرد، برای مثال:



              و یا می‌توان از LINQ استفاده کرد:
              برای مثال کاربر VahidNasiri تعریف شده را یافته، اطلاعات آن‌را نمایش دهید؛ سپس نام او را به Vahid ویرایش کرده و دیتابیس را به روز کنید.

              برای اینکه کوئری‌های LINQ ما شبیه به LINQ to SQL شوند، کلاس NewsContext را به صورت ذیل تشکیل می‌دهیم. این کلاس از کلاس پایه NHibernateContext مشتق شده و سپس به ازای تمام موجودیت‌های برنامه، یک متد از نوع IOrderedQueryable را تشکیل خواهیم داد.

              using System.Linq;
              using NHibernate;
              using NHibernate.Linq;
              using NHSample2.Domain;

              namespace NHSample2
              {
              class NewsContext : NHibernateContext
              {
              public NewsContext(ISession session)
              : base(session)
              { }

              public IOrderedQueryable<News> News
              {
              get { return Session.Linq<News>(); }
              }

              public IOrderedQueryable<Category> Categories
              {
              get { return Session.Linq<Category>(); }
              }

              public IOrderedQueryable<User> Users
              {
              get { return Session.Linq<User>(); }
              }
              }
              }
              اکنون جهت یافتن کاربر و به روز رسانی اطلاعات او در دیتابیس خواهیم داشت:

              using System;
              using FluentNHibernate.Cfg.Db;
              using NHibernate;
              using System.Linq;
              using NHSample2.Domain;

              namespace NHSample2
              {
              class Program
              {
              static void Main(string[] args)
              {
              using (ISessionFactory sessionFactory = Config.CreateSessionFactory(
              MsSqlConfiguration
              .MsSql2008
              .ConnectionString("Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true")
              .ShowSql()
              ))
              {
              using (ISession session = sessionFactory.OpenSession())
              {
              using (ITransaction transaction = session.BeginTransaction())
              {
              using (NewsContext db = new NewsContext(session))
              {
              var query = from x in db.Users
              where x.UserName == "VahidNasiri"
              select x;

              //اگر چیزی یافت شد
              if (query.Any())
              {
              User vahid = query.First();
              //نمایش اطلاعات کاربر
              Console.WriteLine("Id: {0}, UserName: {0}", vahid.Id, vahid.UserName);
              //به روز رسانی نام کاربر
              vahid.UserName = "Vahid";
              session.Update(vahid);

              transaction.Commit(); //پایان تراکنش
              }
              }
              }
              }
              }

              Console.WriteLine("Press a key...");
              Console.ReadKey();
              }
              }
              }
              مباحث تکمیلی AutoMapping

              اگر به اسکریپت دیتابیس تولید شده دقت کرده باشید، عملیات AutoMapping یک سری پیش فرض‌هایی را اعمال کرده است. برای مثال فیلد Id را از نوع identity و به صورت کلید تعریف کرده، یا رشته‌ها را به صورت nvarchar با طول 255 ایجاد نموده است. امکان سفارشی سازی این موارد نیز وجود دارد.

              مثال:

              using FluentNHibernate.Conventions.Helpers;

              public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
              {
              var cfg = dbType.ConfigureProperties(new Configuration());

              new AutoPersistenceModel()
              .Conventions.Add()
              .Where(x => x.Namespace.EndsWith("Domain"))
              .Conventions.Add(
              PrimaryKey.Name.Is(x => "ID"),
              DefaultLazy.Always(),
              ForeignKey.EndsWith("ID"),
              Table.Is(t => "tbl" + t.EntityType.Name)
              )
              .AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
              .Configure(cfg);

              return cfg;
              }

              تابع GenerateMapping معرفی شده را اینجا با قسمت Conventions.Add تکمیل کرده‌ایم. به این صورت دقیقا مشخص شده است که فیلدهایی با نام ID باید primary key در نظر گرفته شوند، همواره lazy loading صورت گیرد و نام کلید خارجی به ID ختم شود. همچنین نام جداول با tbl شروع گردد.
              روش دیگری نیز برای معرفی این قرار دادها و پیش فرض‌ها وجود دارد. فرض کنید می‌خواهیم طول رشته پیش فرض را از 255 به 500 تغییر دهیم. برای اینکار باید اینترفیس IPropertyConvention را پیاده سازی کرد:

              using FluentNHibernate.Conventions;
              using FluentNHibernate.Conventions.Instances;

              namespace NHSample2.Conventions
              {
              class MyStringLengthConvention : IPropertyConvention
              {
              public void Apply(IPropertyInstance instance)
              {
              instance.Length(500);
              }
              }
              }
              سپس نحوه‌ی معرفی آن به صورت زیر خواهد بود:

              public static Configuration GenerateMapping(IPersistenceConfigurer dbType)
              {
              var cfg = dbType.ConfigureProperties(new Configuration());

              new AutoPersistenceModel()
              .Conventions.Add()
              .Where(x => x.Namespace.EndsWith("Domain"))
              .Conventions.Add<MyStringLengthConvention>()
              .AddEntityAssembly(typeof(NHSample2.Domain.News).Assembly)
              .Configure(cfg);

              return cfg;
              }

              نکته:
              اگر برای یافتن اطلاعات بیشتر در این مورد در وب جستجو کنید، اکثر مثال‌هایی را که مشاهده خواهید کرد بر اساس نگارش بتای fluent NHibernate هستند و هیچکدام با نگارش نهایی این فریم ورک کار نمی‌کنند. در نگارش رسمی نهایی ارائه شده، تغییرات بسیاری صورت گرفته که آن‌ها را در این آدرس می‌توان مشاهده کرد.

              دریافت سورس برنامه قسمت ششم


              ادامه دارد ...

              مطالب
              کار با Docker بر روی ویندوز - قسمت دوم - نصب Docker
              پس از آشنایی با مفهوم Containers، در این قسمت قصد داریم برنامه‌ی تقریبا 500 مگابایتی Docker for Windows Installer.exe را نصب کنیم.
               
              پیش‌نیازهای نصب Docker بر روی ویندوز

              مطابق مستندات آن، برای نصب داکر بر روی ویندوز به حداقل‌های زیر نیاز است:
              - استفاده از ویندوز 10 نگارش enterprise، که شماره نگارش آن حداقل 1607 باشد (حداقل Anniversary Update باشد).
              - مجازی سازی در BIOS فعال شده باشد.
              البته مجازی سازی عموما به صورت پیش‌فرض فعال است. برای بررسی آن، taskmanager ویندوز را اجرا کرده و در برگه‌ی Performance آن، جائیکه مشخصات CPU را نمایش می‌دهد، یک سطر به Virtualization اختصاص دارد که مقدار آن باید enabled باشد (تصویر زیر) و اگر نیست، برای فعال کردن آن باید به تنظیمات BIOS سیستم خود مراجعه کنید:


              روش دیگر دریافت این اطلاعات، اجرای دستور systeminfo در خط فرمان، با دسترسی مدیریتی است. در خروجی آن، عبارت «Virtualization Enabled In Firmware» را جستجو کنید که باید مقدار آن yes باشد.

              - داشتن CPU با قابلیت SLAT یا Second Level Address Translation.
              برای یافتن این موضوع، برنامه‌ی coreinfo را دریافت کرده و آن‌را به صورت coreinfo -v اجرا کنید. خروجی آن سه سطر مرتبط با مجازی سازی را به همراه دارد. اگر قابلیتی موجود نباشد، جلوی آن یک خط تیره و اگر قابلیتی موجود باشد، روبروی آن یک ستاره را مشاهده خواهید کرد.

              روش دیگر بررسی آن، اجرای دستور msinfo32 در قسمت run ویندوز و سپس enter است. در قسمت system summary، اطلاعات Second Level Address Translation قابل مشاهده هستند (اگر No باشد، امکان اجرای containerهای لینوکسی را بر روی ویندوز نخواهید داشت):


              - داشتن حداقل 4 گیگابایت RAM.
              - فعال بودن Hyper-V نیز برای اجرای Linux Containers بر روی ویندوز، ضروری است (نصاب Docker، این‌کار را به صورت خودکار انجام می‌دهد).


              دریافت نصاب Docker for Windows

              برای دریافت نصاب داکر مخصوص ویندوز، به آدرس زیر مراجعه کنید:
              https://store.docker.com/editions/community/docker-ce-desktop-windows
              که بلافاصله با تصویر کریه زیر مواجه خواهید شد:


              برای رفع این مشکل، می‌توان از روش مطرح شده‌ی در مطلب «یک روش ساده برای دور زدن تحریم‌ها!» استفاده کرد؛ یعنی تنظیم DNS به 178.22.122.100 به صورت زیر:


              پس از این تغییر، چون IP قابل مشاهده‌ی سیستم شما توسط سایت داکر تغییر می‌کند، اینبار صفحه‌ی دریافت Docker Community Edition for Windows به صورت زیر ظاهر می‌شود:


              همانطور که مشاهده می‌کنید، عنوان کرده‌است که لطفا لاگین کنید تا بتوانید این برنامه را دریافت کنید. به همین جهت بر روی لینک آن کلیک کرده، یک اکانت جدید را در سایت docker ایجاد کنید (با یک ایمیل واقعی که تائیدیه آن‌را دریافت خواهید کرد). پس از آن، با این اکانت جدید به سایت داکر وارد شوید تا لینک دریافت فایل exe نصاب آن‌را دریافت کنید.
              در این حالت مرورگر و یا حتی دانلودمنیجر شما بدون مشکل می‌توانند این فایل را دریافت کنند و همان تنظیم DNS فوق، مشکل عدم دسترسی را برطرف می‌کند.


              نصب Docker for Windows

              پس از اجرای نصاب آن و پایان عملیات نصب (که تنها کافی است در صفحه‌ی ابتدایی آن تیک مربوط به Windows Containers را نیز قرار دهید)، نیاز دارد تا شما را یکبار از سیستم Logout و login کند. پس از ورود به سیستم، تنظیمات ابتدایی آن به صورت خودکار صورت گرفته و در صورت فعال نبودن Hyper-V، پیام زیر را مشاهده خواهید کرد:


              بر روی OK کلیک کنید تا اینکار با موفقیت به پایان برسد. البته پس از آن، منتظر حداقل یکبار ری‌استارت شدن خودکار سیستم، بدون اطلاع قبلی نیز باشید.

              یک نکته: کاری که در قسمت فعالسازی Hyper-V به صورت خودکار انجام می‌شود، شامل اجرای سه دستور زیر، در کنسول پاور شل، با دسترسی مدیریتی و سپس ری استارت سیستم است:
              Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -Verbose
              Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -Verbose
              bcdedit /set hypervisorlaunchtype Auto
              پس از آن، خط فرمان را باز کرده و با ستفاده از docker CLI نصب شده، دستور docker info را صادر کنید، تا بتوانید مشخصات نگارش نصب شده را مشاهده نمائید.
              C:\Users\Vahid>docker info
              Containers: 0
               Running: 0
               Paused: 0
               Stopped: 0
              Images: 0
              Server Version: 18.06.1-ce
              OSType: windows
              OSType را در صورتیکه سیستم شما توانمندی‌های سخت افزاری لازم را داشته باشد، می‌توان به Linux نیز تغییر داد.


              بررسی تنظیمات سوئیچ کردن بین Linux Containers و Windows Containers

              پس از اتمام ری‌استارت‌ها، برای آزمایش فعال بودن Hyper-V، در قسمت Run ویندوز، عبارت Virtmgmt.msc را نوشته و enter کنید. اگر تصویر زیر را مشاهده نمی‌کنید:


              یکبار بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینه‌ی switch to Linux containers را انتخاب کنید تا پس از مدتی، آیکن MobyLinuxVM در قسمت virtual machines (تصویر فوق) ظاهر شود.


              اگر پس از انتخاب این گزینه، پیام زیر را دریافت کردید:


              و یا اگر بر روی این ماشین مجازی کلیک راست کردید و گزینه‌ی Start آن‌را انتخاب کردید و پیام زیر ظاهر شد:


              قسمت «پیش‌نیازهای نصب Docker بر روی ویندوز» را با دقت بررسی کنید (خصوصا قسمت BIOS و SLAT). نبود یکی از موارد ذکر شده، سبب بروز این مشکل می‌شود.
              برای مثال اجرای دستور coreinfo -v بر روی سیستم من چنین خروجی را به همراه دارد:
              E:\>coreinfo -v
              
              AuthenticAMD
              Microcode signature: 00000000
              HYPERVISOR      -       Hypervisor is present
              SVM             *       Supports AMD hardware-assisted virtualization
              NP              -       Supports AMD nested page tables (SLAT)
              روبروی HYPERVISOR و همچنین SLAT یک - را قرار داده‌است. یعنی این موارد یا پشتیبانی نمی‌شوند و یا در BIOS فعال نشده‌اند.
              همانطور که مشاهده می‌کنید، قابلیت SLAT در CPU این سیستم وجود ندارد. به همین جهت نمی‌توان به Linux containers سوئیچ کرد. هرچند windows containers آن کار می‌کند.

              روش دیگر مشاهده‌ی این خطا، مراجعه‌ی به event viewer ویندوز است. در قسمت خطاهای سیستم، ممکن است خطای زیر را مشاهده کنید:
              Hypervisor launch failed; Second Level Address Translation is required to launch the hypervisor.


              آزمایش Docker نصب شده

              پس از نصب docker، خط فرمان ویندوز را گشوده و دستور زیر را صادر کنید:
              docker run hello-world
              اگر از قسمت قبل به‌خاطر داشته باشید، hello-world در اینجا نام یک image است. البته چون این image بر روی سیستم ما موجود نیست، این دستور، ابتدا آن‌را از docker hub دریافت کرده و سپس اجرا می‌کند. بنابراین می‌شد ابتدا دستور pull را صادر کرد و سپس run. اما دستور run قادر است هر دو عمل را با هم انجام دهد.

              یک نکته: این image، یک image لینوکسی است. به همین جهت پیش از اجرای این دستور، همانطور که پیشتر نیز عنوان شد، یکبار بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینه‌ی switch to Linux containers را انتخاب کنید. سپس دستور docker run hello-world را اجرا نمائید.

              و یا در همین حال دستور docker run -p 80:80 nginx را صادر کنید تا وب سرور لینوکسی nginx را بتوانید تحت ویندوز اجرا کنید. پس از خاتمه‌ی عملیات دریافت و اجرای وب سرور، با توجه به تنظیم  p 80:80-، پورت 80 میزبان (اولین عدد)، به پورت 80 کانتینر نگاشت شده‌است. به همین جهت تنها با اجرای دستور http://localhost، خروجی این وب سرور را می‌توانید در مرورگر سیستم خود مشاهده کنید.
              همانطور که مشاهده می‌کنید، با استفاده از داکر، پیش از آنکه بدانیم چگونه باید یک نرم افزار را نصب کرد، می‌توان از آن استفاده کرد!


              روش متوقف کردن Containers در حال اجرا

              اگر دستور docker ps را در خط فرمان ویندوز اجرا کنید، لیست پروسه‌های اجرا شده‌ی توسط آن قابل مشاهده هستند. در این لیست container id در حال اجرا نیز مشخص است. برای خاتمه‌ی کار آن، تنها کافی است دستور docker stop id را اجرا کنید.
              یک نکته: ضرورتی به ذکر کامل id نیست. برای مثال ذکر سه حرف اول آن نیز کفایت می‌کند.


              روش اجرای مجدد یک Container

              فرض کنید می‌خواهیم سرور nginx را مجددا اجرا کنیم. یک روش آن، اجرای مجدد دستور docker run -p 80:80 nginx است که پیشتر آن‌را انجام دادیم. در این حالت این image تبدیل به container شده و همانند روش‌های متداول نصب نرم افزار، اکنون به عنوان یک نرم افزار نصب شده در دسترس است. برای مشاهده‌ی لیست آن‌ها، دستور docker ps -a را اجرا کنید. این لیست تا این لحظه باید شامل containerهای nginx و hello-world باشد. متوقف کردن یک container، سبب تخریب یا حذف آن نمی‌شود. در این حالت در لیستی که توسط دستور docker ps -a نمایش داده شده‌است، باز هم container idها قابل مشاهده هستند. فقط کافی است برای اجرای یکی از آن‌ها، دستور docker start id را اجرا کرد. به این صورت دیگر نیازی به ذکر دستور کامل docker run با تمام پارامترهای آن نیست. این id نیز همانطور که ذکر شد، می‌تواند سه حرف ابتدایی این id باشد تا حدی که نسبت به سایر idهای موجود، منحصربفرد شناخته شود. یا بجای container id می‌توان container name نمایش داده شده‌ی در این لیست را استفاده کرد.
              پس از اجرای nginx توسط دستور docker start id، دو روش برای بررسی در حال اجرا بودن آن وجود دارد:
              الف) مرورگر را باز کنیم و آدرس http://localhost را بررسی کنیم.
              ب) دستور docker ps را در خط فرمان اجرا کنیم، تا مشخص شود که آیا پروسه‌ی nginx در حال اجرا است یا خیر؟

              بنابراین دستور docker ps -a لیست تمام containers در حال اجرا و همچنین متوقف شده را نمایش می‌دهد. اما دستور docker ps تنها لیست containers در حال اجرا را نمایش خواهد داد.


              روش حذف Containers از Docker

              همانطور که در قسمت قبل نیز بحث شد، معادل نصب نرم افزار در اینجا، ایجاد یک container از یک image دریافتی از docker hub است. روش عکس آن، یعنی تخریب یک container، دقیقا معادل عزل نرم افزار از سیستم، در حالت‌های متداول است. برای اینکار مجددا دستور docker ps -a را اجرا می‌کنیم تا لیست تمام containerهای در حال اجرا و همچنین متوقف شده نمایش داده شوند. لیستی که در اینجا نمایش داده می‌شود، شبیه به لیستی است که در قسمت add/remove programs ویندوز مشاهده می‌کنید. این لیست معادل لیست نرم افزارهای نصب شده‌ی بر روی سیستم است و یا برای مشاهده‌ی لیست imageهای دریافتی از docker hub می‌توان دستور docker images را صادر کرد.
              قبل از حذف یک container نیاز است آن‌را متوقف کنیم. برای این منظور از دستور docker stop id استفاده می‌شود. سپس اجرای دستور docker rm id، سبب حذف کامل این container خواهد شد. برای آزمایش آن، مجددا دستور docker ps -a را اجرا کنید.
              دستور docker rm چندین id را نیز می‌پذیرد. می‌توان این idها و یا حتی سه حرف ابتدایی آن‌ها را با فاصله در اینجا ذکر کرد. علاوه بر id، ذکر نام containers نیز مجاز است.


              روش حذف Imageهای دریافتی از Docker Hub

              دستور docker rm، فقط containers را از سیستم حذف می‌کند (نرم افزارهای نصب شده). اما خود imageهای اصلی دریافت شده‌ی از docker hub را حذف نمی‌کند (معادل همان فایل‌های zip دریافت نرم افزار یا برنامه‌های نصاب، در حالت متداول و سنتی نصب نرم افزار). برای آزمایش آن دستور docker images را اجرا کنید. هنوز هم در لیست آن، تمام موارد دریافتی موجود هستند.
              برای حذف یک image می‌توان از دستور docker rmi id استفاده کرد (rmi بجای rm). این id نیز در لیست docker images ظاهر می‌شود و ذکر قسمتی از آن، تا حدی که نسبت به سایر idهای لیست شده منحصربفرد باشد، کافی است. در اینجا بجای id، از نام image نیز می‌توان استفاده کرد. همچنین ذکر چندین id و یا نام نیز پس از دستور docker rmi، میسر است.


              روش جستجوی imageها در Docker Hub توسط Docker CLI

              فرض کنید می‌خواهیم image مربوط به راهنمای Docker را از Docker Hub دریافت کنیم. یک روش آن مراجعه‌ی مستقیم به سایت آن است و استفاده از امکانات جستجوی فراهم شده‌ی در آن سایت. روش دیگر، استفاده از Docker CLI است. اگر دستور docker search docs را در خط فرمان اجرا کنیم، لیست تمام مخازن کدی که در آن‌ها واژه‌ی docs قرار دارد، نمایش داده می‌شود. البته پیش از نصب image آن بهتر است به برگه‌ی tags مخزن کد آن نیز مراجعه کنید تا بتوانید حجم آن‌را نیز مشاهده نمائید که حدود یک گیگابایت است. مخازن docker hub، حاوی imageهای نصاب containerهای متناظر هستند. برای دریافت و اجرای آن می‌توان دستور docker run -p 4000:4000 docs/docker.github.io را اجرا کرد.
              پس از دریافت یک گیگابایت مستندات، container آن بر روی پورت 4000 در سیستم ما (http://localhost:4000)، به صورت یک وب سایت استاتیک، قابل دسترسی خواهد بود. به این صورت می‌توان به مستندات کامل داکر به صورت آفلاین دسترسی داشت.


              مفهوم Interactive Terminal در Docker

              زمانیکه دستور اجرای مستندات آفلاین را صادر می‌کنید، در انتهای آن عنوان می‌کند که وب سایت محلی آن بر روی پورت 4000 قابل دسترسی است. سپس در ذیل آن ذکر شده‌است که اگر ctrl+c را فشار دهید، اجرای آن به پایان می‌رسد. اما عملا اینطور نیست و اگر دستور docker ps را صادر کنید، هنوز container در حال اجرای آن را می‌توان مشاهده کرد.
              اما اگر اینبار دستور اجرای docker run را به همراه یک interactive terminal با سوئیچ it و نام docs صادر کنیم:
               docker run -p 4000:4000 -it --name docs docs/docker.github.io
              اکنون اگر ctrl+c را فشار دهیم و پس از آن دستور docker ps را صادر کنیم، دیگر در لیست آن، container در حال اجرای docs مشاهده نمی‌شود.
              سوئیچ it یا interactive terminal سبب می‌شود تا یک container در foreground، بجای background اجرا شود. به این ترتیب دستور ctrl+c، سبب خاتمه‌ی واقعی پروسه‌ی درحال اجرای در container می‌شود.
              روش دیگر خاتمه‌ی این container، استفاده از نام ذکر شده‌است؛ یعنی اجرای دستور docker stop docs.

              یک نکته: اگر می‌خواهید از terminal باز شده قطع شوید (مجددا به command prompt باز گردید) اما سبب خاتمه‌ی container آن نشوید، از ترکیب ctrl+p+q استفاده کنید.


              اجرای containerهای ویندوزی

              در مورد نحوه‌ی سوئیچ بین نوع‌های مختلف containerهای ویندوزی و لینوکسی پیشتر توضیح دادیم. برای این منظور می‌توان بر روی آیکن Docker در قسمت Tray Icons ویندوز کلیک راست کرده و گزینه‌ی switch to Windows/Linux containers را انتخاب کرد. باید دقت داشت که پشتیبانی از containerهای ویندوزی، از ویندوز 10، نگارش  1607، یا همان Anniversary Update آن به بعد، به ویژگی‌های ویندوز اضافه شده‌اند که به صورت خودکار توسط docker فعالسازی می‌شوند:



              اجرای IIS به عنوان یک Windows Container

              تا اینجا imageهای دریافتی، لینوکسی بودند. اگر گزینه‌ی Windows Containers را به روشی که گفته شد، فعال کنید، اینبار با اجرای دستورات docker ps و یا docker images، هیچ خروجی را دریافت نخواهید کرد. از این جهت که کانتینرهای ویندوزی و لینوکسی، به صورت کاملا ایزوله‌ای از هم اجرا و مدیریت می‌شوند. علت آن‌را هم در MobyLinuxVM که پیشتر با اجرای دستور Virtmgmt.msc بررسی کردیم، می‌توان یافت. Containerهای لینوکسی، در داخل MobyLinuxVM اجرا می‌شوند.
              در اینجا به عنوان مثال می‌توان image رسمی مربوط به IIS را از docker hub دریافت و به صورت یک کانتینر ویندوزی اجرا کرد. البته پیش از اجرای دستورات آن بهتر است به برگه‌ی tags آن مراجعه کرده و حجم‌های نگارش‌های مختلف آن‌را بررسی کرد. اجرای دستور docker pull microsoft/iis به معنای دریافت tag ای به نام latest است (به حجم 6 گیگابایت!)؛ یعنی با دستور docker pull microsoft/iis:latest یکی است. بنابراین در اینجا بر اساس tagهای مختلف، می‌توان دستور pull متفاوتی را صادر کرد. برای مثال اگر دستور docker pull microsoft/iis:nanoserver را صادر کردید، نگارش مخصوص nano server آن‌را که فقط 449 مگابایت است، دریافت می‌کند. بنابراین از این پس به tagهای هر مخزن docker hub خوب دقت کنید و نگارش مختص به سیستم عامل خود را دریافت نمائید. عدم ذکر tag ای، همواره tag ویژه‌ای را به نام latest، دریافت می‌کند.
              با اجرای دستور زیر
               docker run -p 81:80 -d --name iis microsoft/iis:nanoserver
              داکر، ابتدا image مخصوص nanoserver آن‌را دریافت و سپس اجرا می‌کند. چون وب سرور است، نیاز به تنظیمات پورت آن‌را داریم. p 81:80- به این معنا است که پورت 80 کانتینر را (پورتی که IIS درون آن بر روی آن اجرا می‌شود)، به پورت 81 بر روی سیستم میزبان (یا همین ویندوز فعلی که داکر را اجرا می‌کند)، نگاشت کن. پارامتر d در اینجا به معنای detach است. یعنی به محض اجرای این دستور، کار اجرای این کانتینر در background انجام شده و سپس به خط فرمان، بازگشت داده می‌شویم. همچنین نامی نیز به این container انتساب داده شده‌است تا ساده‌تر بتوان با آن کار کرد.

              یک نکته: مشکلی با اجرای IIS مخصوص نانوسرور بر روی ویندوز 10 به این صورت و توسط داکر نیست. بنابراین پس از اجرای دستور فوق، کار دریافت image و ساخت container و سپس اجرای آن به صورت خودکار انجام شده و بلافاصله به command prompt بازگشت داده می‌شویم (به علت استفاده‌ی از پارامتر d). اکنون اگر دستور docker ps را صادر کنیم، مشاهده می‌کنیم که کانتینر IIS مخصوص نانوسرور، هم اکنون بر روی ویندوز 10 در حال اجرا است و در آدرس http://localhost:81 قابل دسترسی است.

              جهت تکمیل این بحث، بهتر است image مخصوص nanoserver را نیز از docker hub دریافت و اجرا کنیم:
               docker run microsoft/windowsservercore
              حجم image آن 6GB است.


              تنظیمات کارت شبکه‌ی Containers

              هنگامیکه پروسه‌ای درون یک container اجرا می‌شود، ایزوله سازی‌های بسیاری نیز در مورد آن اعمال خواهد شد؛ به همین جهت گاهی از اوقات عده‌ای containerها را با ماشین‌های مجازی نیز مقایسه می‌کنند. برای مثال کانتینرها به همراه network adapter خاص خود نیز هستند؛ درست مانند اینکه یک کامپیوتر مجزای از سیستم جاری می‌باشند و اگر این network adapter را ping کنیم، می‌توان به این صورت نیز به آن کانتینر، دسترسی داشته باشیم.
              برای یافتن آن، دستور docker inspect iis را صادر می‌کنیم. خروجی آن به همراه یک قسمت network نیز هست که داخل آن یک IP Address قابل مشاهده است. این IP است که مختص و منحصربفرد این container است. در ابتدا برای آزمایش آن، می‌توان آن‌را ping کرد؛ مانند ping 172.27.49.47. همچنین به تمام برنامه‌های داخل این container توسط این IP نیز می‌توان دسترسی یافت. برای مثال فراخوانی http://172.27.49.47:81 در مرورگر، سبب نمایش صفحه‌ی اول IIS می‌شود. البته اگر اینکار را انجام دهیم، کار نمی‌کند. علت اینجا است، نگاشت پورتی را که تعریف کرده‌ایم (پورت 81)، به پورتی در کامپیوتر میزبان است و نه این IP ویژه. برنامه‌ی اصلی IIS در داخل container، به پورت 80 بر روی این آدرس IP گوش فرا می‌دهد. اکنون اگر آدرس http://172.27.49.47:80 را در کامپیوتر میزبان فراخوانی کنیم، کار می‌کند.
              بنابراین هرچند containerها به معنای نرم افزارهای از پیش نصب شده‌ی در حال اجرا هستند، اما ... به همراه ایزوله سازی‌های قابل توجهی بر روی کامپیوتر میزبان اجرا می‌شوند؛ درست مانند یک کامپیوتر مجزای از آن.
              مطالب
              React 16x - قسمت 5 - کامپوننت‌ها - بخش 2 - نمایش لیست‌ها و مدیریت رویدادها و حالات
              در قسمت قبل، اولین کامپوننت React خود را ایجاد کردیم و سپس جزئیات بیشتری از عبارات JSX را مانند نحوه‌ی تعریف المان‌های مختلف و تنظیم مقادیر ویژگی‌های آن‌را بررسی کردیم. در ادامه‌ی همان مثال، در این قسمت، نحوه‌ی نمایش لیست‌ها و تعریف و مدیریت رویدادها را در کامپوننت‌های React، بررسی می‌کنیم.


              نحوه‌ی رندر لیستی از اشیاء در کامپوننت‌های React

              فرض کنید می‌خواهیم لیستی از تگ‌ها را رندر کنیم. برای این منظور ابتدا داده‌های مرتبط را به خاصیت state کامپوننت، اضافه می‌کنیم:
              class Counter extends Component {
                state = {
                  count: 0,
                  tags: ["tag 1", "tag 2", "tag 3"]
                };
              اکنون می‌خواهیم tags را توسط المان‌های ul و ui رندر کنیم. اگر با Angular کار کرده باشید، به همراه یک دایرکتیو ngFor است که توسط آن می‌توان یک حلقه را در قالب جاری، پیاده سازی و رندر کرد. اما در React و عبارات JSX، چیزی به نام مفهوم حلقه‌ها وجود خارجی ندارد؛ چون JSX یک templating engine نیست. فقط بیان ساده‌ی المان‌هایی است که قرار است توسط کامپایلر Babel به کدهای جاوا اسکریپتی ترجمه شوند. بنابراین اکنون این سؤال وجود دارد که چگونه می‌توان لیستی از عناصر را در اینجا رندر کرد؟
              در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا می‌توان توسط متد map، هر المان آرایه‌ی تگ‌ها را به یک المان React تبدیل و سپس رندر کرد:
              class Counter extends Component {
                state = {
                  count: 0,
                  tags: ["tag 1", "tag 2", "tag 3"]
                };
              
                render() {
                  return (
                    <div>
                      <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
                      <button className="btn btn-secondary btn-sm">Increment</button>
                      <ul>
                        {this.state.tags.map(tag => (
                          <li>{tag}</li>
                        ))}
                      </ul>
                    </div>
                  );
                }
              در این مثال، داخل المان ul، با یک {} شروع می‌کنیم تا بتوان به صورت پویا به مقدار آرایه‌ی this.state.tags دسترسی پیدا کرد. سپس متد map را بر روی این آرایه فراخوانی می‌کنیم. متد map، هر عضو آرایه‌ی tags را به callback function آن ارسال کرده و خروجی آن‌را به صورت یک عبارت JSX که در نهایت به یک المان جاوا اسکریپتی خالص ترجمه خواهد شد، تبدیل می‌کند. این فرآیند سبب رندر لیست tags می‌شود:


              هرچند اکنون لیستی از تگ‌ها در مرورگر رندر شده‌اند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شده‌است. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه می‌کنیم:
              <li key={tag}>{tag}</li>
              البته در مثال ما تگ‌ها منحصربفرد هستند؛ بنابراین استفاده‌ی از آن‌ها به عنوان key، مشکلی را ایجاد نمی‌کند. در یک برنامه‌ی مفصل‌تر، تگ‌ها می‌توانند شیء بوده و هر شیء دارای خاصیت id باشد که در این حالت فرضی می‌توان از tag.id به عنوان key استفاده کرد. همچنین باید دانست که این key فقط نیاز است در لیست ul، منحصربفرد باشد و نیازی نیست تا در کل DOM منحصربفرد باشد.


              رندر شرطی عناصر در کامپوننت‌های React

              در اینجا می‌خواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگ‌ها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المان‌ها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
                renderTags() {
                  if (this.state.tags.length === 0) {
                    return <p>There are no tags!</p>;
                  }
              
                  return (
                    <ul>
                      {this.state.tags.map(tag => (
                        <li key={tag}>{tag}</li>
                      ))}
                    </ul>
                  );
                }
              یک روش حل این مساله، نوشتن متدی است که به همراه یک if/else است. در اینجا اگر آرایه‌ی تگ‌ها، دارای عنصری نبود، یک پاراگراف متناظر نمایش داده می‌شود، در غیراینصورت همان قسمت رندر لیست تگ‌ها را که توسعه دادیم، بازگشت می‌دهیم. بنابراین این متد، دو خروجی JSX را بسته به شرایط مختلف می‌تواند داشته باشد. سپس از این متد به صورت {()this.renderTags} در متد render اصلی استفاده می‌کنیم:
                render() {
                  return (
                    <div>
                      <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
                      <button className="btn btn-secondary btn-sm">Increment</button>
                      {this.renderTags()}
                    </div>
                  );
                }
              برای آزمایش آن هم یکبار آرایه‌ی tags را به نحو زیر خالی کنید:
                state = {
                  count: 0,
                  tags: []
                };

              روش دوم حل این نوع مساله‌ها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
              {this.state.tags.length === 0 && "Please create a new tag!"}
              ابتدا شرط مدنظر نوشته می‌شود، سپس پیامی را که باید در این حالت ارائه شود، پس از && می‌نویسیم. در مثال فوق اگر آرایه‌ی tags خالی باشد، پیامی نمایش داده می‌شود.
              اما این روش چگونه کار می‌کند؟! در اینجا && را به دو مقدار مشخص اعمال کرده‌ایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشته‌ای مشخص. در جاوا اسکریپت برخلاف سایر زبان‌های برنامه نویسی، می‌توان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشته‌ی خالی به false تعبیر می‌شود و اگر تنها دارای یک حرف باشد، true درنظر گرفته می‌شود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر می‌شوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت می‌دهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر می‌شود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت می‌دهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.


              مدیریت رخ‌دادها در React


              همانطور که در تصویر فوق نیز مشاهده می‌کنید، رخ‌دادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه می‌نویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نام‌ها حساس به کوچکی و بزرگی حروف نیز هستند.
              روش تعریف متدهای رخ‌دادگردان در اینجا، با ذکر فعل handle شروع می‌شود:
                handleIncrement() {
                  console.log("Increment clicked!");
                }
              سپس ارجاعی از این متد را (نه فراخوانی آن‌را)، به خاصیت برای مثال onClick ارسال می‌کنیم:
              <button
                  onClick={this.handleIncrement}
                  className="btn btn-secondary btn-sm"
              >
                  Increment
              </button>
              اگر دقت کنید، onClick، ارجاع this.handleIncrement را دریافت کرده‌است (یعنی بدون () ذکر شده‌است) و نه فراخوانی این متد را (با ذکر ()).
              اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمه‌ی Increment، یک console.log صورت می‌گیرد.

              در ادامه می‌خواهیم در این رخ‌دادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ می‌کنیم:
                handleIncrement() {
                  console.log("Increment clicked!", this.state.count);
                }
              پس از ذخیره‌ی فایل و اجرای برنامه، اینبار با کلیک بر روی دکمه‌ی Increment، بلافاصله خطای «Uncaught TypeError: Cannot read property 'state' of undefined» در کنسول توسعه دهنده‌های مرورگر ظاهر می‌شود. عنوان می‌کند که شیء this در این متد، undefined است؛ بنابراین امکان خواندن خاصیت state از آن وجود ندارد.


              bind مجدد شیء this در رخ‌دادگردان‌های React

              در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا می‌خواهیم از نتایج آن استفاده کنیم.
              همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبان‌های برنامه نویسی، متفاوت رفتار می‌کند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی می‌شود، this می‌تواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز می‌گرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیش‌فرض ارجاعی را به شیء سراسری window مرورگر، بازگشت می‌دهد و اگر strict mode فعال باشد، تنها undefined را بازگشت می‌دهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت می‌کنیم.
              یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
                constructor() {
                  super();
                  console.log("constructor", this);
                  this.handleIncrement = this.handleIncrement.bind(this);
                }
              زمانیکه شیءای از نوع کلاس جاری ایجاد می‌شود، متد constructor آن نیز فراخوانی خواهد شد. در این مرحله دسترسی کاملی به شیء this وجود دارد که نمونه‌ی آن‌را با console.log نوشته شده می‌توانید آزمایش کنید. در اینجا چون کامپوننت جاری از کلاس Component مشتق شده‌است، پیش از دسترسی به شیء this، نیاز است سازنده‌ی کلاس پایه توسط متد super فراخوانی شود. اکنون که به this دسترسی داریم، می‌توان توسط متد bind، مقدار شیء this شیءای دیگر مانند this.handleIncrement را تنظیم مجدد کنیم (متدها نیز در جاوا اسکریپت شیء هستند). خروجی آن، یک وهله‌ی جدید از شیء handleIncrement است که this آن اینبار به وهله‌ای از شیء جاری اشاره می‌کند. به همین جهت خروجی آن‌را به this.handleIncrement انتساب می‌دهیم تا مشکل تعریف نشده بودن this آن برطرف شود.
              اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمه‌ی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهنده‌های مرورگر ظاهر می‌شود.


              این یک روش است که کار می‌کند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمی‌کنند؛ بلکه آن‌را به ارث می‌برند. بنابراین ابتدا کدهای سازنده‌ی فوق را حذف می‌کنیم (چون دیگر نیازی به آن‌ها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function می‌کنیم:
                handleIncrement = () => {
                  console.log("Increment clicked!", this.state.count);
                }
              به این ترتیب با کلیک بر روی دکمه‌ی Increment، مجددا همان خروجی تصویر قبلی را دریافت می‌کنیم؛ این روش ساده‌تر و تمیزتر است و نیازی به rebind دستی تک تک رویدادگردان‌های کامپوننت جاری در این حالت وجود ندارد.


              به روز رسانی state در کامپوننت‌های React

              اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کرده‌ایم، می‌خواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آن‌را افزایش داده و در UI نمایش دهیم.
              در React، خواص شیء state را جهت نمایش آن‌ها در UI، مستقیما تغییر نمی‌دهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
                handleIncrement = () => {
                  this.state.count++;
                };
              اگر تغییر فوق را اعمال و سپس برنامه را اجرا کنید، با کلیک بر روی دکمه‌ی Increment ... اتفاقی رخ نمی‌دهد! رفتار React با Angular متفاوت است و در اینجا هرچند توسط فراخوانی {()this.formatCount} کار نمایش خاصیت count انجام می‌شود، اما به ظاهر، تغییرات مقدار count، به عبارات JSX متصل نیست. در کامپوننت‌های Angular اگر مقدار خاصیتی را تغییر دهید و اگر این خاصیت در قالب آن کامپوننت، به آن خاصیت bind شده باشد، شاهد به روز رسانی آنی UI خواهید بود (Change Detection آنی و به ازای هر تغییری)؛ اما در React خیر. هرچند در همان Angular هم توصیه می‌شود که از حالت changeDetection: ChangeDetectionStrategy.OnPush برای رسیدن به حداکثر کارآیی نمایشی کامپوننت‌ها استفاده شود؛ حالت OnPush در Angular، به روش تشخیص تغییرات React که در ادامه توضیح داده می‌شود، بیشتر شبیه است.

              در کدهای فوق هرچند با کلیک بر روی دکمه‌ی Increment، مقدار count افزایش یافته‌است، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمی‌کنید.
              با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:
                Line 33:5:  Do not mutate state directly. Use setState()  react/no-direct-mutation-state

              برای رفع این مشکل باید از یکی از متدهای به ارث برده شده‌ی از کلاس پایه‌ی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام می‌کنیم که state تغییر کرده‌است (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبه‌ی تغییرات کرده و در نتیجه قسمت‌های متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی می‌کند.
              زمانیکه از متد setState استفاده می‌کنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه می‌شوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی می‌کنند:
                handleIncrement = () => {
                  this.setState({ count: this.state.count + 1 });
                };
              در اینجا به متد this.setState که از قسمت extends Component جاری به ارث رسیده‌است، یک شیء را با خاصیت count و مقدار جدیدی، ارسال می‌کنیم.
              در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمه‌ی Increment کلیک کنید. اینبار ... کار می‌کند! چون React از تغییرات مطلع شده‌است:


              وقتی state تغییر می‌کند، چه اتفاقاتی رخ می‌دهند؟

              با فراخوانی متد this.setState، به React اعلام می‌کنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار می‌دهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شده‌است. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار می‌دهد و محاسبه می‌کند که در اینجا دقیقا کدام المان‌ها نسبت به قبل تغییر کرده‌اند. برای نمونه در اینجا تشخیص می‌دهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده می‌شود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی می‌کند و نه کل DOM را (و نه اعمال مجدد کل المان‌های حاصل از متد render را).
              این مورد را می‌توان به نحو زیر آزمایش و مشاهده کرد:
              در مرورگر بر روی المان span که شماره‌ها را نمایش می‌دهد، کلیک راست کرده و گزینه‌ی inspect را انتخاب کنید. سپس بر روی دکمه‌ی Increment کلیک نمائید. مرورگر قسمتی را که به روز می‌شود، با رنگی مشخص و متمایز، به صورت لحظه‌ای نمایش می‌دهد:



              ارسال پارامترها به متدهای رویدادگردان

              تا اینجا متد handleIncrement، بدون پارامتر تعریف شده‌است. فرض کنید در یک برنامه‌ی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمی‌توان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شده‌است.
              یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی می‌کند:
                doHandleIncrement = () => {
                  this.handleIncrement({ id: 1, name: "Product 1" });
                };
              و در این حالت برای مثال متد handleIncrement یک شیء را پذیرفته‌است:
                handleIncrement = product => {
                  console.log(product);
                  this.setState({ count: this.state.count + 1 });
                };
              سپس بجای تعریف onClick={this.handleIncrement}، از متد doHandleIncrement استفاده خواهیم کرد؛ یعنی onClick={this.doHandleIncrement}

              هرچند این روش کار می‌کند، اما بیش از اندازه طولانی شده‌است. راه حل بهتر، استفاده از یک inline function است:
              onClick={() => this.handleIncrement({ id: 1, name: "Product 1" })}
              یعنی کل arrow function مربوط به doHandleIncrement را داخل onClick قرار می‌دهیم و چون یک سطری است، نیازی به ذکر {} و سمی‌کالن انتهای آن‌را هم ندارد.


              کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-04-part02.zip
              مطالب
              مبانی TypeScript؛ Mixins
              یکی از راه‌های محبوب دیگر برای ساخت کلاس‌ها با استفاده از اجزایی با قابلیت استفاده مجدد، ساخت آنها با ترکیب partial class‌های ساده، می‌باشد. mixins در زبان‌های برنامه نویسی مانند ++C و Lisp، کلاس‌هایی هستند که یکسری توابع را از طریق ارث بری در اختیار SubClass‌ها قرار می‌دهند. شاید با ایده استفاده از mixins، یا traits در زبانی مانند Scala و الگوی mixins که بین جامعه جاوااسکریپت هم تا حدودی محبوب شده است، آشنا هستید.
              در جاوااسکریپت، ارث بری کردن از mixins، شبیه به افزایش عملکرد خود از طریق extension‌ها می‌باشد؛ چرا که این mixin‌ها این امکان را در اختیار اشیاء می‌گذارند که با کمترین پیچیدگی، بتوانند عملکردی (functionality) را از آنها به امانت بگیرند. Mixins در جاوااسکریپت به عنوان الگویی است که با object prototype‌ها کار می‌کند و امکان ارث بری چندگانه را هم به ما خواهد داد.

              یک مثال از استفاده از Mixins در جاوااسکریپت
              var myMixins = {
               
                moveUp: function(){
                  console.log( "move up" );
                },
               
                moveDown: function(){
                  console.log( "move down" );
                },
               
                stop: function(){
                  console.log( "stop! in the name of love!" );
                }
               
              };
              کد بالا نشان دهنده یک object literal با 3 متد می‌باشد و نشان دهنده mixins ما نیز هست.
              برای توسعه prototype مربوط به اشیاء، به منظور استفاده از رفتارهای تعریف شده در mixins بالا، می‌توان از هلپرهایی به مانند ()extend._ موجود در Underscore.js استفاده کرد.
              // A skeleton carAnimator constructor
              function CarAnimator(){
                this.moveLeft = function(){
                  console.log( "move left" );
                };
              }
               
              // A skeleton personAnimator constructor
              function PersonAnimator(){
                this.moveRandomly = function(){ /*..*/ };
              }
               
              // Extend both constructors with our Mixin
              _.extend( CarAnimator.prototype, myMixins );
              _.extend( PersonAnimator.prototype, myMixins );
               
              // Create a new instance of carAnimator
              var myAnimator = new CarAnimator();
              myAnimator.moveLeft();
              myAnimator.moveDown();
              myAnimator.stop();
               
              // Outputs:
              // move left
              // move down
              // stop! in the name of love!
              در کد بالا دو شیء جدید را تعریف کرده‌ایم که برای prototype هر کدام از آنها هم یک متد در نظر گرفته‌ایم. با استفاده از هلپر مذکور توانستیم عملیات مربوط به myMixins را به prototype هرکدام از اشیاء تعریف شده‌ی در بالا نسبت دهیم. استفاده‌ی از متدهای stop یا moveDown بر روی نمونه‌ای از CarAnimator نشان دهنده‌ی این ادعا می‌باشد.

              پیاده سازی این الگو در TypeScript
              // Disposable Mixin
              class Disposable {
                  isDisposed: boolean;
                  dispose() {
                      this.isDisposed = true;
                  }
              
              }
              
              // Activatable Mixin
              class Activatable {
                  isActive: boolean;
                  activate() {
                      this.isActive = true;
                  }
                  deactivate() {
                      this.isActive = false;
                  }
              }
              از دو  کلاس  بالا به عنوان mixin‌های خودمان استفاده خواهیم کردم. همانطور که مشخص است هر کدام از آنها بر روی فعالیت خاصی متمرکز شده‌اند.
              class SmartObject implements Disposable, Activatable {
                  constructor() {
                      setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
                  }
              
                  interact() {
                      this.activate();
                  }
              
                  // Disposable
                  isDisposed: boolean = false;
                  dispose: () => void;
                  // Activatable
                  isActive: boolean = false;
                  activate: () => void;
                  deactivate: () => void;
              }
              گام بعدی تعریف کلاسی برای ترکیب شدن با دو mixins تعریف شده، میباشد. اگر توجه کنید در کد بالا به جای استفاده از کلمه کلیدی extend، از implements استفاده شده است. در واقع mixin‌ها شبیه به اینترفیس هایی  با امکان پیاده سازی درون خود، هستند. کلاس‌های استفاده کننده فقط باید تعریف این متدها و خصوصیت‌ها را به منظور تشخیص این خصوصیات در زمان اجرا، در خود تعریف کرده باشند.
              applyMixins(SmartObject, [Disposable, Activatable]);
              در نهایت با استفاده از هلپر applyMixins که در ادامه کد آن را مشاهده خواهید کرد، عملیات میکس را انجام دادیم.
              در مثال زیر یک نمونه از کلاس SmartObject تهیه کرده‌ایم که به راحتی به دلیل پیاده سازی Activatable، دسترسی به استفاده از متد activate را داشته و آن را درون متد interact خود فراخوانی کرده است. اجرای خط دوم کد زیر، مبنی بر درستی ادعای ما میباشد.
              let smartObj = new SmartObject();
              setTimeout(() => smartObj.interact(), 1000);

              پیاده سازی هلپر applyMixins
              function applyMixins(derivedCtor: any, baseCtors: any[]) {
                  baseCtors.forEach(baseCtor => {
                      Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
                          derivedCtor.prototype[name] = baseCtor.prototype[name];
                      });
                  });
              }
              کد بالا تمام خصوصیات موجود در baseCtors‌ها را که همان mixin‌های ما هستند، واکشی کرده و در خصوصیات موجود در کلاس پیاده سازی کننده‌ی آن Mixin، پر میکند. 
              نظرات مطالب
              Blazor 5x - قسمت 34 - توزیع برنامه‌های Blazor بر روی IIS
              یک نکته: base href حساس به بزرگی و کوچکی حروف است!
              بین تنظیم
              <base href="/blazor/" />
              و تنظیم زیر
              <base href="/Blazor/" />
              تفاوت وجود دارد. یعنی اگر اولی تنظیم شده باشد و کاربر در مرورگر http://localhost/Blazor را وارد کند، با پیام خطای زیر مواجه می‌شود:
              System.ArgumentException: The URI is not contained by the base URI
              فعلا برای رفع این مشکل می‌توان قطعه کد زیر را پیش از تگ js مربوط به blazor قرار داد تا base href را به صورت پویا تنظیم کند:
              <script>
                var path = window.location.pathname.split('/');
                var baseTag = document.getElementsByTagName('base');
                baseTag[0].setAttribute('href', '/' + path[1] + '/');
              </script>
              اطلاعات بیشتر
              مطالب
              کلاس کمکی جهت مشاهده آیتم های موجود در حافظه کش و حذف آنها
              مواقع بسیاری پیش می‌آید که در زمان کار با یک نرم افزار تحت وب زمان اشکال زدایی پیش می‌آید که به دلیل موجود بودن داده در حافظه کش برنامه نویس نمی‌تواند داده‌های واقعی را ببیند و داده‌های موجود در حافظه کش را مشاهده می‌کند (بیشتر مواقعی که از طریق بانک اطلاعاتی مستقیما اقدام به حذف و اضافه داده می‌کنیم) در این بخش یک کلاس آماده کرده ام که همیشه خودم در نرم افزار هایم استفاده می‌کنم.

              شما می‌توانید این کلاس را به یک GridView یا کنترل‌های دیگر بایند کرده و کلید‌های موجود در حافظه کش را مشاهده کنید، و در صورتی که خواستید یک کلید خاص را از حافظه کش حذف نمایید (البته این کلاس بیشتر برای مدیر نرم فزار کاربرد دارد).

                می‌توانید فایل مورد نظر را از طریق لینک کلاس کمکی جهت مشاهده آیتم‌های موجود در حافظه کش و حذف آنها دانلود نمایید.
              در کلاس زیر هر کدام از قسمت‌ها را شرح می‌دهیم.
              using System;
              using System.Collections.Generic;
              using System.ComponentModel;
              using System.Web;
              using System.Web.Caching;
              
              namespace PWS.BLL
              {
                  /// <summary>
                  /// کلاس  آیتم‌های حافظه کش
                  /// </summary>
                  [DataObject(true)]
                  public class CacheItems
                  {
              #region Constructors (2) 
              
                      /// <summary>
                      /// سازنده اصلی
                      /// </summary>
                      /// <param name="cacheItem">عنوان آیتم ذخیره شده در حافظه کش</param>
                      public CacheItems(String cacheItem)
                      {
                          CacheItem = cacheItem;
                      }
              
                      /// <summary>
                      /// سازنده پیش فرض
                      /// </summary>
                      public CacheItems(){}
              
              #endregion Constructors 
              
              #region Properties (2) 
              
                      /// <summary>
                      /// کش کانتکست جاری
                      /// </summary>
                      /// <value>
                      /// The cache.
                      /// </value>
                      private static Cache Cache
                      {
                          get {return HttpContext.Current.Cache; }
                      }
              
                      /// <summary>
                      /// عنوان آیتم ذخیره شده در حافظه کش
                      /// </summary>
                      public String CacheItem{ get; set;}
              
              #endregion Properties 
              
              #region Methods (4) 
              
              // Public Methods (3) 
              
                      /// <summary>
                      /// لیست تمام آیتم‌های ذخیره شده در حافظه کش
                      /// </summary>
                      /// <returns></returns>
                      public List<CacheItems> GetCaches()
                      {
                          var items = new List<CacheItems>();
                          //بازیابی کل کلید‌های موجود در حافظه کش و اضافه کردن آن به لیست مربوطه
                          var enumerator = Cache.GetEnumerator();
                          while (enumerator.MoveNext())
                          {
                               items.Add(new CacheItems(enumerator.Key.ToString()));
                          }
                          return items;
                      }
              
                      /// <summary>
                      /// حذف آیتم جاری از حافظه کش
                      /// </summary>
                      public void RemoveItemFromCache()
                      {
                          RemoveItemFromCache(CacheItem);
                      }
              
                      /// <summary>
                      /// حذف کردن یک آیتم از حافظه کش
                      /// </summary>
                      /// <param name="key">کلید ذخیره شده در حافظه کش</param>
                      public static void RemoveItemFromCache(string key)
                      {
                          PurgeCacheItems(key);
                      }
              // Private Methods (1) 
              
                      /// <summary>
                      /// حذف کردن یک ایتم از حافظه کش با پشوند وارد شده
                      /// </summary>
                      /// <param name="prefix">پیشوندی از کلید موجود در حافظه کش</param>
                      private static void PurgeCacheItems(String prefix)
                      {
                          prefix = prefix.ToLower();
                          var itemsToRemove = new List<String>();
                         //لیست آیتم‌های موجود در حافظه کش
                          var enumerator = Cache.GetEnumerator();
                          while (enumerator.MoveNext())
                          {
              //در صورتی که کلید مورد نظر با پارامتر وارد شده شروع شده باشد آن را به یک لیست اضافه می‌کنیم
               if (enumerator.Key.ToString().ToLower().StartsWith(prefix)) itemsToRemove.Add(enumerator.Key.ToString()); } //لیست مورد نظر را پیمایش کرده و گزینه‌های آن را از حافظه کش حذف می‌کنیم foreach (var itemToRemove in itemsToRemove) Cache.Remove(itemToRemove); } #endregion Methods  } }
              موفق وموید باشید