نظرات مطالب
غیرمعتبر کردن توکن و یا کوکی سرقت شده در برنامه‌های مبتنی بر ASP.NET Core

یک نکته‌ی تکمیلی: به روز رسانی کتابخانه‌ی UAParser

در این مطلب از کتابخانه‌ی UAParser استفاده شد. این کتابخانه، چندسالی است که به‌روز نشده؛ البته چون نیازی نبوده! در اصل، این کتابخانه از فایل yaml مخصوصی که به صورت جاسازی شده (embedded) در آن قرار دارد، برای شناسایی مرورگرها استفاده می‌کند (مفهوم استفاده از متد ()Parser.GetDefault همین مورد است). بنابراین یا باید خودتان این فایل yaml را دستی به روز کرده (کار مخزن کد فعال UAParser-Core، فقط همین یک مورد است) و سپس کتابخانه را مجددا کامپایل و استفاده کنید و یا می‌توانید محتویات فایل yaml ذکر شده را دریافت و سپس با استفاده از متد Parser.FromYaml این کتابخانه، اطلاعات جدید دریافتی را پردازش و استفاده کنید؛ مانند UAParserService.

نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت ششم - کار با User Claims
- بله. مفهوم یک سیستم «مرکزی» همین هست. نمونه‌اش اکتیو دایرکتوری در دومین‌های سرورهای ویندوزی است که کار مدیریت کاربران و سطوح دسترسی آن‌ها را یک یا چند نفر کارمند تمام وقت به نام مدیر شبکه انجام می‌دهد و این تعاریف به صورت مجزایی در کلاینت‌های متصل، انجام نمی‌شوند. در این سری در انتهای آن روشی برای ثبت اطلاعات کاربران در بانک اطلاعاتی پیشنهاد شده. علاوه بر آن چند پیاده سازی کننده‌ی سورس باز مدیریت IDP هم معرفی شده‌اند.
- در اینجا هر کلاینت ASP.NET Core هم می‌تواند سطوح دسترسی سفارشی و پیچیده‌تر خاص خودش را بر اساس ترکیب Claims و نقش‌های دریافتی از IDP، تعریف و تنظیم کند.
نظرات مطالب
بررسی روش دسترسی به HttpContext در ASP.NET Core
- توسط httpContextAccessor امکان دسترسی به سشن هم وجود دارد: httpContextAccessor.HttpContext.Session.GetInt32("count").Value 
- متغیر Application مربوط هست به دوران Classics ASP دهه‌ی نود میلادی (حتی پیش از معرفی ASP.NET Web Forms). این متغیر این روزها با یک ConcurrentDictionary که بدون نیاز به قفل گذاری، امکان تهیه یک دیکشنری thread-safe را میسر می‌کند، قابلیت جایگزینی را دارد. یک مثال از کاربرد ConcurrentDictionary (OnlineVisitorsModule.zip برای ASP.NET 4.x و MVC 5.x)
- رویدادهای Session_Start و Session_End و کلا مباحث Global.asax در اصل بهتر است به HTTP Modules تبدیل و refactor شوند. HTTP Modules هم در ASP.NET Core به صورت کامل حذف و با مفهوم جدیدی به نام Middlewares جایگزین شده‌اند. امکان نوشتن Middlewareهای سفارشی هم وجود دارد.
نظرات مطالب
ساخت دیتابیس sqlite با EF6 Code First
- با به روز رسانی بسته‌های نیوگت مثال پیوستی EF 6x، مشکلی مشاهده نشد و مثال قابل اجرا است. نگارش ویژوال استودیو در اینجا مهم نیست و تنها اجرای دستور ذیل مهم است:
PM> update-package
الف) فایل کانفیگ پروژه خودتان را بررسی کنید و با محتوای فایل App.config پیوستی مطابقت دهید.
ب) همچنین رشته‌ی اتصالی آن باید به محل دقیق قرارگیری فایل phonebook.sqlite اشاره کند.
ج) نام بسته‌های موجود در فایل packages.config خودتان را با نمونه‌ی پروژه‌ی پیوستی مطابقت دهید.
این موارد را بررسی کنید؛ وگرنه EF 6x در حال توسعه‌ی پیوسته نیست و تغییر خاصی از زمان نگارش این مطلب نداشته‌است. 

+ اگر می‌خواهید نسخه‌ی EF Core آن‌را بررسی کنید:
- نیاز است سری EF Core را در سایت از ابتدا مطالعه کنید. 
- VS 2015 دیگر برای کار با NET Core‌. مناسب نیست. حتما نیاز است VS 2017 را نصب کنید. اطلاعات بیشتر
- پیشنیازهای جدید کار با SQLite در فایل csproj آن برای VS 2017 و EF Core 1.1.1 به صورت ذیل هستند:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" PrivateAssets="All" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" PrivateAssets="All" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.*" />
  </ItemGroup>
</Project>
- قسمت‌های تنظیم Context آن‌را در مطالب «استفاده از EF7 با پایگاه داده SQLite تحت NET Core. به کمک Visual Studio Code» و «استفاده از پروایدر SQLite در Entity Framework 7» پیگیری کنید.
اشتراک‌ها
باید از View Componentها به جای Partial Viewها در ASP.NET Core استفاده کنید

Why use View Components and not Partial Views? The biggest reason is that when inserting a Partial View into a Razor page, all the ViewData associated with the calling View is automatically associated with the Partial View. This means that a Partial View may behave very differently on one Razor page than on another. With View Components, you control what gets shared to your View Components. 

باید از View Componentها به جای Partial Viewها در ASP.NET Core استفاده کنید
اشتراک‌ها
سوالاتی که قبل از مهاجرت به کلاد باید از خود بپرسید

Cloud Computing is currently the hot topic in the developer world these days, and it seems all anyone wants to talk about is the cloud. If you're like me you signed up for something like Windows Azure just to see what the hype was all about. There are a lot of good reasons to move an app to the cloud, but it's still not for everyone. There are some things you need to think about before taking this gamble with your app.  

سوالاتی که قبل از مهاجرت به کلاد باید از خود بپرسید
مطالب
روشی برای محاسبه‌ی تعداد کاربران آنلاین در ASP.NET Core
در این مقاله قصد دارم روشی را برای محاسبه‌ی تعداد کاربران لاگین شده‌ی فعال در یک پروژه‌ی  Asp.net Core، توضیح دهم. در این روش، کاربرانی را آنلاین در نظر گرفته‌ایم که در 10 دقیقه‌ی گذشته، فعالیتی روی سامانه داشته‌اند. البته این زمان را می‌توانید تغییر دهید. برای اینکار ابتدا یک Middleware را به صورت زیر طراحی می‌کنیم :
public class OnlineUserMiddleWare
    {
        private readonly RequestDelegate _next;
        private readonly IMemoryCache _memoryCache;

        public OnlineUserMiddleWare(RequestDelegate next, IMemoryCache memoryCache)
        {
            _next = next;
            _memoryCache = memoryCache;
        }

        public async Task Invoke(HttpContext context)
        {
            if (!_memoryCache.TryGetValue("OnlineUsers", out Dictionary<string,DateTime> onlineUsers))
            {
                onlineUsers = new Dictionary<string, DateTime>();
                _memoryCache.Set("OnlineUsers", onlineUsers);
            }
            if (context.User.Identity.IsAuthenticated)
            {
                var name = context.User.Identity.Name;
                if (name != null)
                {
                    if (onlineUsers.ContainsKey(name))
                        onlineUsers[name] = DateTime.Now;
                    else
                        onlineUsers.Add(name, DateTime.Now);
                }

            }

           
                foreach (var online in onlineUsers)
                {
                    if (online.Value < DateTime.Now.AddMinutes(-10))
                        onlineUsers.Remove(online.Key);
                }
           
            await _next(context);
        }

        
    }
در اینجا به کمک IMemoryCache، یک کلید را به نام OnlineUsers در کش ایجاد کرده‌ایم. ابتدا بررسی می‌کنیم، اگر این کلید وجود نداشت، آن را ایجاد کند. نوع این کلید، یک  Dictionary است که نام کاربری و ساعت آخرین در خواست کاربران، در آن ذخیره می‌شود.
پس از اینکه مقدار OnlineUsers را از کش دریافت کردیم، چک می‌کنیم اگر کاربر لاگین شده‌است، نام کاربری را به همراه زمان جاری، در دیکشنری ذخیره کند و اگر کاربر درون دیکشنری موجود بود، فقط زمان آخرین درخواست را تغییر دهد (به‌روز کند). بنابراین تا به اینجا هر کسی در سامانه لاگین کند، نام کاربری او، به همراه زمان آخرین درخواست، در دیکشنری ذخیره می‌شود.
نهایتا باید بررسی کنیم، اگر کاربری 10 دقیقه از آخرین درخواستش گذشته باشد، از دیکشنری حذف شود. 
لازم است این Middleware به Startup اضافه شود: 
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
        {
         ...
            app.UseMiddleware<OnlineUserMiddleWare>();
         ...
        }

برای نمایش تعداد کاربران آنلاین، کافی است در View یا مکان مورد نظر، مقدار دیکشنری را از memorycache فراخوانی کنیم: (حتی می‌توان لیست کاربران را هم نمایش داد؛ چون نام کاربری آنها در دیکشنری ذخیره شده‌است)
public class DashboardController 
    {
        private readonly IMemoryCache _memoryCache;
        public DashboardController(memoryCache)
        {
            _memoryCache = memoryCache;
        }
        public IActionResult Index()
        {
         
            _memoryCache.TryGetValue("OnlineUsers", out Dictionary<string, DateTime> onlineUsers);
            ViewBag.OnlineUsers = onlineUsers.Count;
            return View();
        }       
    }
اما یک نکته وجود دارد و اینکه در MiddleWare به ازای هر درخواست، کل دیکشنری را بررسی کرده‌ایم و کاربرانی را که 10 دقیقه از آخرین  فعالیت آنها گذشته است، حذف کرده‌ایم. این بررسی به ازای هر درخواست، کار مناسبی نیست؛ چرا که ممکن است در ترافیک بالا، مشکل ساز شود. لذا می‌توان سناریوهای مختلفی را برای آن در نظر گرفت:
 - مثلا فقط جائیکه می‌خواهیم آمار نمایش داده شود، این بررسی صورت گیرد. البته اگر این آمار در صفحه‌ای پرتکرار نمایش داده شود، این سناریو می‌تواند مفید باشد. ولی مثلا اگر قرار است آمار تنها برای مدیر نمایش داده شود و مدیر چندین روز این صفحه را بررسی نکند، ممکن است حجم این دیکشنری هر روز حجیم‌تر شود.
- می‌توان در صفحاتی که اطمینان داریم روزانه چندین بار فراخوانی می‌شوند، این فرآیند را بررسی کنیم؛ مثل صفحه لاگین . لذا قسمت بررسی در MiddleWare را به صورت زیر تغییر دادم :
           if(context.Request.Path.StartsWithSegments("/Identity/Account/Login"))
            {
                foreach (var online in onlineUsers)
                {
                    if (online.Value < DateTime.Now.AddMinutes(-10))
                        onlineUsers.Remove(online.Key);
                }
            }

بازخوردهای پروژه‌ها
بررسی خروجی متد Find

سلام؛ لایه سرویس رو که بررسی می‌کردم از متد Find بدون بررسی نال بودن آن تقریبا در همه جا استفاده شده. یک نمونه برای مثال:

 _posts.Find(id).VisitedNumber += 1;

این مورد باید در تمام کلاس‌های لایه سرویس بررسی و اصلاح بشه. یعنی ابتدا بررسی شود اگر نال نبود آنگاه از خاصیت  شیء نهایی دریافتی استفاده شود.

مطالب
کش خروجی 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 به فرمت خاص استفاده می‌کنید، باید در آن تغییرات کوچکی را انجام دهید تا با این حالت هماهنگ شود. 
مطالب
آغاز به کار با Twitter Bootstrap در ASP.NET MVC
Twitter Bootstrap یک فریم ورک CSS بسیار محبوب سورس باز تولید برنامه‌های وب به کمک HTML، CSS و جاوا اسکریپت است. این فریم ورک حاوی بسیاری از المان‌های مورد نیاز جهت تولید وب سایت‌هایی زیبا، مانند دکمه‌ها، عناصر فرم‌ها، منوها، ویجت‌ها و غیره است. تمام این‌ها نیز همانطور که عنوان شد برمبنای HTML، CSS و جاوا اسکریپت تهیه شده‌اند؛ بنابراین در هر نوع فناوری سمت سروری مانند ASP.NET، PHP، روبی و امثال آن قابل استفاده است.


دریافت Twitter Bootstrap

محل اصلی دریافت Twitter Bootstrap، آدرس ذیل است:
البته ما از آن در اینجا به شکل خام فوق استفاده نخواهیم کرد؛ زیرا نیاز است قابلیت‌های استفاده در محیط‌های راست به چپ فارسی نیز به آن اضافه شود. برای این منظور می‌توانید از یکی از دو بسته نیوگت ذیل استفاده نمائید:


و یا حتی از منابع سایت http://rbootstrap.ir نیز می‌توان استفاده کرد.
برای نمونه دستور زیر را در کنسول پاورشل ویژوال استودیو وارد نمائید تا اسکریپت‌ها و فایل‌های CSS مورد نیاز به پروژه جاری اضافه شوند:
 PM> Install-Package Twitter.BootstrapRTL
پس از نصب، شاهد افزوده شدن چنین فایل‌هایی به یک پروژه ASP.NET MVC خواهیم بود:


در اینجا فایل‌های min، نگارش‌های فشرده شده فایل‌های js یا css هستند که با توجه به امکانات اضافه شده به ASP.NET MVC4، از آن‌ها استفاده نخواهیم کرد و برای افزودن و تعریف آن‌ها از امکانات Bundling and minification توکار فریم ورک ASP.NET MVC به نحوی که در ادامه توضیح داده خواهد شد، استفاده می‌کنیم.
فایل‌های png اضافه شده، آیکون‌های مخصوص Twitter Bootstrap هستند که اصطلاحا به آن‌ها sprite images نیز گفته می‌شود. در این نوع تصاویر، تعداد زیادی آیکون در کنار هم، برای بهینه سازی تعداد بار رفت و برگشت به سرور جهت دریافت تصاویر، طراحی شده و قرار گرفته‌اند.
فایل‌های js این مجموعه اختیاری بوده و برای استفاده از ویجت‌های Twitter Bootstrap مانند آکاردئون کاربرد دارند. این فایل‌ها برای اجرا، نیاز به jQuery خواهند داشت.


افزودن تعاریف اولیه Twitter Bootstrap به یک پروژه ASP.NET MVC

امکانات Bundling and minification در نوع پروژه‌های نسبتا جامع‌تر ASP.NET MVC به صورت پیش فرض لحاظ شده است. اما اگر یک پروژه خالی را شروع کرده‌اید، نیاز است بسته نیوگت آن‌را نیز نصب کنید:
 PM> Install-Package Microsoft.AspNet.Web.Optimization
پس از آن، کلاس کمکی BundleConfig ذیل را به پروژه جاری اضافه نمائید. به کمک آن قصد داریم امکانات Bundling and minification را فعال کرده و همچنین به آن بگوییم اسکریپت‌ها و فایل‌های CSS تعریف شده را به همان نحوی که معرفی می‌کنیم، پردازش کن (توسط کلاس سفارشی AsIsBundleOrderer).

using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Optimization;

namespace Mvc4TwitterBootStrapTest.Helper
{
    /// <summary>
    /// A custom bundle orderer (IBundleOrderer) that will ensure bundles are 
    /// included in the order you register them.
    /// </summary>
    public class AsIsBundleOrderer : IBundleOrderer
    {
        public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
        {
            return files;
        }
    }

    public static class BundleConfig
    {
        private static void addBundle(string virtualPath, bool isCss, params string[] files)
        {
            BundleTable.EnableOptimizations = true;

            var existing = BundleTable.Bundles.GetBundleFor(virtualPath);
            if (existing != null)
                return;

            Bundle newBundle;
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                newBundle = new Bundle(virtualPath);
            }
            else
            {
                newBundle = isCss ? new Bundle(virtualPath, new CssMinify()) : new Bundle(virtualPath, new JsMinify());
            }
            newBundle.Orderer = new AsIsBundleOrderer();

            foreach (var file in files)
                newBundle.Include(file);

            BundleTable.Bundles.Add(newBundle);
        }

        public static IHtmlString AddScripts(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Render(virtualPath);
        }

        public static IHtmlString AddStyles(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Render(virtualPath);
        }

        public static IHtmlString AddScriptUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, false, files);
            return Scripts.Url(virtualPath);
        }

        public static IHtmlString AddStyleUrl(string virtualPath, params string[] files)
        {
            addBundle(virtualPath, true, files);
            return Styles.Url(virtualPath);
        }
    }
}
نکته دیگری که در این کلاس سفارشی درنظر گرفته شده، عدم فعال سازی مباحث Bundling and minification در حالت Debug است. اگر در وب کانفیگ، تنظیمات پروژه را بر روی Release قرار دهید فعال خواهد شد (مناسب برای توزیع) و در حالت توسعه برنامه (حالت دیباگ)، این اسکریپت‌ها و فایل‌های CSS، قابلیت دیباگ و بررسی را نیز خواهند داشت.

پس از افزودن کلاس‌های کمکی فوق، به فایل layout پروژه مراجعه کرده و تعاریف ذیل را به ابتدای فایل اضافه نمائید:
@using Mvc4TwitterBootStrapTest.Helper
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    @BundleConfig.AddStyles("~/Content/css",
                            "~/Content/bootstrap.css",
                            "~/Content/bootstrap-responsive.css",
                            "~/Content/Site.css"
                            )
    @BundleConfig.AddScripts("~/Scripts/js",
                            "~/Scripts/jquery-1.9.1.min.js",
                            "~/Scripts/jquery.validate.min.js",
                            "~/Scripts/jquery.unobtrusive-ajax.min.js",
                            "~/Scripts/jquery.validate.unobtrusive.min.js",
                            "~/Scripts/bootstrap.min.js"
                            )
    @RenderSection("JavaScript", required: false)
</head>
و اگر برنامه را اجرا کنید، بجای حداقل 8 مدخل اشاره کننده به فایل‌های اسکریپت و CSS مورد نیاز یک برنامه ASP.NET MVC، فقط دو مدخل ذیل را مشاهده خواهید نمود:
<!DOCTYPE html>
<html>
<head>
    <title>Index</title>
    <link href="/Content/css?v=vsUQD0OJg4AJ-RZH8jSRRCu_rjl2U1nZrmSsaUyxoAc1" rel="stylesheet"/>
    <script src="/Scripts/js?v=GezdoTDiWY3acc3mI2Ujm_7nKKzh6Lu1Wr8TGyyLpW41"></script>
</head>
به این ترتیب تعداد رفت و برگشت‌های مرورگر به سرور، برای دریافت فایل‌های مورد نیاز جهت رندر صحیح یک صفحه، به شدت کاهش خواهد یافت. همچنین این فایل‌ها در حالت release فشرده نیز خواهند شد؛ به همراه تنظیم خودکار هدر کش شدن آن‌ها برای مدتی مشخص، جهت کاهش بار سرور و کاهش پهنای باند مصرفی آن.


مفاهیم پایه‌ای Twitter Bootstrap

الف) Semantic class names
به عبارتی کلاس‌های Twitter Bootstrap دارای نام‌هایی معنا دار و مفهومی می‌باشند؛ مانند کلاس‌های CSS‌ایی، به نام‌های Succes، Error، Info و امثال آن. این نام‌ها مفهومی را می‌رسانند؛ اما در مورد نحوه پیاده سازی آن‌ها جزئیاتی را بیان نمی‌کنند.
برای نمونه می‌توان کلاسی را به نام redText ایجاد کرد. هر چند این نام، توضیحاتی را در مورد علت وجودی‌اش بیان می‌کند، اما بسیار ویژه بوده و در مورد جزئیات پیاده سازی آن نیز اطلاعاتی را ارائه می‌دهد. در این حالت redText معنایی ندارد. چرا یک Text باید قرمز باشد؟ برای مثال این متن قرمز است چون مثلا شخصی، به آن رنگ ویژه علاقه دارد، یا اینکه قرمز است بخاطر نمایش خطایی در صفحه؟ به همین جهت در Twitter Bootstrap از نام‌های مفهومی یاده شده، مانند Error استفاده می‌شود. نام‌هایی معنا دار اما بدون دقیق شدن در مورد ریز جزئیات پیاده سازی آن‌ها. در این حالت می‌توان قالب جدیدی را تدارک دید و با ارائه تعاریف جدیدی برای کلاس Error و نحوه نمایش دلخواهی را به آن اعمال نمود.
یا برای نمونه نام rightside را برای نمایش ستونی در صفحه، درنظر بگیرید. این نام بسیار ویژه است؛ اما Sematic name آن می‌تواند sidebar باشد تا بدون دقیق شدن در جزئیات پیاده سازی آن، در چپ یا راست صفحه قابل اعمال باشد.
Semantic class names کلیدهایی هستند جهت استفاده مجدد از قابلیت‌های یک فریم ورک CSS.

ب) Compositional classes
اکثر کلاس‌های Twitter Bootstrap دارای محدوده کاری کوچکی هستند و به سادگی قابل ترکیب با یکدیگر جهت رسیدن به نمایی خاص می‌باشند. برای مثال به سادگی می‌توان به یک table سه ویژگی color، hover و width برگرفته شده از Twitter Bootstrap را انتساب داد و نهایتا به نتیجه دلخواه رسید؛ بدون اینکه نگران باشیم افزودن کلاس جدیدی در اینجا بر روی سایر کلاس‌های انتساب داده شده، تاثیر منفی دارد.

ج) Conventions
برای استفاده از اکثر قابلیت‌های این فریم ورک CSS یک سری قراردادهای پیش فرضی وجود دارند. برای مثال اگر از کلاس توکار pagination به همراه یک سری ul و li استفاده کنید، به صورت خودکار یک pager شکیل ظاهر خواهد شد. یا برای مثال اگر به یک html table کلاس‌های table table-striped table-hover را انتساب دهیم، به صورت خودکار قراردادهای پیش فرض table مجموعه Twitter Bootstrap به آن اعمال شده، به همراه رنگی ساختن یک درمیان زمینه ردیف‌ها و همچنین فعال سازی تغییر رنگ ردیف‌ها با حرکت ماوس از روی آن‌ها.


طرحبندی صفحات یک سایت به کمک Twitter Bootstrap

بررسی Grid layouts

Layout به معنای طرحبندی و چیدمان محتوا در یک صفحه است. یکی از متداولترین روش‌های طرحبندی صفحات چه در حالت چاپی و چه در صفحات وب، چیدمان مبتنی بر جداول و گریدها است. از این جهت که نحوه سیلان و نمایش محتوا از چپ به راست و یا راست به چپ را به سادگی میسر می‌سازند؛ به همراه اعمال حاشیه‌های مناسب جهت قسمت‌های متفاوت محتوای ارائه شده. Grid در طرحبندی، نمایش بصری نخواهد داشت اما در ساختار صفحه وجود داشته و مباحثی مانند جهت، موقعیت و یکپارچگی و یکدستی طراحی را سبب می‌شود.
به علاوه مرورگرها و مفهوم Grid نیز به خوبی با یکدیگر سازگار هستند. در دنیای HTML و ،CSS طراحی‌ها بر اساس مفهوم ساختار مستطیلی اشیاء صورت می‌گیرد:


برای نمونه در اینجا تصویر CSS Box Model را مشاهده می‌کنید. به این ترتیب، هر المان دارای محدوده‌ای مستطیلی با طول و عرض مشخص، به همراه ویژگی‌هایی مانند Margin، Border و Padding است.
در سال‌های اولیه طراحی وب، عموما کارهای طراحی صفحات به کمک HTML Tables انجام می‌شد. اما با پخته‌تر شدن CSS، استفاده از Tables برای طراحی صفحات کمتر و کمتر گشت تا اینکه نهایتا فریم ورک‌های CSS ایی پدید آمدند تا طراحی‌های مبتنی بر CSS را با ارائه گرید‌ها، ساده‌تر کنند. مانند Blue print، 960 GS و ... Twitter Bootstrap که طراحی مبتنی بر گرید‌های CSS ایی را به مجموعه قابلیت‌های دیگر خود افزوده است.


بررسی Fixed Grids

در اینجا در صفحه layout برنامه، یک Div دربرگیرنده دو Div دیگر را مشاهده می‌کنید:
<body>
    <div>
        <div>
            <h1>
                Title Title
            </h1>
            Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text
        </div>
        <div>
            @RenderBody()
        </div>
    </div>
</body>
اگر در این حالت صفحه را در مرورگر بررسی کنیم، تمام قسمت‌های نمایش داده شده به همین نحو از بالا به پایین صفحه قرار خواهند گرفت. اما قصد داریم این محتوا را، دو ستونی نمایش دهیم. Div به همراه Title در یک طرف صفحه و Div دربرگیرنده محتوای صفحات، در قسمتی دیگر.
برای اینکار در Twitter Bootstrap از کلاسی به نام row استفاده می‌شود که بیانگر یک ردیف است. این کلاس را به خارجی‌ترین Div موجود اعمال خواهیم کرد. در یک صفحه، هر تعداد row ایی را که نیاز باشد، می‌توان تعریف کرد. داخل این ردیف‌ها، امکان تعریف ستون‌های مختلف و حتی تعریف ردیف‌های تو در تو نیز وجود دارد. هر ردیف Twitter Bootstrap از 12 ستون تشکیل می‌شود و برای تعریف آن‌ها از کلاس span استفاده می‌گردد. در این حالت جمع اعداد ذکر شده پس از span باید 12 را تشکیل دهند.
<body>
    <div class="row">
        <div class="span7">
            <h1>
                Title Title
            </h1>
            Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text
        </div>
        <div  class="span5">
            @RenderBody()
        </div>
    </div>
</body>
برای نمونه در اینجا کلاس row به خارجی‌ترین Div موجود اعمال شده است. دو Div داخلی هر کدام دارای کلاس‌های span ایی جهت تشکیل ستون‌های داخل این ردیف هستند. جمع اعداد بکارگرفته شده پس از آن‌ها، عدد 12 را تشکیل می‌دهد. به این ترتیب به یک طراحی دو ستونی خواهیم رسید.


در این تصویر، قسمت RenderBody کار رندر اکشن متد Index کنترلر Home برنامه را با Viewایی معادل کدهای ذیل، انجام داده است:
@{
    ViewBag.Title = "Index";
}
<h2>
    Index</h2>
<div class="hero-unit">
    <h2>@ViewBag.Message</h2>
    <p>
        This is a template to demonstrate the way to beautify the default MVC template
        using Twitter Bootstrap website. It is pretty simple and easy.</p>
    <p>
        <a href="http://asp.net/mvc" class="btn btn-primary btn-large" style="color: White;">
            To learn more about ASP.NET MVC visit &raquo;</a></p>
</div>

درک نحوه عملکرد Grid در Twitter Bootstrap

در مثال ذیل 5 ردیف را مشاهده می‌کنید:
    <div class="row">
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
        <div class="span1">1</div>
    </div>
    <div class="row">
        <div class="span3">3</div>
        <div class="span4">4</div>
        <div class="span5">5</div>
    </div>
    <div class="row">
        <div class="span5">5</div>
        <div class="span7">7</div>
    </div>
    <div class="row">
        <div class="span3">3</div>
        <div class="span7 offset2">7 offset 2</div>
    </div>
    <div class="row">
        <div class="span12">12</div>
    </div>
در هر ردیف، امکان استفاده از حداکثر 12 ستون وجود دارد که یا می‌توان مانند ردیف اول، 12 ستون را ایجاد کرد و یا مانند ردیف دوم، سه ستون با عرض‌های متفاوت (و یا هر ترکیب دیگری که به جمع 12 برسد).
در ستون چهارم، از کلاس offset نیز استفاده شده است. این مورد سبب می‌شود ستون جاری به تعدادی که مشخص شده است به سمت چپ (با توجه به استفاده از حالت RTL در اینجا) رانده شود و سپس ترسیم گردد.
یا اینکه می‌توان مانند ردیف آخر، یک ستون را به عرض 12 که در حقیقت 940 پیکسل است، ترسیم نمود.
برای اینکه بتوانیم این گرید تشکیل شده و همچنین ستون‌ها را بهتر مشاهده کنیم، به فایل style.css سایت، تنظیم زیر را اضافه کنید:
[class*="span"]
{
background-color: lightblue;
text-align: center;
margin-top: 15px;
}


نکته جالب این گرید، Responsive یا واکنشگرا بودن آن است. در این حالت، عرض مرورگر را کم و زیاد کنید. خواهید دید که ستون‌ها در صورتیکه در عرض نمایشی جاری، قابل ارائه نباشند، به ردیف‌های بعدی منتقل خواهند شد.
البته باید دقت داشت که این گرید هیچگاه یک ستون را نخواهد شکست. برای نمونه ردیف آخر، همواره با همان عرض ثابتش نمایش داده می‌شود و با کوچکتر کردن اندازه مرورگر، یک اسکرول افقی برای نمایش محتوای آن ظاهر خواهد شد.


یک نکته
اگر نمی‌خواهید که چنین رفتار واکنش‌گرایی بروز کند، نیاز است کلیه ردیف‌ها را در div ایی با کلاسی به نام container محصور کنید.
به این ترتیب ابتدا گرید نمایش داده شده، در میانه صفحه ظاهر خواهد شد (پیشتر از سمت راست شروع شده بود). همچنین دیگر با کوچک و بزرگ شدن اندازه مرورگر، ستون‌ها به شکل یک پشته بر روی هم قرار نخواهند گرفت. (اگر پس از این تنظیم، چنین قابلیتی را مشاهده نکردید و هنوز هم طراحی، واکنشگرا بود، تعریف bootstrap-responsive.css را نیاز است برای آزمایش، از هدر صفحه حذف کنید)


بررسی Fluid Grids

به گرید قسمت قبل از این جهت Fixed Grid گفته می‌شود که عرض هر span آن با یک عدد مشخص تعیین گشته است. اما در حالت Fluid Grid، عرض هر span برحسب درصد تعیین می‌شود. بکارگیری درصد در اینجا به معنای امکان تغییر عرض یک ستون بر اساس عرض جاری Container آن است. در اینجا span12 دارای عرض 100 درصد خواهد بود.
در مثال قبل، برای استفاده از Fluid grids، تنها کافی است هرجایی کلاسی مساوی row وجود دارد، به row-fluid تغییر کند. همچنین کلاس container را به container-fluid تغییر دهید.
برای آزمایش آن، اندازه و عرض نمایشی مرورگر خود را تغییر دهید. اینبار مشاهده خواهید کرد که برخلاف حالت Fixed Grid، عرض ستون‌ها به صورت خودکار کم و زیاد می‌شوند. این مورد بر روی محتوای قرار گرفته در این ستون‌ها نیز تاثیر گذار است. برای مثال اگر یک تصویر را در حالت Fluid grid در ستونی قرار دهید، با تغییر عرض مرورگر، اندازه این تصویر نیز تغییر خواهند کرد؛ اما در حالت Fixed Grid خیر.
حالت Fluid، شیوه متداول استفاده از bootstrap در اکثر سایت‌های مهمی است که تابحال از این فریم ورک CSS استفاده کرده‌اند.



مروری بر طراحی واکنشگرا یا Responsive

این روزها تعدادی از کاربران، با استفاده از ابزارهای موبایل و تبلت‌ها از وب سایت‌ها بازدید می‌کنند. هر کدام از این‌ها نیز دارای اندازه نمایشی متفاوتی می‌باشند. بنابراین نیاز خواهد بود تا حالت بهینه‌ای را جهت اینگونه وسایل نیز طراحی نمود. حالت بهینه در اینجا به معنای قابل خواندن بودن متون، امکان استفاده از لینک‌های ورود به صفحات مختلف و همچنین حذف اسکرول و مباحث زوم جهت مشاهده صفحات است.
یکی از ویژگی‌های CSS به نام media چنین قابلیتی را فراهم می‌سازد. برای نمونه قسمتی از فایل bootstrap-responsive.css دارای چنین تعاریفی است:
@media (min-width: 768px) and (max-width: 979px) {
    .hidden-desktop {
        display: inherit !important;
    }
توسط این تنظیمات، شیوه نامه تعیین شده، تنها به صفحاتی با اندازه‌هایی بین 768 تا 979 پیکسل اعمال می‌شود (تعدادی از تبلت‌ها برای نمونه).
Bootstrap برای مدیریت اندازه‌های مختلف وسایل موبایل، شیوه‌نامه‌های خاصی را تدارک دیده است که از اندازه px480 و یا کمتر، تا px1200 و یا بیشتر را پوشش می‌دهد.
به این ترتیب با اندازه px940 که پیشتر در مورد آن بحث شد، اندازه span12 به صورت خودکار به اندازه‌های متناسب با صفحات نمایشی کوچکتر تنظیم می‌گردد. همچنین برای اندازه‌های صفحات کوچکتر از 768px به صورت خودکار از Fluid columns استفاده می‌گردد.
تنها کاری که برای اعمال این قابلیت باید صورت گیرد، افزودن تعاریف فایل bootstrap-responsive.css به هدر صفحه است که در قسمت قبل انجام گردید. این فایل باید پس از فایل اصلی bootstrap.css اضافه شود.


کلاس‌های کمکی طراحی واکنشگرا

Bootstrap شامل تعدادی کلاس کمکی در فایل bootstrap-responsive.css خود می‌باشد شامل visible-phone، visible-tablet و visible-desktop به علاوه hidden-phone، hidden-tablet و hidden-desktop. به این ترتیب می‌توان محتوای خاصی را جهت وسایل ویژه‌ای نمایان یا مخفی ساخت.
برای مثال محتوای مشخص شده با کلاس hidden-desktop، در اندازه وسایل دسکتاپ نمایش داده نخواهد شد.
برای آزمایش آن، شش div را با کلاس‌های یاد شده و محتوایی دلخواه ایجاد کرده و سپس اندازه عرض مرورگر را تغییر دهید تا بهتر بتوان مخفی یا نمایان ساختن محتوا را بر اساس اندازه صفحه جاری درک کرد.
یکی از کاربردهای این قابلیت، قرار دادن تبلیغاتی با اندازه‌های تصاویری مشخص برای وسایل مختلف است؛ بجای اینکه منتظر شویم تا Fluid layout اندازه تصاویر را به صورت خودکار کوچک یا بزرگ کند، که الزاما بهترین کیفیت را حاصل نخواهد ساخت.
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span4">
                <div class="visible-phone">
                    visible-phone</div>
            </div>
            <div class="span4">
                <div class="visible-tablet">
                    visible-tablet</div>
            </div>
            <div class="span4">
                <div class="visible-desktop">
                    visible-desktop</div>
            </div>
        </div>
    </div>
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span4">
                <div class="hidden-phone">
                    hidden-phone</div>
            </div>
            <div class="span4">
                <div class="hidden-tablet">
                    hidden-tablet</div>
            </div>
            <div class="span4">
                <div class="hidden-desktop">
                    hidden-desktop</div>
            </div>
        </div>
    </div>