در مورد افزونه YSlow افزونه Firebug فایرفاکس پیشتر صحبت شد. این افزونه پس از آنالیز یک سایت، پیشنهاداتی را نیز جهت بهبود سرعت، ارائه میدهد.
همانطور که در شکل بالا مشخص است، عناصری مانند css و js ، قسمت expires اشان (تاریخ منقضی شدن کش آنها در سمت کلاینت) خالی است و پیشنهاد داده که به هر کدام از این عناصر، هدر مخصوص مشخص سازی مدت زمان کش شدن در سمت کلاینت اضافه شود.
ASP.Net در مورد کش کردن اطلاعات صفحات پویا به اندازهی کافی امکانات در اختیار برنامه نویس قرار میدهد اما در مورد اضافه کردن این هدر جهت یک فایل css غیر پویا شاید نتوان مطلب خاصی را یافت.
در IIS7 امکانات ویژهای برای این منظور در نظر گرفته شده که نحوه استفاده از آن در ASP.Net به صورت زیر است:
فایل وب کانفیگ سایت را باز کرده و به قسمت system.webServer چند سطر زیر را اضافه کنید:
<staticContent>
<clientCache httpExpires="Sun, 29 Mar 2020 00:00:00 GMT" cacheControlMode="UseExpires" />
</staticContent>
این مورد فقط مختص به IIS7 است و بر روی نگارشهای پایینتر کار نمیکند.
با این کار، تاریخ منقضی شدن هر آنچه که توسط موتور ASP.net سرو نمیشود به سال 2020 تنظیم خواهد شد. (کلیه محتوای غیرپویای سایت، اعم از تصاویر، فایلهای css ، js و غیره)
پس از این تنظیم مجددا YSlow را اجرا کرده و Performance Grade ایی را که نمایش میدهد بررسی نمائید.
بدیهی است اگر یکی از فایلهای css یا js شما تغییر کند، کلاینت، اطلاعات جدیدی را تا سال 2020 دریافت نمیکند. برای حل این مشکل یک کوئری استرینگ ساده به انتهای لینک مربوط به css یا js خود اضافه کنید تا URL جدید با URL قبلی آن یکسان نباشد (این کوئری استرینگ تاثیری روی محتوای ایستای ما ندارد). به این صورت این آدرس جدید، مجددا دریافت شده و تا سال 2020 کش خواهد شد.
نکته:
اعمال تنظیم فوق، در IIS7 ویندوز سرور 2008 مجاز است؛ اما در IIS7 ویندوز ویستا قفل شده است و قابل override نیست. برای تغییر آن، فایل زیر را پیدا کنید:
open %systemroot%\System32\inetsrv\config\applicationHost.config
<section name="staticContent" overrideModeDefault="Deny" />
<section name="staticContent" overrideModeDefault="Allow" />
مآخذ:
YSlow: Add expires header to images in IIS 7
IIS7: How to set cache control for static content?
سری فیبوناچی و دات نت 4 !
https://www.dntips.ir/2010/08/openssl.html
مشارکت در نگهداری پروژه
- مشکلی را در این کتابخانه پیدا کردهاید؟ آنرا در سیستم bug tracking پروژه گزارش کنید و بیتفاوت از کنار آن عبور نکنید.
- مشکلی برطرف شده است؟ بررسی کنید، آیا واقعا این تغییرات مفید بوده است یا خیر و نتیجه را اعلام کنید.
بهبود کدهای موجود
- در بهترین حالت، کدی را جهت رفع یک مشکل ارسال کنید. همچنین در این حالت سعی کنید یک مطلب جدید را ایجاد کرده و در مورد کدهای خود توضیح دهید. برای ارسال کدی جدید بهتر است تنها قسمتهای تغییر کرده را ارسال کنید و اصطلاحا باید بتوانید توسط قابلیتهای سورس کنترلها یک patch را تهیه نمائید.
- یک unit test جدید را به پروژه اضافه کنید. یک مثال جدید را برای قسمتی خاص تهیه نمائید.
- ثوابت برنامه را به زبانهای دیگر ترجمه کنید.
- یک گزینه و قابلیت جدید را درخواست دهید.
بهبود مستندات پروژه
- اگر توضیحات قسمتهای مختلف و commentهای ارائه شده به نظر شما کافی نیست؛ آنها را تکمیل کرده و یک patch برای آن ارائه دهید.
- مستندات موجود را تکمیل کنید یا بهبود ببخشید.
- یک مقالهی جدید در مورد نحوهی استفاده از آن پروژه بنویسید.
- یک ویدیوی ساده آموزشی را در مورد آن تهیه کنید.
مشارکت در انجمنها و شبکههای اجتماعی
- به لیست سؤالات مطرح شده در یک پروژه مراجعه کرده و در آن مشارکت کنید. سعی کنید حضور مثبتی داشته باشید.
- به دیگران در مورد وجود این پروژه اطلاع رسانی کنید.
- اگر پروژه مفیدی است، به دیگران بگوئید این پروژه چقدر بر روی کار شما تاثیر داشته است و چه برنامههایی را از طریق آن پیش بردهاید.
برای طراحی گزارش شما میتوانید به سه روش این کار را انجام دهید.
1- طراحی در برنامه طراح گزارش
2- طراحی از داخل ویژوال استودیو
3- طراحی گزارش در زمان اجرا
برای شروع شما میتوانید نسخه آزمایشی این گزارشساز را دریافت کنید. تنها محدودیت این نسخه نمایش عبارت Demo در چاپ میباشد.
برنامه Designer را اجرا کنید. در صورتی که برای اولین بار است این برنامه را اجرا میکنید ابتدا باید رابط کاربری خود را انتخاب نمایید. نوار ابزار سمت چپ تمامی ابزارهای پرکاربرد طراحی گزارش را در اختیارتان قرار میدهد. ابزارهایی که در این بخش درباره آنها توضیح داده خواهد شد عبارتند از:
Header, Footer, Data, Page Header, Page Footer, Report Title, Report Summery
*به ابزارهای بالا Band گفته میشود.
Header , Footer :
همانطور که از نامشان پیداست در قسمت بالا و پایین بخشی از گزارش قرار میگیرند که برای استفاده در بالا و پایین بند Data میباشد. به عنوان مثال بند Header مناسب طراحی سرستونهای یک جدول میباشد و بند Footer هم جهت نمایش اطلاعات انتهایی یک جدول. ولی شما میتوانید با تنظیم خصوصیات هر بند رفتار و نمایش آنها را به طور کل تغییر دهید. نکته مثبت این گزارشساز این است که شما میتوانید بیش از یک واحد از هر بند را بر روی صفحه طراح خود قرار دهید، به عنوان مثال شما میتوانید دو بند Header داشته باشید که یکی در صفحات زوج و دیگری در صفحات فرد نمایش داده شود.
Data :
این بند جهت نمایش اطلاعات از منبع دادهها میباشد. به این معنا که به ازای هر سطر از دادهها یک بار این بخش نمایش داده میشود. تعداد دفعات نمایش این بند محدود به تعداد سطرهای منبع داده و یا اندازه صفحه و همچنین خصوصیت محدوده نمایش سطرها در یک صفحه میباشد.
Page Header , Page Footer :
این دو بند با توجه به نامشان جهت نمایش در بالا و پایین هر صفحه از گزارش میباشد. البته باز هم یادآور میشوم که با تغییر در خصوصیاتشان میتوانید رفتار و نحوه نمایش آنها را تغییر دهید.
Report Title :
این بند فقط در ابتدای گزارش نمایش داده خواهد شد.
Report Summery :
این بند بلافاصله بعد از اتمام گزارش نمایش داده خواهد شد.
مثال :
برای شروع در Designer یک گزارش جدید از نوع Blank Report ایجاد نمایید. سپس در پنل Dictionary بر روی New Item کلیک کرده و گزینه XML Data را انتخاب نمایید. با توجه به محل نصب گزارشساز وارد مسیر …\Bin\Data شده و فایلهای Demo.xsd و Demo.xml را برای قسمتهای مربوطه انتخاب نمایید. یک بار دیگر بر رو New Item کلیک کرده و گزینه New Data Source را انتخاب نمایید، از لیست ظاهر شده کانکشنی را که ایجاد کردهاید را انتخاب نمایید؛ نتیجه کار تا اینجا باید به صورت زبر باشد.
جدول Product را دراگ کرده و بر روی صفحه طراحی گزارش رها کنید. فرم Data ظاهر میشود این فرم را مطابق تصویر زیر تنظیم نمایید.
حال بر روی صفحه طراحی گزارش بندهای Header, Data, Footer مشاهده میشود؛ حال شما میتوانید با کلیک بر روی سربرگ Preview خروجی گزارش را ببینید.
توابع :
این گزارشساز دارای توابع بسیاری است که اکثر نیازهای شما را برطرف میکند به عنوان مثال تابع تبدیل عدد به حروف به زبان فارسی. همچنین شما میتوانید توابع خاص خود را ساخته و به صورت رفرنس به گزارش اضافه نمایید.
در این بخش ما از توابع موجود در گزارش استفاده خواهیم کرد. برای شروع بر روی کامپوننت Text در بند Footer زیر ستون UnitPrice دابل کلیک کرده تا فرم TextEditor ظاهر شود. سربرگ Summery را انتخاب نمایید. مطابق اطلاعات زیر بخشها را تنظیم نمایید.
Summery Function: Sum
Data Band: DataProducts
Data Column: Products.UnitPrice
حال بر روی سربرگ Preview
کلیک نمایید تا خروجی گزارش را ببینید. جمع ستون UnitPrice
فقط در صفحه آخر نمایش داده خواهد شد. اگر بخواهید جمع ستون در پایین هر صفحه
نمایش داده شود ابتدا باید خصوصیت Print on All Pages بند Footer به True
ست شود. سپس بر روی کامپوننت Text در بند Footer،
دابل کلیک نمایید و در فرم TextEditor
سربرگ Summery
تیک Running Total
را به حالت انتخاب شده در بیاورید، حال خروجی گزارش را ببینید، جمع در انتهای هر
صفحه ظاهر میشود.
متغیرها :
در این گزارش ساز دو نوع متغیر وجود دارد؛ نرمال و سیستمی. نوع سیستمی شامل متغیرهایی میشود که کاربرد مشخصی در تهیه گزارش دارند، مثل شماره صفحه، شماره ردیف، عنوان گزارش و ...
برای مثال شما میتوانید متغیر سیستمی Line را برای روی صفحه طراحی دراگ کنید. دو کامپوننت Text بر روی صفحه ایجاد میشود. اولی با محتوای Line و دومی با محتوای {Line}. اولی را به بند Header و دومی را به بند Data منتقل کنید و سپس خروجی گزارش را مشاهده نمایید، حال گزارش شما دارای شماره ردیف است.
متغیرهای نرمال تقریبا همانند متغیرهایی هستند که همه روزه شما در برنامههای خود از آنها استفاده میکنید. با کلیک بر روی New Item گزینه New Variable را انتخاب نمایید و نوع متغیر را Decimal انتخاب نمایید، سپس متغیر ایجاد شده را دراگ کرده و بروی صفحه طراحی قرار دهید و مشابه متغیر Line عمل کرده و کامپوننتهای Text را در بندهای مناسب قرار دهید. سپس بند Data بر روی صفحه طراحی را انتخاب نمایید، در پنل Properties بر روی Eventes کلیک کرده سپس در رویداد Rendering کد زیر را وارد نمایید.
Variable1 += Products.UnitPrice
حال در خروجی گزارش میتوانید مقادیر محاسبه
شده را ببینید. توجه داشته باشید که شما میتوانید در رویدادهای این گزارشساز به
زبان VB
و C#
برنامه نویسی کنید و محدود به یک خط کد نمیباشید.
شما میتوانید گزارش ساخته شده را به صورتهای مختلف ذخیره کنید از جمله کد C# و یا یک اپلیکیشن قابل اجرا.
بررسی 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
namespace ImageGallery.MvcClient.WebApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); }
در ادامه اگر مجددا لیست 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
کار با مجموعهی 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"); }); }
اکنون اگر پس از 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() }; }
همین مورد را به لیست AllowedScopes متد GetClients نیز اضافه میکنیم:
AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Address },
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") } } }; }
پس از آن به کلاس 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"); }); }
یک نکته: فراخوانی 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
اکنون برای دریافت دستی اطلاعات آدرس از IDP و UserInfo Endpoint آن، ابتدا نیاز است بستهی نیوگت IdentityModel را به پروژهی Mvc Client اضافه کنیم:
dotnet add package IdentityModel
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)); }
OrderFrameViewModel ارسالی به View، یک چنین شکلی را دارد:
namespace ImageGallery.MvcClient.ViewModels { public class OrderFrameViewModel { public string Address { get; } = string.Empty; public OrderFrameViewModel(string address) { Address = address; } } }
@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") } } }; }
سپس باید برای این 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" }) }; }
همچنین باید به کلاینت مجوز درخواست این 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");
• 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> }
options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = JwtClaimTypes.GivenName, RoleClaimType = JwtClaimTypes.Role, };
اکنون برای آزمایش آن یکبار از سیستم خارج شده و مجددا به آن وارد شوید. پس از آن لینک درخواست نگارش قاب شدهی تصاویر، برای کاربر 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() {
برای آزمایش آن، توسط مشخصات کاربر 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(); } }
<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"; }) // ...
برای آزمایش آن، یکبار از برنامه خارج شده و مجددا با اکانت 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 وارد کنید.
KnockoutJs #5
<<باگ را بدون باگ گزارش کن >>
<<آناتومی یک گزارش خطای خوب >>
ضرورت دانش پایه برای پیشرفت در صنعت نرم افزار کشور
برنامه نویسها سطوح مختلفی دارند. یکی کامپوننت مینویسد، یکی استفاده میکند. یکی پروتکل طراحی میکند، یکی صرفا تنظیمات این پروتکل را در سیستم عامل انجام میدهد. بنابراین این دو لازم و ملزوم هستند. فقط بستگی دارد که در چه سطحی میخواهید کار کنید. همچنین گیرم دانش پایه تولید کامپایلر را بدست آوردید. آیا میتوانید با نمونههای موجود رقابت کنید؟ گیرم دانش بومی تولید سیستم عامل را به دست آوردید، آیا اصلا هزینهی آن قابل توجیه است (توسعه، نگهداری، رفع نواقص امنیتی، ارائه منظم نگارشهای جدید، سازگاری با سخت افزارهای مختلف). آیا تمام کشورهای صاحب نام IT در دنیا وارد این بازی شدهاند؟
به علاوه اندازهی کسب و کارها هستند که تعیین کننده سطح دانش مورد نیاز خودشان هستند. کسی که یک گروه 5 نفره دارد، آیا برایش مقرون به صرفه است که به فکر تولید سیستم عامل باشد؟ برای مثال یک کسب و کار کوچک شاید الزاما نیازی به راه
حلهای NoSQL نداشته باشد؛ اما حتما باید با نحوهی کار با SQLite یا SQL
CE آشنا باشد. در یک کسب و کار بزرگ شاید بانکهای اطلاعاتی موجود پاسخگو
نباشند و نیاز باشد تا واحد تحقیق و توسعهی آنها دست به کار شود و بانک
اطلاعاتی متناسبی را طراحی کند. برای مثال فیس بوک برداشته تمام قابلیتهای سیشارپ رو به PHP اضافه کرده، یک زبان جدید برای خودشون درست کردند.