نظرات اشتراک‌ها
evernote – مرتب‌سازی مبتنی بر برچسب
سلام؛ من هنوز هم از onenote برای کارهام استفاده می‌کنم. همیشه کنار دستم باز است! برای یادداشت برداری تا اختصاص یک قسمت به یک پروژه برای نوشتن ایده‌ها، تهیه بک لاگ و غیره. تا ذخیره سازی صفحات وب با تمام جزئیات آن‌ها.
مطلب بالا رو هم دیدم به نظرم جالب اومد گفتم اینجا مطرح بشه. خیلی از قابلیت‌ها یکی هست و در onenote ساده‌تر و طراحی بهتری داره. نگارش اخیر اون هم مصرف حافظه خیلی کمتری از قبلی‌ها داره. خیلی روان‌تر شده. دریافت اطلاعاتش از وب async شده و برنامه قفل نمی‌کنه.
مطالب
خلاصه‌ای در مورد وضعیت فعلی MySQL

MySQL مدتی است که جزو یکی از محصولات شرکت اوراکل محسوب شده و توسعه دهندگان تجاری باید برای استفاده از آن هزینه کنند. این هزینه نیز اخیرا افزایش یافته و به حداقل 2000 دلار به ازای هر سرور رسیده است (+). این عدد واقعا رقم بالایی برای محصولی محسوب می‌شود که بسیاری از توسعه دهنده‌ها تصور می‌کنند رایگان است. استفاده از این محصول با توجه به مدل تجاری جدید آن فقط در پروژه‌های سورس باز رایگان است (بله فقط در پروژه‌هایی که با مجوز GPL منتشر شوند) و اگر شما یک سیستم تجاری کلاینت سرور را بر این اساس طراحی کنید حتما باید هزینه‌های مرتبط را نیز پرداخت نمائید (+).
توضیحی در مورد GPL و MySQL
MySQL AB offers a commercial license for organizations that do not want to release the source code for their application.
The change from the LGPL to the GPL for the client libraries was made in 2001 during the development of MySQL 4.0 to help MySQL AB more easily differentiate between a proprietary user who should buy a commercial license and a free software user who should use the GPL license.

MySQL با توجه به مجوز GPL آن در شرایط زیر رایگان خواهد بود:
- قصد توزیع مجدد آن‌را نداشته باشید.
- همچنین برنامه‌ی شما نیز به صورت سورس باز تحت مجوز GPL ارائه گردد.
و تنها زمانی در مورد MySQL باید هزینه کنید که:
-قصد توزیع مجدد آن‌را داشته باشید.
-برنامه‌ی شما سورس باز نبوده و قصد ندارید آن‌را تحت مجوز GPL ارائه دهید. (که عموما در مورد برنامه‌های تجاری به همین صورت است)

نکته‌ی دیگری را که باید به آن دقت داشت این است که برای واگذاری MySQL به شرکت اوراکل،‌ اتحادیه اروپا نیز با توجه به وجود بیش از 50 هزار توسعه دهنده‌ی اروپایی که از MySQL استفاده می‌کنند، شرکت اوراکل را موظف کرده است تا این dual licensing (تجاری و سورس باز) را تا سال 2015 حفظ کرده و ادامه دهد (+). به این معنا که شرکت اوراکل پس از سال 2015 هیچگونه تعهدی به ارائه‌ی نگارش سورس باز این محصول به هیچ نهاد و یا سازمانی ندارد.
البته این‌ها به معنای پایان دنیا نیست. هم اکنون چهار fork سورس باز از این محصول وجود دارند (Drizzle ، MariaDB ، OurDelta و Percona Server) ولی تنها آینده است که میزان موفقیت، پایداری و تداوم آن‌ها را مشخص خواهد کرد.

نظرات مطالب
آشنایی با Refactoring - قسمت 9
من تا حد امکان هیچ نوع استثنایی رو مدیریت نمی‌کنم. استثناء یعنی مشکل و باید کاربر با کرش برنامه متوجه آن بشود. فقط برای لاگ کردن خطاهای برنامه‌های ASP.NET از ELMAH استفاده می‌کنم به علاوه تنظیم نمایش صفحه خطای عمومی. بیشتر از این نیازی نیست کاری انجام شود. اولین اصل مدیریت خطاها، عدم مدیریت آن‌ها است.

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها
- سطوح بالا و پایین در اینجا بر اساس رابطه‌ی  Presentation Layer-->BLL-->DAL-->Database مشخص می‌شوند (پایین‌ترین سطح بانک اطلاعاتی است و بالاترین سطح، همانی‌است که کاربر با آن تعامل دارد یا لایه‌ی نمایشی). در اینجا DAL بر اساس اینترفیس IUnitOfWork، امکان دسترسی به بانک اطلاعاتی را به صورت کنترل شده، در اختیار BLL (یا همان لایه سرویس در اینجا) قرار می‌دهد (مانند همان IUnitOfWork‌های تزریق شده‌ی در سازنده‌ی کلاس‌های لایه سرویس). سپس BLL هم منطق تجاری تعریف شده‌ی در آن‌را توسط اینترفیس‌هایی در اختیار بالاترین سطح سیستم که لایه نمایش هست (کنترلرها و قسمت MVC)  قرار خواهد داد. پیاده سازی‌های این‌ها هم توسط سیستم تزریق وابستگی‌ها تامین می‌شود و عموما اینترفیس‌های هر لایه هم در همان لایه تعریف می‌شوند و سپس در اختیار لایه‌های بالاتر قرار می‌گیرند. یعنی لایه‌های مختلف از جزئیات پیاده سازی‌های یک‌دیگر مطلع نیستند و فقط با اینترفیس‌ها کار می‌کنند که سهولت نوشتن آزمون‌های واحد را سبب خواهند شد و همچنین امکان تعویض ساده‌تر پیاده سازی‌ها در صورت نیاز، بدون نیاز به تغییرات جزئیات کدنویسی لایه‌ای خاص (اصل باز بودن برای توسعه و بسته بودن برای تغییر).
- همچنین از آنجائیکه ما در یک دنیای محض زندگی نمی‌کنیم، نیاز خواهید داشت از
IUnitOfWork گاهی از اوقات در لایه نمایشی هم استفاده کنید تا بتوانید یک تراکنش حاصل از چند موجودیت و چند کلاس لایه سرویس را در پایان کار درخواست رسیده (فقط یک تراکنش به ازای کل تعاملات یک درخواست)، به بانک اطلاعاتی اعمال کنید. بنابراین internal تعریف کردن آن در دنیای واقعی میسر نیست. حتی برای تعریف سیم‌کشی‌های تزریق وابستگی‌های اولیه‌ی آن هم نباید internal تعریف شود. همچنین باز هم یک برنامه‌ی واقعی الزاما تمام نیازهای تجاری‌اش در لایه سرویس قرار نمی‌گیرد. مثلا نیاز خواهید داشت با میان‌افزارها، اکشن فیلترها و غیره هم کار کنید که این‌ها محل قرارگیری استاندارد و مشخصی ندارند و هر جائی قابل تعریف هستند.
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت ششم - کار با User Claims
از Claims جهت ارائه‌ی اطلاعات مرتبط با هویت هر کاربر و همچنین Authorization استفاده می‌شود. برای مثال درنگارش‌های قبلی ASP.NET، مفاهیمی مانند «نقش‌ها» وجود دارند. در نگارش‌های جدیدتر آن، «نقش‌ها» تنها یکی از انواع «User Claims» هستند. در قسمت‌های قبل روش تعریف این Claims را در IDP و همچنین تنظیمات مورد نیاز جهت دریافت آن‌ها را در سمت برنامه‌ی Mvc Client بررسی کردیم. در اینجا مطالب تکمیلی کار با User Claims را مرور خواهیم کرد.


بررسی Claims Transformations

می‌خواهیم Claims بازگشت داده شده‌ی توسط IDP را به یکسری Claims که کار کردن با آن‌ها در برنامه‌ی MVC Client ساده‌تر است، تبدیل کنیم.
زمانیکه اطلاعات Claim، توسط میان‌افزار oidc دریافت می‌شود، ابتدا بررسی می‌شود که آیا دیکشنری نگاشت‌ها وجود دارد یا خیر؟ اگر بله، کار نگاشت‌ها از یک claim type به claim type دیگر انجام می‌شود.
برای مثال لیست claims اصلی بازگشت داده شده‌ی توسط IDP، پس از تبدیلات و نگاشت‌های آن در برنامه‌ی کلاینت، یک چنین شکلی را پیدا می‌کند:
Claim type: sid - Claim value: f3940d6e58cbb576669ee49c90e22cb1
Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7
Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local
Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd
Claim type: given_name - Claim value: Vahid
Claim type: family_name - Claim value: N
در ادامه می‌خواهیم نوع‌های آن‌ها را ساده‌تر کنیم و آن‌ها را دقیقا تبدیل به همان claim typeهایی کنیم که در سمت IDP تنظیم شده‌اند. برای این منظور به فایل src\MvcClient\ImageGallery.MvcClient.WebApp\Startup.cs مراجعه کرده و سازنده‌ی آن‌را به صورت زیر تغییر می‌دهیم:
namespace ImageGallery.MvcClient.WebApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        }
در اینجا جدول نگاشت‌های پیش‌فرض Claims بازگشت داده شده‌ی توسط IDP به Claims سمت کلاینت، پاک می‌شود.
در ادامه اگر مجددا لیست claims را پس از logout و login، بررسی کنیم، به این صورت تبدیل شده‌است:
• Claim type: sid - Claim value: 91f5a09da5cdbbe18762526da1b996fb
• Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7
• Claim type: idp - Claim value: local
• Claim type: given_name - Claim value: Vahid
• Claim type: family_name - Claim value: N
اکنون این نوع‌ها دقیقا با آن‌چیزی که IDP ارائه می‌دهد، تطابق دارند.


کار با مجموعه‌ی User Claims

تا اینجا لیست this.User.Claims، به همراه تعدادی Claims است که به آن‌ها نیازی نداریم؛ مانند sid که بیانگر session id در سمت IDP است و یا idp به معنای identity provider می‌باشد. حذف آن‌ها حجم کوکی نگهداری کننده‌ی آن‌ها را کاهش می‌دهد. همچنین می‌خواهیم تعدادی دیگر را نیز به آن‌ها اضافه کنیم.
علاوه بر این‌ها میان‌افزار oidc، یکسری از claims دریافتی را راسا فیلتر و حذف می‌کند؛ مانند زمان منقضی شدن توکن و امثال آن که در عمل واقعا به تعدادی از آن‌ها نیازی نداریم. اما می‌توان این سطح تصمیم گیری فیلتر claims رسیده را نیز کنترل کرد. در تنظیمات متد AddOpenIdConnect، خاصیت options.ClaimActions نیز وجود دارد که توسط آن می‌توان بر روی حذف و یا افزوده شدن Claims، کنترل بیشتری را اعمال کرد:
namespace ImageGallery.MvcClient.WebApp
{
        public void ConfigureServices(IServiceCollection services)
        {
// ...
              .AddOpenIdConnect("oidc", options =>
              {
  // ...
                  options.ClaimActions.Remove("amr");
                  options.ClaimActions.DeleteClaim("sid");
                  options.ClaimActions.DeleteClaim("idp");
              });
        }
در اینجا فراخوانی متد Remove، به معنای حذف فیلتری است که کار حذف کردن خودکار claim ویژه‌ی amr را که بیانگر نوع authentication است، انجام می‌دهد (متد Remove در اینجا یعنی مطمئن شویم که amr در لیست claims باقی می‌ماند). همچنین فراخوانی متد DeleteClaim، به معنای حذف کامل یک claim موجود است.

اکنون اگر پس از logout و login، لیست this.User.Claims را بررسی کنیم، دیگر خبری از sid و idp در آن نیست. همچنین claim از نوع amr نیز به صورت پیش‌فرض حذف نشده‌است:
• Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7
• Claim type: amr - Claim value: pwd
• Claim type: given_name - Claim value: Vahid
• Claim type: family_name - Claim value: N

افزودن Claim جدید آدرس کاربر

برای افزودن Claim جدید آدرس کاربر، به کلاس src\IDP\DNT.IDP\Config.cs مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        // identity-related resources (scopes)
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Address()
            };
        }
در اینجا در لیست Resources، گزینه‌ی IdentityResources.Address نیز اضافه شده‌است که به Claim آدرس مرتبط است.
همین مورد را به لیست AllowedScopes متد GetClients نیز اضافه می‌کنیم:
AllowedScopes =
{
    IdentityServerConstants.StandardScopes.OpenId,
    IdentityServerConstants.StandardScopes.Profile,
    IdentityServerConstants.StandardScopes.Address
},
در آخر Claim متناظر با Address را نیز به اطلاعات هر کاربر، در متد GetUsers، اضافه می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
// ...
                    Claims = new List<Claim>
                    {
// ...
                        new Claim("address", "Main Road 1")
                    }
                },
                new TestUser
                {
// ...
                    Claims = new List<Claim>
                    {
// ...
                        new Claim("address", "Big Street 2")
                    }
                }
            };
        }
تا اینجا تنظیمات IDP برای افزودن Claim جدید آدرس به پایان می‌رسد.
پس از آن به کلاس ImageGallery.MvcClient.WebApp\Startup.cs مراجعه می‌کنیم تا درخواست این claim را به لیست scopes میان‌افزار oidc اضافه کنیم:
namespace ImageGallery.MvcClient.WebApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
// ...
              .AddOpenIdConnect("oidc", options =>
              {
   // ...
                  options.Scope.Add("address");
   // …
   options.ClaimActions.DeleteClaim("address");
              });
        }
همچنین می‌خواهیم مطمئن شویم که این claim درون claims identity قرار نمی‌گیرد. به همین جهت DeleteClaim در اینجا فراخوانی شده‌است تا این Claim به کوکی نهایی اضافه نشود تا بتوانیم همواره آخرین مقدار به روز شده‌ی آن‌را از UserInfo Endpoint دریافت کنیم.

یک نکته: فراخوانی DeleteClaim بر روی address غیر ضروری است و می‌شود این سطر را حذف کرد. از این جهت که اگر به سورس OpenID Connect Options مایکروسافت مراجعه کنیم، مشاهده خواهیم کرد که میان‌افزار اعتبارسنجی استاندارد ASP.NET Core، تنها تعداد معدودی از claims را نگاشت می‌کند. به این معنا که هر claim ای که در token وجود داشته باشد، اما اینجا نگاشت نشده باشد، در claims نهایی حضور نخواهند داشت و address claim یکی از این‌ها نیست. بنابراین در لیست نهایی this.User.Claims حضور نخواهد داشت؛ مگر اینکه مطابق همین سورس، با استفاده از متد options.ClaimActions.MapUniqueJsonKey، یک نگاشت جدید را برای آن تهیه کنیم و البته چون نمی‌خواهیم آدرس در لیست claims وجود داشته باشد، این نگاشت را تعریف نخواهیم کرد.


دریافت اطلاعات بیشتری از کاربران از طریق UserInfo Endpoint

همانطور که در قسمت قبل با بررسی «تنظیمات بازگشت Claims کاربر به برنامه‌ی کلاینت» عنوان شد، میان‌افزار oidc با UserInfo Endpoint کار می‌کند که تمام عملیات آن خودکار است. در اینجا امکان کار با آن از طریق برنامه نویسی مستقیم نیز جهت دریافت اطلاعات بیشتری از کاربران، وجود دارد. برای مثال شاید به دلایل امنیتی نخواهیم آدرس کاربر را در لیست Claims او قرار دهیم. این مورد سبب کوچک‌تر شدن کوکی متناظر با این اطلاعات و همچنین دسترسی به اطلاعات به روزتری از کاربر می‌شود.
درخواستی که به سمت UserInfo Endpoint ارسال می‌شود، باید یک چنین فرمتی را داشته باشد:
GET idphostaddress/connect/userinfo
Authorization: Bearer R9aty5OPlk
یک درخواست از نوع GET و یا POST است که به سمت آدرس UserInfo Endpoint ارسال می‌شود. در اینجا ذکر Access token نیز ضروری است. از این جهت که بر اساس scopes ذکر شده‌ی در آن، لیست claims درخواستی مشخص شده و بازگشت داده می‌شوند.

اکنون برای دریافت دستی اطلاعات آدرس از IDP و UserInfo Endpoint آن، ابتدا نیاز است بسته‌ی نیوگت IdentityModel را به پروژه‌ی Mvc Client اضافه کنیم:
dotnet add package IdentityModel
توسط این بسته می‌توان به DiscoveryClient دسترسی یافت و به کمک آن آدرس UserInfo Endpoint را استخراج می‌کنیم:
namespace ImageGallery.MvcClient.WebApp.Controllers
{
    [Authorize]
    public class GalleryController : Controller
    {
        public async Task<IActionResult> OrderFrame()
        {
            var discoveryClient = new DiscoveryClient(_configuration["IDPBaseAddress"]);
            var metaDataResponse = await discoveryClient.GetAsync();

            var userInfoClient = new UserInfoClient(metaDataResponse.UserInfoEndpoint);

            var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            var response = await userInfoClient.GetAsync(accessToken);
            if (response.IsError)
            {
                throw new Exception("Problem accessing the UserInfo endpoint.", response.Exception);
            }

            var address = response.Claims.FirstOrDefault(c => c.Type == "address")?.Value;
            return View(new OrderFrameViewModel(address));
        }
در اینجا یک اکشن متد جدید را جهت سفارش نگارش قاب شده‌ی تصاویر گالری اضافه کرده‌ایم. کار این اکشن متد، دریافت آخرین آدرس شخص از IDP و سپس ارسال آن به View متناظر است. برای این منظور ابتدا یک DiscoveryClient را بر اساس آدرس IDP تشکیل داده‌ایم. حاصل آن متادیتای این IDP است که یکی از مشخصات آن UserInfoEndpoint می‌باشد. سپس بر این اساس، UserInfoClient را تشکیل داده و Access token جاری را به سمت این endpoint ارسال می‌کنیم. حاصل آن، آخرین Claims کاربر است که مستقیما از IDP دریافت شده و از کوکی جاری کاربر خوانده نمی‌شود. سپس از این لیست، مقدار Claim متناظر با address را استخراج کرده و آن‌را به View ارسال می‌کنیم.
OrderFrameViewModel ارسالی به View، یک چنین شکلی را دارد:
namespace ImageGallery.MvcClient.ViewModels
{
    public class OrderFrameViewModel
    {
        public string Address { get; } = string.Empty;

        public OrderFrameViewModel(string address)
        {
            Address = address;
        }
    }
}
و View متناظر با این اکشن متد در آدرس Views\Gallery\OrderFrame.cshtml به صورت زیر تعریف شده‌است که آدرس شخص را نمایش می‌دهد:
@model ImageGallery.MvcClient.ViewModels.OrderFrameViewModel
<div class="container">
    <div class="h3 bottomMarginDefault">Order a framed version of your favorite picture.</div>
    <div class="text bottomMarginSmall">We've got this address on record for you:</div>
    <div class="text text-info bottomMarginSmall">@Model.Address</div>
    <div class="text">If this isn't correct, please contact us.</div>
</div>

سپس به Shared\_Layout.cshtml مراجعه کرده و لینکی را به این اکشن متد و View، اضافه می‌کنیم:
<li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li>

اکنون اگر برنامه را اجرا کنیم، پس از login، یک چنین خروجی قابل مشاهده است:


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


بررسی Authorization مبتنی بر نقش‌ها

تا اینجا مرحله‌ی Authentication را که مشخص می‌کند کاربر وار شده‌ی به سیستم کیست، بررسی کردیم که اطلاعات آن از Identity token دریافتی از IDP استخراج می‌شود. مرحله‌ی پس از ورود به سیستم، مشخص کردن سطوح دسترسی کاربر و تعیین این مورد است که کاربر مجاز به انجام چه کارهایی می‌باشد. به این مرحله Authorization می‌گویند و روش‌های مختلفی برای مدیریت آن وجود دارد:
الف) RBAC و یا Role-based Authorization و یا تعیین سطوح دسترسی بر اساس نقش‌های کاربر
در این حالت claim ویژه‌ی role، از IDP دریافت شده و توسط آن یکسری سطوح دسترسی کاربر مشخص می‌شوند. برای مثال کاربر وارد شده‌ی به سیستم می‌تواند تصویری را اضافه کند و یا آیا مجاز است نگارش قاب شده‌ی تصویری را درخواست دهد؟
ب) ABAC و یا Attribute based access control روش دیگر مدیریت سطوح دسترسی است و عموما آن‌را نسبت به حالت الف ترجیح می‌دهند که آن‌را در قسمت‌های بعدی بررسی خواهیم کرد.

در اینجا روش «تعیین سطوح دسترسی بر اساس نقش‌های کاربر» را بررسی می‌کنیم. برای این منظور به تنظیمات IDP در فایل src\IDP\DNT.IDP\Config.cs مراجعه کرده و claims جدیدی را تعریف می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
//...
                    Claims = new List<Claim>
                    {
//...
                        new Claim("role", "PayingUser")
                    }
                },
                new TestUser
                {
//...
                    Claims = new List<Claim>
                    {
//...
                        new Claim("role", "FreeUser")
                    }
                }
            };
        }
در اینجا دو نقش FreeUser و PayingUser به صورت claimهایی جدید به دو کاربر IDP اضافه شده‌اند.

سپس باید برای این claim جدید یک scope جدید را نیز به قسمت GetIdentityResources اضافه کنیم تا توسط client قابل دریافت شود:
namespace DNT.IDP
{
    public static class Config
    {
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                // ...
                new IdentityResource(
                    name: "roles",
                    displayName: "Your role(s)",
                    claimTypes: new List<string>() { "role" })
            };
        }
چون roles یکی از scopeهای استاندارد نیست، آن‌را به صورت یک IdentityResource سفارشی تعریف کرده‌ایم. زمانیکه scope ای به نام roles درخواست می‌شود، لیستی از نوع claimTypes بازگشت داده خواهد شد.

همچنین باید به کلاینت مجوز درخواست این scope را نیز بدهیم. به همین جهت آن‌را به AllowedScopes مشخصات Client نیز اضافه می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
// ...
                    AllowedScopes =
                    {
// ...
                        "roles"
                    }
// ...
                }
             };
        }

در ادامه قرار است تنها کاربری که دارای نقش PayingUser است،  امکان دسترسی به سفارش نگارش قاب شده‌ی تصاویر را داشته باشد. به همین جهت به کلاس ImageGallery.MvcClient.WebApp\Startup.cs برنامه‌ی کلاینت مراجعه کرده و درخواست scope نقش‌های کاربر را به متد تنظیمات AddOpenIdConnect اضافه می‌کنیم:
options.Scope.Add("roles");

برای آزمایش آن، یکبار از برنامه خارج شده و مجددا به آن وارد شوید. اینبار در صفحه‌ی consent، از کاربر مجوز دسترسی به نقش‌های او نیز سؤال پرسیده می‌شود:


اما اگر به لیست موجود در this.User.Claims در برنامه‌ی کلاینت مراجعه کنیم، نقش او را مشاهده نخواهیم کرد و به این لیست اضافه نشده‌است:
• Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7
• Claim type: amr - Claim value: pwd
• Claim type: given_name - Claim value: Vahid
• Claim type: family_name - Claim value: N

همانطور که در نکته‌ای پیشتر نیز ذکر شد، چون role جزو لیست نگاشت‌های OpenID Connect Options مایکروسافت نیست، آن‌را به صورت خودکار به لیست claims اضافه نمی‌کند؛ دقیقا مانند آدرسی که بررسی کردیم. برای رفع این مشکل و افزودن نگاشت آن در متد تنظیمات AddOpenIdConnect، می‌توان از متد MapUniqueJsonKey به صورت زیر استفاده کرد:
options.ClaimActions.MapUniqueJsonKey(claimType: "role", jsonKey: "role");
برای آزمایش آن، یکبار از برنامه خارج شده و مجددا به آن وارد شوید. اینبار لیست this.User.Claims به صورت زیر تغییر کرده‌است که شامل role نیز می‌باشد:
• Claim type: sub - Claim value: d860efca-22d9-47fd-8249-791ba61b07c7
• Claim type: amr - Claim value: pwd
• Claim type: given_name - Claim value: Vahid
• Claim type: family_name - Claim value: N
• Claim type: role - Claim value: PayingUser


استفاده از نقش تعریف شده جهت محدود کردن دسترسی به سفارش تصاویر قاب شده

در ادامه قصد داریم لینک درخواست تصاویر قاب شده را فقط برای کاربرانی که دارای نقش PayingUser هستند، نمایش دهیم. به همین جهت به فایل Views\Shared\_Layout.cshtml مراجعه کرده و آن‌را به صورت زیر تغییر می‌دهیم:
@if(User.IsInRole("PayingUser"))
{
   <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li>
}
در این حالت از متد استاندارد User.IsInRole برای بررسی نقش کاربر جاری استفاده شده‌است. اما این متد هنوز نمی‌داند که نقش‌ها را باید از کجا دریافت کند و اگر آن‌را آزمایش کنید، کار نمی‌کند. برای تکمیل آن به فایل ImageGallery.MvcClient.WebApp\Startup.cs مراجعه کرده تنظیمات متد AddOpenIdConnect را به صورت زیر تغییر می‌دهیم:
options.TokenValidationParameters = new TokenValidationParameters
{
    NameClaimType = JwtClaimTypes.GivenName,
    RoleClaimType = JwtClaimTypes.Role,
};
TokenValidationParameters نحوه‌ی اعتبارسنجی توکن دریافتی از IDP را مشخص می‌کند. همچنین مشخص می‌کند که چه نوع claim ای از token دریافتی  از IDP به RoleClaimType سمت ASP.NET Core نگاشت شود.
اکنون برای آزمایش آن یکبار از سیستم خارج شده و مجددا به آن وارد شوید. پس از آن لینک درخواست نگارش قاب شده‌ی تصاویر، برای کاربر User 1 نمایان خواهد بود و نه برای User 2 که FreeUser است.

البته هرچند تا این لحظه لینک نمایش View متناظر با اکشن متد OrderFrame را امن کرده‌ایم، اما هنوز خود این اکشن متد به صورت مستقیم با وارد کردن آدرس https://localhost:5001/Gallery/OrderFrame در مرورگر قابل دسترسی است. برای رفع این مشکل به کنترلر گالری مراجعه کرده و دسترسی به اکشن متد OrderFrame را توسط فیلتر Authorize و با مقدار دهی خاصیت Roles آن محدود می‌کنیم:
namespace ImageGallery.MvcClient.WebApp.Controllers
{
    [Authorize]
    public class GalleryController : Controller
    {
        [Authorize(Roles = "PayingUser")]
        public async Task<IActionResult> OrderFrame()
        {
خاصیت Roles فیلتر Authorize، می‌تواند چندین نقش جدا شده‌ی توسط کاما را نیز دریافت کند.
برای آزمایش آن، توسط مشخصات کاربر User 2 به سیستم وارد شده و آدرس https://localhost:5001/Gallery/OrderFrame را مستقیما در مرورگر وارد کنید. در این حالت یک چنین تصویری نمایان خواهد شد:


همانطور که مشاهده می‌کنید، علاوه بر عدم دسترسی به این اکشن متد، به صفحه‌ی Account/AccessDenied که هنوز در برنامه‌ی کلاینت تعریف نشده‌است، هدایت شده‌ایم. به همین جهت خطای 404 و یا یافت نشد، نمایش داده شده‌است.
برای تغییر مقدار پیش‌فرض صفحه‌ی عدم دسترسی، ابتدا Controllers\AuthorizationController.cs را با این محتوا ایجاد می‌کنیم:
using Microsoft.AspNetCore.Mvc;

public class AuthorizationController : Controller
{
    public IActionResult AccessDenied()
    {
        return View();
    }
}
سپس View آن‌را در فایل Views\Authorization\AccessDenied.cshtml به صورت زیر تکمیل خواهیم کرد که لینک به logout نیز در آن وجود دارد:
<div class="container">
    <div class="h3">Woops, looks like you're not authorized to view this page.</div>
    <div>Would you prefer to <a asp-controller="Gallery" asp-action="Logout">log in as someone else</a>?</div>
</div>

اکنون نیاز است تا این آدرس جدید را به کلاس ImageGallery.MvcClient.WebApp\Startup.cs معرفی کنیم.
namespace ImageGallery.MvcClient.WebApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
            {
                // ...
            }).AddCookie("Cookies", options =>
              {
                  options.AccessDeniedPath = "/Authorization/AccessDenied";
              })
// ...
آدرس جدید Authorization/AccessDenied باید به تنظیمات AddCookie اضافه شود تا توسط سیستم شناخته شده و مورد استفاده قرار گیرد. علت اینجا است که role claims، از کوکی رمزنگاری شده‌ی برنامه استخراج می‌شوند. به همین جهت تنظیم آن مرتبط است به AddCookie.
برای آزمایش آن، یکبار از برنامه خارج شده و مجددا با اکانت User 2 به آن وارد شوید و آدرس https://localhost:5001/Gallery/OrderFrame را مستقیما در مرورگر وارد کنید. اینبار تصویر زیر که همان آدرس جدید تنظیم شده‌است نمایش داده خواهد شد:




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
مطالب دوره‌ها
لغو Lazy Loading در حین کار با AutoMapper و Entity Framework
پیشنیازها
- مطالعه‌ی مطالب گروه AutoMapper در سایت، دید خوبی را برای شروع به کار با آن فراهم می‌کنند و در اینجا قصد تکرار این مباحث پایه‌ای را نخواهیم داشت. هدف بیشتر بررسی یک سری نکات پیشرفته‌تر و عمیق‌تر است از کار با AutoMapper.
- آشنایی با Lazy loading و Eager loading در حین کار با EF


ساختار و پیشنیازهای برنامه‌ی مطلب جاری

جهت سهولت پیگیری مطلب و تمرکز بیشتر بر روی مفاهیم اصلی مورد بحث، یک برنامه‌ی کنسول را آغاز کرده و سپس بسته‌های نیوگت ذیل را به آن اضافه کنید:
PM> install-package AutoMapper
PM> install-package EntityFramework
به این ترتیب بسته‌های AutoMapper و EF به پروژه‌ی جاری اضافه خواهند شد.


آشنایی با ساختار مدل‌های برنامه

در اینجا ساختار جداول مطالب یک بلاگ را به همراه نویسندگان آن‌ها، مشاهده می‌کنید:
public class BlogPost
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
 
    [ForeignKey("UserId")]
    public virtual User User { get; set; }
    public int UserId { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
 
    public virtual ICollection<BlogPost> BlogPosts { get; set; }
}
هر کاربر می‌تواند تعدادی مطلب تهیه کند و هر مطلب توسط یک کاربر نوشته شده‌است.


هدف از این مثال

فرض کنید اطلاعاتی که قرار است به کاربر نمایش داده شوند، توسط ViewModel ذیل تهیه می‌شود:
public class UserViewModel
{
    public int Id { set; get; }
    public string Name { set; get; }
 
    public ICollection<BlogPost> BlogPosts { get; set; }
}
در اینجا می‌خواهیم اولین کاربر ثبت شده را یافته و سپس لیست مطالب آن‌را نمایش دهیم. همچنین می‌خواهیم این کوئری تهیه شده به صورت خودکار اطلاعاتش را بر اساس ساختار ViewModel ایی که مشخص کردیم (و این ViewModel الزاما تمام عناصر آن با عناصر مدل اصلی یکی نیست)، بازگشت دهیم.


تهیه نگاشت‌های AutoMapper

برای مدیریت بهتر نگاشت‌های AutoMapper توصیه شده‌است که کلاس‌های Profile ایی را به شکل ذیل تهیه کنیم:
public class TestProfile : Profile
{
    protected override void Configure()
    {
        this.CreateMap<User, UserViewModel>();
    }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
کار با ارث بری از کلاس پایه Profile کتابخانه‌ی AutoMapper شروع می‌شود. سپس باید متد Configure آن‌را بازنویسی کنیم. در اینجا می‌توان با استفاده از متدی مانند Create مشخص کنیم که قرار است اطلاعاتی با ساختار شیء User، به اطلاعاتی با ساختار از نوع شیء UserViewModel به صورت خودکار نگاشت شوند.


ثبت و معرفی پروفایل‌های AutoMapper

پس از تهیه‌ی پروفایل مورد نیاز، در ابتدای برنامه با استفاده از متد Mapper.Initialize، کار ثبت این تنظیمات صورت خواهد گرفت:
Mapper.Initialize(cfg => // In Application_Start()
{
    cfg.AddProfile<TestProfile>();
});


روش متداول کار با AutoMapper جهت نگاشت اطلاعات User به ViewModel آن

در ادامه به نحو متداولی، ابتدا اولین کاربر ثبت شده را یافته و سپس با استفاده از متد Mapper.Map اطلاعات این شیء user به ViewModel آن نگاشت می‌شود:
using (var context = new MyContext())
{
    var user1 = context.Users.FirstOrDefault();
    if (user1 != null)
    {
        var uiUser = new UserViewModel();
        Mapper.Map(source: user1, destination: uiUser);
 
        Console.WriteLine(uiUser.Name);
        foreach (var post in uiUser.BlogPosts)
        {
            Console.WriteLine(post.Title);
        }
    }
}
تا اینجا اگر برنامه را اجرا کنید، مشکلی را مشاهده نخواهید کرد، اما این کدها سبب اجرای حداقل دو کوئری خواهند شد:
الف) یافتن اولین کاربر
ب) واکشی لیست مطالب او در یک کوئری دیگر


کاهش تعداد رفت و برگشت‌ها به سرور با استفاده از متدهای ویژه‌ی AutoMapper

در حالت متداول کار با EF، با استفاده از متد Include می‌توان این Lazy loading را لغو کرد و در همان اولین کوئری، مطالب کاربر یافت شده را نیز دریافت نمود:
 var user1 = context.Users.Include(user => user.BlogPosts).FirstOrDefault();
و سپس این اطلاعات را توسط AutoMapper نگاشت کرد.
در این حالت، AutoMapper برای ساده سازی این مراحل، متدهای Project To را معرفی کرده‌است:
 var uiUser = context.Users.Project().To<UserViewModel>().FirstOrDefault();
در اینجا نیز Lazy loading لغو شده و به صورت خودکار جوینی به جدول مطالب کاربران ایجاد خواهد شد.
بنابراین با استفاده از متد‌های Project To می‌توان از ذکر Includeهای EF صرفنظر کرد و همچنین دیگر نیازی به نوشتن متد Select جهت نگاشت دستی خواص مورد نظر به خواص ViewModel نیست.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample01.zip
مطالب
Best Practice هایی برای طراحی RESTful API - قسمت اول

با آمدن Asp.Net Web API کار ساختن Web API‌ها برای برنامه نویس‌ها به خصوص دسته ای که با ساخت API و وب سرویس آشنا نبودند خیلی ساده‌تر شد . اگر با Asp.Net MVC آشنا باشید خیلی سریع می‌توانید اولین Web Service خودتان را بسازید .

در صفحه مربوط به Asp.Net Web API آمده است که این فریمورک بستر مناسبی برای ساخت و توسعه برنامه ‌های RESTful است . اما تنها ساختن کنترلر و اکشن و برگشت دادن داده‌ها به سمت کلاینت ، به خودی خود برنامه شما رو تبدیل به یک RESTful API نمی‌کند .

مثل تمام مفاهیم و ابزارها ، طراحی و ساختن RESTful API هم دارای اصول و Best Practice هایی است که رعایت آنها به خصوص در این زمینه از اهمیت زیادی برخوردار است . همانطور که از تعریف API برمی آید شما در حال طراحی رابطی هستید تا به توسعه دهندگان دیگر امکان دهید از داده‌ها و یا خدمات شما در برنامه‌ها و سرویس هایشان استفاده کنند . مانند API‌های توئیتر و نقشه گوگل که برنامه‌های زیادی بر مبنای آنها ساخته شده اند . در واقع  توسعه دهندگان مشتریان API شما هستند .

بهره وری توسعه دهنده مهمترین اصل

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


تهیه مستندات API

اگر برای پروژه وب سایتتان هیچ نوشته ای یا توضیحی ندارید ، جالب نیست اما خودتان ساختار برنامه خود را می‌شناسید و کار را پیش می‌برید. اما توسعه دهنده ای که از API شما می‌خواهد استفاده کند و به احتمال زیاد شما را نمی‌شناسد ، عضو تیم شما هم نیست ، هیچ ایده ای درباره ساختار آن ، روش نامگذاری توابع و منابع، ساختار Url‌‌ها ، چگونگی و گام‌های پروسه درخواست تا دریافت پاسخ ندارد ،و به مستندات شما وابسته است و تمام اینها باید در مستندات شما باشد. بیشتر توسعه دهنده‌ها قبل از تست کردن API شما سری به مستندات می‌زنند ، دنبال نمونه کد آموزشی می‌گردند و در اینترنت درباره آن جستجو می‌کنند . ازینرو مستندات ( کارامد ) یک ضرورت است :
1- در مستندات باید هم درباره کلیت و هم در مورد تک تک توابع ( پارامترهای معتبر ، ساختار پاسخ‌ها و ... ) توضیحات وجود داشته باشد.
2- باید شامل مثالهایی از سیکل کامل درخواست‌ها / پاسخ‌ها باشند .
3- تغییرات اعمال شده نسبت به نسخه‌های قبلی باید در مستندات بیان شوند .
4- (در وب ) یافتن و جستجو کردن در مستنداتی که به صورت فایل Pdf هستند یا برای دسترسی نیاز به Login داشته باشند سخت و آزاردهنده هستند.
5- کسی را داشته باشید تا با و بدون مستندات با API شما کار کند و از این روش برای تکمیل و اصلاح مستندات استفاده کنید.

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

داشتن یک روش مناسب برای اعلام تغییرات و ارائه مستندات و البته دریافت بازخورد از استفاده کنندگان
تعامل با کاربران برنامه باید از کانال‌های مختلف وجود داشته باشد .از وبلاگ ، Mailing List ، Google Groups و دیگر ابزارهایی که در اینترنت وجود دارند برای انتشار مستندات ، اعلام بروزرسانی‌ها ، قرار دادن مقالات و نمونه کدهای آموزشی ، پرسش و پاسخ با کاربران استفاده کنید .

مدیریت خطاها به شکل صحیح که به توسعه دهنده در آزمودن برنامه اش کمک کند.
از منظر برنامه نویسی که از API شما استفاده می‌کند هرآنچه در آنسوی API اتفاق می‌افتد یک جعبه سیاه است . به همین جهت خطاهای API شما ابزار کلیدی برای او هستند که خطایابی و اصلاح برنامه در حال توسعه اش را ممکن می‌کنند . علاوه بر این ، زمانی که برنامه نوشته شده با API شما مورد استفاده کاربر نهایی قرار گرفت ، خطاهای به دقت طراحی شده API شما کمک بزرگی برای توسعه دهنده در عیب یابی هستند .
1- از Status Code های HTTP استفاده کنید و سعی کنید تا حد ممکن آنها را نزدیک به مفهوم استانداردشان بکار ببرید .
2- خطا و علت آن را به زبان روشن توضیح دهید و در توضیح خساست به خرج ندهید .
3- در صورت امکان لینکی به یک صفحه وب که حاوی توضیحات بیشتری است را در خطا بگنجانید .

رعایت ثبات و یکدستی در تمام بخش‌های طراحی که توانایی پیش بینی توسعه دهنده را در استفاده از API افزایش می‌دهد .
داشتن مستندات لازم است اما این بدین معنی نیست که خود API نباید خوانا و قابل پیش بینی باشد . از هر روش و تکنیکی که استفاده می‌کنید آن را در تمام پروژه حفظ کنید . نامگذاری توابع/منابع ، ساختار پاسخ‌ها ، Url‌‌ها ، نقش و عملیاتی که HTTP method‌‌ها در API شما انجام می‌دهند باید ثبات داشته باشند . از این طریق توسعه دهنده لازم نیست برای هر بخشی از API شما به سراغ فایل‌ها راهنما برود . و به سرعت کار خود را به پیش می‌برد .

انعطاف پذیر بودن API
API توسط کلاینت‌های مختلفی و برای افراد مختلفی مورد استفاده قرار می‌گیرد که لزوما همه‌ی آنها ساختار یکسانی ندارند و API شما باید تا جای ممکن بتواند همه آنها را پوشش دهد . محدود بودن فرمت پاسخ ، ثابت بودن فیلدهای ارسالی به کلاینت ، ندادن امکان صفحه بندی ، مرتب سازی و جستجو در داده‌ها به کلاینت ، داشتن تنها یک نوع احراز هویت ، وابسته بودن به کوکی و ... از مشخصات یک API منجمد و انعطاف ناپذیر هستند . 

اینها اصولی کلی بودند که بسیاری از آنها مختص طراحی API نیستند و در تمام حوزه‌ها قابل استفاده بوده ، جز الزامات هستند . در قسمت‌های بعدی نکات اختصاصی‌تری را بررسی خواهیم کرد .
مطالب
آشنایی با فرمت OPML

OPML یا Outline Processor Markup Language اساسا فایلی است مبتنی بر XML که امروزه بیشتر جهت توزیع لینک‌های تغذیه خبری سایت‌ها (RSS/Atom و امثال آن) مورد استفاده قرار می‌گیرد.
به بیانی ساده‌تر، بجای این‌که بگویند ما به این 100 وبلاگ علاقمند هستیم و لینک تک تک آنها را به شما ارائه بدهند، یک فایل OPML استاندارد از آن‌ها درست کرده و در اختیار شما قرار می‌دهند. به این صورت با چند کلیک ساده، این فایل در نرم افزار فیدخوان شما import شده و آدرس‌ها بلافاصله قابل استفاده خواهند بود.
نمونه‌ای از این فرمت:
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
  <head>
      <title>Subscriptions in Google Reader</title>
  </head>
  <body>
      <outline title="Programming">
          <outline
               text="Vahid's Blog"
               title="Vahid's Blog"
               type="atom"
               xmlUrl="http://feeds.feedburner.com/vahidnasiri"
               htmlUrl="https://www.dntips.ir/"/>
چند نمونه فایل OPML مرتبط با برنامه نویسی را از سایت‌های مختلف جمع آوری کرده‌ام که آنها را از این آدرس می‌توانید دریافت کنید.

نحوه استفاده از آنها در Google reader
بعد از ورود به قسمت تنظیمات Google reader ، با استفاده از قسمت import/export می‌توان یک فایل OPML را به آن معرفی کرد (شکل زیر):



و یا با استفاده از برنامه باکیفیت و رایگان FeedDemon و قسمت import feeds آن می‌توان یک فایل OPML را به برنامه وارد کرد. البته این‌جا امکانات بیشتری را نسبت به Google reader دراختیار شما قرار می‌دهد و می‌توانید از لیست دریافتی، موارد مورد نظر را انتخاب کنید و نه تمامی آنها را.




اگر علاقمند بودید که این فایل‌ها را در برنامه‌های دات نت خود import کنید، کتابخانه سورس باز Argotic Syndication Framework این امکان را در اختیار شما قرار می‌دهد.


به روز رسانی
- «از کدام فیدخوان تحت وب استفاده می‌کنید؟»  
- «به روز رسانی فایل OPML وبلاگ‌های IT ایرانی؛ شهریور 94»  
نظرات مطالب
حذف هدرهای مربوط به وب سرور از طریق برنامه نویسی
- محل تست کردن واقعی این کدها بر روی ویندوز سرور است و نه دیباگر و وب سرور آزمایشی ویژوال استودیو.
- شما اگر تا این حد دسترسی به IIS دارید، اصلا نیازی به کدنویسی برای حذف هدرهای وب سرور نخواهید داشت. به قسمت HTTP response headers کنسول مدیریتی مراجعه و مداخل موجود را حذف یا ویرایش کنید.
+ حذف هدر مربوط به نام Server کار ساده‌ای نیست. خیلی‌ها از روش HTTP module هم جواب نگرفته‌اند، اما با استفاده از URL Scan خود مایکروسافت قابل حذف است (این برنامه روی ویندوزهای سرور 2003 به بعد قابل نصب است). بعد از نصب به فایل C:\Windows\System32\inetsrv\urlscan\UrlScan.ini مراجعه و RemoveServerHeader را با 1 مقدار دهی کنید. ضمنا قبل از نصب URLScan تغییر زیر را هم امتحان کنید (بجای Remove از Set استفاده شده):
void OnPreSendRequestHeaders(object sender, EventArgs e) 
{
   HttpContext.Current.Response.Headers.Set("Server", "CERN httpd"); 
}
مطالب دوره‌ها
بوت استرپ (نگارش 3) چیست؟
بوت استرپ یک فریم ورک CSS واکنشگرا (responsive) است، که جهت ساخت سریع برنامه‌های استاتیک و همچنین پویای وب کاربرد دارد. در حال حاضر این پروژه جزو محبوب‌ترین و فعال‌ترین پروژه‌های سایت Github است. اگر علاقمند هستید که لیستی از سایت‌های استفاده کننده از بوت استرپ را مشاهده کنید، به آدرس‌های ذیل مراجعه نمائید:


تازه‌های بوت استرپ 3 کدامند؟

- بوت استرپ 3 جهت کار با صفحه‌های نمایش کوچک دستگاه‌های موبایل به شدت بهینه سازی شده است و به همین جهت به آن mobile-first CSS framework نیز می‌گویند.
- در نگارش 2 بوت استرپ، حداقل دو نوع گرید واکنشگرا و غیر واکنشگرا قابل تعریف بودند. در نگارش سوم آن، تنها یک نوع گرید جدید واکنشگرا در این فریم ورک وجود دارد که می‌تواند چهار نوع سایز از بزرگ تا کوچک را شامل شود.
- بوت استرپ 3 با IE7 به قبل و همچنین فایرفاکس 3.6 و پایین‌تر دیگر سازگار نیست. البته برای پشتیبانی از IE8، نیاز به اندکی تغییرات نیز وجود خواهد داشت که در قسمت‌های بعد این جزئیات را بیشتر بررسی خواهیم کرد. به عبارت دیگر بدون این تغییرات، بوت استرپ 3 در حالت پیش فرض با IE9 به بعد سازگار است.
- در بوت استرپ 3 برخلاف نگارش قبلی آن که لیستی از آیکن‌های خود را در قالب چند فایل PNG image sprite که آیکن‌ها را به صورت فشرده در کنار هم قرار داده بود، اینبار تنها از Font icons استفاده می‌کند. به این ترتیب تغییر اندازه این آیکن‌ها با توجه به برداری بودن نمایش قلم‌ها و همچنین قابلیت اعمال رنگ به آن‌ها نیز بسیار ساده‌تر می‌گردد.


سؤال: آیا نیاز است از یک فریم ورک CSS واکنشگرا استفاده شود؟

در سال‌های قبل، عموما طراحی وب بر اساس تهیه یا خرید یک سری قالب‌های از پیش آماده شده، شکیل صورت می‌گرفته‌است. این قالب‌ها به سرعت با برنامه، یکپارچه شده و حداکثر قلم یا رنگ‌های آن‌ها‌را اندکی تغییر می‌دادیم و یا اینکه خودمان کل این مسیر را از صفر طی می‌کردیم. این پروسه سفارشی، بسیار سنگین بوده و مشکل مهم آن، عدم امکان استفاده مجدد از طراحی‌های انجام شده می‌باشد که نهایتا در دراز مدت هزینه‌ی بالایی را برای ما به همراه خواهند داشت. اما با استفاده از فریم ورک‌های CSS واکنشگرا به این مزایا خواهیم رسید:
- قسمت عمده‌ای از کار پیشتر برای شما انجام شده است.
برای مثال نیازی نیست تا حتما برای طرحبندی صفحه، سیستم گرید خاص خودتان را طراحی کنید و یا اینکه مانند سال‌های دور، به استفاده از HTML tables پناه ببرید.
- قابلیت سفارشی سازی بسیار بالایی دارند.
برای مثال با استفاده از فناوری‌هایی مانند less می‌توان بوت استرپ را تا حد بسیار زیادی سفارشی سازی کرد. به این ترتیب دیگر یک سایت بوت استرپ، شبیه به بوت استرپ به نظر نخواهد رسید! شاید عده‌ای عنوان کنند که تمام سایت‌های بوت استرپ یک شکل هستند، اما واقعیت این است که این سایت‌ها تنها از قابلیت‌های سفارشی سازی بوت استرپ و less استفاده نکرده‌اند.
 

دریافت بوت استرپ 3

سایت رسمی دریافت بوت استرپ، آدرس ذیل می‌باشد:

البته ما از این نگارش خام استفاده نخواهیم کرد و نیاز است برای کارهای سایت‌های فارسی، از نگارش راست به چپ آن استفاده کنیم. بنابراین اگر از ویژوال استودیو استفاده می‌کنید، می‌توانید به یکی از دو بسته نیوگت ذیل مراجعه نمائید:
و اگر می‌خواهید صرفا به فایل‌های درون این بسته‌ها دسترسی پیدا کنید، از دو آدرس ذیل استفاده کنید:
فایل‌های دریافت شده با پسوند nupkg، در حقیقت یک فایل zip استاندارد هستند.

اگر بوت استرپ اصل را از سایت اصلی آن دریافت کنید، شامل تعداد فایل‌ها و پوشه‌های بسیار بیشتری است نسبت به نمونه RTL فوق. اما فایل‌های نهایی آن که مورد استفاده قرار خواهند گرفت، درون پوشه dist یا توزیع آن قرار گرفته‌اند و آنچنان تفاوتی با نگارش RTL ندارند. فقط در نگارش اصل، فایل‌های min و فشرده شده نیز همراه این بسته هستند که در نگارش RTL لحاظ نشده‌اند. این موضوع در آینده به نفع ما خواهد بود. از این لحاظ که اگر از سیستم bundling & minification مربوط بهASP.NET  استفاده کنید (جهت تولید خودکار فایل‌های min در زمان اجرا)، این سیستم به صورت پیش فرض از فایل‌های min موجود استفاده می‌کند و ممکن است مدتی سردرگم باشید که چرا تغییراتی را که به فایل CSS بوت استرپ اعمال کرده‌ام، در سایت اعمال نمی‌شوند. به علاوه امکان اعمال تغییرات و حتی دیباگ فایل‌های غیرفشرده خصوصا جاوا اسکریپتی آن نیز بسیار ساده‌تر و مفهوم‌تر است.

جهت مطالعه مباحث تکمیلی در مورد نحوه فشرده سازی فایل‌های CSS یا JS می‌توانید به مقالات ذیل، در سایت جاری مراجعه نمائید:

علاوه بر این‌ها در نگارش سوم بوت استرپ، تعدادی فایل CSS جدید به نام قالب یا theme نیز اضافه شده‌اند که همراه نسخه RTL نیست. برای مثال اگر به پوشه bootstrap-3.0.0.zip\bootstrap-3.0.0\dist\css مراجعه کنید، فایل bootstrap-theme.css نیز قابل مشاهده است. به این ترتیب قالبی و لایه‌ای بر روی مقادیر پیش فرض موجود در فایل bootstrap.css اعمال خواهند شد؛ برای مثال اعمال طراحی تخت یا flat مدرن آن به دکمه‌ها و عناصر دیگر این مجموعه.


شروع یک فایل HTML با بوت استرپ

تا اینجا فرض بر این است که فایل‌های بوت استرپ را دریافت کرده‌اید. در ادامه قصد داریم، نحوه معرفی این فایل‌ها را در یک فایل ساده HTML بررسی کنیم.
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Website</title>
    <link href="Content/css/bootstrap-rtl.css" rel="stylesheet">    
<link href="Content/css/custom.css" rel="stylesheet">    
</head>
<body>


</body>
</html>
صفحه آغازین کار با بوت استرپ 3 یک چنین شکلی را خواهد داشت و می‌تواند پایه تشکیل فایل masterpage یا layout برنامه‌های ASP.NET قرار گیرد. متا تگ viewport اضافه شده، جهت طراحی‌های واکنشگرا اضافه شده است و در ادامه لینک شدن فایل CSS بوت استرپ 3 را ملاحظه می‌کنید.
اگر سایت شما از تعاریف CSS سفارشی دیگری نیز استفاده می‌کند، تعاریف آن‌ها باید پس از بوت استرپ، ذکر گردند.


افزودن اسکریپت‌های بوت استرپ 3

برای کار با اسکریپت‌های بوت استرپ 3 نیاز است ابتدا jQuery را به صورت جداگانه دریافت کنیم. در حال حاضر اگر به سایت جی‌کوئری مراجعه کنید با دو نگارش 1.x و 2.x این کتابخانه مواجه خواهید شد. اگر نیاز به پشتیبانی از IE 8 را در محل کار خود دارید، باید از نگارش 1.x استفاده کنید. نگارش آخر 1.x کتابخانه جی‌کوئری را از طریق CDN آن همواره می‌توان مورد استفاده قرار داد:
 <script src="http://code.jquery.com/jquery-latest.min.js"></script>
بهتر است تعاریف فایل‌های جاوا اسکریپت را پیش از بسته شدن تگ body قرار دهید. یکی از مزایای مهم آن مشاهده نشدن یک فلش کوتاه مدت سفید رنگ در ابتدای بارگذاری صفحاتی با پس زمینه غیر روشن است. از این جهت که هر المانی که در head صفحه تعریف شود، حتما باید پیش از بارگذاری کل صفحه دریافت گردد. به این ترتیب با سرعت‌های دریافت کمتر، این مساله سبب خالی ماندن صفحه برای مدتی کوتاه خواهد شد و همان فلش سفید رنگ عنوان شده را پدید می‌آورد؛ چون هنوز مابقی صفحه بارگذاری نشده و خالی است.
پس از تعریف جی‌کوئری، تعریف اسکریپت‌های بوت استرپ قرار می‌گیرد (چون وابسته است به جی‌کوئری). فایل bootstrap-rtl.js شامل تمام زیر فایل‌های مورد نیاز نیز می‌باشد:
 <script src="Scripts/bootstrap-rtl.js"></script>
برای سازگار سازی بوت استرپ 3 با IE8 نیاز به یک فایل اسکریپت دیگر نیز داریم. این فایل را از آدرس ذیل دریافت نمائید:
این فایل 4 کیلوبایتی را نیز باید به تعاریف اسکریپت‌های مورد نیاز، اضافه کرد:
 <script src="Scripts/respond.min.js"></script>
البته این اسکریپت خاص، مطابق توضیحات آن باید به head صفحه اضافه شود تا با IE8 بهتر کار کند.
تا اینجا ساختار صفحه HTML تهیه شده جهت استفاده از امکانات بوت استرپ 3، شکل زیر را خواهد داشت:
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Website</title>

    <link href="Content/css/bootstrap-rtl.css" rel="stylesheet">    
<link href="Content/css/custom.css" rel="stylesheet">       
<script src="Scripts/respond.min.js"></script>
</head>
<body>


<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script src="Scripts/bootstrap-rtl.js"></script>
</body>
</html>

فایل‌های نهایی این قسمت را از اینجا نیز می‌توانید دریافت کنید:
bs3-sample01.zip