نظرات مطالب
EF Code First #3
منظور من مثال کتاب و نویسنده نیست ، سوال من اگر من تعداد روابط یک به چند بین دو جدول زیاد بشه بر روی کارایی تاثیر دارد یا نه ؟ 
مثال واقعی هم اینکه یک جدول داریم یک کاربر درخواست کننده داره یک کار بر تایید کننده یک انجام دهنده ، یک تحویل دهنده  ، تعداد کاربران در اینجا n تا نیست 4 کاربر است یا مثلا ممکن است با توجه به موضوع بیشتر هم باشد ولی n نیست.
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
با سلام ، در مورد استفاده از Claim‌ها با استفاده از روش شما ، بعد از لاگین نام و نام خانوادگی کاربر را که قرار است در سایت نمایش بدهد را نمایش نداده ، بعد از refresh صفحه نمایش میدهد ؟!
نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت سیزدهم- فعالسازی اعتبارسنجی دو مرحله‌ای
- روش متداول آن به همین صورت است. با یک برنامه‌ی Angular هم کار کنید، ابتدا کاربر باید به این صفحه‌ی لاگین خارجی مراجعه کند. این یک برنامه‌ی مستقل است؛ مثل لاگین کاربر به گوگل و سپس هدایت آن به برنامه‌ی اصلی.
- ولی در کل، بله. روش کار آن با فعالسازی GrantTypes.ResourceOwnerPassword و ایجاد یک new TokenClient، در مستندات اصلی آن توضیح داده شده‌است.
مطالب
پیاده سازی سیاست‌های دسترسی پویای سمت سرور و کلاینت در برنامه‌های Blazor WASM
فرض کنید در حال توسعه‌ی یک برنامه‌ی Blazor WASM هاست شده هستید و می‌خواهید که نیازی نباشد تا به ازای هر صفحه‌ای که به برنامه اضافه می‌کنید، یکبار منوی آن‌را به روز رسانی کنید و نمایش منو به صورت خودکار توسط برنامه صورت گیرد. همچنین در این حالت نیاز است در قسمت مدیریتی برنامه، بتوان به صورت پویا، به ازای هر کاربری، مشخص کرد که به کدامیک از صفحات برنامه دسترسی دارد و یا خیر و به علاوه اگر به صفحاتی دسترسی ندارد، مشخصات این صفحه، در منوی پویا برنامه ظاهر نشود و همچنین با تایپ آدرس آن در نوار آدرس مرورگر نیز قابل دسترسی نباشد. امن سازی پویای سمت کلاینت، یک قسمت از پروژه‌است؛ قسمت دیگر چنین پروژه‌ای، لیست کردن اکشن متدهای API سمت سرور پروژه و انتساب دسترسی‌های پویایی به این اکشن متدها، به کاربران مختلف برنامه‌است.


دریافت کدهای کامل این پروژه

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


پیشنیازها

در پروژه‌ی فوق برای شروع به کار، از اطلاعات مطرح شده‌ی در سلسله مطالب زیر استفاده شده‌است:

- «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
- پیاده سازی اعتبارسنجی کاربران در برنامه‌های Blazor WASM؛ قسمت‌های 31 تا 33 .
- «غنی سازی کامپایلر C# 9.0 با افزونه‌ها»
- «مدیریت مرکزی شماره نگارش‌های بسته‌های NuGet در پروژه‌های NET Core.»
- «کاهش تعداد بار تعریف using‌ها در C# 10.0 و NET 6.0.»
- «روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامه‌ی ASP.NET Core»


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

صفحات امن سازی شده‌ی سمت کلاینت، با ویژگی Authorize مشخص می‌شوند. بنابراین قید آن الزامی است، تا صرفا جهت کاربران اعتبارسنجی شده، قابل دسترسی شوند. در اینجا می‌توان یک نمونه‌ی سفارشی سازی شده‌ی ویژگی Authorize را به نام ProtectedPageAttribute نیز مورد استفاده قرار داد. این ویژگی از AuthorizeAttribute ارث‌بری کرده و دقیقا مانند آن عمل می‌کند؛ اما این اضافات را نیز به همراه دارد:
- به همراه یک Policy از پیش تعیین شده به نام CustomPolicies.DynamicClientPermission است تا توسط قسمت‌های بررسی سطوح دسترسی پویا و همچنین منوساز برنامه، یافت شده و مورد استفاده قرار گیرد.
- به همراه خواص اضافه‌تری مانند GroupName و Title نیز هست. GroupName نام سرتیتر منوی dropdown نمایش داده شده‌ی در منوی اصلی برنامه‌است و Title همان عنوان صفحه که در این منو نمایش داده می‌شود. اگر صفحه‌ی محافظت شده‌ای به همراه GroupName نباشد، یعنی باید به صورت یک آیتم اصلی نمایش داده شود. همچنین در اینجا یک سری Order هم درنظر گرفته شده‌اند تا بتوان ترتیب نمایش صفحات را نیز به دلخواه تغییر داد.


نمونه‌ای از استفاده‌ی از ویژگی فوق را در مسیر src\Client\Pages\Feature1 می‌توانید مشاهده کنید که خلاصه‌ی آن به صورت زیر است:
 @attribute [ProtectedPage(GroupName = "Feature 1", Title = "Page 1", GlyphIcon = "bi bi-dot", GroupOrder = 1, ItemOrder = 1)]

ویژگی ProtectedPage را معادل یک ویژگی Authorize سفارشی، به همراه چند خاصیت بیشتر، جهت منوساز پویای برنامه درنظر بگیرید.


نیاز به لیست کردن صفحات علامتگذاری شده‌ی با ویژگی ProtectedPage

پس از اینکه صفحات مختلف برنامه را توسط ویژگی ProtectedPage علامتگذاری کردیم، اکنون نوبت به لیست کردن پویای آن‌ها است. اینکار توسط سرویس ProtectedPagesProvider صورت می‌گیرد. این سرویس با استفاده از Reflection، ابتدا تمام IComponentها یا همان کامپوننت‌های تعریف شده‌ی در برنامه را از اسمبلی جاری استخراج می‌کند. بنابراین اگر نیاز دارید که این جستجو در چندین اسمبلی صورت گیرد، فقط کافی است ابتدای این کدها را تغییر دهید. پس از یافت شدن IComponent ها، فقط آن‌هایی که دارای RouteAttribute هستند، پردازش می‌شوند؛ یعنی کامپوننت‌هایی که به همراه مسیریابی هستند. پس از آن بررسی می‌شود که آیا این کامپوننت دارای ProtectedPageAttribute هست یا خیر؟ اگر بله، این کامپوننت در لیست نهایی درج خواهد شد.


نیاز به یک منوساز پویا جهت نمایش خودکار صفحات امن سازی شده‌ی با ویژگی ProtectedPage

اکنون که لیست صفحات امن سازی شده‌ی توسط ویژگی ProtectedPage را در اختیار داریم، می‌توانیم آن‌ها را توسط کامپوننت سفارشی NavBarDynamicMenus به صورت خودکار نمایش دهیم. این کامپوننت لیست صفحات را توسط کامپوننت NavBarDropdownMenu نمایش می‌دهد.


تهیه‌ی جداول و سرویس‌های ثبت دسترسی‌های پویای سمت کلاینت


جداول و فیلدهای مورد استفاده‌ی در این پروژه را در تصویر فوق ملاحظه می‌کنید که در پوشه‌ی src\Server\Entities نیز قابل دسترسی هستند. در این برنامه نیاز به ذخیره سازی اطلاعات نقش‌های کاربران مانند نقش Admin، ذخیره سازی سطوح دسترسی پویای سمت کلاینت و همچنین سمت سرور است. بنابراین بجای اینکه به ازای هر کدام، یک جدول جداگانه را تعریف کنیم، می‌توان از همان طراحی ASP.NET Core Identity مایکروسافت با استفاده از جدول UserClaimها ایده گرفت. یعنی هر کدام از این موارد، یک Claim خواهند شد:


در اینجا نقش‌ها با Claim استانداردی به نام http://schemas.microsoft.com/ws/2008/06/identity/claims/role که توسط خود مایکروسافت نامگذاری شده و سیستم‌های اعتبارسنجی آن بر همین اساس کار می‌کنند، قابل مشاهده‌است. همچنین دو Claim سفارشی دیگر ::DynamicClientPermission:: برای ذخیره سازی اطلاعات صفحات محافظت شده‌ی سمت کلاینت و ::DynamicServerPermission::  جهت ذخیره سازی اطلاعات اکشن متدهای محافظت شده‌ی سمت سرور نیز تعریف شده‌اند. رابطه‌ای این اطلاعات با جدول کاربران، many-to-many است.


به این ترتیب است که مشخص می‌شود کدام کاربر، به چه claimهایی دسترسی دارد.

برای کار با این جداول، سه سرویس UsersService، UserClaimsService و UserTokensService پیش بینی شده‌اند. UserTokens اطلاعات توکن‌های صادر شده‌ی توسط برنامه را ذخیره می‌کند و توسط آن می‌توان logout سمت سرور را پیاده سازی کرد؛ از این جهت که JWTها متکی به خود هستند و تا زمانیکه منقضی نشوند، در سمت سرور پردازش خواهند شد، نیاز است بتوان به نحوی اگر کاربری غیرفعال شد، از آن ثانیه به بعد، توکن‌های او در سمت سرور پردازش نشوند که به این نکات در مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» پیشتر پرداخته شده‌است.
اطلاعات این سرویس‌ها توسط اکشن متدهای UsersAccountManagerController، در اختیار برنامه‌ی کلاینت قرار می‌گیرند.


نیاز به قسمت مدیریتی ثبت دسترسی‌های پویای سمت کلاینت و سرور

قبل از اینکه بتوان قسمت‌های مختلف کامپوننت NavBarDynamicMenus را توضیح داد، نیاز است ابتدا یک قسمت مدیریتی را جهت استفاده‌ی از لیست ProtectedPageها نیز تهیه کرد:


در این برنامه، کامپوننت src\Client\Pages\Identity\UsersManager.razor کار لیست کردن کاربران، که اطلاعات آن‌را از کنترلر UsersAccountManagerController دریافت می‌کند، انجام می‌دهد. در مقابل نام هر کاربر، دو دکمه‌ی ثبت اطلاعات پویای دسترسی‌های سمت کلاینت و سمت سرور وجود دارد. سمت کلاینت آن توسط کامپوننت UserClientSidePermissions.razor مدیریت می‌شود و سمت سرور آن توسط UserServerSidePermissions.razor.
کامپوننت UserClientSidePermissions.razor، همان لیست صفحات محافظت شده‌ی توسط ویژگی ProtectedPage را به صورت گروه بندی شده و به همراه یک سری chekmark، ارائه می‌دهد. اگر در اینجا صفحه‌ای انتخاب شد، اطلاعات آن به سمت سرور ارسال می‌شود تا توسط Claim ای به نام ::DynamicClientPermission:: به کاربر انتخابی انتساب داده شود.


شبیه به همین عملکرد در مورد دسترسی سمت سرور نیز برقرار است. UserServerSidePermissions.razor، لیست اکشن متدهای محافظت شده را از کنترلر DynamicPermissionsManagerController دریافت کرده و نمایش می‌دهد. این اطلاعات توسط سرویس ApiActionsDiscoveryService جمع آوری می‌شود. همچنین این اکشن متدهای ویژه نیز باید با ویژگی Authorize(Policy = CustomPolicies.DynamicServerPermission) مزین شده باشند که نمونه مثال آن‌ها را در مسیر src\Server\Controllers\Tests می‌توانید مشاهده کنید. اگر در سمت کلاینت و قسمت مدیریتی آن، اکشن متدی جهت کاربر خاصی انتخاب شد، اطلاعات آن ذیل Claimای به نام ::DynamicServerPermission::  به کاربر انتخابی انتساب داده می‌شود.



بازگشت اطلاعات پویای دسترسی‌های سمت کلاینت از API

تا اینجا کامپوننت‌های امن سازی شده‌ی سمت کلاینت و اکشن متدهای امن سازی شده‌ی سمت سرور را توسط صفحات مدیریتی برنامه، به کاربران مدنظر خود انتساب دادیم و توسط سرویس‌های سمت سرور، اطلاعات آن‌ها را در بانک اطلاعاتی ذخیره کردیم. اکنون نوبت به استفاده‌ی از claims تعریف شده و مرتبط با هر کاربر است. پس از یک لاگین موفقیت آمیز توسط UsersAccountManagerController، سه توکن به سمت کاربر ارسال می‌شوند:
- توکن دسترسی: اطلاعات اعتبارسنجی کاربر به همراه نام و نقش‌های او در این توکن وجود دارند.
- توکن به روز رسانی: هدف از آن، دریافت یک توکن دسترسی جدید، بدون نیاز به لاگین مجدد است. به این ترتیب کاربر مدام نیاز به لاگین مجدد نخواهد داشت و تا زمانیکه refresh token منقضی نشده‌است، برنامه می‌تواند از آن جهت دریافت یک access token جدید استفاده کند.
- توکن سطوح دسترسی پویای سمت کلاینت: در اینجا لیست ::DynamicClientPermission::ها به صورت یک توکن مجزا به سمت کاربر ارسال می‌شود. این اطلاعات به توکن دسترسی اضافه نشده‌اند تا بی‌جهت حجم آن اضافه نشود؛ از این جهت که نیازی نیست تا به ازای هر درخواست HTTP به سمت سرور، این لیست حجیم claims پویای سمت کلاینت نیز به سمت سرور ارسال شود. چون سمت سرور از claims دیگری به نام ::DynamicServerPermission:: استفاده می‌کند.


اگر دقت کنید، هم refresh-token و هم DynamicPermissions هر دو به صورت JWT ارسال شده‌اند. می‌شد هر دو را به صورت plain و ساده نیز ارسال کرد. اما مزیت refresh token ارسال شده‌ی به صورت JWT، انجام اعتبارسنجی خودکار سمت سرور اطلاعات آن است که دستکاری سمت کلاینت آن‌را مشکل می‌کند.
این سه توکن توسط سرویس BearerTokensStore، در برنامه‌ی سمت کلاینت ذخیره و بازیابی می‌شوند. توکن دسترسی یا همان access token، توسط ClientHttpInterceptorService به صورت خودکار به تمام درخواست‌های ارسالی توسط برنامه الصاق خواهد شد.


مدیریت خودکار اجرای Refresh Token در برنامه‌های Blazor WASM

دریافت refresh token از سمت سرور تنها قسمتی از مدیریت دریافت مجدد یک access token معتبر است. قسمت مهم آن شامل دو مرحله‌ی زیر است:
الف) اگر خطاهای سمت سرور 401 و یا 403 رخ دادند، ممکن است نیاز به refresh token باشد؛ چون احتمالا یا کاربر جاری به این منبع دسترسی ندارد و یا access token دریافتی که طول عمر آن کمتر از refresh token است، منقضی شده و دیگر قابل استفاده نیست.
ب) پیش از منقضی شدن access token، بهتر است با استفاده از refresh token، یک access token جدید را دریافت کرد تا حالت الف رخ ندهد.

- برای مدیریت حالت الف، یک Policy ویژه‌ی Polly طراحی شده‌است که آن‌را در کلاس ClientRefreshTokenRetryPolicy مشاهده می‌کنید. در این Policy ویژه، هرگاه خطاهای 401 و یا 403 رخ دهند، با استفاده از سرویس جدید IClientRefreshTokenService، کار به روز رسانی توکن انجام خواهد شد. این Policy در کلاس program برنامه ثبت شده‌است. مزیت کار با Policyهای Polly، عدم نیاز به try/catch نوشتن‌های تکراری، در هر جائیکه از سرویس‌های HttpClient استفاده می‌شود، می‌باشد.

- برای مدیریت حالت ب، حتما نیاز به یک تایمر سمت کلاینت است که چند ثانیه پیش از منقضی شدن access token دریافتی پس از لاگین، کار دریافت access token جدیدی را به کمک refresh token موجود، انجام دهد. پیاده سازی این تایمر را در کلاس ClientRefreshTokenTimer مشاهده می‌کنید که محل فراخوانی و راه اندازی آن یا پس از لاگین موفق در سمت کلاینت و یا با ریفرش صفحه (فشرده شدن دکمه‌ی F5) و در کلاس آغازین ClientAuthenticationStateProvider می‌باشد.



نیاز به پیاده سازی Security Trimming سمت کلاینت

از داخل DynamicPermissions دریافتی پس از لاگین، لیست claimهای دسترسی پویای سمت کلاینت کاربر لاگین شده استخراج می‌شود. بنابراین مرحله‌ی بعد، استخراج، پردازش و اعمال این سطوح دسترسی پویای دریافت شده‌ی از سرور است.
سرویس BearerTokensStore، کار ذخیره سازی توکن‌های دریافتی پس از لاگین را انجام می‌دهد و سپس با استفاده از سرویس DynamicClientPermissionsProvider، توکن سوم دریافت شده که مرتبط با لیست claims دسترسی کاربر جاری است را پردازش کرده و تبدیل به یک لیست قابل استفاده می‌کنیم تا توسط آن بتوان زمانیکه قرار است آیتم‌های منوها را به صورت پویا نمایش داد، مشخص کنیم که کاربر، به کدامیک دسترسی دارد و به کدامیک خیر. عدم نمایش قسمتی از صفحه که کاربر به آن دسترسی ندارد را security trimming گویند. برای نمونه کامپوننت ویژه‌ی SecurityTrim.razor، با استفاده از نقش‌ها و claims یک کاربر، می‌تواند تعیین کند که آیا قسمت محصور شده‌ی صفحه توسط آن قابل نمایش به کاربر است یا خیر. این کامپوننت از متدهای کمکی AuthenticationStateExtensions که کار با user claims دریافتی از طریق JWTها را ساده می‌کنند، استفاده می‌کند. یک نمونه از کاربرد کامپوننت SecurityTrim را در فایل src\Client\Shared\MainLayout.razor می‌توانید مشاهده کنید که توسط آن لینک Users Manager، فقط به کاربران دارای نقش Admin نمایش داده می‌شود.
نحوه‌ی مدیریت security trimming منوی پویای برنامه، اندکی متفاوت است. DynamicClientPermissionsProvider لیست claims متعلق به کاربر را بازگشت می‌دهد. این لیست پس از لاگین موفقیت آمیز دریافت شده‌است. سپس لیست کلی صفحاتی را که در ابتدای برنامه استخراج کردیم، در طی حلقه‌ای از سرویس ClientSecurityTrimmingService عبور می‌دهیم. یعنی مسیر صفحه و همچنین دسترسی‌های پویای کاربر، مشخص هستند. در این بین هر مسیری که در لیست claims پویای کاربر نبود، در لیست آیتم‌های منوی پویای برنامه، نمایش داده نمی‌شود.


نیاز به قطع دسترسی به مسیرهایی در سمت کلاینت که کاربر به صورت پویا به آن‌ها دسترسی ندارد

با استفاده از ClientSecurityTrimmingService، در حلقه‌ای که آیتم‌های منوی سایت را نمایش می‌دهد، موارد غیرمرتبط با کاربر جاری را حذف کردیم و نمایش ندادیم. اما این حذف، به این معنا نیست که اگر این آدرس‌ها را به صورت مستقیم در مرورگر وارد کند، به آن‌ها دسترسی نخواهد داشت. برای رفع این مشکل، نیاز به پیاده سازی یک سیاست دسترسی پویای سمت کلاینت است. روش ثبت این سیاست را در کلاس DynamicClientPermissionsPolicyExtensions مشاهده می‌کنید. کلید آن همان CustomPolicies.DynamicClientPermission که در حین تعریف ProtectedPageAttribute به عنوان مقدار Policy پیش‌فرض مقدار دهی شد. یعنی هرگاه ویژگی ProtectedPage به صفحه‌ای اعمال شد، از این سیاست دسترسی استفاده می‌کند که پردازشگر آن DynamicClientPermissionsAuthorizationHandler است. این هندلر نیز از ClientSecurityTrimmingService استفاده می‌کند. در هندلر context.User جاری مشخص است. این کاربر را به متد تعیین دسترسی مسیر جاری به سرویس ClientSecurityTrimming ارسال می‌کنیم تا مشخص شود که آیا به مسیر درخواستی دسترسی دارد یا خیر؟


نیاز به قطع دسترسی به منابعی در سمت سرور که کاربر به صورت پویا به آن‌ها دسترسی ندارد

شبیه به ClientSecurityTrimmingService سمت کلاینت را در سمت سرور نیز داریم؛ به نام ServerSecurityTrimmingService که کار آن، پردازش claimهایی از نوع ::DynamicServerPermission::  است که در صفحه‌ی مدیریتی مرتبطی در سمت کلاینت، به هر کاربر قابل انتساب است. هندلر سیاست دسترسی پویایی که از آن استفاده می‌کند نیز DynamicServerPermissionsAuthorizationHandler می‌باشد. این سیاست دسترسی پویا با کلید CustomPolicies.DynamicServerPermission در کلاس ConfigureServicesExtensions تعریف شده‌است. به همین جهت هر اکشن متدی که Policy آن با این کلید مقدار دهی شده باشد، از هندلر پویای فوق جهت تعیین دسترسی پویا عبور خواهد کرد. منطق پیاده سازی شده‌ی در اینجا، بسیار شبیه به مطلب «سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا» است؛ اما بدون استفاده‌ی از ASP.NET Core Identity.


روش اجرای برنامه

چون این برنامه از نوع Blazor WASM هاست شده‌است، نیاز است تا برنامه‌ی Server آن‌را در ابتدا اجرا کنید. با اجرای آن، بانک اطلاعاتی SQLite برنامه به صورت خودکار توسط EF-Core ساخته شده و مقدار دهی اولیه می‌شود. لیست کاربران پیش‌فرض آن‌را در اینجا می‌توانید مشاهده کنید. ابتدا با کاربر ادمین وارد شده و سطوح دسترسی سایر کاربران را تغییر دهید. سپس بجای آن‌ها وارد سیستم شده و تغییرات منوها و سطوح دسترسی پویا را بررسی کنید.
مطالب
چک لیست شروع به ساخت یک نرم افزار بزرگ یا متوسط
 کتابها و منابع آموزشی بسیاری در جهت یادگیری برنامه سازی و مهندسی نرم­ افزار وجود دارند که اکثراً هم مطالب مفید و بسیار خوبی را ارائه می­دهند؛ با این‌حال یکی از سؤالات بزرگی که بعد از مطالعه آنها در ذهن افراد ممکن است پیش بیاید این است که با خود می­پرسند حالا چه کنم؟ از کجا شروع کنم؟ در واقع ذهن افراد پر است از اطلاعات تخصصی بسیار مفید ولی نمیدانند آنها را چگونه سرهم بندی کنند تا یک سیستم نرم­ افزاری قابل اتکا تولید کنند. توسعه گران با تجربه با گذشت زمان، مطالعه کد نرم­ افزارهای موجود، مطالعه مضاعف، شرکت در بحثهای تخصصی و ... معمولاً می­دانند که باید از کجا شروع کنند. در اینجا بنده سعی کرد‌ه‌ام مواردی را که توسعه گران باتجربه در شروع ساخت یک نرم ­افزار متوسط یا بزرگ با رویکرد توسعه برای وب در مورد آنها تصمیم می­گیرند، به صورت مختصر توضیح دهم. طبیعی هست که ممکن است این لیست کامل نباشد، نظرات دوستان میتواند آنرا کاملتر کند.

در اینجا غیر از مورد زمانبندی انجام پروژه سعی شده است به دیگر موارد غیره از قبیل شناخت نیازمندیها، نحوه بستن قرارداد، نحوه قیمت دهی و ... اشاره نشود.

 

در ابتدا در مورد موضوعات کلی و عمومی بحث می‌کنیم.

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

2- زمانبندی انجام پروژه، به نظر خود بنده، سخت‌ترین و اساسی‌ترین مرحله، برای هر پروژه‌­ای، زمانبندی مناسب آن است که نیازمندی اساسی آن، شناخت سایر مواردی است که در این متن بدان‌ها اشاره می­شود. زمانبندی دقیق، قرار ملاقاتها و تحویل به‌موقع پیش نمایشهای نرم­ افزار، ارتباط مستمر با کارفرما و تحویل حتی زودتر از موعد پروژه باعث رضایت بیشتر کارفرما و حس اطمینان بیشتر خواهد شد. اگر در تحویل پروژه دیرکرد وجود داشته باشد، باعث دلسردی کارفرما و نوعی تبلیغ منفی خواهد بود. حتی زمانبندی و تحویل به موقع پروژه برای کارفرما بیشتر از کیفیت اهمیت دارد.

3- انتخاب معماری نرم­ افزار، معماری نرم ­افزار در اصل تعیین کننده نحوه قطعه بندی و توزیع تکه‌های نرم افزار، نحوه ارتباط اجزاء،، قابلیت تست پذیری، قابلیت نگهداری و قابلیت استفاده مجدد از کدهای تولید شده می­باشد. یکی از اهداف اساسی‌ای که باید در معماری نرم­‌افزار بدان توجه کرد، قابلیت استفاده مجدد از کد است. در یک معماری خوب ما قطعاتی درست خواهیم کرد که به‌راحتی می­توانیم از آن در نرم‌افزارهای دیگر نیز استفاده کنیم. البته قابلیت تست پذیری و قابلیت نگهداری نیز حداقل به همان اندازه اهمیت دارند. در این سایت موارد بسیار زیاد و کاملی جهت ساخت معماری مناسب و design patterns وجود دارد که می­توانید در اینجا یا اینجا مشاهده کنید.

 4- قابلیت اجرا بر روی پلتفرمهای مختلف، هرچند این مورد ممکن است بیشتر به نظر کارفرما بستگی داشته باشد، اما در کل اگر کارفرما بتواند سیستم را در پلتفرمهای مختلفی اجرا کند، راضی‌تر خواهد شد. اگر قصد فروش نرم‌­افزار طراحی شده را داشته باشیم، در اینصورت نیز می­توانیم کاربران پلتفرمهای مختلف را مورد هدف قرار دهیم یا سیستم را در سرورهای مختلفی میزبانی کنیم.

5- انتخاب سیستم بانک اطلاعاتی و نحوه ارتباط با آن. باید تصمیم بگیرید که از چند سیستم بانک اطلاعاتی، چگونه و به چه منظوری استفاده خواهید کرد. مواردی وجود دارند که سیستم را طوری طراحی کرد‌ه‌اند تا در زمان بهره برداری امکان انتخاب بانک‌های اطلاعاتی یا نحوه ذخیره اطلاعات برای مدیر سیستم وجود دارد. مثلا در BlogEngine.net میتوان انتخاب کرد که اطلاعات در SQL Server ذخیره شوند یا در سیستم فایل مبتنی بر XML . بحثهای بسیار زیادی در این سایت و کل فضای وب پیرامون نحوه انتخاب و استفاده از ORM ها، چگونگی معماری مناسب آن وجود دارد. بطور مثال همیشه بحث سر اینکه از الگوی Repository استفاده شود یا نشود وجود دارد! باید به خودمان پاسخ دهیم که آیا واقعاً نیاز است که سیستم را برای امکان استفاده از Orm‌های مختلف طراحی کنیم؟

6- نحوه ماژول بندی سیستم و امکان افزودن راحت ماژولهای جدید به آن. امروزه و با افزایش کاربران محصولات انفورماتیک که باعث بیشتر شدن سواد مصرف کننده در این زمینه و بالطبع افزایش نیازهای وی شده، همیشه احتمال اینکه کارفرما موارد جدیدی را بخواهد وجود خواهد داشت. باید سیستم را طوری طراحی کرد که حتی بتوان بدون توقف اجرای آن موارد جدید (پلاگینهای جدید) را بدان افزود و اجرا کرد.

7- میزان مشارکت دیگران در رفع نیازمندیهای کابران. ممکن است این گزینه در درجه اول زیاد با اهمیت جلوه ندهد، اما با تعمق در وبسایت‌ها و نرم‌­افزارهای بزرگ که هم اکنون در دنیا صاحب نامی شده‌اند می‌بینیم همه آنها تمهیداتی اندیشیده‌اند تا با وجود کپسوله کردن موارد پس زمینه، امکاناتی را در جهت مشارکت دیگران فراهم کنند. اکثر شبکه­‌های اجتماعی api هایی را مهیا کرده­ اند که افراد ثالث می­توانند از آنها استفاده کنند. اکثر سیستم‌های مدیریت محتوا و ابزارهای e-commerce تمهیداتی را برای راحتی ساخت plugin و api‌های برای راحتی برقراری ارتباط اشخاص ثالث اندیشیده‌اند. از نظر این جانب موارد 6 و 7 برای ادامه حیات و قابلیت رقابت پذیری پروژه از درجه اهمیت زیادی برخوردار است.

8- معماری Multi tenancy بلی یا خیر؟ Multi tenancy یک از بحثهای مهم رایانش ابری است. در این حالت فقط یک نمونه از نرم­ افزار در سمت سرور در حال اجراست ولی کاربر یا گروهی از کاربران دید یا تنظیمات متفاوتی از آن‌را دارند.

در ادامه به موارد فنی‌تری خواهیم پرداخت:

9- بحث انتخاب ابزار Dependency injection مناسب و مهیا سازی امکاناتی جهت هرچه راحت‌تر کردن امکان تنظیم و register کردن اشیا بدان. نحوه پیکربندی مناسب این مورد می­تواند کد نویسی را برایتان بسیار راحت کند. دات نت تیپس مطالب بسیاری را در این مورد ارائه داده است میتوانید اینجا را ببینید.

10- کشینگ. استفاده از یک سیستم کشینگ مناسب در ارتباط با بانکهای اطلاعاتی و یا سایر سیستمهای ذخیره و بازیابی اطلاعات می­تواند کمک بسیاری در پرفرمنس برنامه داشته باشد. سیستمها و روشهای مختلفی در مورد کشینگ وجود دارند. می‌توانید برای اطلاعات بیشتر اینجا را مطالعه فرمایید.

11- Logging. یک سیستم لاگر مناسب می­تواند وارنینگ‌ها و خطاهای بوجود آمده در سیستم را در یک رسانه ذخیره سازی حفظ کند و شما به عنوان توسعه دهنده می­توانید با مطالعه آن نسبت به رفع خطاهای احتمالی و بهبود در نسخه‌های آتی کمک بگیرید.

12- Audit logging یا Activity logging و Entity History. می­توانید کل یا برخی از فعالیتهای کاربر را در یک رسانه ذخیره سازی، ذخیره کنید، از قبیل زمان ورود و خروج، آی‌پی مورد استفاده، سیستم عامل، مرورگر، بازبینی از صفحه وغیره. همچنین در audit logging میتوانید زمانهای دقیق تغییرات مختلف موجود در موجودیتهای سیستم، فرد انجام دهنده تغییرات، سرویس انجام دهنده تغییرات، مدت زمان سپری شده و ... را ذخیره کرد. Entity History : ممکن است تصمیم بگیرید که کل اتفاقاتی را که برای یک موجودیت در طول زمان حیاتش در سیستم می‌افتد، ذخیره کنید.

13- Eventing ، Background Worker‌ها و Backgroudn jobs ( Scheduled tasks ). باید سیستم را طوری طراحی کرد که بتواند به تغییرات و اتفاقات افتاده در سیستم پاسخ دهد. همچنین این مورد یکی از نیازمندیهای معماری بر اساس پلاگین است. Background Worker‌ها در واقع کارهایی هستند که در پس زمینه انجام میشوند و نیازی نیست که کاربر برای اتمام آن منتظر بماند؛ مثلاً ارسال ایمیل خوش آمدگویی را میتوان با آن انجام داد. Background jobs کمی متفاوت هستند در واقع اینها فعالیتهای پس زمینه­ای هستند که ممکن است در فواصل زمانی مختلف اتفاق بیافتند، مثل پاکسازی کش در فواصل زمانی مناسب. در سیستمهای مختلف تمهیداتی برای ذخیره سازی فعالیتهایی که توسط background jobs انجام میشود اندیشیده می­شود.

14- پیکربندی صحیح نحوه ذخیره و بازیابی تنظیمات سیستم. در یک سیستم ممکن است شما تنظیمات متعددی را در اختیار کاربر و یا حتی خودتان قرار دهید. باید سیستم را طوری طراحی کنید که بتواند با راحت‌ترین و سریعترین روش ممکن به تنظیمات موجود دستیابی داشته باشد.

15- خطاهای کاربر را در نظر بگیریم، باید یادمان باشد کاربر ممکن الخطاست و ما برای رضایت مشتری و قابلیت اتکای هرچه بیشتر برنامه باید سیستم را طوری طراحی کنیم که امکان برگشت از خطا برای کاربر وجود داشته باشد. مثلاً در SoftDelete مواردی که حذف می­شوند در واقع به طور کامل از بانک اطلاعاتی حذف نمیشوند بلکه تیک حذف شده میخورند. پس امکان بازگردانی وجود خواهد داشت.

16- Mapping یا Object to object mapping. در توسعه شی‌‌ءگرا مخصوصاً در معماری‌هایی مثل MVC یا Domain driven در موارد بسیاری نیاز خواهید داشت که مقادیر اشیاء مختلفی را در اشیای دیگری کپی کنید. سیستمهای زیادی برای این کار موجود هستند. باید تلاش کرد ضمن اینکه یک سیستم مناسب انتخاب کنیم، باید تمهیدی بیاندیشیم که تنظیمات آن شامل کد نویسی هرچه کمتری باشد.

17- Authorization یا تعیین هویت. باید با مطالعه و بررسی، سیستم و ابزار مناسبی را برای هویت سنجی اعضاء، تنظیم نقشها و دسترسی‌های کاربران انتخاب کرد. باید امکان عضویت از طریق شبکه‌های اجتماعی مختلف را مورد بررسی قرار داد.

18- سرویس‌های Realtime. کاربری یکی از مطالب شما را می­‌پسندد و شما نوتیفیکشن آنرا سریع در صفحه‌­ای که باز کردید می­بینید. این یک مورد بسیار کوچکی از استفاده از سرویسهای realtime هست. ابزارهای مختلفی برای زبانها و فریم‌ورکهای مختلف وجود دارند؛ مثلاً میتوانید اینجا را مطالعه کنید.

19- هندل کردن خطاهای زمان اجرا، در سیستمهای قدیمی یکی از کابوس‌های کاربران، قطعی سیستم، هنگ کردن با کوچکترین خطا و موارد این چنینی بود. با تنظیم یک سیستم Exception handling مناسب هم میتوانیم گزارشاتی از خطاهای بوجود آمده را تهیه کنیم، هم میتوانیم کاربر را در جهت انجام صحیح کارها هدایت کنیم و هم از کرش بیجای نرم‌افزار جلوگیری کنیم.

20- استفاده از منابع ابری یا توزیع شده، امروزه برای بسیاری از کارها تمهیداتی از طرف شرکتهای بزرگ به صورت رایگان و یا غیر رایگان اندیشیده شده است که به راحتی می­توان از آنها استفاده کرد. برای نمونه میتوان از سرویسهای Email به عنوان ساده‌ترین و معمول‌ترین این سیستمها یاد کرد. اما امروزه شرکتها حتی امکاناتی جهت ذخیره سازی داده‌های blob (مجموعه ای از بایتها با حجم زیاد) را ارائه می­دهند؛ امکانات دیگری نظیر کم کردن حجم تصاویر، تبدیل انواع mime type‌ها و ...

21- امنیت، فریم‌ورکها اغلب موارد امنیتی پایه‌ای را به صورت مطلوب یا نسبتا مطلوبی رعایت می­کنند؛ ولی با این‌حال باید در مورد امنیت سیستمی که توسعه می‌دهیم مطالعه داشته باشیم و موارد امنیتی ضروری را رعایت کنیم و همیشه مواظب باشیم که آنها را رعایت کنیم.
مطالب
معماری وب گرا (سبکی از سرویس گرایی)
در ابتدای مقاله، پیش از آن که وارد بحث معماری وب گرا بشوم، یک سوال را مطرح میکنم که شاید برای شما هم جالب باشد. آن سوال اینست : آیا SOA پاسخی برای همه چیز در 
حوزه معماری است؟ شاید اینطور نباشد. به ترکیب زیر دقت کنید :
WOA  / SOA + WWW + REST
ترکیب فوق ما را چند قدم جلوتر برده و کاستی‌های سرویس گرایی را پر می‌کند و ما را یاری می‌کند تا اپلیکیشن‌های کامل end-to-end بسازیم.

اگر چه مفهوم WOA شاید چندان فراگیر نباشد، ولی بسیاری از آنچه تاکنون در سطح اینترنت می‌بینیم شالوده همین تفکر وب گرایی است.

معماری وب گرا یا Web-Oriented Architecture در 2006 توسط  Nick Gall از گروه Gartner ابداع شده است. 
معماری وب گرا یک سبک معماری نرم افزاری است که معماری سرویس گرا (Service-Oriented Architecture) را در راستای اپلیکیشن‌های تحت وب گسترش می‌دهد. 
معماری وب گرا در اصل توسط بسیاری از شبکه‌های اجتماعی و وب سایت‌های شخصی ساخته شده است.

تعریف رسمی Gartner از معماری وب گرا چنین است :
 “معماری وب گرا یا Web-Oriented Architecture سبکی معمارگونه از معماری سرویس گرا یا همان Service-Oriented Architecture می‌باشد که به یکپارچگی سیستم‌ها و کاربران از طریق ابررسانه‌های مرتبط با هم در سطح جهانی بر اساس معماری وب می‌پردازد.
 این نوع معماری بر تمامی اینترفیس‌ها (رابط کاربری و  رابط کاربردی برنامه نویسی) به منظور دستیابی به تاثیرات شبکه‌ی جهانی از طریق پنج عنصر رابط اساسی ذیل تاکید دارد :
  • شناسایی منابع
  • بکارگیری منابع از طریق نمایش آنها (منابع وب)
  • پیام‌های خودتوصیفی
  • ابررسانه بعنوان قلب تپنده موقعیت برنامه
  • درگیر نکردن برنامه “
Nick Gall همچنین فرمولی را برای تعریف معماری وب گرا (WOA) ارائه داده است که بدین شکل است:
WOA = SOA + WWW + REST

Dion Hinchcliffe مدعی است که معماری وب گرا چنین است: 

“مجموعه‌ای از هسته پروتکل‌های وب مانند HTTP, XML است و اینکه تنها تفاوت معماری سرویس گرای سنتی و مفاهیم معماری وب گرا اینست که WOA از REST حمایت می‌کند. REST متدی به طور فزاینده محبوب ، قدرتمند و ساده به منظور اعمال نفوذ پروتکل انتقال ابر متن HTTP بعنوان یک وب سرویس در چارچوب حقوق خودش است.“

پشته‌ی معماری وب گرا WOA شامل چنین مواردی است :
  • توزیع (HTTP , Feeds)
  • ترکیب (Hypermedia , Mashups)
  • امنیت (Open ID, SSL)
  • قابلیت انتقال داده (XML,RDF)
  • قابلیت نمایش داده (ATOM, JSON)
  • متدهای انتقال (REST, HTTP, Bit Torrent)

بطور کل باید گفت WOA هر چیزی است که در اینترنت وجود دارد و هر چیزی که بر مبنای REST می‌باشد والبته سرویس گراست. در کلامی گویا باید گفت امروزه معماری وب گرا پراستفاده‌ترین نوع معماری در جهان تا به امروز بوده است. طبق پیش بینی Gartner در 2014 سبک معماری وب گرای (مبتنی بر REST) در 80% سازمان هایی که سرویس گرایی را دنبال می‌کنند فراگیر خواهد شد. واقعا صحت یا عدم صحت تحقق این پیش بینی Gartner شاید مهم نباشد ؛ چرا که هر کسی می‌بایست WOA را بشناسد.
اما REST چیست ؟ Representational State Transfer سبکی از معماری نرم افزار برای سیستم‌های ابررسانه توزیع شده مانند شبکه جهانی وب است (منبع : ویکی پدیا). با هم اصول REST را مرور کنیم:
  • هر چیزی یک منبع است.
  • هر منبعی یک تمثیل دارد.
  • هر منبعی یک نام بخصوص دارد.
  • انتقال موقعیت نیازمند کشف و شهود (Discovery) است.
  • پروتکل شبکه پایه WOA می‌باشد

بطور خلاصه WOA را بررسی می‌کنیم :
  • اطلاعات در قالب منابع (Resources) نمایش می‌یابند.
  • منابع توسط URI‌ها شناخته می‌شوند.
  • منابع از طریق HTTP اداره می‌شوند.
  • معاهدات به صورت ضمنی در نمایش منابع می‌باشند.
  • رابط‌ها بطور کلی عام هستند.

معماری وب گرای سازمانی
معماری وب گرای سازمانی یا Enterprise Web Oriented Architecture (EWOA) یکی از زیر سبک‌های SOA می‌باشد. EWOA مجموعه ای از عناصر، اصول و فرآیندهای معماری مبتنی بر وب می‌باشد. وب سایت‌ها و برنامه‌های کاربردی جدید مانند Google AdSense, Wikipedia و دیگر سرویس‌های RESTful از WOA استفاده می‌کنند.
مثال حال حاضر WOA را می‌توان Google’s Open Social  یا MindTouch دانست. در حال حاضر Mobile API بنایی اساسی بر تمرکز در استفاده از تکنولوژی WOA را دارند. ساخت چنین سرویس هایی با استفاده از پروتکل‌های ساده شده وب نظیر Rest , JSON بیش از پیش آسان شده است. این پروتکل‌ها برای توسعه دهندگان وب بسیار راحت‌تر است چرا که CPU و پهنای باند کمتری را طلب می‌کنند. این پروتکل‌ها بیشتر بخاطر شبکه‌های اجتماعی بزرگ نظیر فیس بوک ، آمازون ، توییتر و … شناخته شده‌اند.
MindTouch هم یک شرکت اوپن سورس و یک سکوی مستندسازی استراتژیک و نوعی جدید از ECM می‌باشد. از جمله پروژه‌هایی که ارایه کرده‌است می‌توان به موارد ذیل اشاره کرد :
DReAM
SGML Reader
MindTouch Core/2010
در ادامه، بکارگیری REST را در قالب شبکه جهانی وب (W3) در قالب جدول زیر با دیدگاه مقایسه‌ای با تلفیق در وب بررسی می‌کنیم:
{بنده می‌گویم} REST بدون WWW بی معناست. REST با Web است که تکمیل می‌شود و معنا پیدا می‌کند.

بررسی مزایای WOA
  • ساده سازی توسعه پذیری، مقیاس پذیری
  • کاهش زمان توسعه ویژگی‌های جدید
  • کاهش زمان مهندسی مورد نیاز برای یکپارچه سازی
  • سازنده فرصت‌هایی جدید برای mash-ups و دیگر داستان‌های غیرقابل پیش بینی کاربری
  • اما وضعیت ارتباطی کلاینت‌ها و سرورها در WOA چگونه است ؟
  • سرویس‌ها وابسته به دیگر سرویس‌ها هستند.
  • ارتباطات از طریق HTTP صورت می‌گیرد.
  • کلاینت‌ها حکم منبع و سرویس دهی به دیگر کلاینت‌ها را دارند.
  • مقیاس پذیری ، توسعه پذیری == اتصالات داخی 


بعنوان مثال می‌توان با ترکیب تصاویر و آدرس‌های مختلف دانشگاه‌های تهران، یک map Mashup درست کرد.


برای Photo Mashup ابزار Color Picker هم هست که امکان جستجوی تصاویر را بر اساس رنگ فراهم می‌کند و از سرویس اشتراک گذاری عکس Flickr استفاده می‌کند که در این آدرس قابل استفاده است.


معماری Mashup هم مثل معماری MVC (البته با تفاوت‌های فاحش) سه لایه‌ای است :


لایه نمایش / تعامل کاربر (همان رابط کاربری است)

تکنولوژی‌ها : HTML/XHTML, CSS, Javascript, Asynchronous JS and Xml (Ajax).

وب سرویس‌ها : عملکرد محصول از طریق سرویس‌های API هم قابل دسترسی است

تکنولوژی‌ها : XMLHTTPRequest, XML-RPC, JSON-RPC, SOAP, REST

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

تکنولوژی‌ها : XML , JSON , KML

از نظر معماری  Mashup  دارای 2 سبک است : الف) مبتنی بر وب – ب) مبتنی بر سرور


 در ادامه با هم نمونه ای از استقرار معماری وب گرا WOA را در سازمان، بصورت شماتیک می‌بینیم. با هم مشاهده می‌کنیم با این پیاده سازی، موانع سر راه ما کاهش پیدا می‌کنند و سرعت یکپارچگی افزایش پیدا می‌کند. بدین صورت که می‌توان از قدرت شبکه جهانی وب در جهت انتقال محتوای مورد نیازمان به هر جا و در هر زمانی بهره جست.


شاید برای شما سوال پیش بیاید که ما در معماری وب گرا بحث می‌کردیم، اصلا چرا وارد مفهوم Mashup شدیم؟


به‌عبارت فنی‌تر چرا معماری وب گرا (WOA) برای Mashups اهمیت دارد ؟

 پاسخ یک کلمه است : REST . همانطور که بالاتر نیز اشاره کردم، Mashup از REST بهره می‌برد. به منظور افزایش اطلاعات در رابطه با REST باید گفت Roy Fielding آنرا بنیان نهاده‌است. میخواهید او را بهتر معرفی کنم؟ وی یکی از خالقان HTTP است و مگر می‌توان وب را بدون HTTP فرض کرد که مهمترین پروتکل انتقال ابر متن در جهان و پروتکل زیربنایی وب است؟! 

REST به خوبی با معماری اینترنت عجین شده است! بپرسید چرا؟ چون پروتکل اصلی اینترنت HTTP است و هر دوی این‌ها از یک ذهن نشات گرفته و او کسی نیست جز Roy Fielding. اما باید بگویم REST یک استاندارد نیست؛ با وجود سادگی بسیار زیاد، تنها یک سبک استفاده از HTTP است.


REST همچنین از متدهای اختصاصی HTTP نظیر GET, PUT , POST , DELETE در بالای یک URL استفاده می‌کند تا نشان دهد چه رویدادی رخ می‌دهد.


در پایان گفته‌ها در رابطه با REST باید بگویم ATOM همان REST است. منظورم از ATOM ویرایشگر معروف متنی نیست که برای نوشتن کدهای برنامه نویسی استفاده قرار می‌گیرد؛ آنرا غالبا به شکل Atom می‌نویسند چرا که مخفف چند کلمه نیست و یک کلمه خاص است  اما ATOM یک استاندارد وب به زبان XML است که برای خوراک وب بعنوان جایگزینی برای RSS استفاده می‌شود. ATOM را با AtomPub یا APP اشتباه نگیرید؛ چرا که APP پروتکل انتشاری است مبتنی بر پروتکل انتقال ابرمتن (HTTP) و برای به روزرسانی محتوی وب مورد استفاده قرار می‌گیرد.


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

فرمول جالبی هم برای تعریف وب ارائه شده‌است که با هم می‌بینیم :


HTTP + URIs = Web


ظرافت فرمول بالا به اهمیت پروتکل زیربنایی وب یعنی HTTP اشاره دارد. URI هم مجموعه‌ای از رشته‌هاست که برای شناسایی یک منبع خاص تحت وب به کار می‌روند. در شکل زیر رابطه بین URI , URN , URL را بررسی می‌کنیم. URI تشکیل شده‌است از URL و URN .URL متد دسترسی به منبع را مشخص می‌کند، در حالیکه URN تنها مشخص کننده نام منبع می‌باشد و هیچگونه روشی را برای دسترسی به ما ارائه نمی‌دهد. بعنوان مثال یک شماره ISBN کتاب، یک نوع URN است. 





مطالب
آشنایی با CLR: قسمت هفتم
کدهای IL و تایید آن ها

ساختار استکی
IL از ساختار استک استفاده می‌کند. به این معنی که تمامی دستور العمل‌ها داخل آن push شده و نتیجه‌ی اجرای آن‌ها pop می‌شوند. از آنجا که IL به طور مستقیم ارتباطی با ثبات‌ها ندارد، ایجاد زبانهای برنامه نویسی جدید بر اساس CLR بسیار راحت‌تر هست و عمل کامپایل، تبدیل کردن به کدهای IL می‌باشد.

بدون نوع بودن(Typeless)
از دیگر مزیت‌های آن این است که کدهای IL بدون نوع هستند. به این معنی که موقع افزودن دستورالعملی به داخل استک، دو عملگر وارد می‌شوند و هیچ جداسازی در رابطه با سیستم‌های 32 یا 64 بیت صورت نمی‌گیرد و موقع اجرای برنامه است که تصمیم می‌گیرد از چه عملگرهایی باید استفاده شود.

Virtual Address Space
بزرگترین مزیت این سیستم‌ها امنیت و مقاومت آن هاست. موقعی که تبدیل کد IL به سمت کد بومی صورت می‌گیرد، CLR فرآیندی را با نام verification یا تاییدیه، اجرا می‌کند. این فرآیند تمامی کدهای IL را بررسی می‌کند تا از امنیت کدها اطمینان کسب کند. برای مثال بررسی می‌کند که هر متدی صدا زده می‌شود با تعدادی پارامترهای صحیح صدا زده شود و به هر پارامتر آن نوع صحیحش پاس شود و مقدار بازگشتی هر متد به درستی استفاده شود. متادیتا شامل اطلاعات تمامی پیاده سازی‌ها و متدها و نوع هاست که در انجام تاییدیه مورد استفاده قرار می‌گیرد.
در ویندوز هر پروسه، یک آدرس مجازی در حافظه دارد و این جدا سازی حافظه و ایجاد یک حافظه مجازی کاری لازم اجراست. شما نمی‌توانید به کد یک برنامه اعتماد داشته باشید که از حد خود تخطی نخواهد کرد و فرآیند برنامه‌ی دیگر را مختل نخواهد کرد. با خواندن و نوشتن در یک آدرس نامعتبر حافظه، ما این اطمینان را کسب می‌کنیم که هیچ گاه تخطی در حافظه صورت نمی‌گیرد.
قبلا به طور مفصل در این مورد ذخیره سازی در حافظه صحبت کرده ایم.

Hosting
از آنجا که پروسه‌های ویندوزی به مقدار زیادی از منابع سیستم عامل نیاز دارند که باعث کاهش منابع و محدودیت در آن می‌شوند و نهایت کارآیی سیستم را پایین می‌آورد، ولی با کاهش تعدادی برنامه‌های در حال اجرا به یک پروسه‌ی واحد می‌توان کارآیی سیستم را بهبود بخشید و منابع کمتری مورد استفاده قرار می‌گیرند که این یکی دیگر از مزایای کدهای managed نسبت به unmanaged است. CLR در حقیقت این قابلیت را به شما می‌دهد تا چند برنامه‌ی مدیریت شده را در قالب یک پروسه به اجرا درآورید. هر برنامه‌ی مدیریت شده به طور پیش فرض بر روی یک appDomain اجرا می‌گردد و هر فایل EXE روی حافظه‌ی مجازی مختص خودش اجرا می‌شود. هر چند پروسه‌هایی از قبیل IIS و SQL Server که پروسه‌های CLR را پشتیبانی یا هاست می‌کنند می‌توانند تصمیم بگیرند که آیا appDomain‌ها را در یک پروسه‌ی واحد اجرا کنند یا خیر که در مقاله‌های آتی آن را بررسی می‌کنیم.

کد ناامن یا غیر ایمن UnSafe Code
به طور پیش فرض سی شارپ کدهای ایمنی را تولید می‌کند، ولی این اجازه را می‌دهد که اگر برنامه نویس بخواهد  کدهای ناامن بزند، قادر به انجام آن باشد. این کدهای ناامن دسترسی مستقیم به خانه‌های حافظه و دستکاری بایت هاست. این مورد قابلیت قدرتمندی است که به توسعه دهنده اجازه می‌دهد که با کدهای مدیریت نشده ارتباط برقرار کند یا یک الگوریتم با اهمیت زمانی بالا را جهت بهبود کارآیی، اجرا کند.
 هر چند یک کد ناامن سبب ریسک بزرگی می‌شود و می‌تواند وضعیت بسیاری از ساختارهای ذخیره شده در حافظه را به هم بزند و امنیت برنامه را تا حد زیادی کاهش دهد.  به همین دلیل سی شارپ نیاز دارد تا تمامی متدهایی که شامل کد unsafe هستند را با کلمه کلیدی unsafe علامت گذاری کند. همچنین کمپایلر سی شارپ نیاز دارد تا شما این کدها را با سوئیچ unsafe/ کامپایل کنید.

موقعیکه جیت تلاش دارد تا یک کد ناامن را کامپایل کند، اسمبلی را بررسی می‌کند که آیا این متد اجازه و تاییدیه آن را دارد یا خیر. آیا  System.Security.Permissions.SecurityPermission با فلگ SkipVerification مقدار دهی شده است یا خیر. اگر پاسخ مثبت بود JIT آن‌ها را کامپایل کرده و اجازه‌ی اجرای آن‌ها را می‌دهد. CLR به این کد اعتماد می‌کند و امیدوار است که آدرس دهی مستقیم و دستکاری بایت‌های حافظه موجب آسیبی نگردد. ولی اگر پاسخ منفی بود، یک استثناء از نوع System.InvalidProgramException یا System.Security.VerificationException را ایجاد می‌کند تا از اجرای این متد جلوگیری به عمل آید. در واقع کل برنامه خاتمه میابد ولی آسیبی به حافظه نمی‌زند.

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

در این زمینه مایکروسافت ابزار سودمندی را با نام PEVerify .exe را معرفی کرده است که به بررسی تمامی متدهای یک اسمبلی پرداخته و در صورت وجود کد ناامن به شما اطلاع میدهد. بهتر است از این موضوع اطلاع داشته باشید که این ابزار نیاز دارد تا به متادیتاهای یک اسمبلی نیاز داشته باشید. باید این ابزار بتواند به تمامی ارجاعات آن دسترسی داشته باشد که در مورد عملیات بایندینگ در آینده بیشتر صحبت می‌کنیم.

IL و حقوق حق تالیف آن
بسیاری از توسعه دهندگان از اینکه IL هیچ شرایطی برای حفظ حق تالیف آن‌ها ایجاد نکرده است، ناراحت هستند. چرا که ابزارهای زیادی هستند که با انجام عملیات مهندسی معکوس می‌توانند به الگوریتم آنان دست پیدا کنند و میدانید که IL خیلی سطح پایین نیست و برگرداندن آن به شکل یک کد، کار راحت‌تری هست و بعضی ابزارها کدهای خوبی هم ارائه می‌کنند. از دست این ابزارها می‌توان به ILDisassembler و  JustDecompile اشاره کرد.
اگر علاقمند هستید این عیب را برطرف کنید، می‌توانید از ابزارهای ثالث که به ابزارهای obfuscator (یک نمونه سورس باز ) معروف هستند استفاده کنید تا با کمی پیچیدگی در متادیتاها، این مشکل را تا حدی برطرف کنند. ولی این ابزارها خیلی کامل نیستند، چرا که نباید به کامپایل کردن کار لطمه بزنند. پس اگر باز خیلی نگران این مورد هستید می‌توانید الگوریتم‌های حساس و اساسی خود را در قالب unmanaged code ارائه کنید که در بالا اشاراتی به آن کرده‌ایم.
برنامه‌های تحت وب به دلیل عدم دسترسی دیگران از امنیت کاملتری برخوردار هستند.
مطالب
دریافت اطلاعات بیشتر از Social Provider ها در VS 2013
هنگامی که یک پروژه جدید ASP.NET را در VS 2013 می‌سازید و متد احراز هویت آن را Individual User Accounts انتخاب می‌کنید، قالب پروژه، امکانات لازم را برای استفاده از تامین کنندگان ثالث، فراهم می‌کند، مثلا مایکروسافت، گوگل، توییتر و فیسبوک. هنگامی که توسط یکی از این تامین کننده‌ها کاربری را احراز هویت کردید، می‌توانید اطلاعات بیشتری درخواست کنید. مثلا عکس پروفایل کاربر یا لیست دوستان او. سپس اگر کاربر به اپلیکیشن شما سطح دسترسی کافی داده باشد می‌توانید این اطلاعات را دریافت کنید و تجربه کاربری قوی‌تر و بهتری ارائه کنید.

در این پست خواهید دید که چطور می‌شود از تامین کننده Facebook اطلاعات بیشتری درخواست کرد. پیش فرض این پست بر این است که شما با احراز هویت فیسبوک و سیستم کلی تامین کننده‌ها آشنایی دارید. برای اطلاعات بیشتر درباره راه اندازی احراز هویت فیسبوک به  این لینک  مراجعه کنید.

برای دریافت اطلاعات بیشتر از فیسبوک مراحل زیر را دنبال کنید.

  • یک اپلیکیشن جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
  • احراز هویت فیسبوک را توسط کلید هایی که از Facebook دریافت کرده اید فعال کنید. برای اطلاعات بیشتر در این باره می‌توانید به این لینک مراجعه کنید.
  • برای درخواست اطلاعات بیشتر از فیسبوک، فایل Startup.Auth.cs را مطابق لیست زیر ویرایش کنید.
 List<string> scope = newList<string>() { "email", "user_about_me", "user_hometown", "friends_about_me", "friends_photos" };
 var x = newFacebookAuthenticationOptions();
 x.Scope.Add("email");
 x.Scope.Add("friends_about_me");
 x.Scope.Add("friends_photos");
 x.AppId = "636919159681109";
 x.AppSecret = "f3c16511fe95e854cf5885c10f83f26f";
 x.Provider = newFacebookAuthenticationProvider()
{
    OnAuthenticated = async context =>
    {
         //Get the access token from FB and store it in the database and
        //use FacebookC# SDK to get more information about the user
        context.Identity.AddClaim(
        new System.Security.Claims.Claim("FacebookAccessToken",
                                             context.AccessToken));
    }
};
 x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
 app.UseFacebookAuthentication(x);

در خط 1 مشخص می‌کنیم که چه scope هایی از داده را می‌خواهیم درخواست کنیم.
از خط 10 تا 17 رویداد OnAuthenticated را مدیریت می‌کنیم که از طرف Facebook OWIN authentication اجرا می‌شود. این متد هر بار که کاربری با فیسبوک خودش را احراز هویت می‌کند فراخوانی می‌شود. پس از آنکه کاربر احراز هویت شد و به اپلیکیشن سطح دسترسی لازم را اعطا کرد، تمام داده‌ها در FacebookContext ذخیره می‌شوند. 
خط 14 شناسه FacebookAccessToken را ذخیره می‌کند. ما این آبجکت را از فیسبوک دریافت کرده و از آن برای دریافت لیست دوستان کاربر استفاده می‌کنیم.
نکته: در این مثال تمام داده‌ها بصورت Claims ذخیره می‌شوند، اما اگر بخواهید می‌توانید از ASP.NET Identity برای ذخیره آنها در دیتابیس استفاده کنید.
در قدم بعدی لیست دوستان کاربر را از فیسبوک درخواست می‌کنیم. ابتدا فایل Views/Shared/_LoginPartial.cshtml را باز کنید و لینک زیر را به آن بیافزایید.
 <li>
      @Html.ActionLink("FacebookInfo", "FacebookInfo","Account")
</li>

 هنگامی که کاربری وارد سایت می‌شود و این لینک را کلیک می‌کند، ما لیست دوستان او را از فیسبوک درخواست می‌کنیم و بهمراه عکس‌های پروفایل شان آنها را لیست می‌کنیم.
تمام Claim‌ها را از UserIdentity بگیرید و آنها را در دیتابیس ذخیره کنید. در این قطعه کد ما تمام Claim هایی که توسط OWIN دریافت کرده ایم را می‌خوانیم، و شناسه FacebookAccessToken را در دیتابیس عضویت ASP.NET Identity ذخیره می‌کنیم.
//
        // GET: /Account/LinkLoginCallback
        publicasyncTask<ActionResult> LinkLoginCallback()
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
            if (loginInfo == null)
            {
                return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
            }
            var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
            if (result.Succeeded)
            {
                var currentUser = await UserManager.FindByIdAsync(User.Identity.GetUserId());
                //Add the Facebook Claim
                await StoreFacebookAuthToken(currentUser);
                return RedirectToAction("Manage");
            }
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }
خط 14-15 شناسه FacebookAccessToken را در دیتابیس ذخیره می‌کند.
StoreFacebookAuthToken تمام اختیارات (claim)‌های کاربر را از UserIdentity می‌گیرد و Access Token را در قالب یک User Claim در دیتابیس ذخیره می‌کند. اکشن LinkLoginCallback هنگامی فراخوانی می‌شود که کاربر وارد سایت شده و یک تامین کننده دیگر را می‌خواهد تنظیم کند.
اکشن ExternalLoginConfirmation هنگام اولین ورود شما توسط تامین کنندگان اجتماعی مانند فیسبوک فراخوانی می‌شود.
در خط 26 پس از آنکه کاربر ایجاد شد ما یک FacebookAccessToken را بعنوان یک Claim برای کاربر ذخیره می‌کنیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
        {
            if (User.Identity.IsAuthenticated)
            {
                return RedirectToAction("Manage");
            }
 
            if (ModelState.IsValid)
            {
                // Get the information about the user from the external login provider
                var info = await AuthenticationManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    return View("ExternalLoginFailure");
                }
                var user = newApplicationUser() { UserName = model.Email };
                var result = await UserManager.CreateAsync(user);
                if (result.Succeeded)
                {
                    result = await UserManager.AddLoginAsync(user.Id, info.Login);
                    if (result.Succeeded)
                    {
                        await StoreFacebookAuthToken(user);
                        await SignInAsync(user, isPersistent: false);
                        return RedirectToLocal(returnUrl);
                    }
                }
                AddErrors(result);
            }
 
            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }


اکشن ExternalLoginCallback هنگامی فراخوانی می‌شود که شما برای اولین بار یک کاربر را به یک تامین کننده اجتماعی اختصاص می‌دهید. در خط 17 شناسه دسترسی فیسبوک را بصورت یک claim برای کاربر ذخیره می‌کنیم.
//
        // GET: /Account/ExternalLoginCallback
        [AllowAnonymous]
        publicasyncTask<ActionResult> ExternalLoginCallback(string returnUrl)
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }
 
            // Sign in the user with this external login provider if the user already has a login
            var user = await UserManager.FindAsync(loginInfo.Login);
            if (user != null)
            {
                //Save the FacebookToken in the database if not already there
                await StoreFacebookAuthToken(user);
                await SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", newExternalLoginConfirmationViewModel { Email = loginInfo.Email });
            }
        }
در آخر شناسه FacebookAccessToken را در دیتابیس ASP.NET Identity ذخیره کنید.
privateasyncTask StoreFacebookAuthToken(ApplicationUser user)
        {
            var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
            if (claimsIdentity != null)
            {
                // Retrieve the existing claims for the user and add the FacebookAccessTokenClaim
                var currentClaims = await UserManager.GetClaimsAsync(user.Id);
                var facebookAccessToken = claimsIdentity.FindAll("FacebookAccessToken").First();
                if (currentClaims.Count() <=0 )
                {
                    await UserManager.AddClaimAsync(user.Id, facebookAccessToken);
                }

پکیج Facebook C#SDK را نصب کنید. http://nuget.org/packages/Facebook
فایل AccountViewModel.cs را باز کنید و کد زیر را اضافه کنید.
    public class FacebookViewModel
     {
         [Required]
         [Display(Name = "Friend's name")]
         public string Name { get; set; }
  
        public string ImageURL { get; set; }
    }

کد زیر را به کنترلر Account اضافه کنید تا عکس‌های دوستان تان را دریافت کنید.
//GET: Account/FacebookInfo
[Authorize]
publicasyncTask<ActionResult> FacebookInfo()
{
    var claimsforUser = await UserManager.GetClaimsAsync(User.Identity.GetUserId());
    var access_token = claimsforUser.FirstOrDefault(x => x.Type == "FacebookAccessToken").Value;
    var fb = newFacebookClient(access_token);
    dynamic myInfo = fb.Get("/me/friends");
    var friendsList = newList<FacebookViewModel>();
    foreach (dynamic friend in myInfo.data)
    {
        friendsList.Add(newFacebookViewModel()
           {
               Name = friend.name,
               ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large"
           });
    }
 
    return View(friendsList);
}

در پوشه Views/Account یک نمای جدید با نام FacebookInfo.cshtml بسازید و کد Markup آن را مطابق لیست زیر تغییر دهید.
@model IList<WebApplication96.Models.FacebookViewModel>
 @if (Model.Count > 0)
 {
     <h3>List of friends</h3>
     <div class="row">
             @foreach (var friend in Model)
             {
               <div class="col-md-3">
                <a href="#" class="thumbnail">
                  <img src=@friend.ImageURL alt=@friend.Name />
                 </a>
               </div>
              }
     </div>
 }
در این مرحله، شما می‌توانید لیست دوستان خود را بهمراه عکس‌های پروفایل شان دریافت کنید.
پروژه را اجرا کنید و توسط Facebook وارد سایت شوید. باید به سایت فیسبوک هدایت شوید تا احراز هویت کنید و دسترسی لازم را به اپلیکیشن اعطا کنید. پس از آن مجددا به سایت خودتان باید هدایت شوید.
حال هنگامی که روی لینک FacebookInfo کلیک می‌کنید باید صفحه ای مشابه تصویر زیر ببینید.

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

مطالب
آشنایی با Permission Level ها در شیرپوینت 2010
شیرپوینت به شما به عنوان یک Farm Admin امکان مدیریت نقش‌ها و سطوح امنیتی را در سطح بسیار جزء می‌دهد. به این معنا که می‌توانید یک کاربر را در یک سایت در نقش مدیر و مالک آن سایت انتصاب داده و در سایتی دیگر به همان شخص امکان خواندن از فقط یک لیست خاص را بدهید . در این پست با جزییات بیشتری در باب ایجاد و مدیریت افراد در گروه‌ها آشنا می‌شویم

پس از نصب شیرپوینت ، لیستی مانند زیر به عنوان Permission Level‌ها برای شما نمایش داده می‌شود : 

برای مشاهده این سطوح در سایت خود می‌توانید به منوی Site Action رفته و گزینه Site Permission را انتخاب کنید . 

به طور کل ، سه مورد از موارد فوق استفاده بیشتری از بقیه دارند : Full Control ، Contribute و Read .

(برای مثال Design سطحی بین Full Control و Contribute است )

در سطح Read ، کاربر میتواند فقط برای استفاده در حد خواندن از محتوا به آن دسترسی داشته باشد

در سطح Contribute ، کاربر میتواند در حد انجام عملیات CRUD روی یک لیست یا کتابخانه موجود دسترسی داشته باشد (امکان تعریف کتابخانه یا لیست جدیدی را ندارد)

در سطح Full Control ، کاربر امکان انجام هر گونه عملیاتی را دارد . 

اما این موارد خود می‌تواند در حد یک لیست باشد و یا در حد یک Site Collection .
 علاوه بر موارد فوق نقش‌های دیگری هم هستند مانند Farm Administrator که به دلیل استفاده کمتر از آن‌ها در اینجا راجع به آن صحبت نمی‌کنم . چرا که این نقش‌ها تقریبا یکتا و Unique بوده و فقط برای تعدادی محدود از افراد در قالب یک سازمان تعریف می‌شود . 

هنگامی که یک Site Collection ایجاد میشود شیرپوینت 3 گروه امنیتی یا Security Group ایجاد میکند : Owners و Members و Visitors . 
متناسب با 3 سطح تعریف شده در بالا ، Owners دارای سطح Full Control و Members دارای سطح Contribute و Visitors دارای سطح Read هستند . 
( تصویر زیر می‌تواند گویای این مسئله باشد ) 


اگر شما برای سایت خود یک Sub Site تعریف کنید ، دقیقا همان ویژگی‌های امنیتی برای آن نیز نمایش داده می‌شوند و اصطلاحا از والد ارث بری کرده است . البته به شما امکان لغو این ارث بری نیز داده می‌شود در این صورت باید خود مدیریت آنها را به عهده بگیرید . 
برای مثال سایتی مانند زیر را در نظر بگیرید ؛ در سازمان ، دپارتمانی به نام IT در حال فعالیت است که Security Group آن به صورت مجزا تعریف شده اند و از والد ارث بری نکرده اند : 


همانطور که مشاهده می‌کنید ، Unique Permission بودن سایت در بالای سایت نمایش داده شده و دکمه Inherite Permission فعال شده است .

در زیر مجموعه آن یک سایت با عنوان meeting Workspace ایجاد می‌کنیم که Security Group آن از IT ارث بری کرده است : 



و در این قسمت ارث بری سایت از والد نمایش داده شده و دکمه Stop Inheriting Permission فعال شده است . 

همچنین در صورتی که تمایل به ایحاد یک گروه جدید داشته باشید می‌توانید از طریق ریبون بالای صفحه روی Create Group کلیک کرده و گروه خود را ایجاد نمایید 


موفق باشید  


مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت سوم - ورود به سیستم
پس از ایجاد AuthService در قسمت قبل، اکنون می‌خواهیم از آن برای تکمیل صفحه‌ی ورود به سیستم و همچنین تغییر منوی بالای برنامه یا همان کامپوننت header استفاده کنیم.


ایجاد ماژول Dashboard و تعریف کامپوننت صفحه‌ی محافظت شده

قصد داریم پس از لاگین موفق، کاربر را به یک صفحه‌ی محافظت شده هدایت کنیم. به همین جهت ماژول جدید Dashboard را به همراه کامپوننت یاد شده، به برنامه اضافه می‌کنیم:
>ng g m Dashboard -m app.module --routing
>ng g c Dashboard/ProtectedPage
پس از اجرای این دستورات، ابتدا به فایل app.module.ts مراجعه کرده و تعریف این ماژول را که به صورت خودکار به قسمت imports اضافه شده‌است، به قبل از AppRoutingModule منتقل می‌کنیم تا مسیریابی‌های آن توسط catch all ماژول AppRouting لغو نشوند:
import { DashboardModule } from "./dashboard/dashboard.module";

@NgModule({
  imports: [
    //...
    DashboardModule,
    AppRoutingModule
  ]
})
export class AppModule { }
همچنین به فایل dashboard-routing.module.ts ایجاد شده مراجعه کرده و مسیریابی کامپوننت جدید ProtectedPage را اضافه می‌کنیم:
import { ProtectedPageComponent } from "./protected-page/protected-page.component";

const routes: Routes = [
  { path: "protectedPage", component: ProtectedPageComponent }
];


ایجاد صفحه‌ی ورود به سیستم

در قسمت اول این سری، کارهای «ایجاد ماژول Authentication و تعریف کامپوننت لاگین» انجام شدند. اکنون می‌خواهیم کامپوننت خالی لاگین را به نحو ذیل تکمیل کنیم:
export class LoginComponent implements OnInit {

  model: Credentials = { username: "", password: "", rememberMe: false };
  error = "";
  returnUrl: string;
مدل login را همان اینترفیس Credentials تعریف شده‌ی در قسمت قبل درنظر گرفته‌ایم. به همراه دو خاصیت error جهت نمایش خطاها در ذیل قسمت لاگین و همچنین ذخیره سازی returnUrl در صورت وجود:
  constructor(
    private authService: AuthService,
    private router: Router,
    private route: ActivatedRoute) { }

  ngOnInit() {
    // reset the login status
    this.authService.logout(false);

    // get the return url from route parameters
    this.returnUrl = this.route.snapshot.queryParams["returnUrl"];
  }
AuthService را به سازنده‌ی کامپوننت لاگین تزریق کرده‌ایم تا بتوان از متدهای login و Logout آن استفاده کرد. همچنین از سرویس Router برای استفاده‌ی از متد navigate آن استفاده می‌کنیم و از سرویس ActivatedRoute برای دریافت کوئری استرینگ returnUrl، در صورت وجود، کمک خواهیم گرفت.
اکنون متد ارسال این فرم چنین شکلی را پیدا می‌کند:
  submitForm(form: NgForm) {
    this.error = "";
    this.authService.login(this.model)
      .subscribe(isLoggedIn => {
        if (isLoggedIn) {
          if (this.returnUrl) {
            this.router.navigate([this.returnUrl]);
          } else {
            this.router.navigate(["/protectedPage"]);
          }
        }
      },
      (error: HttpErrorResponse) => {
        console.log("Login error", error);
        if (error.status === 401) {
          this.error = "Invalid User name or Password. Please try again.";
        } else {
          this.error = `${error.statusText}: ${error.message}`;
        }
      });
  }
در اینجا توسط وهله‌ی تزریق شده‌ی this.authService، کار فراخوانی متد login و ارسال شیء Credentials به سمت سرور صورت می‌گیرد. خروجی متد login یک Observable از نوع boolean است. بنابراین در صورت true بودن این مقدار و یا موفقیت آمیز بودن عملیات لاگین، کاربر را به یکی از دو صفحه‌ی protectedPage و یا this.returnUrl (در صورت وجود)، هدایت خواهیم کرد.
صفحه‌ی خالی protectedPage را در ابتدای بحث، در ذیل ماژول Dashboard ایجاد کردیم.
در سمت سرور هم در صورت شکست اعتبارسنجی کاربر، یک return Unauthorized صورت می‌گیرد که معادل error.status === 401 کدهای فوق است و در اینجا در قسمت خطای عملیات بررسی شده‌است.

قالب این کامپوننت نیز به صورت ذیل به model از نوع Credentials آن متصل شده‌است:
<div class="panel panel-default">
  <div class="panel-heading">
    <h2 class="panel-title">Login</h2>
  </div>
  <div class="panel-body">
    <form #form="ngForm" (submit)="submitForm(form)" novalidate>
      <div class="form-group" [class.has-error]="username.invalid && username.touched">
        <label for="username">User name</label>
        <input id="username" type="text" required name="username" [(ngModel)]="model.username"
          #username="ngModel" class="form-control" placeholder="User name">
        <div *ngIf="username.invalid && username.touched">
          <div class="alert alert-danger"  *ngIf="username.errors['required']">
            Name is required.
          </div>
        </div>
      </div>

      <div class="form-group" [class.has-error]="password.invalid && password.touched">
        <label for="password">Password</label>
        <input id="password" type="password" required name="password" [(ngModel)]="model.password"
          #password="ngModel" class="form-control" placeholder="Password">
        <div *ngIf="password.invalid && password.touched">
          <div class="alert alert-danger"  *ngIf="password.errors['required']">
            Password is required.
          </div>
        </div>
      </div>

      <div class="checkbox">
        <label>
          <input type="checkbox" name="rememberMe" [(ngModel)]="model.rememberMe"> Remember me
        </label>
      </div>

      <div class="form-group">
        <button type="submit" class="btn btn-primary" [disabled]="form.invalid ">Login</button>
      </div>

      <div *ngIf="error" class="alert alert-danger " role="alert ">
        {{error}}
      </div>
    </form>
  </div>
</div>
در اینجا خاصیت‌های نام کاربری، کلمه‌ی عبور و همچنین rememberMe مدل، از کاربر دریافت می‌شوند؛ به همراه بررسی اعتبارسنجی سمت کلاینت آن‌ها؛ با این شکل:


برای آزمایش برنامه، نام کاربری Vahid و کلمه‌ی عبور 1234 را وارد کنید.


تکمیل کامپوننت Header برنامه

در ادامه، پس از لاگین موفق شخص، می‌خواهیم صفحه‌ی protectedPage را نمایش دهیم:


در این صفحه، Login از منوی سایت حذف شده‌است و بجای آن Logout به همراه «نام نمایشی کاربر» ظاهر شده‌اند. همچنین توکن decode شده به همراه تاریخ انقضای آن نمایش داده شده‌اند.
برای پیاده سازی این موارد، ابتدا از کامپوننت Header شروع می‌کنیم:
export class HeaderComponent implements OnInit, OnDestroy {

  title = "Angular.Jwt.Core";

  isLoggedIn: boolean;
  subscription: Subscription;
  displayName: string;

  constructor(private authService: AuthService) { }
این کامپوننت وضعیت گزارش شده‌ی ورود شخص را توسط خاصیت isLoggedIn در اختیار قالب خود قرار می‌دهد. برای این منظور به سرویس AuthService تزریق شده‌ی در سازنده‌ی خود نیاز دارد.
اکنون در روال رخ‌دادگردان ngOnInit، مشترک authStatus می‌شود که یک BehaviorSubject است و از آن جهت صدور رخ‌دادهای authService به تمام کامپوننت‌های مشترک به آن استفاده کرده‌ایم:
  ngOnInit() {
    this.subscription = this.authService.authStatus$.subscribe(status => {
      this.isLoggedIn = status;
      if (status) {
        this.displayName = this.authService.getDisplayName();
      }
    });
  }
Status بازگشت داده شده‌ی توسط آن از نوع boolean است و در صورت true بودن، خاصیت isLoggedIn را نیز true می‌کند که از آن در قالب این کامپوننت برای نمایش و یا مخفی کردن لینک‌های login و logout استفاده خواهیم کرد.
همچنین اگر این وضعیت true باشد، مقدار DisplayName کاربر را نیز از سرویس authService دریافت کرده و توسط خاصیت this.displayName در اختیار قالب Header قرار می‌دهیم.
در آخر برای جلوگیری از نشتی حافظه، ضروری است اشتراک به authStatus، در روال رخ‌دادگردان ngOnDestroy لغو شود:
  ngOnDestroy() {
    // prevent memory leak when component is destroyed
    this.subscription.unsubscribe();
  }

همچنین در قالب Header، مدیریت دکمه‌ی Logout را نیز انجام خواهیم داد:
  logout() {
    this.authService.logout(true);
  }

با این مقدمات، قالب Header اکنون به صورت ذیل تغییر می‌کند:
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" [routerLink]="['/']">{{title}}</a>
    </div>
    <ul class="nav navbar-nav">
      <li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a class="nav-link" [routerLink]="['/welcome']">Home</a>
      </li>
      <li *ngIf="!isLoggedIn" class="nav-item" routerLinkActive="active">
        <a class="nav-link" queryParamsHandling="merge" [routerLink]="['/login']">Login</a>
      </li>
      <li *ngIf="isLoggedIn" class="nav-item" routerLinkActive="active">
        <a class="nav-link" (click)="logout()">Logoff [{{displayName}}]</a>
      </li>
      <li *ngIf="isLoggedIn" class="nav-item" routerLinkActive="active">
        <a class="nav-link" [routerLink]="['/protectedPage']">Protected Page</a>
      </li>
    </ul>
  </div>
</nav>
در اینجا توسط ngIfها وضعیت خاصیت isLoggedIn این کامپوننت را بررسی می‌کنیم. اگر true باشد، Logoff به همراه نام نمایشی کاربر را در منوی راهبری سایت ظاهر خواهیم کرد و در غیراینصورت لینک به صفحه‌ی Login را نمایش می‌دهیم.


تکمیل کامپوننت صفحه‌ی محافظت شده

در تصویر قبل، نمایش توکن decode شده را نیز مشاهده کردید. این نمایش توسط کامپوننت صفحه‌ی محافظت شده، مدیریت می‌شود:
import { Component, OnInit } from "@angular/core";
import { AuthService } from "../../core/services/auth.service";

@Component({
  selector: "app-protected-page",
  templateUrl: "./protected-page.component.html",
  styleUrls: ["./protected-page.component.css"]
})
export class ProtectedPageComponent implements OnInit {

  decodedAccessToken: any = {};
  accessTokenExpirationDate: Date = null;

  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.decodedAccessToken = this.authService.getDecodedAccessToken();
    this.accessTokenExpirationDate = this.authService.getAccessTokenExpirationDate();
  }
}
در اینجا به کمک سرویس تزریقی AuthService، یکبار با استفاده از متد getDecodedAccessToken آن، مقدار اصلی توکن را و بار دیگر توسط متد getAccessTokenExpirationDate آن، تاریخ انقضای توکن دریافتی از سمت سرور را نمایش می‌دهیم؛ با این قالب:
<h1>
  Decoded Access Token
</h1>

<div class="alert alert-info">
  <label> Access Token Expiration Date:</label> {{accessTokenExpirationDate}}
</div>

<div>
  <pre>{{decodedAccessToken | json}}</pre>
</div>


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.