نظرات مطالب
پیاده سازی JSON Web Token با ASP.NET Web API 2.x
- قسمت web api برنامه با mvc یکپارچه هست؟ اگر بله، برای کار با web api نیازی به این روش ندارید. همینقدر که کاربر برای مثال از طریق روش‌هایی مانند ASP.NET Identity و یا Forms authentication به سایت وارد شده باشد، کوکی حاصل از اعتبارسنجی آن‌ها، به همراه اعمال Ajax ای مختص Web API هم به صورت خودکار ارسال می‌شود و ... کاربر با موفقیت اعتبارسنجی خواهد شد. پروژه‌ی مطلب «اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity» یک قسمت Web API محافظت شده هم دارد؛ با این view که کاملا عادی به نظر می‌رسد. چون سیستم یکپارچه هست.
- کاربرد JWT بیشتر در برنامه‌های SPA مانند Angular است: «احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular»
در اینجا کاربر پس از لاگین (از طریق صفحه‌ی لاگینی، کاملا عادی و معمولی که هیچ اطلاعاتی هم از پیش در آن ذخیره نشده)، چیزی را که سمت کلاینت ذخیره می‌کند (برای مثال در local storage مرورگر)، فقط توکن‌های دریافتی پس از اعتبارسنجی موفق است و نه کلمه‌ی عبور و نه هیچ اطلاعات دیگری. سپس در هر درخواست به منابع محافظت شده‌ی سمت سرور، صرفا این توکن‌ها را ارسال می‌کند تا توسط آن، کار اعتبارسنجی خودکار کاربر صورت گیرد و هویت و همچنین سطوح دسترسی آن مشخص شوند.
- مثالی که در این پروژه ارائه شده، نمونه‌ی دیگر «آزمایش Web APIs توسط Postman - قسمت ششم - اعتبارسنجی مبتنی بر JWT» است؛ جهت ارائه‌ی یک سری مفهوم.
نظرات مطالب
بررسی تغییرات Blazor 8x - قسمت دوازدهم - قالب جدید پیاده سازی اعتبارسنجی و احراز هویت - بخش دوم
چند مطلب و پروژه‌ی تکمیلی، اگر علاقمند به پیاده سازی سفارشی اعتبارسنجی و احراز هویت Blazor 8x در حالت SSR هستید:
- Custom cookie authentication in Blazor SSR  (اگر موفق به خواندن کامل آن نشدید، از این آدرس استفاده کنید)
- «SparkPoc» یک پیاده سازی سفارشی مبتنی بر کوکی مخصوص SSR
که اصل و اساس آن‌ها این مطلب است: «اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» و در Blazor SSR هم قابل استفاده‌است.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
- مطلبی که در اینجا بحث شده، در مورد کوکی‌ها هست و سیستم مبتنی بر کوکی Identity. بحث دیگری تحت عنوان «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» مختص توکن‌ها است. 
- راه حل استانداردی که در این موارد برای JWTها استفاده می‌شود، تولید یک reference token است. مفهوم آن‌را در مطلب « امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت نهم- مدیریت طول عمر توکن‌ها » مطالعه کنید.
- بنابراین به صورت خلاصه می‌توانید بجای کل توکن، فقط یک Guid امن کوتاه را به سمت کاربر ارسال کنید. در این حالت باید اصل توکن طولانی را در بانک اطلاعاتی ذخیره کنید. سپس زمانیکه از کاربر Guid را دریافت می‌کنید (که اینجا reference token نام دارد)، توکن متناظر با آن‌را در بانک اطلاعاتی یافته و سپس مطابق نکته‌ی OnMessageReceived مطلب «اعتبارسنجی مبتنی بر JWT »، این توکن بازیابی شده‌ی از بانک اطلاعاتی را به عنوان context.Token معرفی کنید، تا به صورت خودکار از آن استفاده شود.
مطالب
افزودن ASP.NET Identity به یک پروژه Web Forms
  • با نصب و اجرای Visual Studio 2013 Express for Web یا Visual Studio 2013 شروع کنید.
  • یک پروژه جدید بسازید (از صفحه شروع یا منوی فایل)
  • گزینه #Visual C و سپس ASP.NET Web Application را انتخاب کنید. نام پروژه را به "WebFormsIdentity" تغییر داده و OK کنید.

  • در دیالوگ جدید ASP.NET گزینه Empty را انتخاب کنید.

دقت کنید که دکمه Change Authentication غیرفعال است و هیچ پشتیبانی ای برای احراز هویت در این قالب پروژه وجود ندارد.


افزودن پکیج‌های ASP.NET Identity به پروژه

روی نام پروژه کلیک راست کنید و گزینه Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.E" را وارد کرده و  این پکیج را نصب کنید.

دقت کنید که نصب کردن این پکیج وابستگی‌ها را نیز بصورت خودکار نصب می‌کند: Entity Framework و ASP.NET Idenity Core.


افزودن فرم‌های وب لازم برای ثبت نام کاربران

یک فرم وب جدید بسازید.

در دیالوگ باز شده نام فرم را به Register تغییر داده و تایید کنید.

فایل ایجاد شده جدید را باز کرده و کد Markup آن را با قطعه کد زیر جایگزین کنید.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="WebFormsIdentity.Register" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body style="
    <form id="form1" runat="server">
    <div>
        <h4 style="Register a new user</h4>
        <hr />
        <p>
            <asp:Literal runat="server" ID="StatusMessage" />
        </p>                
        <div style="margin-bottom:10px">
            <asp:Label runat="server" AssociatedControlID="UserName">User name</asp:Label>
            <div>
                <asp:TextBox runat="server" ID="UserName" />                
            </div>
        </div>
        <div style="margin-bottom:10px">
            <asp:Label runat="server" AssociatedControlID="Password">Password</asp:Label>
            <div>
                <asp:TextBox runat="server" ID="Password" TextMode="Password" />                
            </div>
        </div>
        <div style="margin-bottom:10px">
            <asp:Label runat="server" AssociatedControlID="ConfirmPassword">Confirm password</asp:Label>
            <div>
                <asp:TextBox runat="server" ID="ConfirmPassword" TextMode="Password" />                
            </div>
        </div>
        <div>
            <div>
                <asp:Button runat="server" OnClick="CreateUser_Click" Text="Register" />
            </div>
        </div>
    </div>
    </form>
</body>
</html>

این تنها یک نسخه ساده شده Register.aspx است که از چند فیلد فرم و دکمه ای برای ارسال آنها به سرور استفاده می‌کند.

فایل کد این فرم را باز کرده و محتویات آن را با قطعه کد زیر جایگزین کنید.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Linq;

namespace WebFormsIdentity
{
   public partial class Register : System.Web.UI.Page
   {
      protected void CreateUser_Click(object sender, EventArgs e)
      {
         // Default UserStore constructor uses the default connection string named: DefaultConnection
         var userStore = new UserStore<IdentityUser>();
         var manager = new UserManager<IdentityUser>(userStore);

         var user = new IdentityUser() { UserName = UserName.Text };
         IdentityResult result = manager.Create(user, Password.Text);

         if (result.Succeeded)
         {
            StatusMessage.Text = string.Format("User {0} was created successfully!", user.UserName);
         }
         else
         {
            StatusMessage.Text = result.Errors.FirstOrDefault();
         }
      }
   }
}

کد این فرم نیز نسخه ای ساده شده است. فایلی که بصورت خودکار توسط VS برای شما ایجاد می‌شود متفاوت است.

کلاس IdentityUser پیاده سازی پیش فرض EntityFramework از قرارداد IUser است. قرارداد IUser تعریفات حداقلی یک کاربر در ASP.NET Identity Core را در بر می‌گیرد.

کلاس UserStore پیاده سازی پیش فرض EF از یک فروشگاه کاربر (user store) است. این کلاس چند قرارداد اساسی ASP.NET Identity Core را پیاده سازی می‌کند: IUserStore, IUserLoginStore, IUserClaimStore و IUserRoleStore.

کلاس UserManager دسترسی به API‌های مربوط به کاربران را فراهم می‌کند. این کلاس تمامی تغییرات را بصورت خودکار در UserStore ذخیره می‌کند.

کلاس IdentityResult نتیجه یک عملیات هویتی را معرفی می‌کند (identity operations).

پوشه App_Data را به پروژه خود اضافه کنید.

فایل Web.config پروژه را باز کنید و رشته اتصال جدیدی برای دیتابیس اطلاعات کاربران اضافه کنید. این دیتابیس در زمان اجرا (runtime) بصورت خودکار توسط EF ساخته می‌شود. این رشته اتصال شبیه به رشته اتصالی است که هنگام ایجاد پروژه بصورت خودکار برای شما تنظیم می‌شود.

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
   <connectionStrings>
      <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\WebFormsIdentity.mdf;Initial Catalog=WebFormsIdentity;Integrated Security=True"
            providerName="System.Data.SqlClient" />
   </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>

همانطور که مشاهده می‌کنید نام این رشته اتصال DefaultConnection است.

روی فایل Register.aspx کلیک راست کنید و گزینه Set As Start Page را انتخاب کنید. اپلیکیشن خود را با کلیدهای ترکیبی Ctrl + F5 اجرا کنید که تمام پروژه را کامپایل نیز خواهد کرد. یک نام کاربری و کلمه عبور وارد کنید و روی Register کلیک کنید.

ASP.NET Identity از اعتبارسنجی نیز پشتیبانی می‌کند، مثلا در این مرحله می‌توانید از اعتبارسنج هایی که توسط ASP.NET Identity Core عرضه می‌شوند برای کنترل رفتار فیلد‌های نام کاربری و کلمه عبور استفاده کنید. اعتبارسنج پیش فرض کاربران (User) که UserValidator نام دارد خاصیتی با نام AllowOnlyAlphanumericUserNames دارد که مقدار پیش فرضش هم true است. اعتبارسنج پیش فرض کلمه عبور (MinimumLengthValidator) اطمینان حاصل می‌کند که کلمه عبور حداقل 6 کاراکتر باشد. این اعتبارسنج‌ها بصورت property‌ها در کلاس UserManager تعریف شده اند و می‌توانید آنها را overwrite کنید و اعتبارسنجی سفارشی خود را پیاده کنید. از آنجا که الگوی دیتابیس سیستم عضویت توسط Entity Framework مدیریت می‌شود، روی الگوی دیتابیس کنترل کامل دارید، پس از Data Annotations نیز می‌توانید استفاده کنید.


تایید دیتابیس LocalDbIdentity که توسط EF ساخته می‌شود

از منوی View گزینه Server Explorer را انتخاب کنید.

گره (DefaultConnection (WebFormsIdentity و سپس Tables را باز کنید. روی جدول AspNetUsers کلیک راست کرده و Show Table Data را انتخاب کنید.


پیکربندی اپلیکیشن برای استفاده از احراز هویت OWIN

تا این مرحله ما تنها امکان ایجاد حساب‌های کاربری را فراهم کرده ایم. حال نیاز داریم امکان احراز هویت کاربران برای ورود آنها به سایت را فراهم کنیم. ASP.NET Identity برای احراز هویت مبتنی بر فرم (forms authentication) از OWIN Authentication استفاده می‌کند. OWIN Cookie Authentication مکانیزمی برای احراز هویت کاربران بر اساس cookie‌ها و claim‌ها است (claims-based). این مکانیزم می‌تواند توسط Entity Framework روی OWIN یا IIS استفاده شود.
با چنین مدلی، می‌توانیم از پکیج‌های احراز هویت خود در فریم ورک‌های مختلفی استفاده کنیم، مانند ASP.NET MVC و ASP.NET Web Forms. برای اطلاعات بیشتر درباره پروژه Katana و نحوه اجرای آن بصورت Host Agnostic به لینک Getting Started with the Katana Project مراجعه کنید.


نصب پکیج‌های احراز هویت روی پروژه

روی نام پروژه خود کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.Owin" را وارد کنید و این پکیج را نصب کنید.

به دنبال پکیجی با نام Microsoft.Owin.Host.SystemWeb بگردید و آن را نیز نصب کنید.

پکیج Microsoft.Aspnet.Identity.Owin حاوی یک سری کلاس Owin Extension است و امکان مدیریت و پیکربندی OWIN Authentication در پکیج‌های ASP.NET Identity Core را فراهم می‌کند.

پکیج Microsoft.Owin.Host.SystemWeb حاوی یک سرور OWIN است که اجرای اپلیکیشن‌های مبتنی بر OWIN را روی IIS و با استفاده از ASP.NET Request Pipeline ممکن می‌سازد. برای اطلاعات بیشتر به OWIN Middleware in the IIS integrated pipeline مراجعه کنید.


افزودن کلاس‌های پیکربندی Startup و Authentication

روی پروژه خود کلیک راست کرده و گزینه Add و سپس Add New Item را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "owin" را وارد کنید. نام کلاس را "Startup" تعیین کرده و تایید کنید.

فایل Startup.cs را باز کنید و قطعه کد زیر را با محتویات آن جایگزین کنید تا احراز هویت OWIN Cookie Authentication پیکربندی شود.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;

[assembly: OwinStartup(typeof(WebFormsIdentity.Startup))]

namespace WebFormsIdentity
{
   public class Startup
   {
      public void Configuration(IAppBuilder app)
      {
         // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
         app.UseCookieAuthentication(new CookieAuthenticationOptions
         {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Login")
         });
      }
   }
}

این کلاس حاوی خاصیت OwinAttribute است که کلاس راه انداز OWIN را نشانه گذاری می‌کند. هر اپلیکیشن OWIN یک کلاس راه انداز (startup) دارد که توسط آن می‌توانید کامپوننت‌های application pipeline را مشخص کنید. برای اطلاعات بیشتر درباره این مدل، به OWIN Startup Class Detection مراجعه فرمایید.


افزودن فرم‌های وب برای ثبت نام و ورود کاربران

فایل Register.cs را باز کنید و قطعه کد زیر را وارد کنید. این قسمت پس از ثبت نام موفقیت آمیز کاربر را به سایت وارد می‌کند.
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using System;
using System.Linq;
using System.Web;

namespace WebFormsIdentity
{
   public partial class Register : System.Web.UI.Page
   {
      protected void CreateUser_Click(object sender, EventArgs e)
      {
         // Default UserStore constructor uses the default connection string named: DefaultConnection
         var userStore = new UserStore<IdentityUser>();
         var manager = new UserManager<IdentityUser>(userStore);
         var user = new IdentityUser() { UserName = UserName.Text };

         IdentityResult result = manager.Create(user, Password.Text);

         if (result.Succeeded)
         {
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
            authenticationManager.SignIn(new AuthenticationProperties() { }, userIdentity);
            Response.Redirect("~/Login.aspx");
         }
         else
         {
            StatusMessage.Text = result.Errors.FirstOrDefault();
         }
      }
   }
}
از آنجا که ASP.NET Identity و OWIN Cookie Authentication هر دو مبتنی بر Claims هستند، فریم ورک از برنامه نویس اپلیکیشن انتظار دارد تا برای کاربر یک آبجکت از نوع ClaimsIdentity تولید کند. این آبجکت تمام اطلاعات اختیارات کاربر را در بر می‌گیرد، مثلا اینکه کاربر به چه نقش هایی تعلق دارد. همچنین در این مرحله می‌توانید اختیارات (Claims) جدیدی به کاربر اضافه کنید.
شما با استفاده از AuthenticationManager که متعلق به OWIN است می‌توانید کاربر را به سایت وارد کنید. برای این کار شما متد SignIn را فراخوانی می‌کنید و آبجکتی از نوع ClaimsIdentity را به آن پاس می‌دهید. این کد کاربر را به سایت وارد می‌کند و یک کوکی برای او می‌سازد. این فراخوانی معادل همان FormAuthentication.SetAuthCookie است که توسط ماژول FormsAuthentication استفاده می‌شود.
روی پروژه خود کلیک راست کرده، فرم وب جدیدی با نام Login بسازید.

فایل Login.aspx را باز کنید و کد Markup آن را مانند قطعه کد زیر تغییر دهید.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebFormsIdentity.Login" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
   <title></title>
</head>
<body style="font-family: Arial, Helvetica, sans-serif; font-size: small">
   <form id="form1" runat="server">
      <div>
         <h4 style="font-size: medium">Log In</h4>
         <hr />
         <asp:PlaceHolder runat="server" ID="LoginStatus" Visible="false">
            <p>
               <asp:Literal runat="server" ID="StatusText" />
            </p>
         </asp:PlaceHolder>
         <asp:PlaceHolder runat="server" ID="LoginForm" Visible="false">
            <div style="margin-bottom: 10px">
               <asp:Label runat="server" AssociatedControlID="UserName">User name</asp:Label>
               <div>
                  <asp:TextBox runat="server" ID="UserName" />
               </div>
            </div>
            <div style="margin-bottom: 10px">
               <asp:Label runat="server" AssociatedControlID="Password">Password</asp:Label>
               <div>
                  <asp:TextBox runat="server" ID="Password" TextMode="Password" />
               </div>
            </div>
            <div style="margin-bottom: 10px">
               <div>
                  <asp:Button runat="server" OnClick="SignIn" Text="Log in" />
               </div>
            </div>
         </asp:PlaceHolder>
         <asp:PlaceHolder runat="server" ID="LogoutButton" Visible="false">
            <div>
               <div>
                  <asp:Button runat="server" OnClick="SignOut" Text="Log out" />
               </div>
            </div>
         </asp:PlaceHolder>
      </div>
   </form>
</body>
</html>

محتوای فایل Login.aspx.cs را نیز مانند لیست زیر تغییر دهید.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using System;
using System.Web;
using System.Web.UI.WebControls;

namespace WebFormsIdentity
{
   public partial class Login : System.Web.UI.Page
   {
      protected void Page_Load(object sender, EventArgs e)
      {
         if (!IsPostBack)
         {
            if (User.Identity.IsAuthenticated)
            {
               StatusText.Text = string.Format("Hello {0}!", User.Identity.GetUserName());
               LoginStatus.Visible = true;
               LogoutButton.Visible = true;
            }
            else
            {
               LoginForm.Visible = true;
            }
         }
      }

      protected void SignIn(object sender, EventArgs e)
      {
         var userStore = new UserStore<IdentityUser>();
         var userManager = new UserManager<IdentityUser>(userStore);
         var user = userManager.Find(UserName.Text, Password.Text);

         if (user != null)
         {
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);

            authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, userIdentity);
            Response.Redirect("~/Login.aspx");
         }
         else
         {
            StatusText.Text = "Invalid username or password.";
            LoginStatus.Visible = true;
         }
      }

      protected void SignOut(object sender, EventArgs e)
      {
         var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
         authenticationManager.SignOut();
         Response.Redirect("~/Login.aspx");
      }
   }
}
  • متد Page_Load حالا وضعیت کاربر جاری را بررسی می‌کند و بر اساس وضعیت Context.User.Identity.IsAuthenticated تصمیم گیری می‌کند. 
نمایش نام کاربر جاری: فریم ورک ASP.NET Identity روی  System.Security.Principal.Identity  متدهایی نوشته است که به شما امکان دریافت نام و شناسه کاربر جاری را می‌دهد. این متدها در اسمبلی Microsoft.AspNet.Identity.Core وجود دارند. این متدها جایگزین  HttpContext.User.Identity.Name  هستند.
  • متد SignIn
این متد، متد CreateUser_Click را که پیشتر بصورت خودکار ایجاد شده جایگزین می‌کند و پس از ایجاد موفقیت آمیز حساب کاربری، کاربر جاری را به سایت وارد می‌کند. فریم ورک OWIN متدهایی روی System.Web.HttpContext افزوده است که به شما این امکان را می‌دهند که یک ارجاع از نوع IOwinContext بگیرید. این متد‌ها در اسمبلی Microsoft.Owin.Host.SystemWeb وجود دارند. کلاس OwinContext خاصیتی از نوع IAuthenticationManager دارد که امکانات احراز هویت موجود برای درخواست جاری را معرفی می‌کند.
  • پروژه را با Ctrl + F5 اجرا کنید و کاربر جدیدی بسازید. پس از وارد کردن نام کاربری و کلمه عبور و کلیک کردن دکمه Register باید بصورت خودکار به سایت وارد شوید و نام خود را مشاهده کنید.

  • همانطور که مشاهده می‌کنید در این مرحله حساب کاربری جدید ایجاد شده و به سایت وارد شده اید. روی Log out کلیک کنید تا از سایت خارج شوید. پس از آن باید به صفحه ورود هدایت شوید.
  • حالا یک نام کاربری یا کلمه عبور نامعتبر وارد کنید و روی Log in کلیک کنید. 
متد UserManager.Find مقدار null بر می‌گرداند، بنابراین پیام خطای "Invalid username or password" نمایش داده خواهد شد.

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت اول - تزریق وابستگی‌ها در برنامه‌های کنسول
پیشتر با مقدمات تزریق وابستگی‌ها در برنامه‌های ASP.NET Core آشنا شده‌ایم:
در ادامه در طی چند مطلب می‌خواهیم نکات و سناریوهای تکمیلی مرتبط با امکانات تزریق وابستگی‌های توکار برنامه‌های مبتنی بر NET Core. را بررسی کنیم.


تزریق وابستگی‌ها در برنامه‌های کنسول مبتنی بر NET Core.

تزریق وابستگی‌ها، یکی از پرکاربردترین الگوهای طراحی برنامه‌های مدرن است. در نگارش‌های قبلی ASP.NET، به کمک DependencyResolver آن، کتابخانه‌های ثالث کمکی تزریق وابستگی‌ها می‌توانستند خودشان را به سیستم متصل کنند. اینبار ASP.NET Core به همراه IoC Container توکار خودش ارائه شده‌است که این کتابخانه، در خارج از آن، مانند برنامه‌های کنسول نیز قابل استفاده است.


سرویس نمونه‌‌ای برای تزریق آن در یک برنامه‌ی کنسول NET Core.

در پوشه‌ی جدید CoreIocServices، دستور dotnet new classlib را صادر می‌کنیم تا یک پروژه‌ی class library جدید را ایجاد کند. سپس اینترفیس ITestService و یک نمونه پیاده سازی آن‌را به این پروژه اضافه می‌کنیم تا در ادامه بتوانیم تنظیمات تزریق وابستگی‌های آن‌را در یک پروژه‌ی کنسول، ایجاد کنیم:
using System;
using Microsoft.Extensions.Logging;

namespace CoreIocServices
{
    public interface ITestService
    {
        void Run();
    }

    public class TestService : ITestService
    {
        private readonly ILogger<TestService> _logger;

        public TestService(ILogger<TestService> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public void Run()
        {
            _logger.LogWarning("A Warning from the TestService!");
        }
    }
}
در اینجا این سرویس نمونه نیز دارای یک وابستگی تزریق شده‌ی در سازنده‌ی آن است. این وابستگی، همان امکانات توکار logging مربوط به ASP.NET Core است که در برنامه‌های کنسول نیز قابل استفاده است. برای اینکه پروژه قابل کامپایل باشد، نیاز است وابستگی Microsoft.Extensions.Logging را نیز به آن افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
  </ItemGroup>

</Project>


دسترسی به سرویس TestService از طریق تزریق وابستگی‌ها در یک برنامه‌ی کنسول

در ادامه، یک پوشه‌ی جدید را به نام CoreIocSample01 ایجاد کرده و دستور dotnet new console را در آن اجرا می‌کنیم تا یک برنامه‌ی کنسول جدید را ایجاد کند.
سپس اولین قدم برای استفاده‌ی از سرویس TestService از طریق تزریق وابستگی‌ها، افزودن وابستگی‌های مورد نیاز آن است:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreIocServices\CoreIocServices.csproj" />
  </ItemGroup>

</Project>
در اینجا بسته‌ی Microsoft.Extensions.DependencyInjection جهت دسترسی به امکانات تزریق وابستگی‌های NET Core. به پروژه اضافه شده و همچنین ارجاعی نیز به پروژه‌ی class library که پیشتر ایجاد کردیم، افزوده شده‌است.
اکنون می‌توانیم همان روشی را که در یک برنامه‌ی ASP.NET Core با ارائه‌ی متد ConfigureServices به صورت از پیش آماده شده برای ما مهیا است، در اینجا نیز پیاده سازی کنیم:
using CoreIocServices;
using Microsoft.Extensions.DependencyInjection;

namespace CoreIocSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
            var serviceProvider = serviceCollection.BuildServiceProvider();

            var testService = serviceProvider.GetService<ITestService>();
            testService.Run();
        }

        private static void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITestService, TestService>();
        }
    }
}
کار با تعریف یک ServiceCollection جدید شروع می‌شود. سپس در متد ConfigureServices، همانند کاری که در برنامه‌های ASP.NET Core انجام می‌دهیم، ارتباطات اینترفیس‌ها و پیاده سازی‌های آن‌ها، به همراه طول عمر آن‌ها را تعریف می‌کنیم.
سپس نیاز است بر روی این ServiceCollection، متد BuildServiceProvider فراخوانی شود تا بتوانیم به IServiceProvider دسترسی پیدا کنیم. به آن Dependency Management Container نیز می‌گویند. این Container است که امکان دسترسی به وهله‌ای از ITestService و سپس فراخوانی متد Run آن‌را میسر می‌کند.


مشکل! برنامه‌ی کنسول اجرا نمی‌شود!

اگر سعی کنیم مثال فوق را اجرا کنیم، با استثنای زیر برنامه خاتمه می‌یابد:
Exception has occurred: CLR/System.InvalidOperationException
An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.Extensions.DependencyInjection.dll:
'Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[CoreIocServices.TestService]'
while attempting to activate 'CoreIocServices.TestService'.'
عنوان می‌کند که وابستگی تزریق شده‌ی در سازنده‌ی کلاس TestService را نمی‌تواند پیدا کند. علت اینجا است که هرچند ILogger را به سازنده‌ی کلاس سرویس خود اضافه کرده‌ایم، اما هنوز پیاده سازی کننده‌ی آن‌را مشخص نکرده‌ایم. به همین جهت امکان وهله سازی از این کلاس وجود ندارد. عموما در برنامه‌های ASP.NET Core نیازی به تنظیم زیر ساخت logging آن نیست؛ چون این مورد نیز به صورت پیش‌فرض انجام شده‌است. اما در اینجا خیر. به همین جهت دو وابستگی جدید Microsoft.Extensions.Logging.Console و Microsoft.Extensions.Logging.Debug را به پروژه‌ی کنسول اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreIocServices\CoreIocServices.csproj" />
  </ItemGroup>

</Project>
پس از آن متد ConfigureServices ما جهت تعریف logging در دو حالت دیباگ و کنسول، به صورت زیر تغییر می‌کند:
private static void ConfigureServices(IServiceCollection services)
{
   services.AddLogging(configure => configure.AddConsole().AddDebug());
   services.AddTransient<ITestService, TestService>();
}
اکنون اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:
 CoreIocServices.TestService:Warning: A Warning from the TestService!


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-01.zip
نظرات مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت نهم- مدیریت طول عمر توکن‌ها
چگونه می‌توان sign out را پیاده سازی کرد 
نمی‌توان. کل توضیح بحث راجع به همین موضوع هست. نمونه‌ی دیگر آن بحث «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» هست که شبیه به مفهوم همین reference token را به همراه یک ITokenValidatorService پیاده سازی کرده (قسمت «تهیه یک اعتبارسنج توکن سفارشی» آن) تا بتوان logout را به همراه JWTها داشت.
اشتراک‌ها
اجرای Asp.Net Core 2.1 به صورت ویندوز سرویس

ASP.NET Core 2.1 introduces new application host for Windows services. We can now run ASP.NET Core applications as Windows services with minimal effort. This blog post introduces how it is done and how to build and run Windows services on ASP.NET Core without any need for dirty hacks. 

اجرای Asp.Net Core 2.1 به صورت ویندوز سرویس
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 3 - Middleware چیست؟
پیشنیازها
- «با HttpHandler بیشتر آشنا شوید»
یکی از بزرگترین تغییرات ASP.NET Core نسبت به نگارش‌های قبلی آن، مدیریت HTTP pipeline آن است. به عنوان یک توسعه دهنده‌ی ASP.NET به طور قطع با مفاهیمی مانند HttpHandler و HttpModules آشنایی دارید و ... هر دوی این‌ها با نگارش جدید ASP.NET حذف و با مفهوم جدیدی به نام Middleware جایگزین شده‌اند.
 - HttpHandlerها عموما مبتنی بر پسوندهای فایل‌ها عمل می‌کنند. برای نمونه، رایج‌ترین آن‌ها ASP.NET page handler است که هرگاه درخواستی، ختم شده‌ی به پسوند aspx، به موتور ASP.NET Web forms وارد شد، به این page handler، جهت پردازش نهایی هدایت می‌شود. محل تنظیم آن‌ها نیز در فایل web.config است.
 - HttpModuleها مبتنی بر رخدادها عمل می‌کنند. HttpModuleها به عنوان جزئی از request pipeline عمل کرده و دسترسی کاملی دارند به رخدادهای طول عمر درخواست رسیده. آن‌ها را می‌توان توسط فایل‌های global.asax و یا web.config تنظیم کرد.
 - MiddleWareها را که جزئی از طراحی OWIN نیز هستند، می‌توان به عنوان کامپوننت‌های کوچکی از برنامه که قابلیت یکپارچه شدن با HTTP request pipeline را دارند، درنظر گرفت. عملکرد آن‌ها ترکیبی است از هر دوی HttpHandler و HttpModule‌ها که در نگارش‌های قبلی ASP.NET مورد استفاده بودند. از MiddleWareها می‌توان برای پیاده سازی اعمال مختلفی جهت پردازش درخواست‌های رسیده مانند اعتبارسنجی، کار با سشن‌ها، ثبت وقایع سیستم، مسیریابی و غیره استفاده کرد. OWIN یا Open Web Interface for .NET به توسعه دهنده‌ها امکان غیروابسته کردن برنامه‌ی ASP.NET خود را از وب سرور مورد استفاده می‌دهد. به علاوه OWIN امکان پلاگین نویسی اجزای مختلف برنامه را بدون وابسته کردن آن‌ها به یکدیگر فراهم می‌کند. برای مثال می‌توان یک پلاگین logger را تهیه کرد تا اطلاعات مختلفی را از درخواست‌های رسیده ثبت کند.



مواردی که با ارائه‌ی ASP.NET Core 1.0 حذف شده‌اند

- System.Web: یکی از اهداف اصلی OWIN، مستقل کردن برنامه‌ی وب، از هاست آن است تا بتوان از وب سرورهای بیشتری استفاده کرد. System.Web انحصارا برای IIS طراحی شده بود و در این نگارش دیگر وجود خارجی ندارد.
- HttpModules: با Middlewareها جایگزین شده‌اند.
- HttpHandlers: البته HttpHandlers از زمان ارائه‌ی اولین نگارش ASP.NET MVC، در چندین سال قبل منسوخ شدند. زیرا پردازش صفحات وب در ASP.NET MVC برخلاف وب فرم‌ها، از فایل‌هایی با پسوندهای خاص شروع نمی‌شوند و نقطه‌ی آغازین آن‌ها، اکشن متدهای کنترلرها است.
- فایل global.asax: با حذف شدن HttpModules، دیگر ضرورتی به وجود این فایل نیست.


شباهت‌های بینMiddleware و HttpModuleها

همانند HttpModuleها، Middlewareها نیز به ازای هر درخواست رسیده اجرا می‌شوند و از هر دو می‌توان جهت تولید response ایی خاص استفاده کرد.


تفاوت‌های بینMiddleware و HttpModuleها

- عموما HttpModule از طریق web.config و یا فایل global.asax تنظیم می‌شوند. اما Middlewareها تنها از طریق کد و در فایل Startup.cs برنامه‌های ASP.NET Core 1.0 قابل معرفی هستند.
- به عنوان یک توسعه دهنده، کنترلی را بر روی ترتیب اجرای انواع و اقسام HttpModuleها نداریم. این مشکل در Middlewareها برطرف شده و اکنون ترتیب اجرای آن‌ها، دقیقا مطابق ترتیب افزوده شدن و تعریف آن‌ها در فایل Startup.cs است.
- ترتیب اجرای HttpModuleها هرچه که باشد، برای حالت‌های request و response یکی است. اما ترتیب اجرای Middlewareهای مخصوص response، عکس ترتیب اجرای Middlewareهای مخصوص request هستند (تصویر ذیل).
- HttpModuleها را صرفا جهت اتصال کدهایی به رخدادهای طول عمر برنامه می‌توان طراحی کرد؛ اما Middleware مستقل هستند از این رخدادها.
- HttpModuleها به System.Web وابسته‌اند؛ برخلاف Middlewareها که وابستگی به هاست خود ندارند.



Middleware‌های توکار ASP.NET Core 1.0

ASP.NET Core 1.0 به همراه تعدادی Middleware توکار است؛ مانند:
- Authentication: جهت پشتیبانی از اعتبارسنجی
- CORS: برای فعال سازی Cross-Origin Resource Sharing
- Routing: جهت تعریف و محدود سازی مسیریابی‌های برنامه
- Session: برای پشتیبانی و مدیریت سشن‌های کاربران
- Diagnostics: پشتیبانی از صفحات خطا و اطلاعات زمان اجرای برنامه
و مواردی دیگر که برای توزیع فایل‌های استاتیک و یا مرور محتویات پوشه‌ها و امثال آن‌ها طراحی شده‌اند که در طی این مطلب و همچنین مطالب آتی، آن‌ها را بررسی خواهیم کرد.

یک مثال: اگر یک پروژه‌ی خالی ASP.NET Core 1.0 را در ویژوال استودیو ایجاد کنید، این پروژه قادر نیست حتی فایل‌های استاتیک مانند تصاویر، فایل‌های پیش فرض مانند index.html و یا قابلیت مرور ساده‌ی فایل‌های موجود در یک پوشه را ارائه دهد (حتی اگر به آن‌ها نیازی نداشته باشید).
طراحی این نگارش از ASP.NET، مبتنی است بر سبک وزن بودن و ماژولار بودن. هر قابلیتی را که نیاز دارید، باید middleware آن‌را نیز خودتان به صورت صریح اضافه کنید و یا در غیر اینصورت فعال نیست. برخلاف نگارش‌های قبلی ASP.NET که HTTP Module‌های از سشن گرفته تا اعتبارسنجی و غیره، همگی به صورت پیش فرض در فایل Web.Config فعال بودند؛ مگر اینکه آن‌ها را به صورت دستی حذف می‌کردید.



ثبت و فعال سازی اولین Middleware

در ادامه‌ی تکمیل و معرفی برنامه‌ی خالی ASP.NET Core 1.0، قصد داریم یک Middleware توکار را به نام Welcome Page، بجای نمایش سطر Hello world پیش فرض این برنامه، ثبت و فعال سازی کنیم. این Middleware ویژه، در اسمبلی به نام Microsoft.AspNetCore.Diagnostics قرار دارد. اگر فایل project.json پیش فرض این پروژه را باز کنید، این اسمبلی به صورت پیش فرض در آن ثبت و معرفی شده‌است:
{
    "dependencies": {
        "Microsoft.NETCore.App": {
            "version": "1.0.0",
            "type": "platform"
        },
        "Microsoft.AspNetCore.Diagnostics": "1.0.0",
        "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
        "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
        "Microsoft.Extensions.Logging.Console": "1.0.0"
    },
بنابراین هم اکنون بسته‌ی نیوگت آن نیز به پروژه‌ی جاری اضافه شده و قابل استفاده است. در غیراینصورت همانطور که در قسمت قبل در مطلب «نقش فایل project.json» عنوان شد، می‌توان بر روی گره references کلیک راست کرده و سپس از منوی ظاهر شده،‌گزینه‌ی manage nuget packages را انتخاب کرد. سپس، ابتدا برگه‌ی browse را انتخاب کنید و در اینجا نام Microsoft.AspNetCore.Diagnostics را جستجو کرده و در آخر آن‌را نصب کنید.
پس از اطمینان حاصل کردن از نصب بسته‌ی نیوگت Microsoft.AspNetCore.Diagnostics، اکنون جهت معرفی Middleware توکار Welcome Page ، فایل Startup.cs را گشوده و کدهای آن‌را به نحو ذیل تغییر  دهید:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
 
namespace Core1RtmEmptyTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }
 
        public void Configure(IApplicationBuilder app)
        {
            app.UseWelcomePage();
 
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("Hello DNT!");
            });
        }
    }
}
در اینجا نحوه‌ی ثبت این middleware را با افزودن آن به IApplicationBuilder تزریق شده‌ی توسط OWIN، به کمک متد الحاقی UseWelcomePage مشاهده می‌کنید.
به علاوه middleware دومی را نیز با متد الحاقی Run مشاهده می‌کنید. به این نوع middlewareهای خاص، اصطلاحا terminal middleware می‌گویند. از این جهت که درخواستی را دریافت و یک response را تولید می‌کنند و کار همینجا خاتمه می‌یابد و زنجیره‌ی پردازشی middlewareها ادامه نخواهد یافت. در اینجا پارامتر context آن از نوع HttpContext است و باید دقت داشت، زمانیکه کار نوشتن در Response، در اینجا انجام شد، اگر پس از متد Run یک Middleware دیگر را ثبت کنید، هیچگاه اجرا نخواهد شد.

در این حالت اگر برنامه را اجرا کنید، خروجی ذیل را مشاهده خواهید کرد:


welcome page نیز یک terminal middleware است. به همین جهت middleware بعدی ثبت شده‌ی در اینجا یا همان متد Run، دیگر اجرا نخواهد شد.

در قسمت‌های بعدی، تعداد بیشتری از Middlewareهای توکار ASP.NET Core 1.0 را بررسی خواهیم کرد.
نظرات مطالب
Blazor 5x - قسمت 21 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 1 - افزودن قالب ابتدایی Identity
معرفی دو پروژه‌ی تکمیلی
اگر علاقمند به استفاده‌ی از ASP.NET Core Identity نیستید، دو پروژه بر پایه‌ی مطالب «اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core بدون استفاده از سیستم Identity» و «اعتبارسنجی مبتنی بر JWT در ASP.NET Core بدون استفاده از سیستم Identity» در ذیل توسعه یافته‌اند:
- BlazorServer CookieAuthentication (مخصوص Blazor Server)
- JWT WebApi Blazor (مخصوص Blazor WASM)