مطالب
چند نکته کوتاه و عمومی در مورد قبل و بعد از نصب ویندوز 7

- فایل ISO ایی که از سایت‌های این دور و اطراف قابل دریافت است، یک DVD از نوع bootable به شما ارائه نخواهد داد. بنابراین نیاز است تا این قابلیت را فراهم کرد. خود مایکروسافت برنامه سورس بازی را در آدرس زیر جهت تهیه یک bootable DVD یا حتی bootable flash drive از روی فایل ISO دریافت شده ارائه داده است:


- نیاز است تا کلیه افزونه‌ها، کلمات عبور ذخیره شده و غیره‌ی فایرفاکس را بتوان به سیستم جدید انتقال داد. ابزار رایگان و سورس باز MozBackup این‌کار را به بهترین نحو ممکن میسر می‌سازد: +

- پس از نصب، نیاز به صفحه کلید فارسی مرغوب می‌باشد : + و +

- پس از نصب ویندوز 7 ، درایو ویندوز قبلی موجود شما ناپدید شده است! نحوه‌ی رفع مشکل : +

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

- اگر هنگام کار با Virtual PC مایکروسافت هنگام اتصال به VPN با خطای 721 متوقف شدید، نگران نباشید. با انتخاب گزینه‌ی run as admin این مشکل برطرف می‌شود.

- تنظیم DNS جدید گوگل نیز توصیه می‌شود: +

و یک گجت تقویم فارسی هم کفایت می‌کند : + و یا +

اشتراک‌ها
سری 10 قسمتی توسعه‌ی برنامه‌های React با TypeScript از JetBrains

ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way, we'll show test-driven development and emphasize tips and tricks in the IDE.

Other videos from this series:
1. Project Setup: https://youtu.be/wm8WdAB64gw
2. Project Cleanup: https://youtu.be/b0KrB31hN5k
3. Testing: https://youtu.be/Y_TGIsFnvo4
4. Debugging Node.js: https://youtu.be/r1kwXZnO8gw
5. Debugging in Chrome: https://youtu.be/dvmZi_DWu9I
6. TSX and ES6: https://youtu.be/JXrZDUzkc2Q
7. Class Props: https://youtu.be/HYmoeUF9ZH0
8. Class State: https://youtu.be/21-VMTmiV8E
9. Rich Events and Testing: https://youtu.be/OO7OmA5UlQM
10. Presentation Components: https://youtu.be/SnCGW6JUo4E 

سری 10 قسمتی توسعه‌ی برنامه‌های React با TypeScript از JetBrains
اشتراک‌ها
مرتب سازی دیکشنری
// Create dictionary and add five keys and values.
        var dictionary = new Dictionary<string, int>();
        dictionary.Add("car", 2);
        dictionary.Add("apple", 1);
        dictionary.Add("zebra", 0);
        dictionary.Add("mouse", 5);
        dictionary.Add("year", 3);

        // Acquire keys and sort them.
        var list = dictionary.Keys.ToList();
        list.Sort();

        // Loop through keys.
        foreach (var key in list)
        {
            Console.WriteLine("{0}: {1}", key, dictionary[key]);
        }
مرتب سازی دیکشنری
نظرات مطالب
مروری بر کاربردهای Action و Func - قسمت دوم
سلام میشه یه توضیحی درباره کد زیر بدید؟
    public static T CacheRead<T>(this HttpContextBase httpContext, string key, int durationMinutes, Func<T> ifNullRetrievalMethod)
        {
            var item = httpContext.Cache[key];
            if (item == null)
            {
                item = ifNullRetrievalMethod();
                if (item == null)
                    return default(T);
 
                CacheInsert(httpContext, key, item, durationMinutes);
            }
            return (T)item;
        }
مطالب
کار با Docker بر روی ویندوز - قسمت دوم - نصب Docker
پس از آشنایی با مفهوم Containers، در این قسمت قصد داریم برنامه‌ی تقریبا 500 مگابایتی Docker for Windows Installer.exe را نصب کنیم.
 
پیش‌نیازهای نصب Docker بر روی ویندوز

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


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

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

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


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


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

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


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


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


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


نصب Docker for Windows

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


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

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


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

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


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


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


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


قسمت «پیش‌نیازهای نصب Docker بر روی ویندوز» را با دقت بررسی کنید (خصوصا قسمت BIOS و SLAT). نبود یکی از موارد ذکر شده، سبب بروز این مشکل می‌شود.
برای مثال اجرای دستور coreinfo -v بر روی سیستم من چنین خروجی را به همراه دارد:
E:\>coreinfo -v

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

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


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

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

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

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


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

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


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

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

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


روش حذف Containers از Docker

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


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

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


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

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


مفهوم Interactive Terminal در Docker

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

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


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

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



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

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

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

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


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

هنگامیکه پروسه‌ای درون یک container اجرا می‌شود، ایزوله سازی‌های بسیاری نیز در مورد آن اعمال خواهد شد؛ به همین جهت گاهی از اوقات عده‌ای containerها را با ماشین‌های مجازی نیز مقایسه می‌کنند. برای مثال کانتینرها به همراه network adapter خاص خود نیز هستند؛ درست مانند اینکه یک کامپیوتر مجزای از سیستم جاری می‌باشند و اگر این network adapter را ping کنیم، می‌توان به این صورت نیز به آن کانتینر، دسترسی داشته باشیم.
برای یافتن آن، دستور docker inspect iis را صادر می‌کنیم. خروجی آن به همراه یک قسمت network نیز هست که داخل آن یک IP Address قابل مشاهده است. این IP است که مختص و منحصربفرد این container است. در ابتدا برای آزمایش آن، می‌توان آن‌را ping کرد؛ مانند ping 172.27.49.47. همچنین به تمام برنامه‌های داخل این container توسط این IP نیز می‌توان دسترسی یافت. برای مثال فراخوانی http://172.27.49.47:81 در مرورگر، سبب نمایش صفحه‌ی اول IIS می‌شود. البته اگر اینکار را انجام دهیم، کار نمی‌کند. علت اینجا است، نگاشت پورتی را که تعریف کرده‌ایم (پورت 81)، به پورتی در کامپیوتر میزبان است و نه این IP ویژه. برنامه‌ی اصلی IIS در داخل container، به پورت 80 بر روی این آدرس IP گوش فرا می‌دهد. اکنون اگر آدرس http://172.27.49.47:80 را در کامپیوتر میزبان فراخوانی کنیم، کار می‌کند.
بنابراین هرچند containerها به معنای نرم افزارهای از پیش نصب شده‌ی در حال اجرا هستند، اما ... به همراه ایزوله سازی‌های قابل توجهی بر روی کامپیوتر میزبان اجرا می‌شوند؛ درست مانند یک کامپیوتر مجزای از آن.
نظرات مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
ممنون ولی مشکلی  با سمت کلاینت ندارم مشکل من شرطی هست که بدون واکشی کل سطرها ، خود ارجاعی رو تا هر سطحی که هست پیمایش کنه.
فکر کنم سوالم رو اینجوری مطرح کنم بهتر باشه، ما تعدادی کارمند داریم که عضو گروههای زیر هستند.گروه بندی خود ارجاع دهنده هست.
GroupRoot {id=1, Name="گروه اصلی",ParentId=null}
Group2 {id=2, Name="بازرگانی",ParentId=1}
Group3 {id=3, Name="فروش",ParentId=2}
Group4 {id=4, Name="فروش قطعات",ParentId=3}
Group5 {id=5, Name="خدمات",ParentId=1}
person1 {name="Ali",  GroupId=2}//بازرگانی
person2 {name="reza", GroupId=3}//فروش
person3 {name="iman", GroupId=3}//فروش
person4 {name="hamid",GroupId=4}//فروش قطعات
person5 {name="hasan",GroupId=4}//فروش قطعات
person6 {name="Ahmad",GroupId=5}//خدمات
person7 {name="vahid",GroupId=1}//گروه اصلی

گزارش 1: نمایش گروه بازگانی شناسه 2 به همراه زیر مجموعه هاش ؟{بازرگانی،فروش،فروش قطعات}
گزارش 2 : نمایس پرسنل گروه فروش و زیر مجموعه‌های گروه فروش؟{reza,iman,hamid,hasan}
 
مطالب
Blazor 5x - قسمت 25 - تهیه API مخصوص Blazor WASM - بخش 2 - تامین پایه‌ی اعتبارسنجی و احراز هویت
در این قسمت می‌خواهیم پایه‌ی اعتبارسنجی و احراز هویت سمت سرور برنامه‌ی کلاینت Blazor WASM را بر اساس JWT یکپارچه با ASP.NET Core Identity تامین کنیم. اگر با JWT آشنایی ندارید، نیاز است مطالب زیر را در ابتدا مطالعه کنید:
- «معرفی JSON Web Token»

توسعه‌ی IdentityUser

در قسمت‌های 21 تا 23، روش نصب و یکپارچگی ASP.NET Core Identity را با یک برنامه‌ی Blazor Server بررسی کردیم. در پروژه‌ی Web API جاری هم از قصد داریم از ASP.NET Core Identity استفاده کنیم؛ البته بدون نصب UI پیش‌فرض آن. به همین جهت فقط از ApplicationDbContext آن برنامه که از IdentityDbContext مشتق شده و همچنین قسمتی از تنظیمات سرویس‌های ابتدایی آن که در قسمت قبل بررسی کردیم، در اینجا استفاده خواهیم کرد.
IdentityUser پیش‌فرض که معرف موجودیت کاربران یک سیستم مبتنی بر ASP.NET Core Identity است، برای ثبت نام یک کاربر، فقط به ایمیل و کلمه‌ی عبور او نیاز دارد که نمونه‌ای از آن‌را در حین معرفی «ثبت کاربر ادمین Identity» بررسی کردیم. اکنون می‌خواهیم این موجودیت پیش‌فرض را توسعه داده و برای مثال نام کاربر را نیز به آن اضافه کنیم. برای اینکار فایل جدید BlazorServer\BlazorServer.Entities\ApplicationUser .cs را به پروژه‌ی Entities با محتوای زیر اضافه می‌کنیم:
using Microsoft.AspNetCore.Identity;

namespace BlazorServer.Entities
{
    public class ApplicationUser : IdentityUser
    {
        public string Name { get; set; }
    }
}
برای توسعه‌ی IdentityUser پیش‌فرض، فقط کافی است از آن ارث‌بری کرده و خاصیت جدیدی را به خواص موجود آن اضافه کنیم. البته برای شناسایی IdentityUser، نیاز است بسته‌ی نیوگت Microsoft.AspNetCore.Identity.EntityFrameworkCore را نیز در این پروژه نصب کرد.
اکنون که یک ApplicationUser سفارشی را ایجاد کردیم، نیازی نیست تا DbSet خاص آن‌را به ApplicationDbContext برنامه اضافه کنیم. برای معرفی آن به برنامه ابتدا باید به فایل BlazorServer\BlazorServer.DataAccess\ApplicationDbContext.cs مراجعه کرده و نوع IdentityUser را به IdentityDbContext، از طریق آرگومان جنریکی که می‌پذیرد، معرفی کنیم:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
زمانیکه این آرگومان جنریک قید نشود، از همان نوع IdentityUser پیش‌فرض خودش استفاده می‌کند.
پس از این تغییر، در فایل BlazorWasm\BlazorWasm.WebApi\Startup.cs نیز باید ApplicationUser را به عنوان نوع جدید کاربران، معرفی کرد:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
  
            // ...

پس از این تغییرات، باید از طریق خط فرمان به پوشه‌ی BlazorServer.DataAccess وارد شد و دستورات زیر را جهت ایجاد و اعمال Migrations متناظر با تغییرات فوق، اجرا کرد. چون در این دستورات اینبار پروژه‌ی آغازین، به پروژه‌ی Web API اشاره می‌کند، باید بسته‌ی نیوگت Microsoft.EntityFrameworkCore.Design را نیز به پروژه‌ی آغازین اضافه کرد، تا بتوان آن‌ها را با موفقیت به پایان رساند:
dotnet tool update --global dotnet-ef --version 5.0.4
dotnet build
dotnet ef migrations --startup-project ../../BlazorWasm/BlazorWasm.WebApi/ add AddNameToAppUser --context ApplicationDbContext
dotnet ef --startup-project ../../BlazorWasm/BlazorWasm.WebApi/ database update --context ApplicationDbContext


ایجاد مدل‌های ثبت نام

در ادامه می‌خواهیم کنترلری را ایجاد کنیم که کار ثبت نام و لاگین را مدیریت می‌کند. برای این منظور باید بتوان از کاربر، اطلاعاتی مانند نام کاربری و کلمه‌ی عبور او را دریافت کرد و پس از پایان عملیات نیز نتیجه‌ی آن‌را بازگشت داد. به همین جهت دو مدل زیر را جهت مدیریت قسمت ثبت نام، به پروژه‌ی BlazorServer.Models اضافه می‌کنیم:
using System.ComponentModel.DataAnnotations;

namespace BlazorServer.Models
{
    public class UserRequestDTO
    {
        [Required(ErrorMessage = "Name is required")]
        public string Name { get; set; }

        [Required(ErrorMessage = "Email is required")]
        [RegularExpression("^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$",
                ErrorMessage = "Invalid email address")]
        public string Email { get; set; }

        public string PhoneNo { get; set; }

        [Required(ErrorMessage = "Password is required.")]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [Required(ErrorMessage = "Confirm password is required")]
        [DataType(DataType.Password)]
        [Compare("Password", ErrorMessage = "Password and confirm password is not matched")]
        public string ConfirmPassword { get; set; }
    }
}
و مدل پاسخ عملیات ثبت نام:
    public class RegistrationResponseDTO
    {
        public bool IsRegistrationSuccessful { get; set; }

        public IEnumerable<string> Errors { get; set; }
    }


ایجاد و تکمیل کنترلر Account، جهت ثبت نام کاربران

در ادامه نیاز داریم تا جهت ارائه‌ی امکانات اعتبارسنجی و احراز هویت کاربران، کنترلر جدید Account را به پروژه‌ی Web API اضافه کنیم:
using System;
using BlazorServer.Entities;
using BlazorServer.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using System.Linq;
using BlazorServer.Common;

namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class AccountController : ControllerBase
    {
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;

        public AccountController(SignInManager<ApplicationUser> signInManager,
            UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole> roleManager)
        {
            _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager));
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
        }

        [HttpPost]
        [AllowAnonymous]
        public async Task<IActionResult> SignUp([FromBody] UserRequestDTO userRequestDTO)
        {
            var user = new ApplicationUser
            {
                UserName = userRequestDTO.Email,
                Email = userRequestDTO.Email,
                Name = userRequestDTO.Name,
                PhoneNumber = userRequestDTO.PhoneNo,
                EmailConfirmed = true
            };
            var result = await _userManager.CreateAsync(user, userRequestDTO.Password);
            if (!result.Succeeded)
            {
                var errors = result.Errors.Select(e => e.Description);
                return BadRequest(new RegistationResponseDTO { Errors = errors, IsRegistrationSuccessful = false });
            }

            var roleResult = await _userManager.AddToRoleAsync(user, ConstantRoles.Customer);
            if (!roleResult.Succeeded)
            {
                var errors = result.Errors.Select(e => e.Description);
                return BadRequest(new RegistationResponseDTO { Errors = errors, IsRegistrationSuccessful = false });
            }

            return StatusCode(201); // Created
        }
    }
}
- در اینجا اولین اکشن متد کنترلر Account را مشاهده می‌کنید که کار ثبت نام یک کاربر را انجام می‌دهد. نمونه‌‌ای از این کدها پیشتر در قسمت 23 این سری، زمانیکه کاربر جدیدی را با نقش ادمین تعریف کردیم، مشاهده کرده‌اید.
- در تعریف ابتدایی این کنترلر، ویژگی‌های زیر ذکر شده‌اند:
[Route("api/[controller]/[action]")]
[ApiController]
[Authorize]
می‌خواهیم مسیریابی آن با api/ شروع شود و به صورت خودکار بر اساس نام کنترلر و نام اکشن متدها، تعیین گردد. همچنین نمی‌خواهیم مدام کدهای بررسی معتبر بودن ModelState را در کنترلرها قرار دهیم. به همین جهت از ویژگی ApiController استفاده شده تا اینکار را به صورت خودکار انجام دهد. قرار دادن فیلتر Authorize بر روی یک کنترلر سبب می‌شود تا تمام اکشن متدهای آن به کاربران اعتبارسنجی شده محدود شوند؛ مگر اینکه عکس آن به صورت صریح توسط فیلتر AllowAnonymous مشخص گردد. نمونه‌ی آن‌را در اکشن متد عمومی SignUp در اینجا مشاهده می‌کنید.

تا اینجا اگر برنامه را اجرا کنیم، می‌توان با استفاده از Swagger UI، آن‌را آزمایش کرد:


که با اجرای آن، برای نمونه به خروجی زیر می‌رسیم:


که عنوان می‌کند کلمه‌ی عبور باید حداقل دارای یک عدد و یک حرف بزرگ باشد. پس از اصلاح آن، status-code=201 را دریافت خواهیم کرد.
و اگر سعی کنیم همین کاربر را مجددا ثبت نام کنیم، با خطای زیر مواجه خواهیم شد:



ایجاد مدل‌های ورود به سیستم

در پروژه‌ی Web API، از UI پیش‌فرض ASP.NET Core Identity استفاده نمی‌کنیم. به همین جهت نیاز است مدل‌های قسمت لاگین را به صورت زیر تعریف کنیم:
using System.ComponentModel.DataAnnotations;

namespace BlazorServer.Models
{
    public class AuthenticationDTO
    {
        [Required(ErrorMessage = "UserName is required")]
        [RegularExpression("^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$",
            ErrorMessage = "Invalid email address")]
        public string UserName { get; set; }

        [Required(ErrorMessage = "Password is required.")]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}
به همراه مدل پاسخ ارائه شده‌ی حاصل از عملیات لاگین:
using System.Collections.Generic;

namespace BlazorServer.Models
{
    public class AuthenticationResponseDTO
    {
        public bool IsAuthSuccessful { get; set; }

        public string ErrorMessage { get; set; }

        public string Token { get; set; }

        public UserDTO UserDTO { get; set; }
    }

    public class UserDTO
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string PhoneNo { get; set; }
    }
}
که در اینجا اگر عملیات لاگین با موفقیت به پایان برسد، یک توکن، به همراه اطلاعاتی از کاربر، به سمت کلاینت ارسال خواهد شد؛ در غیر اینصورت، متن خطای مرتبط بازگشت داده می‌شود.


ایجاد مدل مشخصات تولید JSON Web Token

پس از لاگین موفق، نیاز است یک JWT را تولید کرد و در اختیار کلاینت قرار داد. مشخصات ابتدایی تولید این توکن، توسط مدل زیر تعریف می‌شود:
namespace BlazorServer.Models
{
    public class BearerTokensOptions
    {
        public string Key { set; get; }

        public string Issuer { set; get; }

        public string Audience { set; get; }

        public int AccessTokenExpirationMinutes { set; get; }
    }
}
که شامل کلید امضای توکن، مخاطبین، صادر کننده و مدت زمان اعتبار آن به دقیقه‌است و به صورت زیر در فایل BlazorWasm\BlazorWasm.WebApi\appsettings.json تعریف می‌شود:
{
  "BearerTokens": {
    "Key": "This is my shared key, not so secret, secret!",
    "Issuer": "https://localhost:5001/",
    "Audience": "Any",
    "AccessTokenExpirationMinutes": 20
  }
}
پس از این تعاریف، جهت دسترسی به مقادیر آن توسط سیستم تزریق وابستگی‌ها، مدخل آن‌را به صورت زیر به کلاس آغازین Web API اضافه می‌کنیم:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions<BearerTokensOptions>().Bind(Configuration.GetSection("BearerTokens"));
            // ...

ایجاد سرویسی برای تولید JSON Web Token

سرویس زیر به کمک سرویس توکار UserManager مخصوص Identity و مشخصات ابتدایی توکنی که معرفی کردیم، کار تولید یک JWT را انجام می‌دهد:
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using BlazorServer.Entities;
using BlazorServer.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace BlazorServer.Services
{

    public interface ITokenFactoryService
    {
        Task<string> CreateJwtTokensAsync(ApplicationUser user);
    }

    public class TokenFactoryService : ITokenFactoryService
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly BearerTokensOptions _configuration;

        public TokenFactoryService(
                UserManager<ApplicationUser> userManager,
                IOptionsSnapshot<BearerTokensOptions> bearerTokensOptions)
        {
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            if (bearerTokensOptions is null)
            {
                throw new ArgumentNullException(nameof(bearerTokensOptions));
            }
            _configuration = bearerTokensOptions.Value;
        }

        public async Task<string> CreateJwtTokensAsync(ApplicationUser user)
        {
            var signingCredentials = new SigningCredentials(
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Key)),
                SecurityAlgorithms.HmacSha256);
            var claims = await getClaimsAsync(user);
            var now = DateTime.UtcNow;
            var tokenOptions = new JwtSecurityToken(
                issuer: _configuration.Issuer,
                audience: _configuration.Audience,
                claims: claims,
                notBefore: now,
                expires: now.AddMinutes(_configuration.AccessTokenExpirationMinutes),
                signingCredentials: signingCredentials);
            return new JwtSecurityTokenHandler().WriteToken(tokenOptions);
        }

        private async Task<List<Claim>> getClaimsAsync(ApplicationUser user)
        {
            string issuer = _configuration.Issuer;
            var claims = new List<Claim>
            {
                // Issuer
                new Claim(JwtRegisteredClaimNames.Iss, issuer, ClaimValueTypes.String, issuer),
                // Issued at
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64, issuer),
                new Claim(ClaimTypes.Name, user.Email, ClaimValueTypes.String, issuer),
                new Claim(ClaimTypes.Email, user.Email, ClaimValueTypes.String, issuer),
                new Claim("Id", user.Id, ClaimValueTypes.String, issuer),
                new Claim("DisplayName", user.Name, ClaimValueTypes.String, issuer),
            };

            var roles = await _userManager.GetRolesAsync(user);
            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role, ClaimValueTypes.String, issuer));
            }
            return claims;
        }
    }
}
کار افزودن نقش‌های یک کاربر به توکن او، به کمک متد userManager.GetRolesAsync انجام شده‌است. نمونه‌ای از این سرویس را پیشتر در مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» مشاهده کرده‌اید. البته در آنجا از سیستم Identity برای تامین نقش‌های کاربران استفاده نمی‌شود و مستقل از آن عمل می‌کند.

در آخر، این سرویس را به صورت زیر به لیست سرویس‌های ثبت شده‌ی پروژه‌ی Web API، اضافه می‌کنیم:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ITokenFactoryService, TokenFactoryService>();
            // ...


تکمیل کنترلر Account جهت لاگین کاربران

پس از ثبت نام کاربران، اکنون می‌خواهیم امکان لاگین آن‌ها را نیز فراهم کنیم:
namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class AccountController : ControllerBase
    {
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly ITokenFactoryService _tokenFactoryService;

        public AccountController(
            SignInManager<ApplicationUser> signInManager,
            UserManager<ApplicationUser> userManager,
            ITokenFactoryService tokenFactoryService)
        {
            _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
            _signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
            _tokenFactoryService = tokenFactoryService;
        }

        [HttpPost]
        [AllowAnonymous]
        public async Task<IActionResult> SignIn([FromBody] AuthenticationDTO authenticationDTO)
        {
            var result = await _signInManager.PasswordSignInAsync(
                    authenticationDTO.UserName, authenticationDTO.Password,
                    isPersistent: false, lockoutOnFailure: false);
            if (!result.Succeeded)
            {
                return Unauthorized(new AuthenticationResponseDTO
                {
                    IsAuthSuccessful = false,
                    ErrorMessage = "Invalid Authentication"
                });
            }

            var user = await _userManager.FindByNameAsync(authenticationDTO.UserName);
            if (user == null)
            {
                return Unauthorized(new AuthenticationResponseDTO
                {
                    IsAuthSuccessful = false,
                    ErrorMessage = "Invalid Authentication"
                });
            }

            var token = await _tokenFactoryService.CreateJwtTokensAsync(user);
            return Ok(new AuthenticationResponseDTO
            {
                IsAuthSuccessful = true,
                Token = token,
                UserDTO = new UserDTO
                {
                    Name = user.Name,
                    Id = user.Id,
                    Email = user.Email,
                    PhoneNo = user.PhoneNumber
                }
            });
        }
    }
}
در اکشن متد جدید لاگین، اگر عملیات ورود به سیستم با موفقیت انجام شود، با استفاده از سرویس Token Factory که آن‌را پیشتر ایجاد کردیم، توکن مخصوصی را به همراه اطلاعاتی از کاربر، به سمت برنامه‌ی کلاینت بازگشت می‌دهیم.

تا اینجا اگر برنامه را اجرا کنیم، می‌توان در قسمت ورود به سیستم، برای نمونه مشخصات کاربر ادمین را وارد کرد:


و پس از اجرای درخواست، به خروجی زیر می‌رسیم:


که در اینجا JWT تولید شده‌ی به همراه قسمتی از مشخصات کاربر، در خروجی نهایی مشخص است. می‌توان محتوای این توکن را در سایت jwt.io مورد بررسی قرار داد که به این خروجی می‌رسیم و حاوی claims تعریف شده‌است:
{
  "iss": "https://localhost:5001/",
  "iat": 1616396383,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "vahid@dntips.ir",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "vahid@dntips.ir",
  "Id": "582855fb-e95b-45ab-b349-5e9f7de40c0c",
  "DisplayName": "vahid@dntips.ir",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin",
  "nbf": 1616396383,
  "exp": 1616397583,
  "aud": "Any"
}


تنظیم Web API برای پذیرش و پردازش JWT ها

تا اینجا پس از لاگین، یک JWT را در اختیار کلاینت قرار می‌دهیم. اما اگر کلاینت این JWT را به سمت سرور ارسال کند، اتفاق خاصی رخ نخواهد داد و توسط آن، شیء User قابل دسترسی در یک اکشن متد، به صورت خودکار تشکیل نمی‌شود. برای رفع این مشکل، ابتدا بسته‌ی جدید نیوگت Microsoft.AspNetCore.Authentication.JwtBearer را به پروژه‌ی Web API اضافه می‌کنیم، سپس به کلاس آغازین پروژه‌ی Web API مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            var bearerTokensSection = Configuration.GetSection("BearerTokens");
            services.AddOptions<BearerTokensOptions>().Bind(bearerTokensSection);
            // ...

            var apiSettings = bearerTokensSection.Get<BearerTokensOptions>();
            var key = Encoding.UTF8.GetBytes(apiSettings.Key);
            services.AddAuthentication(opt =>
            {
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(cfg =>
            {
                cfg.RequireHttpsMetadata = false;
                cfg.SaveToken = true;
                cfg.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidAudience = apiSettings.Audience,
                    ValidIssuer = apiSettings.Issuer,
                    ClockSkew = TimeSpan.Zero,
                    ValidateLifetime = true
                };
            });

            // ...
در اینجا در ابتدا اعتبارسنجی از نوع Jwt تعریف شده‌است و سپس پردازش کننده و وفق دهنده‌ی آن‌را به سیستم اضافه کرده‌ایم تا توکن‌های دریافتی از هدرهای درخواست‌های رسیده را به صورت خودکار پردازش و تبدیل به Claims شیء User یک اکشن متد کند.


افزودن JWT به تنظیمات Swagger

هر کدام از اکشن متدهای کنترلرهای Web API برنامه که مزین به فیلتر Authorize باشد‌، در Swagger UI با یک قفل نمایش داده می‌شود. در این حالت می‌توان این UI را به نحو زیر سفارشی سازی کرد تا بتواند JWT را دریافت و به سمت سرور ارسال کند:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        // ...

        public void ConfigureServices(IServiceCollection services)
        {
            // ...

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "BlazorWasm.WebApi", Version = "v1" });
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    In = ParameterLocation.Header,
                    Description = "Please enter the token in the field",
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey
                });
                c.AddSecurityRequirement(new OpenApiSecurityRequirement {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            }
                        },
                        new string[] { }
                    }
                });
            });
        }

        // ...


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-25.zip
مطالب
Pipeها در Angular 2 – قسمت سوم – Pipeهای Pure و Impure
 در قسمت قبل بیان شد که Angular برای اعمال Pipe بر روی Template expressions باید تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهده‌ی هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها می‌توان به رخدادهای mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد می‌تواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتم‌های سریع و ساده‌ای در نظر گرفته است که آن‌ها را در این بخش مورد برسی قرار خواهیم داد.


Pipeهای Pure و Impure

Pipeها کلا در دو دسته‌ی Pure و Impure قرار می‌گیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
@Pipe({ name: 'impurePipe', pure: false })
تفاوت این Pipeها در زمان فراخوانی دوباره آنها است.


Pure Pipe

این نوع Pipeها تنها زمانی فراخوانی مجدد می‌شوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند  Date ، Array ، Function و Object نیز تغییر محض محسوب می‌شود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمی‌شوند:
numbers.push(10);
obj.name = ‘javad’;
زیرا با اضافه شدن عنصری به یک آرایه یا تغییر خصوصیتی از یک شیء، باعث تغییری در ارجاع آنها نمی‌شود و همانطور که اشاره شد، در عبارات از نوع آرایه و Object، فقط تغییر در ارجاع آن‌ها یک تغییر محض محسوب می‌شود.
حالا می‌توان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا به‌روزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت می‌گیرد).


Impure Pipe

این نوع Pipeها در اغلب رخدادهای کامپوننت از جمله فشره شدن کلید یا حرکت ماوس و رخدادهای دیگر فراخوانی مجدد می‌شوند. با در نظر گرفتن این نگرانی، هنگام پیاده سازی این نوع Pipeها باید مراقب بود؛ زیرا این نوع Pipeها با اجرای طولانی خود می‌توانند رابط کاربری شما را نابود کنند. برای درک کامل تفاوت این دو نوع از Pipeها مثالی را دنبال می‌کنیم.

مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایه‌ای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد می‌کنیم: 
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'evenNumbers'
})
export class EvenNumbersPipe implements PipeTransform {
  transform(numbers: Array<number>): Array<number> {
    var x=numbers.filter(r => r % 2 == 0);
    return x;
  }
}
همانطور که مشخص است این Pipe در متد transform، آرایه‌ای از اعداد را دریافت کرده و فقط اعداد زوج را بازگشت می‌دهد. حالا باید Pipe تعریف شده خود را در AppModule در قسمت declares تعریف کنیم.
// . . .
import { EvenNumbersPipe } from './pipes/even-numbers.pipe'
@NgModule({
  declarations: [
    . . .
    EvenNumbersPipe
  ],
 . . .
})
export class AppModule { }

سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیه‌ی اعداد از یک تا ده، تعریف می‌کنیم:
numbers: Array<number> = [1,2,3,4,5,6,7,8,9,10];
برای نمایش این اعداد در رابط کاربری تگ‌های زیر را به قالب کامپوننت خود اضافه می‌کنیم:
<h1>All numbers</h1>
<span *ngFor="let number of numbers">
  {{number}}
</span>
همچنین با استفاده از تگ‌های زیر یک input برای اضافه کردن مقدار جدید به آرایه درنظر می‌گیریم:
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers.push(number.value)" value="Add number"/>
</p>

تگ‌های زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه می‌کنیم:
<h1>even numbers</h1>
<span *ngFor="let number of numbers | evenNumbers">
  {{number}}
</span>
بعد از اجرای برنامه، یک عدد جدید زوج را به آرایه اضافه کنید. متوجه خواهید شد با اینکه لیست اعداد در قسمت All numbers به‌روز می‌شوند، ولی Pipe، متوجه تغییری بر روی آرایه نشده‌است و همچنان اعداد قبلی را نمایش می‌دهد. دلیل این امر همانطور که قبلا نیز اشاره شد، بخاطر Pure بودن Pipe و عدم فراخوانی مجدد این نوع Pipe‌ها در زمان اضافه شدن مقداری به آرایه یا تغییری در خصوصیت یک شیء است.

برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را به‌روز رسانی می‌کند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers = numbers.concat(number.value)" value="Add number"/>
</p>
با تغییر نحوه اضافه شدن عنصر به آرایه به شکل بالا خواهیم دید که با افزودن اعداد جدید، لیست اعداد زوج نیز در لحظه اعمال خواهند شد. این راه‌حل همیشه کارآمد نخواهد بود. همیشه تشخیص محل اضافه شدن عنصر به آرایه در برنامه کار ساده‌ای نیست تا در آنجا ارجاع آرایه را نیز تغییر دهیم. راه‌حل، استفاده از Impure Pipe است. کافی است متادیتای Pipe@ را هنگام تعریف به شکل زیر تغییر دهید:
@Pipe({
  name: 'evenNumbers',
  pure: false
})
export class EvenNumbersPipe implements PipeTransform {
   //…
}

کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شده‌اند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیاده‌سازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد. 
مطالب
آموزش فایرباگ - #6 - HTML Panel - Side Panels
در قسمت قبل توضیحاتی در مورد تب HTML ارائه کردیم.
در این قسمت توضیحات کاملی در مورد پنل‌های جانبیِ داخل پنل HTML می‌دهیم.



Side Panels

در پنل HTML درکنار ارائه امکاناتی برای مشاهده و کار با تگ‌های صفحه ، اطلاعات و امکانات دیگری هم برای تگ انتخاب شده در قسمت NodeView وجود دارد.
این امکانات در پنل هایی که سمت راست پنل اصلی قرار دارند گنجانده شده است که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView هستند.




1 - Style
در این تب استایل هایی که در حال حاظر بروی تگ انتخاب شده اعمال شده اند ، نمایش داده می‌شود.
در صورتی که موس را بروی مقادیر استایل هایی که جلوه‌ی بصری دارند بگیرید ، یک پاپ‌آپ کوچک نمایان می‌شود که مقدار را نمایش می‌دهد.


 Options Menu

هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب Style هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


  • Only Show Applied Styles
    در صورت انتخاب ، فقط استایل هایی که اعمال شده اند نمایش داده می‌شوند. (استایل‌های Overwrite شده نمایش داده نمی‌شوند.)
    (این گزینه قابلیت خوبی است ، اما چندبار برای بنده پیش آمده که این مورد به اشتباه استایلی که اعمال شده بود را هم Overwrite شده در نظر گرفته بود. پس در هین طراحی استایل و کار با CSS اگر احیانا یکی از استایل هایتان وجود نداشت و از وجود آن اطمینان داشتید ، غیرفعال کردن این گزینه را امتحان کنید.)

  • Show User Agent CSS
    با فعال کردن این گزینه ، استایل هایی که توسط مرورگر اعمال شده اند هم نمایش داده می‌شوند.

  • Expand Shorthand Properties
    با فعال کردن این گزینه ، استایل هایی که بصورت کوتاه شده تعریف شده اند را بصورت گسترده و باز شده نمایش می‌دهد.
    برای مثال ، دستور margin را بصورت margin-top , margin-right , margin-bottom , margin-left نمایش می‌دهد.

  • سه گزینه ی Colors As Hex ، Colors As RGB و Colors As HSL تعیین کننده‌ی فرمت نمایش رنگ‌ها هستند.

  • سه گزینه ی :active ، :hover و :focus هم برای تعیین کلاس کاذب برای تگ جاری کاربرد دارند.
    برای مثال شما می‌خواهید استایلی که یک لینک زمان موس برویش قرار دارد را بررسی کنید ، لینک را در NodeView انتخاب می‌کنید و سپس از گزینه‌ی :hover را فعال می‌کنید.


Panel

  • Element styles
    استایل هایی که بصورت inline (در خود تگ) تعریف شده اند هم در این قسمت نمایش داده می‌شود و نام rule آن element.style است.



  • Source Links
    در بالا-راست هر بخش ، یک لینک قرار دارد که لینک فایل استایلی است که در همان قسمت وجود دارد و عددی که در پرانتز قرار دارد ، شماره خط استایل در همان فایل است.
    اگر نام فایل با نام صفحه‌ی جاری برابر باشد ، به معنی وجود استایل در تگ <style> در صفحه‌ی جاری است و شماره‌ی بعد از # هم ایندکس تگ <style> است.
    (با کلیک بروی لینک فایل ، فایل در خط مورد نظر در پنل CSS نمایش داده می‌شود.)



  • Inherited rules
    rule‌های به ارث رسیده هم در قسمت‌های جداگانه به همراه استایل‌های به ارث رسیده نمایش داده می‌شود و تگی والد که استایل‌ها از آن به ارث رسیده اند هم در قسمت عنوان همان استایل‌ها نمایش قرار داده شده است. (با کلیک بروی آن ، در قسمت Nodeview انتخاب می‌شود.)



  • User agent rules
    استایل هایی که توسط مرورگر اعمال شده اند (User agent rules) ، با عبارت <System> در زیر لینک منبع استایل ، مشخص شده اند.



  • Overwritten styles
    استایل‌های overwrite شده ، با یک خط برویشان مشخص شده اند.



  • Inline editing
    استایل‌های نمایش داده شده در این پنل را براحتی و با کلیک کردن بروی نام یا مقدار هر یک از دستورات می‌توانید تغییر دهید.
    برای نوشتن دستورات و مقادیر آن‌ها می‌توانید از پیشنهاد‌های فایرباگ هم کمک بگیرید و با دکمه‌های Arrow Up و Arrow Down هم بین مقادیر مجاز حرکت کنید.
    دستورات یا مقادیر نا صحیح در هین تایپ ، با رنگ قرمز و مقادیر صحیح با رنگ سبز مشخص می‌شوند.
    (این امکان خیلی مفید است ، برای مثال می‌خواهید فونت‌های مختلف را برای یک استایل امتحان کنید ، دستور font-family را می‌نویسید و بعد از زدن Enter ، با دکمه های Arrow Up و Arrow Down در لحظه نتیجه‌ی اعمال فونت‌های مختلف و دردسترس را مشاهده می‌کنید و بهترین را بر می‌گزینید.
    یا برای یافتن بهترین مقدار margin ، بعد از دستور margin ، زدن کلید Enter ، وارد کردن یک عدد برای شروع ، می‌توان باز هم با دکمه های Arrow Up و Arrow Down به سرعت تغییر را در صفحه مشاهده کرد.)

  • Rendered font highlighted
    برای دستور font ، فایرباگ هوشمندانه عمل کرده و فونتی که در حال استفاده است را پررنگ می‌کند.
    این امکان برای یافتن خطاهای متداول هنگام تعریف فونت‌های غیر سیستمی ، بسیار مفید است.




 Context Menu

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


 گزینه Context توضیحات 
Copy Rule Declaration CSS selector  CSS Rule فعلی را به همراه استایل هایش در clipboard کپی می‌کند
Copy Style Declaration CSS selector  استایل‌های CSS Rule فعلی را در clipboard کپی می‌کند 
Copy Location source link  آدرس فایل تعریف CSS Rule را در clipboard کپی می‌کند
Open in New Tab source link   آدرس فایل تعریف CSS Rule را در تب جدید باز می‌کند
Edit Element Style... everywhere  امکان تعریف استایل‌های درون تگ (inline) را محیا می‌کند
Add Rule... everywhere  یک Rule جدید ایجاد می‌کند
CSS Rule هایی که در حال حاظر وجود دارد را هم پیشنهاد می‌دهد
Delete "<rule name>" CSS selector  CSS Rule فعلی را حذف می‌کند
New Property... CSS rule  یک استایل جدید به CSS Rule فعلی اضافه می‌کند
Edit "<property name>"... CSS property  Property فعلی را به حالت ویرایش می‌برد
راه دیگر ویرایش Property ، کلیک بروی آن است
Delete "<property name>" CSS property  Property فعلی را حذف می‌کند
Disable "<property name>" CSS property  Property فعلی را غیر فعال می‌کند
را سریع‌تر ، کلیک کردن در ناحیه‌ی پشت Property ، بروی علامت قرمز رنگ است
Refresh everywhere  محتویات پنل را بروز می‌کند
Inspect in DOM Panel
CSS rule  CSS Rule فعلی را در پنل DOM برای بررسی باز می‌کند
Inspect in CSS Panel CSS rule   CSS Rule فعلی را در پنل CSS برای بررسی باز می‌کند
<Default Editor Name> CSS rule  فایل تعریف استایل‌ها را در ادیتور تعریف شده باز می‌کند
(این گزینه در صورت تعریف ادیتور در تنظیمات FireBug نمایش داده خواهد شد)


2 - Computed

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


Style Tracing
برای ردیابی استایل‌ها ، استایل‌ها به ترتیب اعمال شدنشان مرتب شده اند و اولین مقدار ، مقداری است که اعمال شده است.
مقادیر Overwrite بصورت خط کشیده شده و استایل‌های Overwrite شده بصورت خاکستری-کمرنگ نمایش داده می‌شوند.
هر استایل هم مانند تب Style ، یک لینک به منبع خود دارد.


Options Menu

  • Show User Agent CSS
    در صورت انتخاب ، فقط استایل هایی که اعمال شده اند نمایش داده می‌شوند.

  • Sort alphabetically
    در صورت انتخاب ، استایل‌ها به ترتیب الفبا ، و درصورت عدم انتخاب بصورت گروه بندی نمایش داده می‌شوند.

  • Show Mozilla Specific Styles
    در صورت انتخاب ، استایل‌های مخصوص Mozilla را نمایش می‌دهد. (استایل هایی با پیشوند -moz-)

  • سه گزینه‌ی Colors As Hex ، Colors As RGB و Colors As HSL تعیین کننده‌ی فرمت نمایش رنگ‌ها هستند.


Context Menu

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

 گزینه Context توضیحات 
Expand All Styles everywhere  CSS Rule فعلی را به همراه استایل هایش در clipboard کپی می‌کند
Collapse All Styles everywhere  استایل‌های CSS Rule فعلی را در clipboard کپی می‌کند 
Inspect in DOM panel styles   آدرس فایل تعریف CSS Rule را در تب جدید باز می‌کند
Copy Location
style source link  امکان تعریف استایل‌های درون تگ (inline) را محیا می‌کند
Open in New Tab style source link  یک Rule جدید ایجاد می‌کند
CSS Rule هایی که در حال حاظر وجود دارد را هم پیشنهاد می‌دهد
Inspect in CSS panel style source link  CSS Rule فعلی را حذف می‌کند
<Default Editor Name> style source link  فایل تعریف استایل‌ها را در ادیتور تعریف شده باز می‌کند
(این گزینه در صورت تعریف ادیتور در تنظیمات FireBug نمایش داده خواهد شد)


3 - Layout

در این تب ، مقادیر Box Model بصورت بصری نمایش می‌دهد. می‌توان با کلیک کردن بروی هریک از مقادیر ، آن را ویرایش کرد. (این تغییر بصورت inline در تگ اعمال می‌شود.)
با حرکت موس بروی قسمت‌های مختلف ، می‌توان همان قسمت‌ها را در صفحه بصورت خط کشی شده مشاهده کرد.
(البته ظاهرا در ورژن 1.10.4 که بنده استفاده می‌کنم ، عملیات ویرایش مقادیر به درستی انجام نمی‌شود.)



 Options Menu

  • Show Rulers and Guides
    در صورت انتخاب ، خط‌های راهنما را هنگام حرکت موس بروی اجزای Box Model در صفحه نمایش می‌دهد.




4 - DOM

این پنل اطلاعات DOM تگ جاری را نمایش می‌دهد.
این پنل تمام قابلیت‌های پنل DOM اصلی را دارا می‌باشد.
(در مقالات آینده با تب DOM آشنا خواهیم شد.)



نظرات مطالب
پیاده سازی JSON Web Token با ASP.NET Web API 2.x
- اگر بر روی سرور machine key تنظیم نشده باشد، IIS هربار که ری‌استارت می‌شود، یک machine key جدید را تولید خواهد کرد و محل نگهداری آن حافظه‌ی سرور است. بنابراین اگر زیاد مشکل لاگین دارید و اطلاعات قابل رمزگشایی نیست و نال بازگشت داده می‌شود، یعنی برنامه‌ی شما بر روی سرور زیاد ری‌استارت می‌شود (متد Application_Start فایل gloal.asax را لاگ کنید تا این مساله مشخص شود).
- امکان تنظیم machine key به صورت دستی در فایل web.config برنامه وجود دارد:
  <system.web>
    <machineKey decryptionKey="Enter decryption Key here" 
                validation="SHA1" 
                validationKey="Enter validation Key here" />
  </system.web>

IIS امکان تولید این کلیدها و به روز رسانی خودکار فایل web.config برنامه را دارد:


روش دوم تولید آن با کدنویسی است:
    protected static string GenerateKey(int length)
    {
        RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
        byte[] buff = new byte[length];
        rngCsp.GetBytes(buff);
        StringBuilder sb = new StringBuilder(buff.Length * 2);
        for (int i = 0; i < buff.Length; i++)
            sb.Append(string.Format("{0:X2}", buff[i]));
        return sb.ToString();
    }

    string validationKey = GenerateKey(64);
    string  decryptionKey = GenerateKey(32);