مطالب
سیستم‌های توزیع شده در NET. - بخش اول - نیازمندی
در حوزه کاری ما همیشه نیازمندی‌های جدید باعث پیشرفت، ارتقاء و پیچیده‌تر شدن سیستم‌های سخت افزاری و نرم افزاری می‌شوند. بطور مثال زمانیکه نیاز شد چندین سیستم از داده‌های مشترکی استفاده کنند، در معماری Single Tier قسمت Database از سایر قسمت‌ها جدا شد و در سخت افزار دیگری قرار گرفت. به این صورت این معماری تبدیل به Two Tier شد و سپس برای اینکه تغییرات، کمترین تاثیر را در سیستم ما داشته باشد و با کمترین هزینه به Platform‌های دیگر نیز سرویس بدهیم، قسمت Presentation از سایر قسمت‌ها جدا شد و در سخت افزارهای دیگری قرار گرفت. به این صورت  تبدیل به معماری Three Tier شد و همینطور نیازهای جدید باعث شد معماری N Tier پیچیده‌تر شود. البته پیچیدگی که باعث تکامل آن شد یا بطور مثال نیاز به پردازش تعداد بیشتری عملیات بصورت همزمان، که باعث شد سیستم‌های ما از حالت Single Task تبدیل به Parallel System و سپس Distributed system شوند. واقعیت این است که در دنیای امروز، نیازهای جدیدی بوجود آمده‌اند؛ نیازهایی که یک سخت افزار به تنهایی قادر به ارائه راهکاری برای آنها نیست. واقعا یک سخت افزار که یک سرویس را ارائه می‌دهد، چه خصوصیاتی باید داشته باشد که مثلا در ثانیه حدود 50 میلیون عملیات را بصورت همزمان انجام دهد؛ یا مثلا سرویس مورد نظر حدود 400 میلیون کاربر فعال که روزانه بیشتر وقت خود را به استفاده از این سرویس اختصاص می‌دهند داشته باشد؟

آیا میدانستید که Facebook در حال حاضر بیشتر از 400 میلیون کاربر فعال دارد که حدود 200 میلیون از این کاربران هر روز از این سرویس استفاده می‌کنند؟ آماری که این سرویس داده به این صورت است که تا سال 2010، کاربرانش در هر روز حدود 16 بیلیون دقیقه از وقت خودشان را به استفاده از این سرویس اختصاص داده‌اند. در هر هفته کاربران این سرویس حدود 6 بیلیون مطلب را که شامل عکس و متن بوده را به اشتراک گذاشته‌اند. هر ماه بیشتر از 3 بیلیون عکس توسط این سرویس Upload شده‌است. کاربرانش روزانه بیشتر از 1 بیلیون عکس را توسط این سرویس مشاهده کرده‌اند. این سرویس در ثانیه حدود 50 میلیون عملیات را انجام می‌دهد!

آیا میدانستید که سرویس Twitter در حال حاضر 350 میلیون کاربر فعال دارد که حدود 100 میلیون از این کاربران روزانه از این سرویس استفاده می‌کنند و هر روز کاربران این سرویس 500 میلیون توییت را ارسال می‌کنند. این سرویس در فینال جام جهانی حدود 618,725 توییت را در یک دقیقه دریافت کرده‌است.
و یا سرویس Telegram حدود 100 میلیون کاربر فعال دارد که بصورت متوسط در هر روز 220 هزار کاربر جدید در آن ثبت نام می‌کنند. کاربران این سرویس روزانه 15 بیلیون پیام را ارسال می‌کنند و 700 میلیون عکس را به اشتراک می‌گذارند.
چه سروری به تنهایی می‌تواند این آمار و ارقام را پوشش دهد؟ هزینه خرید و نگهداری آن چقدر است؟ چقدر باید هزینه کنیم تا این سرور از دسترس خارج نشود؟ یک سرور به تنهایی چه راهکاری را می‌تواند ارائه دهد که هیچوقت از دسترس خارج نشود؟
بهتر است بدانید که سرویس Facebook روی بیش از 60,000 سرور ارائه می‌شود! چه سروری به تنهایی می‌تواند کارآیی 60,000 سرور را داشته باشد؟
چه نوع پایگاه داده ای که روی یک سرور سرویس ارائه می‌دهد، قادر است روزانه به بیشتر از 1 تریلیون درخواست، پاسخ دهد؟ اصلا چه سروری به تنهایی قادر است این حجم داده را که هر روز رو به افزایش است، نگهداری کند؟

بهتر است بدانید پایگاه داده سرویس‌های شرکت Apple، روی بیش از 75,000  Node قرار دارند که روزانه حدود 10 پتابایت داده ذخیره می‌کنند. یا Netflix که از 2,500  Node استفاده می‌کند و روزانه 420 ترابایت داده را در قالب 1 تریلیون درخواست دریافت می‌کند یا Easou که پایگاه داده آن روی 270  Node قرار دارد و روزانه 300 ترابایت داده را در قالب 800 میلیون درخواست دریافت می‌کند! اینها اعداد و ارقامی نیستند که ما بتوانیم با SqlServerی که روی یک سرور قرار دارد، پوشش دهیم.

چه تعداد سرویس را در کشورمان می‌شناسید که در زمان بالا رفتن تعداد درخواست از دسترس خارج می‌شوند؟ چه تعداد سرویس را می‌شناسید که تنها راهکاری را که می‌توان در این زمان برای آنها ارائه داد، ارتقاء سخت افزار آنهاست؟ پس از مدتی با بالا رفتن تعداد کاربران، دوباره سخت افزار را ارتقاء می‌دهیم. تا کجا باید این کار را تکرار کنیم؟ چقدر هزینه کنیم برای سخت افزاری که ممکن است به هر دلیلی در هر لحظه از دسترس خارج شود؟ آیا با توجه به آمار تعداد کاربران، تعداد درخواست و حجم داده‌ی سرویس‌هایی که می‌شناسید و قابل مقایسه با آمار ذکر شده است، باز هم از دسترس خارج می‌شوند؟

در سری مقالات Distributed systems in .NET من سعی می‌کنم شما را با خصوصیات و اصطلاحات موجود در سیستمهای توزیع شده آشنا کنم و ابزارهایی را که در NET. برای پیاده سازی این نوع سیستم‌ها وجود دارد، به شما معرفی کنم و با هم ببینیم که این نوع سیستم‌ها چه راهکاری را برای رفع نیازمندی‌های ما ارائه می‌دهند.


نمونه‌ای از طراحی یک سیستم توزیع شده

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

تست آلفا در دو فاز انجام می‌گیرد:
فاز اول: فاز اول داخل تیم اصلی، توسط توسعه دهندگان هست تا اصلی‌ترین باگ‌ها به سرعت رفع و حل شوند.
در فاز دوم برنامه به دست توسعه دهندگان واحد تضمین کیفیت Quality Assurance - QA مورد تست و ارزیابی قرار می‌گیرد.
تست آلفا قبل از عرضه عمومی اصطلاحا Commercial Off-the Shelf-COTS صورت می‌گیرد و قبل از تست بتا می‌باشد.

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

عوامل زیر در موفقیت هر چه بیشتر تست بتا وابسته هستند:
هزینه تست
تعداد شرکت کنندگان در این تست
نحوه ارسال به کاربر ( که امروزه بیشتر از طریق اینترنت صورت میگیرد)
مدت زمان تست


از نکات مهم در این تست می‌توان گفت که طول دوره تست آلفا، بیشتر از تست بتاست که به طور متوسط 3 تا 5 برابر تست بتا طول می‌کشد و خود تست بتا، عموما در حد چند هفته و گاها تا چند ماه می‌باشد.
در صورتیکه تست آلفا با موفقیت بیرون داده شود، وارد نسخه بتا می‌شود و بعد از اتمام تست بتا وارد ریلیز نهایی می‌شود. تست آلفا با توجه COTS گفته شده می‌تواند کاربران خاص و محیط خاص خود را داشته باشد.
نظرات مطالب
UrlRewriter توسط Intelligencia.UrlRewriter
همونطور که دوستمون اشاره کرد DKP-14997 تو سایت دیجی کالا کلید محصولاته.
روشی که در این مطلب برای انجام  UrlReWrite خوندین به نظرم روش کامل و اصولی برای انجام باز نویسی یو آر ال‌ها نیست . اگه بخواید کاملا بر روی یو آر ال‌های سایت مدیریت داشته باشید باید از امکان مسیر یابی خود ASP.Net استفاده کنید.
در وب فرم این مقاله کمکتون میکنه. در ام وی سی هم  این مطلب   .
سایتهایی که اصولی هستند از قابلیت مسیر یابی خود ASP.Net استفاده میکنند. مثل همون دی جی کالا یا سایت جاری.
نظرات مطالب
فعال‌سازی استفاده از Session در ASP.NET MVC 4 API Controller ها
بله دوست عزیز ، استفاده از Session مزایا و معایب خودش را به همراه دارد.
اما در یک سیستم فروشگاهی حذف و اضافه شدن کالا در سبد خرید بصورت مکرر انجام میشود و برای اینکه شما چه کاربران مهمان و چه کاربران عضو را مدیریت نموده و بتوانید ترافیک روی بانک اطلاعاتی خود را کاهش دهید میتوانید از Session استفاده نمایید . ( بطور مثال اگر 1000 نفر دریک سایت فروشگاهی که حداقل 100 کالا ارائه شده است را به سبد خرید خود اضافه یا تعداد و ... آنها را ویرایش نمایند در بهترین حال 100000 درخواست به بانک اطلاعاتی ارسال و دریافت خواهد شد)
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت هشتم- تعریف سطوح دسترسی پیچیده
تعریف نقش‌ها، ساده‌ترین روش محدود کردن دسترسی به منابع است؛ برای نمونه مشخص می‌کنیم که کاربران دارای نقش PayingUser، امکان سفارش دادن نگارش قاب شده‌ی تصاویر را داشته باشند. اما می‌خواهیم منطق دسترسی به منابع مختلف را پیچیده‌تر کنیم. برای مثال می‌خواهیم بررسی کنیم اگر منبعی واقعا متعلق به کاربر جاری سیستم است، به آن دسترسی داشته باشد. برای مثال هرچند کاربر جاری دارای نقش PayingUser است، اما آیا باید اجازه‌ی ویرایش تصاویر تمام کاربران، برای او صادر شده باشد؟ برای پیاده سازی یک چنین موارد پیچیده‌ای که فراتر از مفهوم نقش‌ها هستند، ویژگی جدیدی به نام Authorization policies به ASP.NET Core اضافه شده‌است که آن‌را در این قسمت بر اساس امکانات IdentityServer 4 بررسی می‌کنیم.


مقایسه تعریف سطوح دسترسی «مبتنی بر نقش‌ها» با سطوح دسترسی «مبتنی بر سیاست‌های امنیتی»

- در سطوح دسترسی «مبتنی بر نقش‌ها»
یک‌سری نقش از پیش تعریف شده وجود دارند؛ مانند PayingUser و یا FreeUser که کاربر توسط هر نقش، به یکسری دسترسی‌های خاص نائل می‌شود. برای مثال PayingUser می‌تواند نگارش قاب شده‌ی تصاویر را سفارش دهد و یا تصویری را به سیستم اضافه کند.

- در سطوح دسترسی «مبتنی بر سیاست‌های امنیتی»
سطوح دسترسی بر اساس یک سری سیاست که بیانگر ترکیبی از منطق‌های دسترسی هستند، اعطاء می‌شوند. این منطق‌ها نیز از طریق ترکیب User Claims حاصل می‌شوند و می‌توانند منطق‌های پیچیده‌تری را به همراه داشته باشند. برای مثال اگر کاربری از کشور A است و نوع اشتراک او B است و اگر در بین یک بازه‌ی زمانی خاصی متولد شده باشد، می‌تواند به منبع خاصی دسترسی پیدا کند. به این ترتیب حتی می‌توان نیاز به ترکیب چندین نقش را با تعریف یک سیاست امنیتی جدید جایگزین کرد. به همین جهت نسبت به روش بکارگیری مستقیم کار با نقش‌ها ترجیح داده می‌شود.


جایگزین کردن بررسی سطوح دسترسی توسط نقش‌ها با روش بکارگیری سیاست‌های دسترسی

در ادامه می‌خواهیم بجای بکارگیری مستقیم نقش‌ها جهت محدود کردن دسترسی به قسمت‌های خاصی از برنامه‌ی کلاینت، تنها کاربرانی که از کشور خاصی وارد شده‌اند و نیز سطح اشتراک خاصی را دارند، بتوانند دسترسی‌های ویژه‌ای داشته باشند؛ چون برای مثال امکان ارسال مستقیم تصاویر قاب شده را به کشور دیگری نداریم.

تنظیم User Claims جدید در برنامه‌ی IDP
برای تنظیم این سیاست امنیتی جدید، ابتدا دو claim جدید subscriptionlevel و country را به خواص کاربران در کلاس src\IDP\DNT.IDP\Config.cs در سطح IDP اضافه می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
                    Username = "User 1",
                    // ...

                    Claims = new List<Claim>
                    {
    // ...
                        new Claim("subscriptionlevel", "PayingUser"),
                        new Claim("country", "ir")
                    }
                },
                new TestUser
                {
                    Username = "User 2",
// ...

                    Claims = new List<Claim>
                    {
    // ...
                        new Claim("subscriptionlevel", "FreeUser"),
                        new Claim("country", "be")
                    }
                }
            };
        }
سپس باید تعاریف این claims جدید را به متد GetIdentityResources افزود تا به صورت scopeهای جدید از طرف کلاینت‌ها قابل درخواست باشند و چون این claimها استاندارد نیستند، برای تعریف آن‌ها از IdentityResource استفاده می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        // identity-related resources (scopes)
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
   // ...     
                new IdentityResource(
                    name: "country",
                    displayName: "The country you're living in",
                    claimTypes: new List<string> { "country" }),
                new IdentityResource(
                    name: "subscriptionlevel",
                    displayName: "Your subscription level",
                    claimTypes: new List<string> { "subscriptionlevel" })
            };
        }
همچنین باید مطمئن شد که کلاینت مدنظر ما قادر است این scopeهای تعریف شده را درخواست کند و IDP مجاز است تا آن‌ها را بازگشت دهد. برای این منظور آن‌ها را به لیست AllowedScopes تعریف کلاینت، اضافه می‌کنیم:
namespace DNT.IDP
{
    public static class Config
    {
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientName = "Image Gallery",
// ...
                    AllowedScopes =
                    {
    // ...
                        "country",
                        "subscriptionlevel"
                    }
// ...
                }
             };
        }
    }

استفاده‌ی از User Claims جدید در برنامه‌ی MVC Client
در ادامه به کلاس ImageGallery.MvcClient.WebApp\Startup.cs برنامه‌ی MVC Client مراجعه کرده و دو scope جدیدی را که در سمت IDP تعریف کردیم، در اینجا در تنظیمات متد AddOpenIdConnect، درخواست می‌دهیم:
options.Scope.Add("subscriptionlevel");
options.Scope.Add("country");
به این ترتیب برنامه‌ی کلاینت می‌تواند دسترسی به این دو claim جدید را از طریق IDP، پیدا کند.
البته همانطور که در قسمت‌های قبل نیز ذکر شد، اگر claim ای در لیست نگاشت‌های تنظیمات میان‌افزار OpenID Connect مایکروسافت نباشد، آن‌را در لیست this.User.Claims ظاهر نمی‌کند. به همین جهت همانند claim role که پیشتر MapUniqueJsonKey را برای آن تعریف کردیم، نیاز است برای این دو claim نیز نگاشت‌های لازم را به سیستم افزود:
options.ClaimActions.MapUniqueJsonKey(claimType: "role", jsonKey: "role");
options.ClaimActions.MapUniqueJsonKey(claimType: "subscriptionlevel", jsonKey: "subscriptionlevel");
options.ClaimActions.MapUniqueJsonKey(claimType: "country", jsonKey: "country");

ایجاد سیاست‌های دسترسی در برنامه‌ی MVC Client

برای تعریف یک سیاست دسترسی جدید در کلاس ImageGallery.MvcClient.WebApp\Startup.cs برنامه‌ی MVC Client، به متد ConfigureServices آن مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
namespace ImageGallery.MvcClient.WebApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthorization(options =>
            {
                options.AddPolicy(
                   name: "CanOrderFrame",
                   configurePolicy: policyBuilder =>
                    {
                        policyBuilder.RequireAuthenticatedUser();
                        policyBuilder.RequireClaim(claimType: "country", requiredValues: "ir");
                        policyBuilder.RequireClaim(claimType: "subscriptionlevel", requiredValues: "PayingUser");
                    });
            });
در اینجا نحوه‌ی تعریف یک Authorization Policy جدید را مشاهده می‌کنید. ابتدا یک نام برای آن تعریف می‌شود که در قسمت‌های دیگر برنامه جهت ارجاع به آن مورد استفاده قرار می‌گیرد. سپس تنظیمات این سیاست دسترسی جدید را مشاهده می‌کنید که در آن نیاز است کاربر مدنظر حتما اعتبارسنجی شده باشد. از کشور ir بوده و همچنین سطح اشتراک او PayingUser باشد. در اینجا پارامتر requiredValues، یک آرایه را می‌پذیرد. بنابراین اگر برای مثال کشورهای دیگری نیز مدنظر هستند، می‌توان لیست آن‌ها را در اینجا اضافه کرد.
به علاوه policyBuilder شامل متد RequireRole نیز هست. به همین جهت است که این روش تعریف سطوح دسترسی، روش قدیمی مبتنی بر نقش‌ها را جایگزین کرده و در برگیرنده‌ی آن نیز می‌شود؛ چون در این سیستم، role نیز تنها یک claim است، مانند country و یا subscriptionlevel فوق.


بررسی نحوه‌ی استفاده‌ی از Authorization Policy تعریف شده و جایگزین کردن آن با روش بررسی نقش‌ها

تا کنون از روش بررسی سطوح دسترسی‌ها بر اساس نقش‌های کاربران در دو قسمت استفاده کرده‌ایم:
الف) اصلاح Views\Shared\_Layout.cshtml برای استفاده‌ی از Authorization Policy
در فایل Layout با بررسی نقش PayingUser، منوهای مرتبط با این نقش را فعال می‌کنیم:
@if(User.IsInRole("PayingUser"))
{
  <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li>
  <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li>
}
برای جایگزین کردن آن جهت استفاده‌ی از سیاست دسترسی جدید CanOrderFrame، ابتدا نیاز است در این View به سرویس IAuthorizationService دسترسی پیدا کنیم که روش تزریق آن‌را در ذیل مشاهده می‌کنید:
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
پس از آن، روش استفاده‌ی از این سرویس را در ذیل مشاهده می‌کنید:
@if (User.IsInRole("PayingUser"))
{
  <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li>
}
@if ((await AuthorizationService.AuthorizeAsync(User, "CanOrderFrame")).Succeeded)
{
  <li><a asp-area="" asp-controller="Gallery" asp-action="OrderFrame">Order a framed picture</a></li>
}
اکنون لینک منوی درخواست نگارش قاب شده‌ی یک تصویر، صرفا به کاربران تامین کننده‌ی سیاست دسترسی CanOrderFrame نمایش داده می‌شود.

ب) اصلاح کنترلر ImageGallery.MvcClient.WebApp\Controllers\GalleryController.cs برای استفاده‌ی از Authorization Policy
namespace ImageGallery.MvcClient.WebApp.Controllers
{
    [Authorize]
    public class GalleryController : Controller
    {
        [Authorize(Policy = "CanOrderFrame")]
        public async Task<IActionResult> OrderFrame()
        {
در اینجا فیلتر Authorize امکان پذیرش نام یک Policy را نیز به همراه دارد.

اکنون برای آزمایش برنامه یکبار از آن خارج شده و سپس توسط اکانت User 1 که از نوع PayingUser در کشور ir است، به آن وارد شوید.
ابتدا به قسمت IdentityInformation آن وارد شوید. در اینجا لیست claims جدید را می‌توانید مشاهده کنید. همچنین لینک سفارش تصویر قاب شده نیز نمایان است و می‌توان به آدرس آن نیز وارد شد.


استفاده از سیاست‌های دسترسی در سطح برنامه‌ی Web API

در سمت برنامه‌ی Web API، در حال حاضر کاربران می‌توانند به متدهای Get ،Put و Delete ای که رکوردهای آن‌ها الزاما متعلق به آن‌ها نیست دسترسی داشته باشند. بنابراین نیاز است از ورود کاربران به متدهای تغییرات رکوردهایی که OwnerID آن‌ها با هویت کاربری آن‌ها تطابقی ندارد، جلوگیری کرد. در این حالت Authorization Policy تعریف شده نیاز دارد تا با سرویس کاربران و بانک اطلاعاتی کار کند. همچنین نیاز به دسترسی به اطلاعات مسیریابی جاری را برای دریافت ImageId دارد. پیاده سازی یک چنین سیاست دسترسی پیچیده‌ای توسط متدهای RequireClaim و RequireRole میسر نیست. خوشبختانه امکان بسط سیستم Authorization Policy با پیاده سازی یک IAuthorizationRequirement سفارشی وجود دارد. RequireClaim و RequireRole، جزو Authorization Requirementهای پیش‌فرض و توکار هستند. اما می‌توان نمونه‌های سفارشی آن‌ها را نیز پیاده سازی کرد:
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace ImageGallery.WebApi.Services
{
    public class MustOwnImageRequirement : IAuthorizationRequirement
    {
    }

    public class MustOwnImageHandler : AuthorizationHandler<MustOwnImageRequirement>
    {
        private readonly IImagesService _imagesService;
        private readonly ILogger<MustOwnImageHandler> _logger;

        public MustOwnImageHandler(
            IImagesService imagesService,
            ILogger<MustOwnImageHandler> logger)
        {
            _imagesService = imagesService;
            _logger = logger;
        }

        protected override async Task HandleRequirementAsync(
            AuthorizationHandlerContext context, MustOwnImageRequirement requirement)
        {
            var filterContext = context.Resource as AuthorizationFilterContext;
            if (filterContext == null)
            {
                context.Fail();
                return;
            }

            var imageId = filterContext.RouteData.Values["id"].ToString();
            if (!Guid.TryParse(imageId, out Guid imageIdAsGuid))
            {
                _logger.LogError($"`{imageId}` is not a Guid.");
                context.Fail();
                return;
            }

            var subClaim = context.User.Claims.FirstOrDefault(c => c.Type == "sub");
            if (subClaim == null)
            {
                _logger.LogError($"User.Claims don't have the `sub` claim.");
                context.Fail();
                return;
            }

            var ownerId = subClaim.Value;
            if (!await _imagesService.IsImageOwnerAsync(imageIdAsGuid, ownerId))
            {
                _logger.LogError($"`{ownerId}` is not the owner of `{imageIdAsGuid}` image.");
                context.Fail();
                return;
            }

            // all checks out
            context.Succeed(requirement);
        }
    }
}
در پروژه‌ی ImageGallery.WebApi.Services ابتدا یک Authorization Requirement و سپس پیاده سازی کننده‌ی آن که Authorization Handler نام دارد را تعریف کرده‌ایم. این پروژه نیاز به وابستگی‌های ذیل را دارد تا کامپایل شود.
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.1.1.0" />
  </ItemGroup>
</Project>

پیاده سازی سیاست‌های پویای دسترسی شامل مراحل ذیل است:
1- تعریف یک نیازمندی دسترسی جدید
public class MustOwnImageRequirement : IAuthorizationRequirement
{
}
ابتدا نیاز است یک نیازمندی دسترسی جدید را با پیاده سازی اینترفیس IAuthorizationRequirement ارائه دهیم. این نیازمندی، خالی است و صرفا به عنوان نشانه‌ای جهت یافت AuthorizationHandler استفاده کننده‌ی از آن استفاده می‌شود. در اینجا در صورت نیاز می‌توان یک سری خاصیت اضافه را تعریف کرد تا آن‌ها را به صورت پارامترهایی ثابت به AuthorizationHandler ارسال کند.

2- پیاده سازی یک AuthorizationHandler استفاده کننده‌ی از نیازمندی دسترسی تعریف شده
که کدهای کامل آن‌را در کلاس MustOwnImageHandler مشاهده می‌کنید. کار آن با ارث بری از AuthorizationHandler شروع شده و آرگومان جنریک آن، همان نیازمندی است که پیشتر تعریف کردیم. از این آرگومان جنریک جهت یافتن خودکار AuthorizationHandler متناظر با آن توسط ASP.NET Core استفاده می‌شود. بنابراین در اینجا MustOwnImageRequirement تهیه شده صرفا کارکرد علامتگذاری را دارد.
در کلاس تهیه شده باید متد HandleRequirementAsync آن‌را بازنویسی کرد و اگر در این بین، منطق سفارشی ما context.Succeed را فراخوانی کند، به معنای برآورده شدن سیاست دسترسی بوده و کاربر جاری می‌تواند به منبع درخواستی بلافاصله دسترسی یابد و اگر context.Fail فراخوانی شود، در همینجا دسترسی کاربر قطع شده و HTTP status code مساوی 401 (عدم دسترسی) را دریافت می‌کند.
در این پیاده سازی از filterContext.RouteData برای یافتن Id تصویر مورد نظر استفاده شده‌است. همچنین Id شخص جاری نیز از sub claim موجود استخراج گردیده‌است. اکنون این اطلاعات را به سرویس تصاویر ارسال می‌کنیم تا توسط متد IsImageOwnerAsync آن مشخص شود که آیا کاربر جاری سیستم، همان کاربری است که تصویر را در بانک اطلاعاتی ثبت کرده‌است؟ اگر بله، با فراخوانی context.Succeed به سیستم Authorization اعلام خواهیم کرد که این سیاست دسترسی و نیازمندی مرتبط با آن با موفقیت پشت سر گذاشته شده‌است.

3- معرفی سیاست دسترسی پویای تهیه شده به سیستم
معرفی سیاست کاری پویا و سفارشی تهیه شده، شامل دو مرحله‌ی زیر است:
مراجعه‌ی به کلاس ImageGallery.WebApi.WebApp\Startup.cs و افزودن نیازمندی آن:
namespace ImageGallery.WebApi.WebApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthorization(authorizationOptions =>
            {
                authorizationOptions.AddPolicy(
                   name: "MustOwnImage",
                   configurePolicy: policyBuilder =>
                    {
                        policyBuilder.RequireAuthenticatedUser();
                        policyBuilder.AddRequirements(new MustOwnImageRequirement());
                    });
            });
            services.AddScoped<IAuthorizationHandler, MustOwnImageHandler>();
ابتدا باید MustOwnImageHandler تهیه شده را به سیستم تزریق وابستگی‌ها معرفی کنیم.
سپس یک Policy جدید را با نام دلخواه MustOwnImage تعریف کرده و نیازمندی علامتگذار خود را به عنوان یک policy.Requirements جدید، اضافه می‌کنیم. همانطور که ملاحظه می‌کنید یک وهله‌ی جدید از MustOwnImageRequirement در اینجا ثبت شده‌است. همین وهله به متد HandleRequirementAsync نیز ارسال می‌شود. بنابراین اگر نیاز به ارسال پارامترهای بیشتری به این متد وجود داشت، می‌توان خواص مرتبطی را به کلاس MustOwnImageRequirement نیز اضافه کرد.
همانطور که مشخص است، در اینجا یک نیازمندی را می‌توان ثبت کرد و نه Handler آن‌را. این Handler از سیستم تزریق وابستگی‌ها بر اساس آرگومان جنریک AuthorizationHandler پیاده سازی شده، به صورت خودکار یافت شده و اجرا می‌شود (بنابراین اگر Handler شما اجرا نشد، مطمئن شوید که حتما آن‌را به سیستم تزریق وابستگی‌ها معرفی کرده‌اید).

پس از آن هر کنترلر یا اکشن متدی که از این سیاست دسترسی پویای تهیه شده استفاده کند:
[Authorize(Policy ="MustOwnImage")]
به صورت خودکار توسط MustOwnImageHandler مدیریت می‌شود.


اعمال سیاست دسترسی پویای تعریف شده به Web API

پس از تعریف سیاست دسترسی MustOwnImage که پویا عمل می‌کند، اکنون نوبت به استفاده‌ی از آن در کنترلر ImageGallery.WebApi.WebApp\Controllers\ImagesController.cs است:
namespace ImageGallery.WebApi.WebApp.Controllers
{
    [Route("api/images")]
    [Authorize]
    public class ImagesController : Controller
    {
        [HttpGet("{id}", Name = "GetImage")]
        [Authorize("MustOwnImage")]
        public async Task<IActionResult> GetImage(Guid id)
        {
        }

        [HttpDelete("{id}")]
        [Authorize("MustOwnImage")]
        public async Task<IActionResult> DeleteImage(Guid id)
        {
        }

        [HttpPut("{id}")]
        [Authorize("MustOwnImage")]
        public async Task<IActionResult> UpdateImage(Guid id, [FromBody] ImageForUpdateModel imageForUpdate)
        {
        }
    }
}
در اینجا در سه قسمت GetImage ،DeleteImage و UpdateImage با اعمال سیاست دسترسی پویای MustOwnImage، اگر کاربر جاری همان Owner تصویر درخواستی نباشد، دسترسی او به اجرای کدهای داخل این اکشن متدها به صورت خودکار بسته خواهد شد.




کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشه‌ی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشه‌ی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آن‌را اجرا کنید تا برنامه‌ی IDP راه اندازی شود.
- در آخر به پوشه‌ی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آن‌را اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحه‌ی login نام کاربری را User 1 و کلمه‌ی عبور آن‌را password وارد کنید.
نظرات مطالب
استفاده از چندین بانک اطلاعاتی به صورت همزمان در EF Code First
سلام.
در این مقاله با استفاده از سشن ، از پردازش‌های موازی چشم پوشی شده است ؟ با توجه به مطلبی که در سایت ارائه داده بودید .آیا بهتر نبود  این رشته در کنار اطلاعات کاربر در دیتابیس به عنوانی فیلدی در نظر گرفته شود؟

با تشکر
نظرات مطالب
کار با نوع داده‌ی HierarchyID توسط Entity framework
با سلام و تشکر؛ از توضیحاتتون برداشتم این بود که Asp.NET Identity با EntityFrameworkWithHierarchyId مشکل داره. برای رفع این مشکل راه حلی هم وجود داره؟ یا اگه بخواهیم از EntityFrameworkWithHierarchyId استفاده کنیم باید از امکان  Asp.NET Identity  چشم پوشی کنیم؟
نظرات مطالب
ASP.NET MVC #24
با سلام و عرض خسته نباشید خدمت جناب نصیری
نکتۀ ناراحت کننده ای در ابتدای این پست به چشم میخورد! «در قسمت آخر سری ASP.NET MVC»
آیا دیگر قصد ادامه این مبحث را ندارید؟
نظرات نظرسنجی‌ها
در محیط برنامه نویسی از دارک مود استفاده می کنید یا لایت مود؟
مدت مدیدی بود که از دارک مود استفاده می‌کردم اما اخیرا احساس بدی در چشم هایم دارم. دیگر تابلو‌های راهنمایی رانندگی را نمیتوانم به خوبی از فاصله مناسب بخوانم. بنابراین فعلا به لایت مد مهاجرت کردم و احساس می‌کنم بهتر شدم.
نظرات مطالب
تاریخ شمسی با Extension Method برای DateTime
سلام خیلی خوبه منم از کلاس زیر استفاده می‌کنم
اما تو سیلورلایت چطور استفاده کنم؟

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;


public class PersianCulture : CultureInfo
{
    private readonly Calendar cal;
    private readonly Calendar[] optionals;


    public PersianCulture(): this("FA-IR", true)
    {

    }

    public PersianCulture(string cultureName, bool useUserOverride): base(cultureName, useUserOverride)
    {
        //Temporary Value for cal.
        cal = base.OptionalCalendars[0];

        //populating new list of optional calendars.
        var optionalCalendars = new List<Calendar>();
        optionalCalendars.AddRange(base.OptionalCalendars);
        optionalCalendars.Insert(0, new PersianCalendar());


        Type formatType = typeof(DateTimeFormatInfo);
        Type calendarType = typeof(Calendar);


        PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic);
        FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars",
                                                                BindingFlags.Instance | BindingFlags.NonPublic);

        //populating new list of optional calendar ids
        var newOptionalCalendarIDs = new Int32[optionalCalendars.Count];
        for (int i = 0; i < newOptionalCalendarIDs.Length; i++)
            newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null);

        optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs);

        optionals = optionalCalendars.ToArray();
        cal = optionals[0];
        DateTimeFormat.Calendar = optionals[0];

        DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
        DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
        DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
        DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };

        DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
        DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
        DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" };

        DateTimeFormat.AMDesignator = "ق.ظ";
        DateTimeFormat.PMDesignator = "ب.ظ";

        
        DateTimeFormat.ShortDatePattern = "yyyy/MM/dd";
        DateTimeFormat.LongDatePattern = "yyyy/MM/dd";
             
        DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd');
        //DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D');
        //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y');
        //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y');
        

    }

    public override Calendar Calendar
    {
        get { return cal; }
    }

    public override Calendar[] OptionalCalendars
    {
        get { return optionals; }
    }
}