مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت چهارم - نصب و راه اندازی IdentityServer
معرفی IdentityServer 4


اگر استاندارد OpenID Connect را بررسی کنیم، از مجموعه‌ای از دستورات و رهنمودها تشکیل شده‌است. بنابراین نیاز به کامپوننتی داریم که این استاندارد را پیاده سازی کرده باشد تا بتوان بر اساس آن یک Identity Provider را تشکیل داد و پیاده سازی مباحثی که در قسمت قبل بررسی شدند مانند توکن‌ها، Flow، انواع کلاینت‌ها، انواع Endpoints و غیره چیزی نیستند که به سادگی قابل انجام باشند. اینجا است که IdentityServer 4، به عنوان یک فریم ورک پیاده سازی کننده‌ی استانداردهای OAuth 2 و OpenID Connect مخصوص ASP.NET Core ارائه شده‌است. این فریم ورک توسط OpenID Foundation تائید شده و داری مجوز رسمی از آن است. همچنین جزئی از NET Foundation. نیز می‌باشد. به علاوه باید دقت داشت که این فریم ورک کاملا سورس باز است.


نصب و راه اندازی IdentityServer 4

همان مثال «قسمت دوم - ایجاد ساختار اولیه‌ی مثال این سری» را در نظر بگیرید. داخل آن پوشه‌های جدید src\IDP\DNT.IDP را ایجاد می‌کنیم.


نام دلخواه DNT.IDP، به پوشه‌ی جدیدی اشاره می‌کند که قصد داریم IDP خود را در آن برپا کنیم. نام آن را نیز در ادامه‌ی نام‌های پروژه‌های قبلی که با ImageGallery شروع شده‌اند نیز انتخاب نکرده‌ایم؛ از این جهت که یک IDP را قرار است برای بیش از یک برنامه‌ی کلاینت مورد استفاده قرار دهیم. برای مثال می‌توانید از نام شرکت خود برای نامگذاری این IDP استفاده کنید.

اکنون از طریق خط فرمان به پوشه‌ی src\IDP\DNT.IDP وارد شده و دستور زیر را صادر کنید:
dotnet new web
این دستور، یک پروژه‌ی جدیدی را از نوع «ASP.NET Core Empty»، در این پوشه، بر اساس آخرین نگارش SDK نصب شده‌ی بر روی سیستم شما، ایجاد می‌کند. از این جهت نوع پروژه خالی درنظر گرفته شده‌است که قرار است توسط اجزای IdentityServer 4 به مرور تکمیل شود.

اولین کاری را که در اینجا انجام خواهیم داد، مراجعه به فایل Properties\launchSettings.json آن و تغییر شماره پورت‌های پیش‌فرض آن است تا با سایر پروژه‌های وبی که تاکنون ایجاد کرده‌ایم، تداخل نکند. برای مثال در اینجا شماره پورت SSL آن‌را به 6001 تغییر داده‌ایم.

اکنون نوبت به افزودن میان‌افزار IdentityServer 4 به پروژه‌ی خالی وب است. اینکار را نیز توسط اجرای دستور زیر در پوشه‌ی src\IDP\DNT.IDP انجام می‌دهیم:
 dotnet add package IdentityServer4

در ادامه نیاز است این میان‌افزار جدید را معرفی و تنظیم کرد. به همین جهت فایل Startup.cs پروژه‌ی خالی وب را گشوده و به صورت زیر تکمیل می‌کنیم:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddIdentityServer()
                .AddDeveloperSigningCredential();
        }
- متد الحاقی AddIdentityServer، کار ثبت و معرفی سرویس‌های توکار IdentityServer را به سرویس توکار تزریق وابستگی‌های ASP.NET Core انجام می‌دهد.
- متد الحاقی AddDeveloperSigningCredential کار تنظیمات کلید امضای دیجیتال توکن‌ها را انجام می‌دهد. در نگارش‌های قبلی IdentityServer، اینکار با معرفی یک مجوز امضاء کردن توکن‌ها انجام می‌شد. اما در این نگارش دیگر نیازی به آن نیست. در طول توسعه‌ی برنامه می‌توان از نگارش Developer این مجوز استفاده کرد. البته در حین توزیع برنامه به محیط ارائه‌ی نهایی، باید به یک مجوز واقعی تغییر پیدا کند.


تعریف کاربران، منابع و کلاینت‌ها

مرحله‌ی بعدی تنظیمات میان‌افزار IdentityServer4، تعریف کاربران، منابع و کلاینت‌های این IDP است. به همین جهت یک کلاس جدید را به نام Config، در ریشه‌ی پروژه ایجاد و به صورت زیر تکمیل می‌کنیم:
using System.Collections.Generic;
using System.Security.Claims;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace DNT.IDP
{
    public static class Config
    {
        // test users
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "d860efca-22d9-47fd-8249-791ba61b07c7",
                    Username = "User 1",
                    Password = "password",

                    Claims = new List<Claim>
                    {
                        new Claim("given_name", "Vahid"),
                        new Claim("family_name", "N"),
                    }
                },
                new TestUser
                {
                    SubjectId = "b7539694-97e7-4dfe-84da-b4256e1ff5c7",
                    Username = "User 2",
                    Password = "password",

                    Claims = new List<Claim>
                    {
                        new Claim("given_name", "User 2"),
                        new Claim("family_name", "Test"),
                    }
                }
            };
        }

        // identity-related resources (scopes)
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }

        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>();
        }
    }
}
توضیحات:
- این کلاس استاتیک، اطلاعاتی درون حافظه‌ای را برای تکمیل دموی جاری ارائه می‌دهد.

- ابتدا در متد GetUsers، تعدادی کاربر آزمایشی اضافه شده‌اند. کلاس TestUser در فضای نام IdentityServer4.Test قرار دارد. در کلاس TestUser، خاصیت SubjectId، بیانگر Id منحصربفرد هر کاربر در کل این IDP است. سپس نام کاربری، کلمه‌ی عبور و تعدادی Claim برای هر کاربر تعریف شده‌اند که بیانگر اطلاعاتی اضافی در مورد هر کدام از آن‌ها هستند. برای مثال نام و نام خانوادگی جزو خواص کلاس TestUser نیستند؛ اما منعی هم برای تعریف آن‌ها وجود ندارد. اینگونه اطلاعات اضافی را می‌توان توسط Claims به سیستم اضافه کرد.

- بازگشت Claims توسط یک IDP مرتبط است به مفهوم Scopes. برای این منظور متد دیگری به نام GetIdentityResources تعریف شده‌است تا لیستی از IdentityResource‌ها را بازگشت دهد که در فضای نام IdentityServer4.Models قرار دارد. هر IdentityResource، به یک Scope که سبب دسترسی به اطلاعات Identity کاربران می‌شود، نگاشت خواهد شد. در اینجا چون از پروتکل OpenID Connect استفاده می‌کنیم، ذکر IdentityResources.OpenId اجباری است. به این ترتیب مطمئن خواهیم شد که SubjectId به سمت برنامه‌ی کلاینت بازگشت داده می‌شود. برای بازگشت Claims نیز باید IdentityResources.Profile را به عنوان یک Scope دیگری مشخص کرد که در متد GetIdentityResources مشخص شده‌است.

- در آخر نیاز است کلاینت‌های این IDP را نیز مشخص کنیم (در مورد مفهوم Clients در قسمت قبل بیشتر توضیح داده شد) که اینکار در متد GetClients انجام می‌شود. فعلا یک لیست خالی را بازگشت می‌دهیم و آن‌را در قسمت‌های بعدی تکمیل خواهیم کرد.


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

پس از تعریف و تکمیل کلاس Config، برای معرفی آن به IDP، به کلاس آغازین برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddIdentityServer()
             .AddDeveloperSigningCredential()
             .AddTestUsers(Config.GetUsers())
             .AddInMemoryIdentityResources(Config.GetIdentityResources())
             .AddInMemoryClients(Config.GetClients());
        }
در اینجا لیست کاربران و اطلاعات آن‌ها توسط متد AddTestUsers، لیست منابع و Scopes توسط متد AddInMemoryIdentityResources و لیست کلاینت‌ها توسط متد AddInMemoryClients به تنظیمات IdentityServer اضافه شده‌اند.


افزودن میان افزار IdentityServer به برنامه

پس از انجام تنظیمات مقدماتی سرویس‌های برنامه، اکنون نوبت به افزودن میان‌افزار IdentityServer است که در کلاس آغازین برنامه به صورت زیر تعریف می‌شود:
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseIdentityServer();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }

آزمایش IDP

اکنون برای آزمایش IDP، به پوشه‌ی src\IDP\DNT.IDP وارد شده و دستور dotnet run را اجرا کنید:


همانطور که ملاحظه می‌کنید، برنامه‌ی IDP بر روی پورت 6001 قابل دسترسی است. برای آزمایش Web API آن، آدرس discovery endpoint این IDP را به صورت زیر در مرورگر وارد کنید:
 https://localhost:6001/.well-known/openid-configuration


در این تصویر، مفاهیمی را که در قسمت قبل بررسی کردیم مانند authorization_endpoint ،token_endpoint و غیره، مشاهده می‌کنید.


افزودن UI به IdentityServer

تا اینجا میان‌افزار IdentityServer را نصب و راه اندازی کردیم. در نگارش‌های قبلی آن، UI به صورت پیش‌فرض جزئی از این سیستم بود. در این نگارش آن‌را می‌توان به صورت جداگانه دریافت و به برنامه اضافه کرد. برای این منظور به آدرس IdentityServer4.Quickstart.UI مراجعه کرده و همانطور که در readme آن ذکر شده‌است می‌توان از یکی از دستورات زیر برای افزودن آن به پروژه‌ی IDP استفاده کرد:
الف) در ویندوز از طریق کنسول پاورشل به پوشه‌ی src\IDP\DNT.IDP وارد شده و سپس دستور زیر را وارد کنید:
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))
ب) و یا درmacOS و یا Linux، دستور زیر را اجرا کنید:
\curl -L https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.sh | bash

یک نکته: در ویندوز اگر در نوار آدرس هر پوشه، عبارت cmd را وارد و enter کنید، کنسول خط فرمان ویندوز در همان پوشه باز خواهد شد. همچنین در اینجا از ورود عبارت powershell هم پشتیبانی می‌شود:


بنابراین در نوار آدرس پوشه‌ی src\IDP\DNT.IDP، عبارت powershell را وارد کرده و سپس enter کنید. پس از آن دستور الف را وارد (copy/paste) و اجرا کنید.


به این ترتیب فایل‌های IdentityServer4.Quickstart.UI به پروژه‌ی IDP جاری اضافه می‌شوند.
- پس از آن اگر به پوشه‌ی Views مراجعه کنید، برای نمونه ذیل پوشه‌ی Account آن، Viewهای ورود و خروج به سیستم قابل مشاهده هستند.
- در پوشه‌ی Quickstart آن، کدهای کامل کنترلرهای متناظر با این Viewها قرار دارند.
بنابراین اگر نیاز به سفارشی سازی این Viewها را داشته باشید، کدهای کامل کنترلرها و Viewهای آن هم اکنون در پروژه‌ی IDP جاری در دسترس هستند.

نکته‌ی مهم: این UI اضافه شده، یک برنامه‌ی ASP.NET Core MVC است. به همین جهت در انتهای متد Configure، ذکر میان افزارهای UseStaticFiles و همچنین UseMvcWithDefaultRoute انجام شدند.

اکنون اگر برنامه‌ی IDP را مجددا با دستور dotnet run اجرا کنیم، تصویر زیر را می‌توان در ریشه‌ی سایت، مشاهده کرد که برای مثال لینک discovery endpoint در همان سطر اول آن ذکر شده‌است:


همچنین همانطور که در قسمت قبل نیز ذکر شد، یک IDP حتما باید از طریق پروتکل HTTPS در دسترس قرار گیرد که در پروژه‌های ASP.NET Core 2.1 این حالت، جزو تنظیمات پیش‌فرض است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت اول - نیاز به تامین کننده‌ی هویت مرکزی
عصر Thick Clients

امن سازی برنامه‌های وب همواره چالش برانگیز بوده‌است؛ خصوصا این روزها که نیاز است برنامه‌ها، خارج از دیوارهای یک شرکت نیز در دسترس باشند و توسط انواع و اقسام وسایل ارتباطی مورد استفاده قرار گیرند. در سال‌های قبل، عموما برنامه‌های thick clients مانند WPF و WinForms برای شرکت‌ها توسعه داده می‌شدند و یا برنامه‌های وب مانند ASP.NET Web Forms که مبتنی بر سرویس‌ها نبودند. در برنامه‌های ویندوزی، پس از لاگین شخص به شبکه و دومین داخلی شرکت، عموما از روش Windows Authentication برای مشخص سازی سطوح دسترسی کاربران استفاده می‌شود. در برنامه‌های Web Forms نیز بیشتر روش Forms Authentication برای اعتبارسنجی کاربران مرسوم است. امن سازی این نوع برنامه‌ها ساده‌است. عموما بر روی یک دومین ارائه می‌شوند و کوکی‌های اعتبارسنجی کاربران برای ارائه‌ی مباحثی مانند single sign-on (داشتن تنها یک صفحه‌ی لاگین برای دسترسی به تمام برنامه‌های شرکت)، میسر است.


عصر شروع به‌کارگیری سرویس‌های وب

در سال‌های اخیر این شیوه‌ی کاری تغییر کرده و بیشتر بر اساس بکارگیری برنامه‌های مبتنی بر سرویس‌ها شده‌است. برای مثال برای این مورد استاندارد WS-Security مربوط به WCF ارائه شده‌است که باز هم مرتبط است به برنامه‌های یک دومین و یا یک Application pool. اگر تعداد دومین‌ها بیشتر شوند و نیاز به ارتباط امن بین آن‌ها باشد، استاندارد SAML 2.0 مورد استفاده قرار می‌گرفت که هدف از آن، انتقال امن اعتبارسنجی و سطوح دسترسی کاربران بین دومین‌های مختلف است. همانطور که ملاحظه می‌کنید تمام این برنامه‌ها و استانداردها، داخل دیوارهای یک شرکت و یک دومین زندگی می‌کنند.


عصر شروع به‌کارگیری Restful-API's

پس از آن باز هم شیوه‌ی طراحی برنامه‌های تغییر کرد و شروع به ایجاد Restful-API's و HTTP API's کردیم. این‌ها دیگر الزاما داخل یک دومین ارائه نمی‌شوند و گاهی از اوقات حتی تحت کنترل ما هم نیستند. همچنین برنامه‌های ارائه شده نیز دیگر thick clients نیستند و ممکن است برنامه‌های سمت کلاینت Angular و یا حتی موبایل باشند که مستقیما با سرویس‌های API برنامه‌ها کار می‌کنند. حتی ممکن است یک API را طراحی کنیم که با یک API دیگر کار می‌کند.


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


عصر شروع به‌کارگیری Security Tokens

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


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


حرکت به سمت «تامین کننده‌ی هویت مرکزی»


در گذشته، هر تک برنامه‌ای دارای صفحه‌ی لاگین و امکانات مدیریت کاربران آن، تغییر کلمه‌ی عبور، تنظیم مجدد آن و این‌گونه عملیات بود. این‌روزها دیگر چنین کاری مرسوم نیست. این وظیفه‌ی برنامه‌ی شما نیست که بررسی کند کاربر وارد شده‌ی به سیستم کیست و آیا ادعای او صحیح است یا خیر؟ این نوع عملیات وظیفه‌ی یک Identity provider و یا به اختصار IDP است. کار IDP اعتبارسنجی کاربر و در صورت نیاز، ارائه‌ی اثبات هویت کاربر، به برنامه‌ی درخواست کننده‌است.
در یک IDP عملیاتی مانند ثبت کاربران و مدیریت آن‌ها انجام می‌شود. اینجا است که مفاهیمی مانند قفل کردن اکانت و یا تغییر کلمه‌ی عبور و امثال آن انجام می‌شود و نه اینکه به ازای هر برنامه‌ی تهیه شده‌ی برای یک شرکت، آن برنامه راسا اقدام به انجام چنین عملیاتی کند. به این ترتیب می‌توان به امکان استفاده‌ی مجدد از اطلاعات هویت کاربران و سطوح دسترسی آن‌ها در بین تمام برنامه‌های موجود رسید.
همچنین با داشتن یک برنامه‌ی IDP خوب پیاده سازی شده، از توزیع باگ‌های امنیتی مختلف در بین برنامه‌های گوناگون تهیه شده که هر کدام سیستم امنیتی خاص خودشان را دارند، جلوگیری خواهد شد. برای مثال فرض کنید می‌خواهید الگوریتم هش کردن پسوردهای سیستم را که امروز نا امن اعلام شده‌است، تغییر دهید. با داشتن یک IDP، دیگر نیازی نیست تا تمام برنامه‌های خود را برای رفع یک چنین باگ‌هایی، تک تک تغییر دهید.


به علاوه این روزها روش استفاده‌ی از نام کاربری و کلمه‌ی عبور، تنها راه ورود به یک سیستم نیست و استفاده از کلیدهای دیجیتال و یا روش‌های ویژه‌ی ابزارهای موبایل نیز به این لیست اضافه شده‌اند.


حرکت به سمت استاندارد OAuth 2

OAuth 2.0 پروتکلی است استاندارد، برای Authorization امن کاربران، توسط برنامه‌های وب، موبایل و دسکتاپ. به این ترتیب می‌توان امکان دسترسی یک برنامه را به یک API، به نحوی استاندارد و امن میسر ساخت. OAuth 2.0 یک توکن دسترسی (Access token) را تولید می‌کند و در اختیار کلاینت قرار می‌دهد. سپس آن کلاینت با ارسال این توکن به API مدنظر، امکان دسترسی به امکانات مختلف آن‌را خواهد یافت. به علاوه چون ماهیت برنامه‌های کلاینت وب و غیر وب متفاوت است، این استاندارد نحوه‌ی دریافت چنین توکنی را برای برنامه‌های مختلف نیز تعریف می‌کند. به این ترتیب موارد مشترکی مانند تولید و نحوه‌ی انتقال توکن‌ها به کلاینت‌های مختلف توسط این پروتکل استاندارد بیان می‌شود. در این حالت راه‌حل‌های خانگی ما تبدیل به راه‌حل‌هایی می‌شوند که استاندارد OAuth 2.0 را پیاده سازی کرده باشند. بنابراین IDP ما باید بر مبنای این استاندارد تهیه شده باشد. برای مثال IdentityServer که در این سری بررسی خواهد شد و یا Azure Active Directory، نمونه‌ای از IDPهایی هستند که این استاندارد را کاملا پیاده سازی کرده‌اند.
البته باید دقت داشت که این توکن‌های دسترسی، تنها سطوح دسترسی به منابع API را مشخص می‌کنند و یا به عبارتی عملیات Authorization توسط آن‌ها میسر می‌شود. عملیات ورود به سیستم اصطلاحا Authentication نام دارد و توسط استاندارد دیگری به نام OpenID Connect مدیریت می‌شود.


حرکت به سمت استاندارد OpenID Connect


OpenID Connect یک لایه‌ی امنیتی بر فراز پروتکل OAuth 2.0 است که به اختصار به آن OIDC نیز گفته می‌شود. توسط آن یک کلاینت می‌تواند یک Identity token را علاوه بر Access token درخواست کند. از این Identity token برای ورود به برنامه‌ی کلاینت استفاده می‌شود (Authentication) و پس از آن، برنامه‌ی کلاینت بر اساس سطوح دسترسی تعریف شده‌ی در Access token، امکان دسترسی به امکانات مختلف یک API را خواهد یافت (Authorization). همچنین OpenID Connect امکان دسترسی به اطلاعات بیشتری از یک کاربر را نیز میسر می‌کند.
بنابراین OpenID Connect پروتکلی است که در عمل استفاده می‌شود و توسعه دهنده و جایگزین کننده‌ی پروتکل OAuth 2.0 می‌باشد. هرچند ممکن است در بسیاری از منابع صرفا به OAuth 2.0 بپردازند، اما در پشت صحنه با همان OpenID Connect کار می‌کنند.
مزیت دیگر کار با OpenID Connect، عدم الزام به استفاده‌ی از API، در برنامه‌ای خاص و یا قدیمی است. اگر برنامه‌ی وب شما با هیچ نوع API ایی کار نمی‌کند، باز هم می‌توانید از امکانات OpenID Connect بهره‌مند شوید.
مطالب
ایجاد پروژه‌ی «کتابخانه» توسط Angular CLI 6.0
یکی از مواردی که با Angular CLI 6.0 به شدت ساده شده‌است، ایجاد پروژه‌های «کتابخانه» Angular است. برای مثال شاید در حین استفاده‌ی از بعضی از کتابخانه‌ی ثالث تهیه شده‌ی برای Angular با خطای ذیل مواجه شده باشید:
Please open an issue in the library repository to alert its author and ask them to 
package the library using the Angular Package Format (https://goo.gl/jB3GVv).
این خطا زمانی رخ می‌دهد که تهیه کننده‌ی کتابخانه، فرمت بسته‌های Angular را رعایت نکرده باشد و ... رعایت کردن آن نیز کار بسیار مشکلی است. نگارش 6 در پشت صحنه، پروژه‌ی موفق ng-packagr را به مجموعه‌ی CLI اضافه کرده‌است و از این پس توسط خود CLI می‌توان کتابخانه‌های استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانه‌ها‌ی npm حاصل را نیز به همراه دارد. مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار با بسته‌های فعلی npm مخصوص Angular وجود دارند؛ مانند خطایی که عنوان شد. برای مثال بدون استفاده‌ی از این ابزار، نیاز است مستندات چند صفحه‌ای ساخت کتابخانه‌های Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0 به صورت خودکار ایجاد و مدیریت می‌شود.


مراحل ایجاد یک پروژه‌ی «کتابخانه» توسط Angular CLI 6.0

مرحله‌ی اول ایجاد یک پروژه‌ی کتابخانه، مانند قبل، توسط دستور ng new و ایجاد یک پروژه‌ی دلخواه جدید است:
 ng new my-lib-test
به همراه Angular CLI 6.0، فرمت تنظیمات آن نیز تغییر کرده‌است و مفهوم workspace به آن اضافه شده‌است که در آن می‌توان چندین پروژه را تعریف کرد.
پس از ایجاد پروژه‌ی my-lib-test توسط دستور فوق و وارد شدن به پوشه‌ی اصلی آن توسط خط فرمان، می‌توان با اجرای دستور زیر، پروژه‌های دیگری را به پروژه‌ی جاری افزود:
 ng generate application my-app-name
اما اگر در اینجا بجای ذکر application، از نام library استفاده کنیم، یک کتابخانه را بجای یک برنامه، به workspace جاری اضافه می‌کند:
 ng generate library my-lib
پس از اجرای این دستور اگر به فایل angular.json دقت کنیم، این پروژه در ذیل projects اضافه شده‌است:


همچنین یک پوشه‌ی جدید به نام projects نیز ایجاد شده و پروژه‌ی my-lib داخل آن قرار گرفته‌است.


فایل جدید public_api.ts

پس از ایجاد کتابخانه‌ی جدید «my-lib»، فایل جدیدی به نام projects\my-lib\src\public_api.ts نیز به آن اضافه شده‌است:


با این محتوا:
/*
* Public API Surface of my-lib
*/
export * from './lib/my-lib.service';
export * from './lib/my-lib.component';
export * from './lib/my-lib.module';
هر خروجی که در اینجا ذکر شود توسط استفاده کنندگان از این کتابخانه قابل دسترسی خواهد بود. برای مثال دستور «ng generate library my-lib» مطابق تصویر فوق، یک سرویس جدید را به نام my-lib.service، یک کامپوننت جدید را به نام my-lib.component و یک ماژول جدید را به نام my-lib.module به صورت پیش‌فرض ایجاد کرده و درون پوشه‌ی lib قرار داده‌است. اکنون آن‌ها را توسط فایل public_api.ts، به نحوی که مشاهده می‌کنید در معرض دید استفاده کنندگان قرار می‌دهد.
برای مثال اگر فایل جدید projects\my-lib\src\lib\my-lib.models.ts را به این کتابخانه اضافه کنیم که شامل تعدادی مدل و اینترفیس قابل دسترسی توسط استفاده کنندگان باشد، باید یک سطر زیر را به انتهای فایل public_api.ts اضافه کنیم:
 export * from './lib/my-lib.models';

این پروژه‌ی کتابخانه حتی به همراه فایل‌های package.json, tsconfig.json, tslint.json مخصوص به خود نیز می‌باشد تا بتوان آن‌ها را صرفا جهت این پروژه سفارشی سازی کرد.


ساختار my-lib.service پیش‌فرض یک پروژه‌ی کتابخانه

اگر به فایل projects\my-lib\src\lib\my-lib.service.ts دقت کنیم:
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MyLibService {

  constructor() { }
}
تمام قسمت‌های آن مانند قبل است، منهای 'providedIn: 'root آن. این مورد تنظیم جدیدی است که در پروژه‌های Angular 6 قابل استفاده‌است. هدف از آن، ارائه‌ی یک سرویس، بدون نیاز به ثبت صریح آن در قسمت providers یک NgModule است.
شاید بپرسید چرا؟ هدف اصلی از آن، بهبود فرآیند tree-shaking یا حذف کدهای مرده و استفاده نشده‌است. ممکن است سرویسی را تعریف کنید، اما در برنامه استفاده نشود. این حالت خصوصا در پروژه‌های کتابخانه‌های ثالث ممکن است زیاد رخ دهد. به همین جهت با ارائه‌ی این قابلیت، امکان حذف ساده‌تر سرویس‌هایی که در برنامه استفاده نشده‌اند از خروجی نهایی کامپایل شده، وجود خواهد داشت.


چگونه به پروژه‌ی کتابخانه‌ی جدید، یک کامپوننت جدید را اضافه کنیم؟

تمام دستورات Angular CLI، در اینجا نیز کار می‌کنند. تنها تفاوت آن‌ها، ذکر صریح نام پروژه‌ی مورد استفاده است:
 ng generate component show-data --project=my-lib
دستور فوق کامپوننت جدید show-data را به پروژه‌ی my-lib اضافه خواهد کرد؛ به همراه به روز رسانی خودکار فایل projects/my-lib/src/lib/my-lib.module.ts این پروژه، جهت ثبت کامپوننت اضافه شده.
البته در اینجا باید فایل my-lib.module.ts را اندکی ویرایش کرد و ShowDataComponent را به قسمت exports نیز افزود:
@NgModule({
  imports: [
    CommonModule,
    HttpClientModule
  ],
  declarations: [MyLibComponent, ShowDataComponent],
  exports: [MyLibComponent, ShowDataComponent]
})
export class MyLibModule { }
به صورت پیش‌فرض، کامپوننت جدید را در قسمت declarations معرفی می‌کند. یک چنین کامپوننتی فقط داخل همان lib قابل استفاده‌است. اگر قرار است خارج از این lib نیز به آن دسترسی داشته باشیم، باید آن‌را در قسمت exports نیز قید کنیم.
همچنین قسمت imports آن نیز به صورت پیش‌فرض خالی است. اگر نیاز است با ngIf کار کنید، باید CommonModule را در اینجا قید کنید و اگر نیاز است تبادلات HTTP وجود داشته باشد، ذکر HttpClientModule نیز ضروری است.


مرحله‌ی ساخت پروژه

پیش از استفاده‌ی از این پروژه‌ی کتابخانه، باید آن‌را build کرد:
 ng build my-lib
در اینجا نیز دستور ng build مانند قبل است، با این تفاوت که نام پروژه‌ی کتابخانه نیز در اینجا ذکر شده‌است.
پس از اجرای این دستور، خروجی ذیل مشاهده می‌شود:
Building Angular Package
Building entry point 'my-lib'
Rendering Stylesheets
Rendering Templates
Compiling TypeScript sources through ngc
Downleveling ESM2015 sources through tsc
Bundling to FESM2015
Bundling to FESM5
Bundling to UMD
Minifying UMD bundle
Remap source maps
Relocating source maps
Copying declaration files
Writing package metadata
Removing scripts section in package.json as it's considered a potential security vulnerability.
Built my-lib
Built Angular Package!
- from: D:\my-lib-test\projects\my-lib
- to: D:\my-lib-test\dist\my-lib
همانطور که ملاحظه می‌کنید، پس از طی مراحل خاص تولید یک کتابخانه، خروجی نهایی آن‌را در پوشه‌ی dist\my-lib قرار داده‌است.


استفاده‌ی از کتابخانه‌ی تولید شده

پس از پایان موفقیت آمیز مرحله‌ی Build، اکنون نوبت به استفاده‌ی از این کتابخانه است. استفاده‌ی از آن نیز همانند تمام کتابخانه‌ها و وابستگی‌های ثالثی است که تا پیش از این از آن‌ها استفاده کرده‌ایم. برای مثال ماژول آن‌را در قسمت imports مربوط به NgModule کلاس AppModule معرفی می‌کنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و MyLibModule را به نحو ذیل اضافه می‌کنیم:
import { MyLibModule } from "my-lib";

@NgModule({
  imports: [
    BrowserModule,
    MyLibModule
  ]
})
export class AppModule { }
نکته‌ی مهمی که در اینجا باید به آن دقت داشت این است که هرچند در این پروژه، MyLibModule داخل پوشه‌ی projects\my-lib\src\lib قرار دارد، اما نباید مسیر نسبی آن‌را در اینجا ذکر کرد و باید صرفا نام پوشه‌ی my-lib واقع در پوشه‌ی node_modules را در اینجا در حین مسیر دهی import آن معرفی کرد (همانند تمام وابستگی‌های ثالث دیگر).
اما سؤال اینجا است که آیا این پوشه پس از build، داخل پوشه‌ی node_modules نیز کپی شده‌است؟ پاسخ آن خیر است و برای مدیریت خودکار آن، به صورت زیر عمل شده‌است:
اگر به فایل tsconfig.json اصلی و واقع در ریشه‌ی workspace دقت کنید، پس از اجرای دستور «ng generate library my-lib»، قسمت paths آن نیز به صورت خودکار ویرایش شده‌است:
{
  "compilerOptions": {
    "paths": {
      "my-lib": [
        "dist/my-lib"
      ]
    }
  }
}
معنای آن این است که هرگاه import ایی در برنامه به my-lib اشاره کند، کامپایلر TypeScript می‌داند که باید آن‌را از پوشه‌ی dist/my-lib دریافت و پردازش کند. به همین جهت در اینجا دیگر نیازی به کپی دستی این پوشه، به پوشه‌ی node_modules وجود ندارد.

برای نمونه اگر شاره‌گر ماوس را بر روی my-lib قرار دهید، به درستی مسیر خوانده شدن آن، تشخیص داده می‌شود.

به این ترتیب مسیر این import‌، چه در این پروژه‌ی محلی و چه برای کسانیکه پوشه‌ی dist/my-lib را به صورت یک بسته‌ی npm جدید دریافت کرده‌اند، یکی خواهد بود.

در ادامه اگر به فایل app.component.html مراجعه کرده و selector کامپوننت show-data را به آن اضافه کنیم:
 <lib-show-data></lib-show-data>
می‌توان محتویات این کامپوننت دریافت شده‌ی از کتابخانه را مشاهده کرد.


توزیع کتابخانه‌ی ایجاد شده برای عموم

برای اینکه این کتابخانه‌ی تولیدی را در اختیار عموم، در سایت npm قرار دهیم، ابتدا باید کتابخانه را در حالت production build تولید و سپس آن‌را publish کرد:
ng build my-lib --prod
cd dist/my-lib
npm publish
سطر اول، کتابخانه‌ی my-lib را در حالت production تواید می‌کند. سپس به پوشه‌ی فایل‌های نهایی تولید شده وارد می‌شویم و دستور npm publish را صادر می‌کنیم.
البته دستور آخر نیاز به ایجاد یک اکانت در سایت npm و وارد شدن به آن‌را دارد. جزئیات بیشتر آن در اینجا.
مطالب
رویه های ذخیره شده خوب یا بد؟!

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

Stored Procedure (که از این به بعد برای ایجاز، SP نوشته خواهد شد) هم از قاعده فوق مستثنی نیست و در صورت انتخاب صحیح میتواند به ارائه‌ی محصول نهایی با کیفیت‌تری در زمان کوتاه‌تری کمک کند و در صورت انتخاب ناآگاهانه ممکن است باعث شکست یک پروژه (بخصوص در بلند مدت) شود.


تاریخچه

SQL توسط شرکت IBM در اوایل دهه 70 میلادی ایجاد شد. با اوج گرفتن زبان‌های رویه‌ای، SQL هم چندان از این قافله عقب نماند که منجر به پذیرش SP به عنوان یک استاندارد، در دهه 90 میلادی و پیاده سازی تدریجی آن توسط غول‌های سازنده دیتابیس شد (رجوع فرمایید به ^ و ^). این فاصله 20 ساله باعث غنی‌تر شدن SQL شد و وجود SP - به معنی انتقال مدل برنامه نویسی رویه‌ای به SQL - بخشی از مشکلات قبلی کار با کوئری‌های پشت سر هم و خام را حل کرد. از سال 2000 میلادی به بعد، ORM‌های قدرتمندی از جمله  Hibernate  و پیاده سازی‌های مختلفی از Active Record  و Entity Framework متولد شدند. بنابر این تقدم و تاخّرهای زمانی، بدیهی است اغلب مزایای SP نسبت به Raw SQL Query و اغلب معایب آن نسبت به ORM‌ها باشد. 

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


معایب SP

- دستورات Alter Table ، Add Column و Drop Column  به این سادگی‌ها هم نیستند؛ ممکن است به یکی از جداول دیتابیس دو ستون اضافه یا از آن حذف شوند. مجبوریم تمامی SP‌ها را بخصوص Insert و Update متناظر با جدول را تغییر دهیم که این تغییرات ممکن است بصورت زنجیره‌وار به سایر SP‌ها هم سرایت کند. حال شرایطی را در نظر بگیرید که تعداد SP‌های شما به چند ده و یا حتی به چند صد عدد و بیشتر، رسیده باشد که این به معنی زحمت بیشتر و تغییرات پر هزینه‌تر است.

- احتمال کند شدن ماشین سرویس دهنده در اثر اجرای تعداد زیادی SP ؛ چناچه بخش زیادی از منطق برنامه از طریق SP اجرا شود، سرور دیتابیس موظف به اجرای آنهاست. اما در صورتیکه منطق، در کد برنامه قرار داشته باشد، امکان توزیع آن بر روی سرور‌های مجزا و یا حتی ماشین کلاینت وجود خواهد داشت. امروزه اکثر کلاینت‌ها به دیتابیس‌های سبک و سریعی مجهز شده‌اند. بنابراین در صورت امکان چرا بار پردازشی را به عهده آنها نگذاریم؟! 

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

- انعطاف پذیری کمتر؛ در یک شرایط ایده آل، عملکرد اپلیکیشن، مستقل از دیتابیس است. اگر نیاز به تغییر دیتابیس، مثلا از اوراکل به Microsoft SQL Server وجود داشته باشد، نیاز به بازنویسی و انتقال فانکشن‌ها و SP ها محتمل است و از آنجائیکه که با وجود استانداردها، دیتابیس‌های مختلف، معمولا در Syntax دستورات، تفاوت‌های فاحشی دارند، هر چه کد بیشتری در SP ها باشد، نیاز به انتقال و تبدیل بیشتری وجود دارد. 

- عدم وجود بازخورد مناسب؛ بسیاری از اوقات در صورت بروز اشکالی در حین اجرای یک SP، فقط با یک متن ساده بصورت Table has no rows   و یا  error مواجه میشویم. چنین خطاهایی هنگام دیباگ اصلا خوشایند نیستند. MS SQL در این بین بازخورد‌های مناسبی را ارائه میکند. اگر تجربه کار با سایر دیتابیس‌ها را داشته باشید، اهمیت بازخورد‌های مناسب، ملموس‌تر خواهد بود.

- کد نویسی سخت‌تر؛ نوشتن کد SQL  معمولا در همان IDE  اپلیکیشن انجام نمیشود. جابجایی مداوم بین دو IDE ، دیباگ و کد نویسی از طریق دو اینترفیس مجزا، اصلا ایده‌ال نیست. 

- SP  منطق را بیش از حد پنهان میکند؛ حتی با دانستن نام صحیح یک SP، باز هم تصویری از پارامتر‌های ارسالی به آن و نتیجه برگشتی نخواهیم داشت. نمیدانیم نتیجه حاصل از اجرای SP ما مقداری را برمیگرداند یا خیر؟ در صورت وجود برگشتی، یک Cursor است یا یک مقدار؟ اگر Cursor است شامل چه ستون‌هایی است؟

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

INSERT INTO User_Table(Id,Username,Password,FirstName,SureName,PhoneNumber,x,Email)
VALUES (1,'VahidN','123456','Vahid','Nasiri','09120000000','vahid_xxx@example.com')

و معادل آن در یک ORM  فرضی:

public void Insert(User user)
{
  _users.Insert(user);
  db.Save();
}

به‌وضوح قطعه کد sql، قبل از خوب یا بد بودن، زشت است. همچنین پارامتر x آن که فرضاً به تازگی اضافه شده، مقداری را دریافت نکرده و باعث بروز خطا خواهد شد.

- نبود Query Chaining؛ یکی از ویژگی‌های جذاب ORM‌‌های امروزی، امکان تشکیل یک کوئری با قابلیت خوانایی بالا و افزودن شرط‌های بیشتر از طریق  الگوی builder است. قطعه کد زیر یک SP برای جستجوی داینامیک نام و نام خانوادگی در یک جدول فرضی به اسم Users است:

public ICollection<User> GetUsers(string firstName,string lastName,Func<User, bool> orderBy)
{
    var query = _users.where(u => u.LastName.StartsWith(lastName));
    query = query.where(u => u.FirstName.StartsWith(firstName));
    query = query.OrderBy(orderBy);
    return  query.ToList();
}

در مقایسه با معادل SP آن:

CREATE PROCEDURE DynamicWhere 
    @LastName varchar(50) = null,
    @FirstName varchar(50) = null,
    @Orderby varchar(50) = null
AS
BEGIN
    DECLARE @where nvarchar(max)
    SELECT @where = '1 = 1'
 
    IF @LastName IS NOT NULL
        SELECT @Where = @Where + " AND A.LastName LIKE @LastName + '%'"
 
    IF @FirstName IS NOT NULL
        SELECT @Where = @Where + " AND A.FirstName LIKE @FirstName + '%'"
 
    DECLARE @orderBySql nvarchar(max)
    SELECT @orderBySql = CASE
        WHEN @OrderBy = "LastName" THEN "A.LastName"
        ELSE @OrderBy = "FirstName" THEN "A.FirstName"
    END
 
    DECLARE @sql nvarchar(max)
    SELECT @sql = "
    SELECT A.Id , A.AccountNoId, A.LastName, A.FirstName, A.PostingDt, 
    A.BillingAmount
    FROM Users 
    WHERE " + @where + " 
    ORDER BY " + @orderBySql
 
    exec sp_executesql @sql,  N'@LastName varchar(50), @FirstName varchar(50)
        @LastName, @FirstName
END

حاجت به گفتن نیست که قطعه کد اول چقدر خواناتر، انعطاف پذیرتر، خلاصه‌تر و قابل نگهداری‌تر است.

- نداشتن امکانات زبان‌های مدرن؛ زبان‌ها و IDE‌های مدرن، امکانات قابل توجهی را برای نگهداری بهتر، انعطاف پذیری بیشتر، مقیاس پذیری بالاتر، تست پذیری دقیق‌تر و... ارائه میکنند. به عنوان مثال:

  • شیءگرایی و امکانات آن که در SP موجود نیست و در مورد قبلی معایب، به آن مختصرا اشاره شد. در نظر بگیرید اگر SQL زبانی شیء گرا بود و مجهز به ارث بری و کپسوله سازی بود، چقدر قابلیت نگهداری آن بالاتر میرفت و حجم کد‌های نوشته شده میتوانست کمتر باشند.
  • نداشتن Lazy Loading که باعث مصرف زیاد حافظه میشود.
  • نداشتن intellisense حین فراخوانی‌ها.
  • نداشتن Navigation Property که باعث join نویسی‌های زیاد خواهد شد.
  • SQL در مقایسه با یک زبان مدرن ناقص بنظر میرسد و این نوشتن کد آن را سخت‌تر میکند.‌
  • نداشتن امکان تغییر منطقی نام جداول و ستون ها
  • مدیریت تراکنش‌ها بصورت دستی، حال آنکه با الگوی Unit Of Work  این مشکل در یک ORM قدرتمند مثل EF حل شده است.


- زمان بر بودن نوشتن SP؛ گاهی نوشتن یک تابع در یک ORM یا بعضا نوشتن یک کوئری SQL کوتاه در یک رشته متنی، ساده‌تر از نوشتن کد SP است. آیا برای هر وظیفه کوچک در دیتابیس، نوشتن یک SP ضروری است؟


مزایای SP :

- کمتر کردن Round Trips در شبکه و متعاقبا کاهش ترافیک شبکه؛ اگر از یک فراخوانی استفاده کنیم، کاهش Round Trip‌ها تاثیر چندانی نخواهد داشت. همچنین ارسال یک کوئری کامل، نسبت به ارسال فقط اسم SP و پارامتر‌های آن، پهنای باند بیشتری اِشغال میکند. البته در یک شبکه با سرعت قابل قبول، بعید است این دو مزیت محسوس باشند؛ اما به هر حال برای موارد خاص، دو مزیت محسوب میشوند. نکته دیگر آنکه بدلیل Pre-Compiled بودن SP‌ها و همچنین کَش شدن Execution Plan آنها، اندکی با سرعت بالاتری اجرا میشوند.

- امکان چک کردن سینتکس قبل از اجرای آن؛ در مقایسه با Raw Query مزیت محسوب میشود.

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

- کمک به ایجاد یک پَک؛ در یک زیر سیستم با نیازمندی مشخص که اعمال تغییرات در آن محتمل نمیباشد نیز SP میتواند یک گزینه مناسب به حساب آید. مثلا یک سیستم Membership را در نظر بگیرید که در پروژه‌های مختلف شما مورد استفاده قرار خواهد گرفت. برای مثال میشود یک سیستم Membership  سفارشی را با امکان  Hash  پسورد و  رمز کردن داده‌های حساس،  به کمک SP و Function ‌های مناسب فراهم کرد و در واقع بین Application Login  و Data Logic تمایز قائل شد. شخصا معماری Restful را به این روش هم ترجیح میدهم. 

بهرمند شدن از امکانات بومی SQL ؛ به عنوان نمونه برای ترانهاده کردن خروجی یک کوئری میتوان از فانکشن  Pivot  استفاده کرد. یا فانکشن‌های تحلیلی  Lead  و  Lag  (لینک مستندات اوراکل این دو فانکشن به ترتیب در ^ و ^ ) که بنظر نمیرسد هنوز معادل مستقیمی درORM  ها  داشته باشند. 

تسلط و کنترل بیشتر و دقیقتر بر کوئری نهایی؛ گفته میشود SP و عبارات SQL در دیتابیس، حکم assembly را در سایر زبان‌ها دارند. بنابراین با SP میتوان عبارات SQL و نحوه اجرای آن را در دیتابیس، بطور کامل تحت فرمان داشت. این در حالی است که هر یک از ORM‌ها دستورات زبان برنامه نویسی مبداء را به یک عبارت SQL ترجمه میکنند که این عبارت چندان تحت کنترل برنامه نویس نیست و بیشتر به مدل کاری ORM بستگی دارد. 

امکان join بین دو یا چند دیتابیس مجزا؛ حال آنکه امکان join بین دو Context در ORM ‌ها وجود ندارد. بعلاوه اگر دو دیتابیس مدنظر ما روی دو سرور مجزا باشند، با SP و  کانفیگ Linked Server  کماکان میشود کوئری join  دار نوشت.

برای عملیات‌های Batch مناسب‌تر است؛ در مقام مقایسه با ORM ‌ها که با تکنیک‌های مختلفی سعی در افزایش سرعت عملیات Batch، بخصوص Insert و Update را دارند، SP  با سرعت قابل قبول‌تری اجرا میشود.

عدم نیاز به یادگیری سینتکس و ابزاری جدید؛ موارد بسیاری وجود دارند که فرصت یادگیری تکنولوژی جدیدی مثل یک ORM و یا SQL Bulk و حتی کتابخانه‌های ثالث مبتنی بر این ابزارها  وجود ندارند و ممکن است مجبور شوید برای باقی ماندن در بازار رقابتی، از دانسته‌های قبلی خود استفاده کنید .

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

- امنیت به نسبت بالاتر؛ میتوان مجوز اجرای SP را به یک کاربر اعطا کرد، بدون آنکه مجوز دسترسی به جداول مورد استفاده در آن SP را داد. همچنین نسبت به کوئری‌های پارامتری نشده، SQL ارجیحت دارند چون احتمال آسیب پذیری در مقابل SQL Injection را کمتر میکنند.


نتیجه‌گیری

اگرچه SP ها برای پردازش داده‌ها آنقدر هم که در وبلاگ‌ها میخوانیم بد نیستند، اما سوء استفاده از آن، مشکلات عدیده‌ای را ایجاد خواهد کرد. با توجه به روند تغییرات تکنولوژی‌های دسترسی به داده‌ها و معماری‌های مدرن بنظر میرسد SP در بهترین حالت، ابزار مناسبی برای انجام عملیات CRUD است و نه بیشتر؛ مگر در مواردی خاص که به تشخیص شما نیاز به استفاده بیشتر از آن وجود داشته باشد.

مطالب
سیستم‌های توزیع شده در NET. - بخش هفتم- معرفی Apache Kafka
سرچشمه Kafka از LinkedIn آغاز و سپس در سال 2011 توسط Apache بصورت open source ارائه شد. هدف آن ارائه یک بستر جریان داده‌ای توزیع شده‌است که اساس آن، Publish-Subscribe می‌باشد . سادگی اضافه کردن قابلیت‌های مقیاس پذیری افقی، تحمل خطا و افزایش کارآیی توسط این بستر باعث شده‌است که هزاران شرکت از آن بعنوان بستر ارتباطی قسمتهای مختلف سیستمها و زیرسیستمهای خود استفاده کنند.
همانطور که گفته شد وظیفه و هدف اصلی Apache Kafka، ارائه یک بستر برای مدیریت و کنترل جریان‌های اطلاعاتی با کارآیی بسیار بالا، در سیستم‌ها و زیرسیستمهای مختلف است. یعنی شما می‌توانید با ایجاد کردن یک Pipeline برای جریان اطلاعات خود، وابستگی مستقیم سیستمها و زیرسیستمها را از بین ببرید؛ آن هم بصورتی که بروز مشکلی در هر قسمت، کمترین میزان تاثیر را در سایر قسمتها داشته باشد.
فرض کنید شما تعداد زیادی سیستم و زیرسیستم مختلف را داشته باشید که هر کدام از آنها نیازمند ارتباط با برخی از قسمتهای دیگر است. در این صورت شما دو راه دارید: اول اینکه در هر قسمت سرویس‌هایی را برای ارتباط با سایر قسمت‌ها پیاده سازی کنید و هر قسمت بصورت مستقیم با سایر قسمتها در ارتباط باشد.

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

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

نمایی از معماری کلی Apache Kafka: 


برای شروع به آموزش Apache Kafka بهتر است ابتدا با مفاهیم و اصطلاحات آن آشنا شویم:

Producer:
  ارسال کننده پیام. Application، سیستم یا زیرسیستمی که عملیات Publish پیام را برای Topic خاص از Kafka Server انجام می‌دهد.

Consumer:
دریافت کننده پیام. Application، سیستم یا زیرسیستمی که بر روی یک یا چند Topic خاص، Subscribe کرده‌است (همچنین هر Consumer می‌تواند روی یک یا چند Partition از یک Topic خاص نیز Subscribe کند).

Consumer Group:
 گروهی از Consumer‌ها می‌باشند که با یک group.id، مشخص شده‌اند. عموما این گروه شامل یک Replicate از یک Application است؛ مانند گروه ارسال کننده ایمیل (یک زیر سیستم ارسال کننده ایمیل که چندین بار در سرور‌های مختلف اجرا شده است). Kafka این ضمانت را به ما می‌دهد که هر پیام ذخیره شده در یک Topic، برای تمامی Consumer Group‌های مرتبط ارسال شود؛ اما در هر Consumer Group، تنها یک دریافت کننده داشته باشد. یعنی هر پیام در هر Consumer Group، تنها توسط یک Consumer دریافت می‌شود.

Broker :
 قسمتی که تمامی پیامها را  از Producer دریافت می‌کند، سپس آن‌ها را در Log مربوط به Topic مشخص شده ذخیره می‌کند و پس از آن، پیام ذخیره شده را برای تمامی Consumerهای مرتبط ارسال می‌کند.

Topic: 
یک دسته بندی برای ذخیره کردن پیامهای Publish شده می‌باشد. Topicها همانند مفهوم Tableها در SQL Server می‌باشند. همانطور که می‌دانید هر Table از قبل تعریف شده‌است. یک کاربر با ارسال یک درخواست ثبت، داده‌ها را در آن ذخیره می‌کند و سپس گروهی از کاربران از داده‌های ثبت شده استفاده می‌کنند. در مفهموم Topic نیز ابتدا ما Topic مورد نظر را با خصوصیاتی که باید داشته باشد تعریف می‌کنیم (البته می‌توان بصورت Dynamic نیز آن را تعریف کرد؛ اما این روش توصیه نمی‌شود). سپس Producer پیام مربوطه را به همراه نام Topic برای Broker ارسال می‌کند. Broker پیام را در Partition مربوطه از Topic ذخیره می‌کند و سپس پیام برای تمامی Consumer‌های مربوطه ارسال می‌شود.

Partition:
یکی از تفاوتهای بسیار مهم Kafka با سایر Message broker‌ها مانند RabitMQ که باعث بالارفتن کارآیی آن نیز شده‌است، قابلیت Partition در Topic‌ها می‌باشد. در واقع هر Topic از یک یا چندین Partition برای ذخیره داده‌ها استفاده می‌کند. تعریف درست تعداد Partition‌ها در یک Topic، تاثیر مستقیمی بر درجه همزمانی و کارآیی در آن Topic و کل سیستم دارد. در Kafka تمامی پیامها به همان ترتیبی که وارد شده‌اند، در Partition‌های یک Topic ذخیره می‌شوند و به همان ترتیب نیز برای Consumer‌ها ارسال می‌شوند.
بطور مثال فرض کنید تعداد Partition‌های یک Topic با نام DepartmentMessage یک می‌باشد (از این Topic برای ذخیره پیامهای واحدهای مختلف یک سازمان استفاده می‌شود). در این صورت تمامی پیامهای دریافتی تنها در یک Partition ذخیره می‌شوند.

هر خانه از یک Partition، توسط یک شناسه از نوع int و با نام offset در دسترس است. تمامی پیامهای جدید ارسالی توسط Producer با offset ی بزرگتر از offset موجود در این Partition ذخیره می‌شوند؛ یعنی در انتهای آن قرار می‌گیرند. در مثال فوق در صورت دریافت پیام جدید، offset آن با عدد 10 مقداردهی می‌شود. همچنین عملیات خواندن نیز از کوچکترین offsetی که هنوز  مقدار آن توسط Consumer‌ها خوانده نشده‌است، انجام می‌شود. همانطور که مشخص است، بدلیل اینکه تعداد Partitionهای این مثال عدد یک می‌باشد، تمامی درخواست‌های Producer‌ها در یک Partition قرار می‌گیرند و تمامی Consumer‌ها نیز از طریق یک Partition به پیامها دسترسی دارند؛ یعنی در صورت بالا بردن تعداد Producer‌ها یا Consumer‌ها، کارآیی بالا نمی‌رود. البته با اینکه کنترل مقدار اولیه offset برای شروع یک Consumer به دست خود Consumer و Zookeeper است، اما در اکثر موارد تمامی Consumer‌های یک Topic باید از یک نقطه، شروع به خواندن داده‌ها کنند. در این حالت تا زمانیکه پیام با offset 1، توسط Consumerی خوانده نشود، هیچ Consumerی نمی‌تواند پیام شماره 2 را بخواند. استفاده کردن از یک Partition بیشتر زمانی کاربرد دارد که بخواهید تمامی پیامهایتان، واقعا در یک صف قرار بگیرند.
حال فرض کنید در سازمان شما سه واحد اداری، مالی و آموزش وجود دارد. در این صورت بدلیل اینکه تمامی پیامها در یک Partition ذخیره می‌شوند، تا زمانی که یک واحد تمامی پیامهای مرتبط با خود را از ابتدای Partition نخوانده‌است، دیگر واحدها نمی‌توانند به پیامهای مرتبط با خود دسترسی داشته باشند. پس در این صورت ما می‌توانیم تعداد Partition‌های این Topic را عدد 3 درنظر بگیریم؛ بصورتی که پیامهای مرتبط با هر واحد در یک Partition جدا قرار بگیرد.

در این روش هر Producer زمانیکه پیامی را برای این Topic ارسال می‌کند، یک Key نیز برای آن مشخص می‌کند و این Key نشان دهنده این است که پیام جدید باید در کدام Partition ذخیره شود. یعنی بصورت همزمان می‌توانید در هر سه Partition، پیامهایتان را ذخیره کنید؛ بصورتی که بطور مثال تمامی پیامهای مربوط به واحد اداری، در Partition 0  و تمامی پیامهای مربوط به واحد مالی، در Partition 1 و واحد آموزش، در Partition 2 ذخیره شوند و همچنین عملیات خواندن از این Topic نیز می‌تواند بصورت همزمان در واحدهای مختلف انجام شود.
باید در تعریف تعداد Partition‌های یک Topic این نکته را در نظر بگیرید که این تعداد کاملا به نیازمندی شما و کارآیی که شما مد نظر دارید، بستگی دارد. تعداد این Partition‌ها حتی می‌تواند به تعداد User‌های یک سیستم نیز تعریف شود. علاوه بر آن باید بدانید که هر Partition در هر زمان تنها توسط یک Primary Broker می‌تواند در دسترس سایر قسمتها قرار بگیرد و تمامی عملیات خواندن و نوشتن در Partition توسط این Kafka Server انجام می‌شود و در صورتیکه به هر دلیلی این سرور از دسترس خارج شود، مدیریت این Partition به سرور‌های دیگر داده می‌شود.

Cluster:
مجموعه ای از Brokerها می‌باشد که بصورت یک Cluster اجرا شده‌اند. این کار باعث بالا رفتن کارآیی و تحمل خطا می‌شود.

Primary Broker:
یک Kafka Server که مسئول خواندن و نوشتن در یک Partition است. در یک Cluster هر Partition در یک زمان تنها یک Primary Broker دارد. این Primary Broker همزمان می‌تواند برای Partition‌های دیگر نقش Replicas Broker را بازی کند. انتخاب یک Primary Broker برای یک Partition توسط ZooKeeper انجام می‌شود.

Replicas Brokers :
Kafka Serverهایی که شامل یک کپی از Partition می‌باشند. عملیات خواندن و نوشتن در Partition توسط Primary انجام می‌شود. در صورتیکه Primary از دسترس خارج شود، ZooKeeper یکی از Replicas Broker‌ها را بعنوان Primary در نظر می‌گیرد. همچنین این نکته را باید در نظر بگیرید که هر Replicate همزمان می‌تواند Primary پارتیشن‌های دیگر باشد.

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

Apache ZooKeeper :
Kafka هیچ Stateی را نگه نمی‌دارد (اصطلاحا stateless می‌باشد). برای ذخیره کردن و مدیریت تمامی Stateها از جمله اینکه در حال حاضر Primary Broker برای یک Partition چه سروری است، یا اینکه پیامهای یک Partition تا کدام offset توسط Consumer‌ها خوانده شده‌اند یا اینکه کدام Consumer در حال حاضر در یک Consumer Group مسئول یک Partition می‌باشد، توسط Apache Zookeeper انجام می‌شود.

ضمانت‌هایی که Kafka می‌دهد:
  1. تمامی پیامهای دریافتی در یک Partition از یک Topic، به همان ترتیبی که دریافت می‌شوند ذخیره می‌شوند.
  2. Consumer‌ها تمامی پیامها را در یک Partition به همان ترتیبی که ذخیره شده‌اند، دریافت می‌کنند.
  3. در یک Topic با Replication Factorی با مقدار N، درجه تحمل خطا N - 1 می‌باشد.

تا اینجا با اهداف، مفاهیم و اصطلاحات Apache Kafka آشنا شدیم. در بخش بعد به راه اندازی قسمتهای مختلف آن در Ubuntu می‌پردازیم و می‌بینیم که به چه صورت می‌توان به راحتی یک Cluster از سرورهای Kafka را ایجاد کرد.
مطالب
فعالسازی Windows Authentication در برنامه‌های ASP.NET Core 2.0
اعتبارسنجی مبتنی بر ویندوز، بر اساس قابلیت‌های توکار ویندوز و اختیارات اعطا شده‌ی به کاربر وارد شده‌ی به آن، کار می‌کند. عموما محل استفاده‌ی از آن، در اینترانت داخلی شرکت‌ها است که بر اساس وارد شدن افراد به دومین و اکتیودایرکتوری آن، مجوز استفاده‌ی از گروه‌های کاربری خاص و یا سطوح دسترسی خاصی را پیدا می‌کنند. میان‌افزار اعتبارسنجی ASP.NET Core، علاوه بر پشتیبانی از روش‌های اعتبارسنجی مبتنی بر کوکی‌‌ها و یا توکن‌ها، قابلیت استفاده‌ی از اطلاعات کاربر وارد شده‌ی به ویندوز را نیز جهت اعتبارسنجی او به همراه دارد.


فعالسازی Windows Authentication در IIS

پس از publish برنامه و رعایت مواردی که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS» بحث شد، باید به قسمت Authentication برنامه‌ی مدنظر، در کنسول مدیریتی IIS رجوع کرد:


و سپس Windows Authentication را با کلیک راست بر روی آن و انتخاب گزینه‌ی Enable، فعال نمود:


این تنظیم دقیقا معادل افزودن تنظیمات ذیل به فایل web.config برنامه است:
  <system.webServer>
    <security>
      <authentication>
        <anonymousAuthentication enabled="true" />
        <windowsAuthentication enabled="true" />
      </authentication>
    </security>
  </system.webServer>
اما اگر این تنظیمات را به فایل web.config اضافه کنید، پیام و خطای قفل بودن تغییرات مدخل windowsAuthentication را مشاهده خواهید کرد. به همین جهت بهترین راه تغییر آن، همان مراجعه‌ی مستقیم به کنسول مدیریتی IIS است.


فعالسازی Windows Authentication در IIS Express

اگر برای آزمایش می‌خواهید از IIS Express به همراه ویژوال استودیو استفاده کنید، نیاز است فایلی را به نام Properties\launchSettings.json با محتوای ذیل در ریشه‌ی پروژه‌ی خود ایجاد کنید (و یا تغییر دهید):
{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:3381/",
      "sslPort": 0
    }
  }
}
در اینجا الزامی به خاموش کردن anonymousAuthentication نیست. اگر برنامه‌ی شما قرار است هم توسط کاربران ویندوزی و هم توسط کاربران وارد شده‌ی از طریق اینترنت (و نه صرفا اینترانت داخلی) به برنامه، قابلیت دسترسی داشته باشد، نیاز است anonymousAuthentication به true تنظیم شده باشد (همانند تنظیمی که برای IIS اصلی ذکر شد).


تغییر مهم فایل web.config برنامه جهت هدایت اطلاعات ویندوز به آن

اگر پروژه‌ی شما فایل web.config ندارد، باید آن‌را اضافه کنید؛ با حداقل محتوای ذیل:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" 
            stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" 
            forwardWindowsAuthToken="true"/>
  </system.webServer>
</configuration>
که در آن خاصیت forwardWindowsAuthToken، حتما به true تنظیم شده باشد. این مورد است که کار اعتبارسنجی و یکی‌سازی اطلاعات کاربر وارد شده به ویندوز و ارسال آن‌را به میان‌افزار IIS برنامه‌ی ASP.NET Core انجام می‌دهد. بدون تنظیم آن، با مراجعه‌ی به سایت، شاهد نمایش صفحه‌ی login ویندوز خواهید بود.


تنظیمات برنامه‌ی ASP.NET Core جهت فعالسازی Windows Authentication

پس از فعالسازی windowsAuthentication در IIS و همچنین تنظیم forwardWindowsAuthToken به true در فایل web.config برنامه، اکنون جهت استفاده‌ی از windowsAuthentication دو روش وجود دارد:

الف) تنظیمات مخصوص برنامه‌های Self host
اگر برنامه‌ی وب شما قرار است به صورت self host ارائه شود (بدون استفاده از IIS)، تنها کافی است در تنظیمات ابتدای برنامه در فایل Program.cs، استفاده‌ی از میان‌افزار HttpSys را ذکر کنید:
namespace ASPNETCore2WindowsAuthentication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                                     .UseKestrel()
                                     .UseContentRoot(Directory.GetCurrentDirectory())
                                     .UseStartup<Startup>()
                                     .UseHttpSys(options => // Just for local tests without IIS, Or self-hosted scenarios on Windows ...
                                     {
                                         options.Authentication.Schemes =
                                              AuthenticationSchemes.Negotiate | AuthenticationSchemes.NTLM;
                                         options.Authentication.AllowAnonymous = true;
                                         //options.UrlPrefixes.Add("http://+:80/");
                                     })
                                     .Build();
            host.Run();
        }
    }
}
در اینجا باید دقت داشت که استفاده‌ی از UseHttpSys با تنظیمات فوق، اعتبارسنجی یکپارچه‌ی با ویندوز را برای برنامه‌های self host خارج از IIS مهیا می‌کند. اگر قرار است برنامه‌ی شما در IIS هاست شود، نیازی به تنظیم فوق ندارید و کاملا اضافی است.


ب) تنظیمات مخصوص برنامه‌هایی که قرار است در IIS هاست شوند

در این‌حالت تنها کافی است UseIISIntegration در تنظیمات ابتدایی برنامه ذکر شود و همانطور که عنوان شد، نیازی به UseHttpSys در این حالت نیست:
namespace ASPNETCore2WindowsAuthentication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                                     .UseKestrel()
                                     .UseContentRoot(Directory.GetCurrentDirectory())
                                     .UseIISIntegration()
                                     .UseDefaultServiceProvider((context, options) =>
                                     {
                                         options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                                     })
                                     .UseStartup<Startup>()
                                     .Build();
            host.Run();
        }
    }
}


فعالسازی میان‌افزار اعتبارسنجی ASP.NET Core جهت یکپارچه شدن با Windows Authentication

در پایان تنظیمات فعالسازی Windows Authentication نیاز است به فایل Startup.cs برنامه مراجعه کرد و یکبار AddAuthentication را به همراه تنظیم ChallengeScheme آن به IISDefaults افزود:
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.Configure<IISOptions>(options =>
            {
                // Sets the HttpContext.User
                // Note: Windows Authentication must also be enabled in IIS for this to work.
                options.AutomaticAuthentication = true;
                options.ForwardClientCertificate = true;
            });
            
            services.AddAuthentication(options =>
            {
                // for both windows and anonymous authentication
                options.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
            });
        }
برای مثال اگر از ASP.NET Core Identity استفاده می‌کنید، سطر services.AddAuthentication فوق، پس از تنظیمات ابتدایی آن باید ذکر شود؛ هرچند روش فوق کاملا مستقل است از ASP.NET Core Identity و اطلاعات کاربر را از سیستم عامل و اکتیودایرکتوری (در صورت وجود) دریافت می‌کند.


آزمایش برنامه با تدارک یک کنترلر محافظت شده

در اینجا قصد داریم اطلاعات ذیل را توسط تعدادی اکشن متد، نمایش دهیم:
        private string authInfo()
        {
            var claims = new StringBuilder();
            if (User.Identity is ClaimsIdentity claimsIdentity)
            {
                claims.Append("Your claims: \n");
                foreach (var claim in claimsIdentity.Claims)
                {
                    claims.Append(claim.Type + ", ");
                    claims.Append(claim.Value + "\n");
                }
            }

            return $"IsAuthenticated: {User.Identity.IsAuthenticated}; Identity.Name: {User.Identity.Name}; WindowsPrincipal: {(User is WindowsPrincipal)}\n{claims}";
        }
کار آن نمایش نام کاربر، وضعیت لاگین او و همچنین لیست تمام Claims متعلق به او می‌باشد:
namespace ASPNETCore2WindowsAuthentication.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [Authorize]
        public IActionResult Windows()
        {
            return Content(authInfo());
        }

        private string authInfo()
        {
            var claims = new StringBuilder();
            if (User.Identity is ClaimsIdentity claimsIdentity)
            {
                claims.Append("Your claims: \n");
                foreach (var claim in claimsIdentity.Claims)
                {
                    claims.Append(claim.Type + ", ");
                    claims.Append(claim.Value + "\n");
                }
            }

            return $"IsAuthenticated: {User.Identity.IsAuthenticated}; Identity.Name: {User.Identity.Name}; WindowsPrincipal: {(User is WindowsPrincipal)}\n{claims}";
        }

        [AllowAnonymous]
        public IActionResult Anonymous()
        {
            return Content(authInfo());
        }

        [Authorize(Roles = "Domain Admins")]
        public IActionResult ForAdmins()
        {
            return Content(authInfo());
        }

        [Authorize(Roles = "Domain Users")]
        public IActionResult ForUsers()
        {
            return Content(authInfo());
        }
    }
}
برای آزمایش برنامه، ابتدا برنامه را توسط دستور ذیل publish می‌کنیم:
 dotnet publish
سپس تنظیمات مخصوص IIS را که در ابتدای بحث عنوان شد، بر روی پوشه‌ی bin\Debug\netcoreapp2.0\publish که محل قرارگیری پیش‌فرض خروجی برنامه است، اعمال می‌کنیم.
اکنون اگر برنامه را در مرورگر مشاهده کنیم، یک چنین خروجی قابل دریافت است:


در اینجا نام کاربر وارد شده‌ی به ویندوز و همچنین لیست تمام Claims او مشاهده می‌شوند. مسیر Home/Windows نیز توسط ویژگی Authorize محافظت شده‌است.
برای محدود کردن دسترسی کاربران به اکشن متدها، توسط گروه‌های دومین و اکتیودایرکتوری، می‌توان به نحو ذیل عمل کرد:
[Authorize(Roles = @"<domain>\<group>")]
//or
[Authorize(Roles = @"<domain>\<group1>,<domain>\<group2>")]
و یا می‌توان بر اساس این نقش‌ها، یک سیاست دسترسی جدید را تعریف کرد:
services.AddAuthorization(options =>
{
   options.AddPolicy("RequireWindowsGroupMembership", policy =>
   {
     policy.RequireAuthenticatedUser();
     policy.RequireRole(@"<domain>\<group>"));  
   }
});
و در آخر از این سیاست دسترسی استفاده نمود:
 [Authorize(Policy = "RequireWindowsGroupMembership")]
و یا با برنامه نویسی نیز می‌توان به صورت ذیل عمل کرد:
[HttpGet("[action]")]
public IActionResult SomeValue()
{
    if (!User.IsInRole(@"Domain\Group")) return StatusCode(403);
    return Ok("Some Value");
}


افزودن Claims سفارشی به Claims پیش‌فرض کاربر سیستم

همانطور که در شکل فوق ملاحظه می‌کنید، یک سری Claims حاصل از Windows Authentication در اینجا به شیء User اضافه شده‌اند؛ بدون اینکه برنامه، صفحه‌ی لاگینی داشته باشد و همینقدر که کاربر به ویندوز وارد شده‌است، می‌تواند از برنامه استفاده کند.
اگر نیاز باشد تا Claims خاصی به لیست Claims کاربر جاری اضافه شود، می‌توان از پیاده سازی یک IClaimsTransformation سفارشی استفاده کرد:
    public class ApplicationClaimsTransformation : IClaimsTransformation
    {
        private readonly ILogger<ApplicationClaimsTransformation> _logger;

        public ApplicationClaimsTransformation(ILogger<ApplicationClaimsTransformation> logger)
        {
            _logger = logger;
        }

        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            if (!(principal.Identity is ClaimsIdentity identity))
            {
                return Task.FromResult(principal);
            }

            var claims = addExistingUserClaims(identity);
            identity.AddClaims(claims);

            return Task.FromResult(principal);
        }

        private IEnumerable<Claim> addExistingUserClaims(IIdentity identity)
        {
            var claims = new List<Claim>();
            var user = @"VahidPC\Vahid";
            if (identity.Name != user)
            {
                _logger.LogError($"Couldn't find {identity.Name}.");
                return claims;
            }

            claims.Add(new Claim(ClaimTypes.GivenName, user));
            return claims;
        }
    }
و روش ثبت آن نیز در متد ConfigureServices فایل آغازین برنامه به صورت ذیل است:
            services.AddScoped<IClaimsTransformation, ApplicationClaimsTransformation>();
            services.AddAuthentication(options =>
            {
                // for both windows and anonymous authentication
                options.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
            });
هر زمانیکه کاربری به برنامه وارد شود و متد HttpContext.AuthenticateAsync فراخوانی گردد، متد TransformAsync به صورت خودکار اجرا می‌شود. در اینجا چون forwardWindowsAuthToken به true تنظیم شده‌است، میان‌افزار IIS کار فراخوانی HttpContext.AuthenticateAsync و مقدار دهی شیء User را به صورت خودکار انجام می‌دهد. بنابراین همینقدر که برنامه را اجرا کنیم، شاهد اضافه شدن یک Claim سفارشی جدید به نام ClaimTypes.GivenName که در متد addExistingUserClaims فوق آن‌را اضافه کردیم، خواهیم بود:


به این ترتیب می‌توان لیست Claims ثبت شده‌ی یک کاربر را در یک بانک اطلاعاتی استخراج و به لیست Claims فعلی آن افزود و دسترسی‌های بیشتری را به او اعطاء کرد (فراتر از دسترسی‌های پیش‌فرض سیستم عامل).

برای دسترسی به مقادیر این Claims نیز می‌توان به صورت ذیل عمل کرد:
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var userName = User.FindFirstValue(ClaimTypes.Name);
var userName = User.FindFirstValue(ClaimTypes.GivenName);


کدهای کامل این برنامه را از اینجا می‌توانید دریافت کنید: ASPNETCore2WindowsAuthentication.zip
مطالب
الگوریتم‌های داده کاوی در SQL Server Data Tools یا SSDT - قسمت چهارم - الگوریتم‌ Clustering یا خوشه بندی
در قسمت قبل با الگوریتم های Decision trees و Linear Regression آشنا شدیم. در این قسمت به الگوریتم Clustering یا خوشه بندی می‌پردازیم.

مقدمه


تصور کنید شما بچه‌ای هستید که با یک کیسه تیله روی زمین نشسته‌اید. لحظه‌ای که تیله‌ها را از کیسه روی زمین می‌ریزید، متوجه می‌شوید که تیله‌ها، چهار رنگ دارند (آبی، قرمز، سبز و زرد). تیله‌ها را در چهار گروه با توجه به رنگ‌هایشان قرار می‌دهید. اما بعد متوجه می‌شوید که اندازه بعضی از تیله‌ها متوسط و برخی بزرگ و تعدادی هم کوچک هستند. شما تصمیم می‌گیرید که تیله‌های کوچک و متوسط، در کنار یکدیگر و در یک گروه قرار گیرند؛ اما تیله‌های بزرگ را در یک گروه دیگر قرار می‌دهید. چرا که تنها یکی از آن‌ها را باید به هر بازیکن داد. تبریک می‌گویم! شما یک عمل خوشه بندی را انجام دادید.
حال زمانیکه قدری با دقت بیشتری به خوشه بندی خود نگاه می‌کنید، متوجه می‌شوید که برخی از تیله‌ها کریستالی، برخی دیگر سه پر و چهارپر، بعضی از آن‌ها صاف و صیقلی و بعضی دیگر دارای خراش می‌باشند. اینجاست که قدری سردرگم می‌شوید. آیا باید همان گروه بندی ساده براساس رنگ و اندازه را مدنظر قرار دهید، یا بهتر است عوامل دیگری مانند سبک، مواد تشکیل دهنده و وضعیت ظاهری را نیز اضافه کنید؟
خوشه بندی، یک عمل انسانی راحت، طبیعی و حتی می‌شود گفت اتوماتیک برای مواجه شدن با مجموعه ویژگی‌های کوچک می‌باشد. اما همینطور که ویژگی‌ها بیشتر می‌شوند، حل مساله برای انسان خیلی سخت و غیرممکن می‌شود. ذهن یک انسان معمولی، تقریبا قادر به درنظر گرفتن 5 یا 6 بُعد می‌باشد. این درحالی است که مجموعه داده‌های مدرن گاها دارای ده‌ها بعد (اگر نگوییم صدها) می‌باشند.
الگوریتم خوشه بندی مایکروسافت، گروه بندی‌هایی ذاتی را داخل مجموعه داده شما پیدا می‌کند که ممکن است به چشم نیایند. به عبارت دیگر، متغیرهای پنهانی را که به طور دقیق داده‌های شما را خوشه بندی می‌کنند، پیدا می‌نماید. برای مثال فرض کنید که شما جزیی از یک گروه بزرگ مسافران هستید که در بخش نوار نقاله حمل بار در فرودگاه منتظر برداشتن چمدان می‌باشید. متوجه می‌شوید که درصد قابل توجهی از مسافران شلوار کوتاه پوشیده و پوستشان در اثر آفتاب قدری تیره‌تر شده است؛ درحالیکه مابقی مسافران لباس گرم مانند ژاکت و کت به تن دارند. بنابراین به یک حقیقت پی می‌برید. یک گروه از نواحی گرمسیری آمده‌اند و دیگری از یک جای سرد و مرطوب. این همان متغیر پنهان است.

الگوریتم Clustering یا خوشه بندی مایکروسافت  

الگوریتم خوشه بندی مایکروسافت رفتارهای خاصی را در مواجه با نوع ویژگی‌ها از خود نشان می‌دهد. در ارتباط با ستون‌های ورودی (Input) و ورودی-خروجی (Predict) مانند آنچه قبلا گذشت عمل می‌کند. البته با یک تفاوت و آن اینکه ستون‌های ورودی-خروجی در حین پیش بینی قابل انتخاب هستند؛ حال آنکه ستون‌های ورودی اینطور نیستند. ستون‌هایی که فقط خروجی (Predict Only) هستند، در طی فاز خوشه بندی برای آموزش مدل به کار نمی‌روند.
همانطور که قبلا نیز اشاره شد، خوشه بندی، رایج‌ترین عملی است که با این الگوریتم انجام می‌دهند. بنابراین جهت کشف خوشه بندی‌ها در یک مجموعه داده می‌توان این الگوریتم را روی مجموعه داده اعمال کرده و خوشه بندی‌های کشف شده را برچسب زد. بعد از برچسب زدن می‌توان از آن، جهت گزارش گیری و تحلیل داده‌ها استفاده نمود. از آنجا که این الگوریتم سربار پردازشی و حافظه‌ای زیادی دارد، بنابراین در رابطه با مجموعه داده‌های بزرگ (رکوردهای میلیونی و پیچیده) بهتر است که فقط بخش کوچکی از داده را برای آموزش استفاده کرده (که البته کافی و وافی است) و از طریق آن‌ها ویژگی‌های خوشه بندی را کشف کرد.
توسط این الگوریتم می‌توان مدل را تجزیه-تحلیل نمود و نابهنجاری‌ها را نیز تشخیص داد.

محتوای مدل خوشه بندی

درک محتوای مدل خوشه بندی بسیار ساده است. شکل زیر دیاگرام خوشه بندی یا Cluster Diagram می‌باشد. همانطور که در شکل آمده است SSAS در نشان دادن نام هر گره به خوبی عمل نمی‌کند زیرا هر گره توسط Cluster و یک ایندکس نشان داده می‌شود و نام معناداری برای آن در نظر نمی‌گیرد. برای مثال خوشه مربوط به تیله‌های آبی بزرگ سه پر (برای مثال Cluster2، Cluster1 و ....).


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


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

    ممکن است تعدادی ویژگی با احتمال بالا در یک خوشه وجود داشته باشند اما سوال اینجاست که از کجا معلوم که تمام این ویژگی‌ها در خوشه‌های دیگر نیز این احتمال را نداشته باشند؟ برای اینکه متوجه شویم که بیشتر چه ویژگی سبب وجه تمایز این خوشه شده‌است باید به برگه Cluster Discrimination مراجعه کنیم که نمونه‌ای از آن در شکل زیر آمده است.

     در این بخش می‌توان خصوصیات خوشه مدنظر را با خوشه‌های دیگر یا با متمم خوشه (Complement) مقایسه کرد و توسط آن، ویژگی‌هایی را که سبب وجه تمایز این خوشه شده‌اند، مشاهد نمود. توجه به این نکته ضروری است که نوار نشان داده شده در رابطه با هر ویژگی تنها نشان دهنده میزان توجه به آن ویژگی در آن خوشه است و به این معنی نیست که خوشه‌های دیگر عاری از آن ویژگی هستند.

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

خوشه بندی سخت و خوشه بندی نرم

مهمترین فرق بین الگوریتم‌های خوشه بندی، روشی است که الگوریتم‌ها در رابطه با انتساب حالت‌ها، به خوشه‌ها اتخاذ می‌کنند. الگوریتم خوشه بندی مایکروسافت، دو روش مختلف را برای اینکار دارند: K-means و Expectation Maximization. روش اول (شکل سمت چپ) براساس فاصله حالت‌ها نسبت به خوشه‌ها، آن‌ها را نسبت می‌دهد و در پایان مرکز خوشه طوری قرار خواهد گرفت که وسط حالت‌ها باشد. به این تکنیک، خوشه بندی سخت می‌گویند (شکل سمت چپ) زیرا همانطور که در شکل سمت چپ مشخص است هر شیء فقط و فقط در یک خوشه قرار می‌گیرد و هیچ یک از خوشه‌ها با یکدیگر هم پوشانی ندارند. روش دوم (شکل سمت راست) به جای استفاده محض از مقیاس فاصله، از یک مقیاس احتمالی استفاده می‌کند. این روش یک منحنی زنگوله شکل را که دارای میانگین و انحراف معیار است برای هر بُعد درنظر می‌گیرد. چنانچه نقطه‌ای داخل یک منحنی بیفتد با یک احتمال معینی به آن خوشه نسبت داده می‌شود. به دلیل اینکه منحنی‌ها می‌توانند هم پوشانی داشته باشند، بنابراین هر نقطه می‌تواند به چندین خوشه منتسب شود؛ البته با احتمالات مختلف. به این تکنیک، خوشه بندی نرم گفته می‌شود (شکل سمت راست). این تکنیک در شناسایی خوشه‌های پیوسته خیلی موثر است مانند وضعیت تراکم جمعیت مناطق. 


خوشه بندی با قابلیت مدرج کردن

یکی از مسایلی که در الگوریتم خوشه بندی وجود دارد این است که جهت به دست آوردن خوشه بندی مناسب، نیاز به تکرار آموزش برروی داده‌ها است. این تکرار در مجموعه داده‌های کوچک، مشکلی ایجاد نمی‌کند، اما در رابطه با مجموعه داده‌های بزرگ این امر امکان پذیر نیست. زیرا کل مجموعه داده داخل رم قرار می‌گیرد و مشکلات کارآیی را ایجاد می‌کند. الگوریتم خوشه بندی مایکروسافت یک چارچوب برای مدرج کردن خوشه بندی را در اختیار ما قرار می‌دهد که با استفاده از آن می‌توان بر این مشکل فایق آمد. این مهم توسط پارامتر Sample_Size مرتفع می‌شود که یکی از پارامترهای این الگوریتم می‌باشد. همانطور که در قسمت قبل نیز گفته شد دسترسی به پارامترهای هر الگوریتم به شکل زیر صورت می‌پذیرد:
مراجعه به برگه mining models ، کلیک بر روی الگوریتم، رفتن به پنجره properties  الگوریتم. حال می‌توان  به بخش Algorithm Parameters رفت و پارامترها را مقداردهی کرد. البته اگر از نظر حافظه رم مشکلی ندارید، می‌توانید مقدار این پارامتر را صفر درنظر بگیرید و با این کار تمام حافظه رم را به پردازش الگوریتم اختصاص بدهید، تا الگوریتم به هر میزانی که نیاز دارد، از حافظه رم استفاده نماید.
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
پیشنیازها
- آشنایی با Angular CLI
- آشنایی با مسیریابی‌ها در Angular

همچنین اگر پیشتر Angular CLI را نصب کرده‌اید، قسمت «به روز رسانی Angular CLI» ذکر شده‌ی در مطلب «Angular CLI - قسمت اول - نصب و راه اندازی» را نیز اعمال کنید. در این سری از angular/cli: 1.1.2@ استفاده شده‌است.


فناوری‌های مختلف کار با فرم‌ها در Angular

Angular (که خلاصه شده‌ی نام تمام نگارش‌های پس از Angular 2 است)، به همراه دو فناوری توکار کار با فرم‌ها است:
الف) فرم‌های مبتنی بر قالب‌ها یا Template driven forms
در اینجا عمده‌ی کار تعاریف فرم‌ها، در قالب‌های HTML ایی کامپوننت‌ها به همراه data binding انجام می‌شود. کار با آن ساده‌تر است و به همراه حداقل کدنویسی در قسمت کامپوننت‌های برنامه است؛ چون two-way data binding بسیاری از مسایل را به صورت خودکار مدیریت می‌کند. همچنین این روش برای کسانیکه با Angular 1.x کار کرده باشند، آشناتر است.
ب) فرم‌های واکنشی یا Reactive forms
در اینجا نیز همانند حالت الف کار تعریف ابتدایی فرم در قالب‌های HTML ایی کامپوننت‌ها انجام می‌شود. اما در اینجا نیاز است مدل فرم را توسط کدهای TypeScript کامپوننت نیز ایجاد کرد و با قالب HTML ایی هماهنگ نمود (به همین جهت به آن model driven forms هم می‌گویند). مزیت این روش نسبت به حالت الف، سادگی Unit testing و همچنین امکان تعریف اعتبارسنجی‌های پیچیده‌است. به علاوه در این حالت می‌توان فرم‌های پویایی را نیز طراحی کرد.

ما در این سری حالت Template driven forms را بررسی خواهیم کرد.


ایجاد ساختار اولیه‌ی مثال این سری

در ادامه، یک پروژه‌ی جدید مبتنی بر Angular CLI را به نام angular-template-driven-forms-lab به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
 > ng new angular-template-driven-forms-lab --routing
به علاوه، قصد استفاده‌ی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشه‌ی پروژه وارد شده و سپس دستور ذیل را صادر کنید، تا بوت استرپ نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
 > npm install bootstrap --save
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوه‌نامه‌ی بوت استرپ را تعریف کنیم:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
به این ترتیب، به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.

در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوش‌آمدگویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه می‌کنیم:
>ng g c welcome
>ng g c PageNotFound
که سبب ایجاد کامپوننت‌های src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد؛ به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
@NgModule({
  declarations: [
    AppComponent,
    WelcomeComponent,
    PageNotFoundComponent
  ],

سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  { path: 'welcome', component: WelcomeComponent },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا زمانیکه کاربر ریشه‌ی سایت را درخواست می‌کند، به کامپوننت welcome هدایت خواهد شد.
همچنین مدیریت مسیریابی آدرس‌های ناموجود در سایت نیز با تعریف ** صورت گرفته‌است.

زمانیکه یک کامپوننت فعالسازی می‌شود، قالب آن در router-outlet نمایش داده خواهد شد. برای این منظور فایل src\app\app.component.html را گشوده و به نحو ذیل تغییر دهید:
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <a class="navbar-brand">{{title}}</a>
    <ul class="nav navbar-nav">
      <li>
        <a [routerLink]="['/']">Home</a>
      </li>
    </ul>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>
تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد:



افزودن ماژول فرم‌ها به برنامه

پس از ایجاد ساختار اولیه برنامه، اولین کاری را که در جهت استفاده‌ی از فرم‌های مبتنی بر قالب‌ها باید انجام داد، افزودن ماژول فرم‌ها به ماژول اصلی برنامه است. برای این منظور فایل src\app\app.module.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ]
در اینجا ابتدا FormsModule، از ماژول Angular مرتبط با آن دریافت شده و سپس به لیست imports ماژول اصلی برنامه اضافه گردیده‌است.


ایجاد ماژول و کامپوننت فرم ثبت نام کارمندان

در ادامه می‌خواهیم فرم ثبت نام یک کارمند را تکمیل کنیم. بنابراین ماژول جدید کارمندان را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
 >ng g m Employee -m app.module --routing
با این خروجی
 installing module
  create src\app\employee\employee-routing.module.ts
  create src\app\employee\employee.module.ts
  update src\app\app.module.ts
اگر به سطر آخر آن دقت کنید، فایل app.module.ts را نیز به صورت خودکار به روز رسانی کرده‌است:
import { EmployeeRoutingModule } from './employee/employee-routing.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    EmployeeRoutingModule
  ]
در اینجا EmployeeRoutingModule را به انتهای لیست imports افزوده‌است که نیاز به اندکی تغییر دارد و باید EmployeeRoutingModule را پیش از AppRoutingModule تعریف کرد. علت این است که AppRoutingModule، دارای تعریف مسیریابی ** یا catch all است که آن‌را جهت مدیریت مسیرهای یافت نشده به برنامه افزوده‌ایم. بنابراین اگر ابتدا AppRoutingModule تعریف شود و سپس EmployeeRoutingModule، هیچگاه فرصت به پردازش مسیریابی‌های ماژول کارمندان نمی‌رسد؛ چون مسیر ** پیشتر برنده شده‌است.
همچنین برای اینکه کامپوننت‌های این ماژول نیز در حین مسیریابی در دسترس باشند، نیاز است بجای EmployeeRoutingModule، خود EmployeeModule را ذکر کرد که حاوی تعاریف مسیریابی (ذکر EmployeeRoutingModule در قسمت imports آن) نیز می‌باشد. بنابراین فایل app.module.ts چنین تعاریفی را پیدا می‌کند:
import { EmployeeModule } from './employee/employee.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,    
    EmployeeModule,
    AppRoutingModule 
  ]

در ادامه کامپوننت جدید ثبت یک کارمند را به این ماژول اضافه می‌کنیم:
 >ng g c employee/employee-register
با این خروجی
 installing component
  create src\app\employee\employee-register\employee-register.component.css
  create src\app\employee\employee-register\employee-register.component.html
  create src\app\employee\employee-register\employee-register.component.spec.ts
  create src\app\employee\employee-register\employee-register.component.ts
  update src\app\employee\employee.module.ts
اگر به سطر آخر آن دقت کنید، کار به روز رسانی فایل ماژول کارمندان، جهت درج این کامپوننت جدید، در قسمت declarations فایل employee.module.ts نیز به صورت خودکار انجام شده‌است:
import { EmployeeRegisterComponent } from './employee-register/employee-register.component';

@NgModule({
  declarations: [EmployeeRegisterComponent]

در ادامه می‌خواهیم قالب این کامپوننت را در منوی اصلی سایت قابل دسترسی کنیم. به همین جهت به فایل src\app\employee\employee-routing.module.ts مراجعه کرده و مسیریابی این کامپوننت را تعریف می‌کنیم:
import { EmployeeRegisterComponent } from './employee-register/employee-register.component';

const routes: Routes = [
  { path: 'register', component: EmployeeRegisterComponent }
];
ابتدا کامپوننت ثبت کارمندان import شده و سپس آرایه‌ی Routes مسیری را به این کامپوننت تعریف کرده‌است.

سپس می‌خواهیم لینکی را به این مسیریابی جدید اضافه کنیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink آن‌را اضافه می‌کنیم:
    <ul class="nav navbar-nav">
      <li>
        <a [routerLink]="['/']">Home</a>
      </li>
      <li>
        <a [routerLink]="['/register']">Register</a>
      </li>
    </ul>
تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد (البته می‌توان پنجره‌ی کنسول ng serve را باز نگه داشت تا کار watch را به صورت خودکار انجام دهد؛ این روش سریعتر و به همراه build تدریجی است):



در قسمت بعد، ایجاد اولین فرم مبتنی بر قالب‌ها را پیگیری می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
VS Code برای توسعه دهندگان ASP.NET Core - قسمت دوم - ایجاد و اجرای اولین برنامه
پس از معرفی ابتدایی VSCode و نصب افزونه‌ی #C در قسمت قبل، در ادامه می‌خواهیم اولین پروژه‌ی ASP.NET Core خود را در آن ایجاد کنیم.


نصب ASP.NET Core بر روی سیستم عامل‌های مختلف

برای نصب پیشنیازهای کار با ASP.NET Core به آدرس https://www.microsoft.com/net/download/core مراجعه کرده و NET Core SDK. را دریافت و نصب کنید. پس از نصب آن جهت اطمینان از صحت انجام عملیات، دستور dotnet --version را در خط فرمان صادر کنید:
 C:\>dotnet --version
1.0.1
در اینجا SDK نصب شده، شامل هر دو نگارش 1.0 و 1,1 است. همچنین در همین صفحه‌ی دریافت فایل‌ها، علاوه بر نگارش ویندوز، نگارش‌های Mac و لینوکس آن نیز موجود هستند. بر روی هر کدام که کلیک کنید، ریز مراحل نصب هم به همراه آن‌ها وجود دارد. برای مثال نصب NET Core. بر روی Mac شامل نصب OpenSSL به صورت جداگانه است و یا نصب آن بر روی لینوکس به همراه چند دستور مختص به توزیع مورد استفاده می‌باشد که به خوبی مستند شده‌اند و نیازی به تکرار آن‌ها نیست و همواره آخرین نگارش آن‌ها بر روی سایت dot.net موجود است.


ایجاد اولین پروژه‌ی ASP.NET Core توسط NET Core SDK.

پس از نصب NET Core SDK.، به پیشنیاز دیگری برای شروع به کار با ASP.NET Core نیازی نیست. نه نیازی است تا آخرین نگارش ویژوال استودیوی کامل را نصب کنید و نه با به روز رسانی آن، نیاز است چندگیگابایت فایل به روز رسانی تکمیلی را دریافت کرد. همینقدر که این SDK نصب شود، می‌توان بلافاصله شروع به کار با این نگارش جدید کرد.
در ادامه ابتدا پوشه‌ی جدید پروژه‌ی خود را ساخته (برای مثال در مسیر D:\vs-code-examples\FirstAspNetCoreProject) و سپس از طریق خط فرمان به این پوشه وارد می‌شویم.

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

پس از وارد شدن به پوشه‌ی جدید از طریق خط فرمان، دستور dotnet new --help را صادر کنید:
D:\vs-code-examples\FirstAspNetCoreProject>dotnet new --help
Getting ready...
Template Instantiation Commands for .NET Core CLI.

Usage: dotnet new [arguments] [options]

Arguments:
  template  The template to instantiate.

Options:
  -l|--list         List templates containing the specified name.
  -lang|--language  Specifies the language of the template to create
  -n|--name         The name for the output being created. If no name is specified, the name of the current directory is used.
  -o|--output       Location to place the generated output.
  -h|--help         Displays help for this command.
  -all|--show-all   Shows all templates


Templates                 Short Name      Language      Tags
----------------------------------------------------------------------
Console Application       console         [C#], F#      Common/Console
Class library             classlib        [C#], F#      Common/Library
Unit Test Project         mstest          [C#], F#      Test/MSTest
xUnit Test Project        xunit           [C#], F#      Test/xUnit
ASP.NET Core Empty        web             [C#]          Web/Empty
ASP.NET Core Web App      mvc             [C#], F#      Web/MVC
ASP.NET Core Web API      webapi          [C#]          Web/WebAPI
Solution File             sln                           Solution

Examples:
    dotnet new mvc --auth None --framework netcoreapp1.1
    dotnet new xunit --framework netcoreapp1.1
    dotnet new --help
همانطور که مشاهده می‌کنید، اینبار بجای انتخاب گزینه‌های مختلف از صفحه دیالوگ new project template داخل ویژوال استودیوی کامل، تمام این قالب‌ها از طریق خط فرمان در اختیار ما هستند. برای مثال می‌توان یک برنامه کنسول و یا یک کتابخانه‌ی جدید را ایجاد کرد.
در ادامه دستور ذیل را صادر کنید:
 D:\vs-code-examples\FirstAspNetCoreProject>dotnet new mvc --auth None
به این ترتیب یک پروژه‌ی جدید ASP.NET Core، بدون تنظیمات اعتبارسنجی و ASP.NET Core Identity، در کسری از ثانیه ایجاد خواهد شد.


سپس جهت گشودن این پروژه در VSCode تنها کافی است دستور ذیل را صادر کنیم:
 D:\vs-code-examples\FirstAspNetCoreProject>code .
در ادامه یکی از فایل‌های #C آن‌را گشوده و منتظر شوید تا کار دریافت خودکار بسته‌های مرتبط با افزونه‌ی #C ایی که در قسمت قبل نصب کردیم به پایان برسند:


اینکار یکبار باید انجام شود و پس از آن امکانات زبان #C و همچنین دیباگر NET Core. در VS Code قابل استفاده خواهند بود.
در تصویر فوق دو اخطار را هم مشاهده می‌کنید. مورد اول برای فعال سازی دیباگ و ساخت پروژه‌ی جاری است. گزینه‌ی Yes آن‌را انتخاب کنید و اخطار دوم جهت بازیابی بسته‌های نیوگت پروژه‌است که گزینه‌ی restore آن‌را انتخاب نمائید. البته کار بازیابی بسته‌ها از طریق کش موجود در سیستم به سرعت انجام خواهد شد.


گشودن کنسول از درون VS Code و اجرای برنامه‌ی ASP.NET Core

روش‌های متعددی برای گشودن کنسول خط فرمان در VS Code وجود دارند:
الف) فشردن دکمه‌های Ctrl+Shift+C
اینکار باعث می‌شود تا command prompt ویندوز از پوشه‌ی جاری به صورت مجزایی اجرا شود.
ب) فشردن دکمه‌های Ctrl+` (و یا Ctrl+ back-tick)
به این ترتیب کنسول پاورشل درون خود VS Code باز خواهد شد. اگر علاقمند نیستید تا از پاورشل استفاده کنید، می‌توانید این پیش‌فرض را به نحو ذیل بازنویسی کنید:
همانطور که در قسمت قبل نیز ذکر شد، از طریق منوی File->Preferences->Settings می‌توان به تنظیمات VS Code دسترسی یافت. پس از گشودن آن، یک سطر ذیل را به آن اضافه کنید:
 "terminal.integrated.shell.windows": "cmd.exe"
اکنون Ctrl+ back-tick را فشرده تا کنسول خط فرمان، داخل VS Code نمایان شود و سپس دستور ذیل را صادر کنید:
 D:\vs-code-examples\FirstAspNetCoreProject>dotnet run
Hosting environment: Production
Content root path: D:\vs-code-examples\FirstAspNetCoreProject
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
در اینجا دستور dotnet run سبب کامپایل و همچنین اجرای پروژه شده‌است و اکنون این برنامه‌ی وب بر روی پورت 5000 قابل دسترسی است:



ساده سازی ساخت و اجرای یک برنامه‌ی ASP.NET Core در VS Code


زمانیکه گزینه‌ی افزودن امکانات ساخت و دیباگ را انتخاب می‌کنیم (تصویر فوق)، دو فایل جدید به پوشه‌ی vscode. اضافه می‌شوند:


دراینجا فایل tasks.json، حاوی وظیفه‌ای است جهت ساخت برنامه. یعنی بجای اینکه مدام بخواهیم به خط فرمان مراجعه کرده و دستوراتی را صادر کنیم، می‌توان از وظایفی که در پشت صحنه همین فرامین را اجرا می‌کنند، استفاده کنیم؛ که نمونه‌ای از آن، به صورت پیش فرض به پروژه اضافه شده است.
برای دسترسی به آن، دکمه‌های ctrl+shift+p را فشرده و سپس در منوی ظاهر شده، واژه‌ی build را جستجو کنید:


با انتخاب این گزینه (که توسط Ctrl+Shift+B هم در دسترس است)، کار ساخت برنامه انجام شده و dll مرتبط با آن در پوشه‌ی bin تشکیل می‌شود.
 
همچنین در اینجا برای ساخت و بلافاصله نمایش آن در مرورگر پیش فرض سیستم، می‌توان مجددا دکمه‌های ctrl+shift+p را فشرد و در منوی ظاهر شده، واژه‌ی without را جستجو کرد:


با انتخاب این گزینه (که توسط Ctrl+F5 نیز در دسترس است)، برنامه ساخته شده، اجرا و نمایش داده می‌شود و برای خاتمه‌ی آن می‌توانید دکمه‌های Ctrl+C را بفشارید تا کار وب سرور موقتی آن خاتمه یابد.


در قسمت بعد مباحث دیباگ برنامه و گردش کار متداول یک پروژه‌ی ASP.NET Core را بررسی خواهیم کرد.
مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
پیاده سازی Unobtrusive Ajax را در ASP.NET MVC 5.x، می‌توانید در مطلب «ASP.NET MVC #21» مطالعه کنید. HTML Helpers مرتبط با Ajax، به طور کامل از ASP.NET Core 1.0 حذف شده‌اند. اما این مورد به این معنا نیست که نمی‌توان Unobtrusive Ajax را در ASP.NET Core که تمرکزش بیشتر بر روی Tag Helpers جدید هست تا HTML Helpers قدیمی، پیاده سازی کرد.


Unobtrusive Ajax چیست؟

در حالت معمولی، با استفاده از متد ajax جی‌کوئری، کار ارسال غیرهمزمان اطلاعات، به سمت سرور صورت می‌گیرد. چون در این روش کدهای جی‌کوئری داخل صفحات برنامه‌های ما قرار می‌گیرند، به این روش، «روش چسبنده» می‌گویند. اما با استفاده از افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، می‌توان این کدهای چسبنده را تبدیل به کدهای غیرچسنبده یا Unobtrusive کرد. در این حالت، پارامترهای متد ajax، به صورت ویژگی‌ها (attributes) به شکل data-ajax به المان‌های مختلف صفحه اضافه می‌شوند و به این ترتیب، افزونه‌ی یاد شده به صورت خودکار با یافتن مقادیر ویژگی‌های data-ajax، این المان‌ها را تبدیل به المان‌های ای‌جکسی می‌کند. در این حالت به کدهایی تمیزتر و عاری از متدهای چسبنده‌ی ajax قرار گرفته‌ی در داخل صفحات وب خواهیم رسید.
روش طراحی Unobtrusive را در کتابخانه‌های معروفی مانند بوت استرپ هم می‌توان مشاهده کرد.


پیشنیازهای فعال سازی Unobtrusive Ajax در ASP.NET Core 1.0

توزیع افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، از طریق bower صورت می‌گیرد که پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران» با آن آشنا شدیم. در اینجا نیز برای دریافت آن، تنها کافی است فایل bower.json را به نحو ذیل تکمیل کرد:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
   "bootstrap": "3.3.6",
   "jquery": "2.2.0",
   "jquery-validation": "1.14.0",
   "jquery-validation-unobtrusive": "3.2.6",
   "jquery-ajax-unobtrusive": "3.2.4"
  }
}
و پس از آن فایل bundleconfig.json مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification» یک چنین شکلی را پیدا می‌کند:
[
  {
   "outputFileName": "wwwroot/css/site.min.css",
   "inputFiles": [
    "bower_components/bootstrap/dist/css/bootstrap.min.css",
    "content/site.css"
   ]
  },
  {
   "outputFileName": "wwwroot/js/site.min.js",
   "inputFiles": [
    "bower_components/jquery/dist/jquery.min.js",
    "bower_components/jquery-validation/dist/jquery.validate.min.js",
    "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js",
    "bower_components/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
    "bower_components/bootstrap/dist/js/bootstrap.min.js"
   ],
   "minify": {
    "enabled": true,
    "renameLocals": true
   },
   "sourceMap": false
  }
]
در اینجا فایل‌های css و اسکریپت مورد نیاز برنامه، به ترتیب اضافه شده و یکی خواهند شد. خروجی نهایی آن‌ها به شکل زیر در صفحات وب مورد استفاده قرار می‌گیرند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/css/site.min.css" rel="stylesheet" />
</head>
<body>
    <div>
        <div>
                @RenderBody()
        </div>
    </div>

    <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا تنها دو فایل نهایی این عملیات، یعنی css/site.min.css و js/site.min.js به صفحه الحاق شده‌اند که حاوی تمام پیشنیازهای اسکریپتی و شیوه‌نامه‌های برنامه هستند و در این حالت دیگر نیاز به افزودن آن‌ها به دیگر صفحات سایت نیست.


استفاده از معادل‌های واقعی Unobtrusive Ajax در ASP.NET Core 1.0

واقعیت این است که HTML Helper ای‌جکسی حذف شده‌ی از ASP.NET Core 1.0، کاری بجز افزودن ویژگی‌های data-ajax را که توسط افزونه‌ی jquery.unobtrusive-ajax.min.js پردازش می‌شوند، انجام نمی‌دهد و این افزونه مستقل است از مباحث سمت سرور و به نگارش خاصی از ASP.NET گره نخورده است. بنابراین در اینجا تنها کاری را که باید انجام داد، استفاده از همان ویژگی‌های اصلی است که این افزونه قادر به شناسایی آن‌ها است.
خلاصه‌ی آن‌ها را جهت انتقال کدهای قدیمی و یا تهیه‌ی کدهای جدید، در جدول ذیل می‌توانید مشاهده کنید:

 HTML attribute   AjaxOptions 
 data-ajax-confirm   Confirm 
 data-ajax-method   HttpMethod 
 data-ajax-mode   InsertionMode 
 data-ajax-loading-duration   LoadingElementDuration 
 data-ajax-loading   LoadingElementId 
 data-ajax-begin   OnBegin 
 data-ajax-complete   OnComplete 
 data-ajax-failure   OnFailure 
 data-ajax-success   OnSuccess 
 data-ajax-update   UpdateTargetId 
 data-ajax-url   Url 
   
در ASP.NET Core 1.0، به علت حذف متدهای کمکی Ajax دیگر خبری از AjaxOptions نیست. اما اگر علاقمند به انتقال کدهای قدیمی به ASP.NET Core 1.0 هستید، معادل‌های اصلی این پارامترها را می‌توانید در ستون HTML attribute مشاهده کنید.

چند نکته:
- اگر قصد استفاده‌ی از این ویژگی‌ها را دارید، باید ویژگی "data-ajax="true را نیز حتما قید کنید تا سیستم Unobtrusive Ajax فعال شود.
- ویژگی data-ajax-mode تنها با ذکر data-ajax-update (و یا همان UpdateTargetId پیشین) معنا پیدا می‌کند.
- ویژگی data-ajax-loading-duration نیاز به ذکر data-ajax-loading (و یا همان LoadingElementId پیشین) را دارد.
- ویژگی data-ajax-mode مقادیر before، after و replace-with را می‌پذیرد. اگر قید نشود، کل المان با data دریافتی جایگزین می‌شود.
- سه callback قابل تعریف data-ajax-complete، data-ajax-failure و data-ajax-success، یک چنین پارامترهایی را از سمت سرور در اختیار کلاینت قرار می‌دهند:

parameters  
 Callback  
 xhr, status   data-ajax-complete 
 data, status, xhr   data-ajax-success 
 xhr, status, error   data-ajax-failure 

برای مثال می‌توان ویژگی data-ajax-success را به نحو ذیل در سمت کلاینت مقدار دهی کرد:
 data-ajax-success = "myJsMethod"
این متد جاوا اسکریپتی یک چنین امضایی را دارد:
  function myJsMethod(data, status, xhr) {
}
در این حالت در سمت سرور، پارامتر data در یک اکشن متد، به صورت ذیل مقدار دهی می‌شود:
 return Json(new { param1 = 1, param2 = 2, ... });
و در سمت کلاینت در متد myJsMethod این پارامترها را به صورت data.param1 می‌توان دریافت کرد.


مثال‌هایی از افزودن ویژگی‌های data-ajax به المان‌های مختلف

 در حالت استفاده از Form Tag Helpers که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» بررسی شدند، یک فرم ای‌جکسی، چنین تعاریفی را پیدا خواهد کرد:
با این ViewModel فرضی
using System.ComponentModel.DataAnnotations;
 
namespace Core1RtmEmptyTest.ViewModels.Account
{
    public class RegisterViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }
    }
}
که در View متناظر Ajax ایی ذیل استفاده شده‌است:
@using Core1RtmEmptyTest.ViewModels.Account
@model RegisterViewModel
@{
}
 
<form method="post"
      asp-controller="TestAjax"
      asp-action="Index"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      class="form-horizontal"
      role="form"
      data-ajax="true"
      data-ajax-loading="#Progress"
      data-ajax-success="myJsMethod">
 
    <input asp-for="Email" class="form-control" />
    <span asp-validation-for="Email" class="text-danger"></span>
 
    <button type="submit">ارسال</button>
 
    <div id="Progress" style="display: none">
        <img src="images/loading.gif" alt="loading..." />
    </div>
</form>
 
@section scripts{
    <script type="text/javascript">
        function myJsMethod(data, status, xhr) {
            alert(data.param1);
        }
    </script>
}
در اینجا تمام تعاریف مانند قبل است؛ تنها سه ویژگی data-ajax جهت فعال سازی jquery-ajax-unobtrusive به فرم اضافه شده‌اند. همچنین یک callback دریافت پیام موفقیت آمیز بودن عملیات Ajax ایی نیز تعریف شده‌است.

این View از کنترلر ذیل استفاده می‌کند:
using Core1RtmEmptyTest.ViewModels.Account;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class TestAjaxController : Controller
    {
 
        public IActionResult Index()
        {
            return View();
        }
 
        [HttpPost]
        public IActionResult Index([FromForm]RegisterViewModel vm)
        {
            var ajax = isAjax();
            if (ajax)
            {
                // it's an ajax post
            }
 
 
            if (ModelState.IsValid)
            {
                //todo: save data
 
                return Json(new { param1 = 1, param2 = 2 });
            }
            return View();
        }
 
        private bool isAjax()
        {
            return Request?.Headers != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest";
        }
    }
}
به ASP.NET Core 1.0، متد کمکی IsAjax اضافه نشده‌است؛ اما تعریف آن‌را در این کنترلر مشاهده می‌کنید. در مورد قید FromForm در ادامه توضیح داده خواهد شد (هرچند در این مورد خاص، حالت پیش فرض است و الزامی به قید آن نیست).

و یا Action Link ای‌جکسی نیز به صورت خلاصه به این نحو قابل تعریف است:
<div id="EmployeeInfo">
<a 
 asp-controller="MyController" asp-action="MyAction"
 data-ajax="true" 
 data-ajax-loading="#Progress" 
 data-ajax-method="POST" 
 data-ajax-mode="replace" 
 data-ajax-update="#EmployeeInfo">
 Get Employee-1 info
</a>

  <div id="Progress" style="display: none">
    <img src="images/loading.gif" alt="loading..."  />
  </div>
</div>


نکته‌ای در مورد اکشن متدهای ای‌جکسی در ASP.NET Core 1.0

همانطور که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API»، قسمت «تغییرات Model binding پیش فرض، برای پشتیبانی از ASP.NET MVC و ASP.NET Web API» نیز ذکر شد:
public IActionResult Index([FromBody] MyViewModel vm)
{
   return View();
}
ذکر ویژگی FromBody در اینجا الزامی است. از این جهت که اطلاعات با فرمت JSON، از قسمت body درخواست استخراج و به MyViewModel بایند خواهند شد (در حالت dataType: json). و اگر dataType : application/x-www-form-urlencoded; charset=utf-8 بود (مانند حالت پیش فرض Unobtrusive Ajax)، باید از ویژگی FromForm استفاده شود. در غیر اینصورت در سمت سرور نال دریافت خواهیم کرد.