WF:Windows Workflow #1
- با نصب و اجرای 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 به پروژه
دقت کنید که نصب کردن این پکیج وابستگیها را نیز بصورت خودکار نصب میکند: 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 ساخته میشود
گره (DefaultConnection (WebFormsIdentity و سپس Tables را باز کنید. روی جدول AspNetUsers کلیک راست کرده و Show Table Data را انتخاب کنید.
پیکربندی اپلیکیشن برای استفاده از احراز هویت 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
فایل 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 مراجعه فرمایید.
افزودن فرمهای وب برای ثبت نام و ورود کاربران
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(); } } } }
فایل 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 تصمیم گیری میکند.
- متد SignIn
- پروژه را با Ctrl + F5 اجرا کنید و کاربر جدیدی بسازید. پس از وارد کردن نام کاربری و کلمه عبور و کلیک کردن دکمه Register باید بصورت خودکار به سایت وارد شوید و نام خود را مشاهده کنید.
- همانطور که مشاهده میکنید در این مرحله حساب کاربری جدید ایجاد شده و به سایت وارد شده اید. روی Log out کلیک کنید تا از سایت خارج شوید. پس از آن باید به صفحه ورود هدایت شوید.
- حالا یک نام کاربری یا کلمه عبور نامعتبر وارد کنید و روی Log in کلیک کنید.
Watch
این پنل متغییرها و تغییرات مقادیر آنها در هنگام دیباگ را نمایش میدهد. بصورت پیشفرض متغییرهایی که در بلاک فعلی (که دیباگر در آن توقف کرده) ایجاد شده اند در این لیست قرار دارند. اما میتوانید متغییرها و عبارات مورد نظر را در این قسمت وارد کنید. برای مثال میتوانید مساوی بودن یک متغییر با یک مقدار را در این لیست وارد کنید و نتیجه را در هر اجرا مشاهده کنید. یا حتی یک متد را در این لیست وارد کنید تا در هر اجرا ، اجرا شود و نتیجه نمایش داده شود.
برای اضافه کردن یک عبارت جدید به این لیست، بروی عبارت New watch expression... در بالای پنل کلیک کرده و عبارت مورد نظر را وارد کنید و در نهایت کلید Enter را بزنید.
برای حذف عبارتی که وارد کردید، بروی علامت X کلیک کنید.
توجه کنید که هر عبارتی که در این قسمت وارد میکنید در اجرای هر خط از برنامه در حالت دیباگ اجرا میشود و اگر عبارتی که وارد کرده اید مقادیر داخل برنامه را تغییر دهند، برنامهی شما متفاوت از حالت عادی عمل خواهد کرد.
Options Menu و Context Menu در این پنل همان موارد پنل DOM هستند و از آنجایی که همهی این آیتمها در این پنل به درستی عمل نمیکنند، توضیح آن را در مقالهی مربوط به پنل DOM ارائه میکنیم.
Stack
در این پنل، زمانی که دیباگر متوقف شده است،لیستی از توابع که با فراخوانی یکدیگر به تابع جاری رسیده اند، نمایش داده میشوند. برای هر تابع هم پارامترها و مقادیر آنها هنگام فراخوانی قابل مشاهده است. با کلیک بروی هر کدام از آن ها، خطی که تابع در آن قرار دارد نمایش داده میشود و همچنین میتوانید مقادیر متغییرهای محلی آن تابع هنگامی که فرخوانی شده است را هم بررسی کنید. (جالب نیست؟)
با راست کلیک کردن بروی نام این پنل یا باز کردن فلش آن، میتوانید نمایش یا عدم نمایش این لیست در Toolbar را مشخص کنید.
Breakpoints
در این پنل لیست Break Pointهای اضافه شده نمایش داده میشود و میتوانید آن هارا فعال، غیرفعال یا حذف نمایید.
برای فعال، غیرفعال و حذف کردن یک یا همهی Break Pointها بروی قسمت از پنل راست کلیک کرده و یکی از گزینههای مورد نظر را انتخاب کنید.
شما میتوانید فایل XML ذخیره شده را بعدا استفاده کنید و یا میتوانید آن را به برنامه نویسان دیگر هم بدهید.
اجازه دهید نگاهی داشته باشیم بر محتویات داخل فایل XML . فایل XML کلکسیونی از تگ BreakPoints داخل BreakpointCollection است.هر تگ Breakpoint حاوی اطلاعاتی در مورد یک Breakpoint خاص است.
اگر شما هر زمانی همه Breakpointها را از کدتان حذف کردید به راحتی میتوانید آن را تنها با کلیک بر روی Import وارد کدتان بکنید و تمام Breakpointهای ذخیره شده را بازآوری کنید.
نکته: Import کردن Breakpoint براساس شماره خط کد شما میباشد یعنی همان خطی که شما Breakpoint را گذاشته اید پس اگر شماره خط کد شما تغییر کند Breakpoint بروی خط قبلی گذاشته میشود.
تکمیل فرم ثبت نام کاربران
در ادامه کدهای کامل کامپوننت فرم ثبت نام کاربران را مشاهده میکنید:
@page "/registration" @inject IClientAuthenticationService AuthenticationService @inject NavigationManager NavigationManager <EditForm Model="UserForRegistration" OnValidSubmit="RegisterUser" class="pt-4"> <DataAnnotationsValidator /> <div class="py-4"> <div class=" row form-group "> <div class="col-6 offset-3 "> <div class="card border"> <div class="card-body px-lg-5 pt-4"> <h3 class="col-12 text-success text-center py-2"> <strong>Sign Up</strong> </h3> @if (ShowRegistrationErrors) { <div> @foreach (var error in Errors) { <p class="text-danger text-center">@error</p> } </div> } <hr style="background-color:aliceblue" /> <div class="py-2"> <InputText @bind-Value="UserForRegistration.Name" class="form-control" placeholder="Name..." /> <ValidationMessage For="(()=>UserForRegistration.Name)" /> </div> <div class="py-2"> <InputText @bind-Value="UserForRegistration.Email" class="form-control" placeholder="Email..." /> <ValidationMessage For="(()=>UserForRegistration.Email)" /> </div> <div class="py-2 input-group"> <div class="input-group-prepend"> <span class="input-group-text"> +1</span> </div> <InputText @bind-Value="UserForRegistration.PhoneNo" class="form-control" placeholder="Phone number..." /> <ValidationMessage For="(()=>UserForRegistration.PhoneNo)" /> </div> <div class="form-row py-2"> <div class="col"> <InputText @bind-Value="UserForRegistration.Password" type="password" id="password" placeholder="Password..." class="form-control" /> <ValidationMessage For="(()=>UserForRegistration.Password)" /> </div> <div class="col"> <InputText @bind-Value="UserForRegistration.ConfirmPassword" type="password" id="confirm" class="form-control" placeholder="Confirm Password..." /> <ValidationMessage For="(()=>UserForRegistration.ConfirmPassword)" /> </div> </div> <hr style="background-color:aliceblue" /> <div class="py-2"> @if (IsProcessing) { <button type="submit" class="btn btn-success btn-block disabled"><i class="fas fa-sign-in-alt"></i> Please Wait...</button> } else { <button type="submit" class="btn btn-success btn-block"><i class="fas fa-sign-in-alt"></i> Register</button> } </div> </div> </div> </div> </div> </div> </EditForm> @code{ UserRequestDTO UserForRegistration = new UserRequestDTO(); bool IsProcessing; bool ShowRegistrationErrors; IEnumerable<string> Errors; private async Task RegisterUser() { ShowRegistrationErrors = false; IsProcessing = true; var result = await AuthenticationService.RegisterUserAsync(UserForRegistration); if (result.IsRegistrationSuccessful) { IsProcessing = false; NavigationManager.NavigateTo("/login"); } else { IsProcessing = false; Errors = result.Errors; ShowRegistrationErrors = true; } } }
- مدل این فرم بر اساس UserRequestDTO تشکیل شدهاست که همان شیءای است که اکشن متد ثبت نام سمت Web API انتظار دارد.
- در این کامپوننت به کمک سرویس IClientAuthenticationService که آنرا در قسمت قبل تهیه کردیم، شیء نهایی متصل به فرم، به سمت Web API Endpoint ثبت نام ارسال میشود.
- در اینجا روشی را جهت غیرفعال کردن یک دکمه، پس از کلیک بر روی آن مشاهده میکنید. میتوان پس از کلیک بر روی دکمهی ثبت نام، با true کردن یک فیلد مانند IsProcessing، بلافاصله دکمهی جاری را برای مثال با ویژگی disabled در صفحه درج کرد و یا حتی آنرا از صفحه حذف کرد. این روش، یکی از روشهای جلوگیری از کلیک چندبارهی کاربر، بر روی یک دکمهاست.
- فرم جاری، خطاهای اعتبارسنجی مخصوص Identity سمت سرور را نیز نمایش میدهد که حاصل از ارسال آنها توسط اکشن متد ثبت نام است:
- پس از پایان موفقیت آمیز ثبت نام، کاربر را به سمت فرم لاگین هدایت میکنیم.
تکمیل فرم ورود به سیستم کاربران
در ادامه کدهای کامل کامپوننت فرم ثبت نام کاربران را مشاهده میکنید:
@page "/login" @inject IClientAuthenticationService AuthenticationService @inject NavigationManager NavigationManager <div id="logreg-forms"> <h1 class="h3 mb-3 pt-3 font-weight-normal text-primary" style="text-align:center;">Sign In</h1> <EditForm Model="UserForAuthentication" OnValidSubmit="LoginUser"> <DataAnnotationsValidator /> @if (ShowAuthenticationErrors) { <p class="text-center text-danger">@Errors</p> } <InputText @bind-Value="UserForAuthentication.UserName" id="email" placeholder="Email..." class="form-control mb-2" /> <ValidationMessage For="(()=>UserForAuthentication.UserName)"></ValidationMessage> <InputText @bind-Value="UserForAuthentication.Password" type="password" placeholder="Password..." id="password" class="form-control mb-2" /> <ValidationMessage For="(()=>UserForAuthentication.Password)"></ValidationMessage> @if (IsProcessing) { <button type="submit" class="btn btn-success btn-block disabled"><i class="fas fa-sign-in-alt"></i> Please Wait...</button> } else { <button type="submit" class="btn btn-success btn-block"><i class="fas fa-sign-in-alt"></i> Sign in</button> } <a href="/registration" class="btn btn-primary text-white mt-3"><i class="fas fa-user-plus"></i> Register as a new user</a> </EditForm> </div> @code { AuthenticationDTO UserForAuthentication = new AuthenticationDTO(); bool IsProcessing = false; bool ShowAuthenticationErrors; string Errors; string ReturnUrl; private async Task LoginUser() { ShowAuthenticationErrors = false; IsProcessing = true; var result = await AuthenticationService.LoginAsync(UserForAuthentication); if (result.IsAuthSuccessful) { IsProcessing = false; var absoluteUri = new Uri(NavigationManager.Uri); var queryParam = HttpUtility.ParseQueryString(absoluteUri.Query); ReturnUrl = queryParam["returnUrl"]; if (string.IsNullOrEmpty(ReturnUrl)) { NavigationManager.NavigateTo("/"); } else { NavigationManager.NavigateTo("/" + ReturnUrl); } } else { IsProcessing = false; Errors = result.ErrorMessage; ShowAuthenticationErrors = true; } } }
- مدل این فرم بر اساس AuthenticationDTO تشکیل شدهاست که همان شیءای است که اکشن متد لاگین سمت Web API انتظار دارد.
- در این کامپوننت به کمک سرویس IClientAuthenticationService که آنرا در قسمت قبل تهیه کردیم، شیء نهایی متصل به فرم، به سمت Web API Endpoint ثبت نام ارسال میشود.
- در اینجا نیز همانند فرم ثبت نام، پس از کلیک بر روی دکمهی ورود به سیستم، با true کردن یک فیلد مانند IsProcessing، بلافاصله دکمهی جاری را با ویژگی disabled در صفحه درج کردهایم تا از کلیک چندبارهی کاربر، جلوگیری شود.
- این فرم، خطاهای اعتبارسنجی مخصوص Identity سمت سرور را نیز نمایش میدهد که حاصل از ارسال آنها توسط اکشن متد لاگین است:
همانطور که مشاهده میکنید، مقدار JWT تولید شدهی پس از لاگین و همچنین مشخصات کاربر دریافتی از Web Api، جهت استفادههای بعدی، در Local Storage مرورگر درج شدهاند.
تغییر منوی راهبری سایت، بر اساس وضعیت لاگین شخص
تا اینجا قسمتهای ثبت نام و ورود به سیستم را تکمیل کردیم. در ادامه نیاز داریم تا منوی سایت را هم بر اساس وضعیت اعتبارسنجی شخص، تغییر دهیم. برای مثال اگر شخصی به سیستم وارد شدهاست، باید در منوی سایت، لینک خروج و نام خودش را مشاهده کند و نه مجددا لینکهای ثبتنام و لاگین را. جهت تغییر منوی راهبری سایت، کامپوننت Shared\NavMenu.razor را گشوده و لینکهای قبلی ثبتنام و لاگین را با محتوای زیر جایگزین میکنیم:
<AuthorizeView> <Authorized> <li class="nav-item p-0"> <NavLink class="nav-link" href="#"> <span class="p-2"> Hello, @context.User.Identity.Name! </span> </NavLink> </li> <li class="nav-item p-0"> <NavLink class="nav-link" href="logout"> <span class="p-2"> Logout </span> </NavLink> </li> </Authorized> <NotAuthorized> <li class="nav-item p-0"> <NavLink class="nav-link" href="registration"> <span class="p-2"> Register </span> </NavLink> </li> <li class="nav-item p-0"> <NavLink class="nav-link" href="login"> <span class="p-2"> Login </span> </NavLink> </li> </NotAuthorized> </AuthorizeView>
تکمیل قسمت خروج از سیستم
اکنون که لینک logout را در منوی سایت، پس از ورود به سیستم نمایش میدهیم، میتوان کدهای کامپوننت آنرا (Pages\Authentication\Logout.razor) به صورت زیر تکمیل کرد:
@page "/logout" @inject IClientAuthenticationService AuthenticationService @inject NavigationManager NavigationManager @code { protected async override Task OnInitializedAsync() { await AuthenticationService.LogoutAsync(); NavigationManager.NavigateTo("/"); } }
مشکل! پس از کلیک بر روی logout، هرچند میتوان مشاهده کرد که اطلاعات Local Storage به درستی حذف شدهاند، اما ... پس از هدایت به صفحهی اصلی برنامه، هنوز هم لینک logout و نام کاربری شخص نمایان هستند و به نظر هیچ اتفاقی رخ ندادهاست!
علت اینجا است که AuthenticationStateProvider سفارشی را که تهیه کردیم، فاقد اطلاع رسانی تغییر وضعیت است:
namespace BlazorWasm.Client.Services { public class AuthStateProvider : AuthenticationStateProvider { // ... public void NotifyUserLoggedIn(string token) { var authenticatedUser = new ClaimsPrincipal( new ClaimsIdentity(JwtParser.ParseClaimsFromJwt(token), "jwtAuthType") ); var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); base.NotifyAuthenticationStateChanged(authState); } public void NotifyUserLogout() { var authState = Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); base.NotifyAuthenticationStateChanged(authState); } } }
namespace BlazorWasm.Client.Services { public class ClientAuthenticationService : IClientAuthenticationService { private readonly HttpClient _client; private readonly ILocalStorageService _localStorage; private readonly AuthenticationStateProvider _authStateProvider; public ClientAuthenticationService( HttpClient client, ILocalStorageService localStorage, AuthenticationStateProvider authStateProvider) { _client = client; _localStorage = localStorage; _authStateProvider = authStateProvider; } public async Task<AuthenticationResponseDTO> LoginAsync(AuthenticationDTO userFromAuthentication) { // ... if (response.IsSuccessStatusCode) { //... ((AuthStateProvider)_authStateProvider).NotifyUserLoggedIn(result.Token); return new AuthenticationResponseDTO { IsAuthSuccessful = true }; } //... } public async Task LogoutAsync() { //... ((AuthStateProvider)_authStateProvider).NotifyUserLogout(); } } }
- ابتدا AuthenticationStateProvider به سازندهی کلاس تزریق شدهاست.
- سپس در حین لاگین موفق، متد NotifyUserLoggedIn آن فراخوانی شدهاست.
- در آخر پس از خروج از سیستم، متد NotifyUserLogout فراخوانی شدهاست.
پس از این تغییرات اگر بر روی لینک logout کلیک کنیم، این گزینه به درستی عمل کرده و اینبار شاهد نمایش مجدد لینکهای لاگین و ثبت نام خواهیم بود.
محدود کردن دسترسی به صفحات برنامه بر اساس نقشهای کاربران
پس از ورود کاربر به سیستم و تامین AuthenticationState، اکنون میخواهیم تنها این نوع کاربران اعتبارسنجی شده بتوانند جزئیات اتاقها (برای شروع رزرو) و یا صفحهی نمایش نتیجهی پرداخت را مشاهده کنند. البته نمیخواهیم صفحهی نمایش لیست اتاقها را محدود کنیم. برای این منظور ویژگی Authorize را به ابتدای تعاریف کامپوننتهای PaymentResult.razor و RoomDetails.razor، اضافه میکنیم:
@attribute [Authorize(Roles = ConstantRoles.Customer)]
@using Microsoft.AspNetCore.Authorization
تکمیل مشخصات هویتی شخصی که قرار است اتاقی را رزرو کند
پیشتر در فرم RoomDetails.razor، اطلاعات ابتدایی کاربر را مانند نام او، دریافت میکردیم. اکنون با توجه به محدود شدن این کامپوننت به کاربران لاگین کرده، میتوان اطلاعات کاربر وارد شدهی به سیستم را نیز به صورت خودکار بارگذاری و تکمیل کرد:
@page "/hotel-room-details/{Id:int}" // ... @code { // ... protected override async Task OnInitializedAsync() { try { HotelBooking.OrderDetails = new RoomOrderDetailsDTO(); if (Id != null) { // ... if (await LocalStorage.GetItemAsync<UserDTO>(ConstantKeys.LocalUserDetails) != null) { var userInfo = await LocalStorage.GetItemAsync<UserDTO>(ConstantKeys.LocalUserDetails); HotelBooking.OrderDetails.UserId = userInfo.Id; HotelBooking.OrderDetails.Name = userInfo.Name; HotelBooking.OrderDetails.Email = userInfo.Email; HotelBooking.OrderDetails.Phone = userInfo.PhoneNo; } } } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } }
public async Task<RoomOrderDetailsDTO> SaveRoomOrderDetailsAsync(RoomOrderDetailsDTO details) { // details.UserId = "unknown user!";
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-32.zip
همچنین Issue tracker آنها هم مدتی است که به آدرس جدیدی منتقل شده است: [^]
و ... اگر علاقمند باشید که از آخرین تغییرات این کتابخانه آگاه شوید، زیاد به دنبال وبلاگ یا سایت خاصی نگردید. روش متداول کار با کتابخانههای سورس باز، دنبال کردن change log ارسالی آنها به سیستمهای سورس کنترل است (همان متنی که حین commit ارسال میکنند). برای مثال جهت آگاه شدن از آخرین تغییرات NHibernate مشترک این فید شوید: [^]
public class ApplicationUser : IdentityUser { public string Email { get; set; } public string ConfirmationToken { get; set; } public bool IsConfirmed { get; set; } }
private string CreateConfirmationToken() { return ShortGuid.NewGuid(); } private void SendEmailConfirmation(string to, string username, string confirmationToken) { dynamic email = new Email("RegEmail"); email.To = to; email.UserName = username; email.ConfirmationToken = confirmationToken; email.Send(); } // // POST: /Account/Register [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { string confirmationToken = CreateConfirmationToken(); var user = new ApplicationUser() { UserName = model.UserName, Email = model.Email, ConfirmationToken = confirmationToken, IsConfirmed = false }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { SendEmailConfirmation(model.Email, model.UserName, confirmationToken); return RedirectToAction("RegisterStepTwo", "Account"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
private bool ConfirmAccount(string confirmationToken) { ApplicationDbContext context = new ApplicationDbContext(); ApplicationUser user = context.Users.SingleOrDefault(u => u.ConfirmationToken == confirmationToken); if (user != null) { user.IsConfirmed = true; DbSet<ApplicationUser> dbSet = context.Set<ApplicationUser>(); dbSet.Attach(user); context.Entry(user).State = EntityState.Modified; context.SaveChanges(); return true; } return false; } [AllowAnonymous] public ActionResult RegisterConfirmation(string Id) { if (ConfirmAccount(Id)) { return RedirectToAction("ConfirmationSuccess"); } return RedirectToAction("ConfirmationFailure"); }
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (ModelState.IsValid) { var user = await UserManager.FindAsync(model.UserName, model.Password); if (user != null && user.IsConfirmed) { await SignInAsync(user, model.RememberMe); return RedirectToLocal(returnUrl); } else { ModelState.AddModelError("", "Invalid username or password."); } } // If we got this far, something failed, redisplay form return View(model); }
توضیحاتی درباره کار با Postal
To: @ViewBag.To From: YOURNAME@gmail.com Subject: Confirm your registration Hello @ViewBag.UserName, Please confirm your registration by following the link bellow. @Html.ActionLink(Url.Action("RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }), "RegisterConfirmation", "Account", new { id = @ViewBag.ConfirmationToken }, null)
- ViewBag.To آدرس ایمیل گیرنده را نشان میدهد.
- ViewBag.UserName نام کاربر جاری را نمایش میدهد.
- ViewBag.ConfirmationToken شناسه تولید شده برای تایید کاربر است.
@{ Layout = null; /* Overrides the Layout set for regular page views. */ }
انتخاب عناصر صفحه
1-انتخاب عناصر صفحه برای ایجاد تغییر
خوشبختانه jQuery یک مکانیزم بسیار قوی و قدرتمند ارایه کرده است که انتخاب هر عنصری از صفحه را به سادگی امکان پذیر میسازد. انتخاب کنندههای jQuery از ساختار مربوط به CSS استفاده میکنند، بنابراین ممکن است شما هم اکنون با تعداد زیادی از آنها آشنا باشید . در ادامه شمار بیشتر و قدرتمندتری خواهید آموخت.
برای درک بهتر شما از مطالب مربوط به بخش انتخاب کننده ها، یک مثال آماده مختص به این مبحث، در قالب یک صفحه اینترنتی، را در فایل صفحه کارگاهی قرار داده ایم، این فایل در ادرس chapter2/lab.selector.htm قابل دسترسی میباشد. این مثال از پیش آماده و کامل (نوشته شده توسط نویسنده کتاب)، این امکان را به شما میدهد تا با وارد کردن یک رشته، به عنوان پارامتر انتخاب کننده، در همان زمان عنصر انتخاب کننده در صفحه را رویت کنید. زمانی که این صفحه را اجرا میکنید تصویری مانند زیر ظاهر خواهد شد.
برای درک بهتر مطالب این سلسله پستها میتوانید فایلهای کتاب را از آدرس اصلی آن یا از این آدرس در همین سایت دانلود نمایید.
این صفحه سه پنجره مجزا دارد. در پنجره سمت چپ ، یک textBox و یک دکمه دیده میشود، که با وارد کردن یک انتخاب کننده در textBox و فشردن دکمه، عنصر مورد نظر در پنجره سمت راست انتخاب میشود. برای شروع در textBox عبارت li را بنویسید و دکمه Apply را کلیک کنید.
با انجام این عمل تصویر زیر باید خروجی شما باشد. میتوانید حالتهای دیگر را خودتان امتحان کنید.
1-1- استفاده از انتخاب کنندههای ابتدایی CSS
برنامه نویسان وب برای اعمال فرمتهای ظاهری گوناگون به بخشها و عناصر مختلف یک صفحه اینترنتی، از ایک راه بسیار ساده، در عین حال قدرتمند و کارا استفاده میکنند که در تمام مرورگرهای مختلف نیز جوابگو باشد. این انتخاب کنندهها عناصر را بر اساس نام شناسه آنها، نام کلاس و یا ساختار سلسله مراتبی موجود در صفحه انتخاب میکنند.
در زیر به معرفی چند نمونه از این انتخاب کنندههای ساده CSS میپردازیم:
- a : تمام عناصر <a> را انتخاب میکند.
- specialID# : عنصری را که دارای ID با عنوان specialID باشد انتخاب میکند.
- specialClass. : عناصری را که دارای کلاس specialClass هستند انتخاب میکند.
- a#specialID.specialClass : این عبارت عنصری را انخاب میکند که شناسه آن specialID باشد، به شرط آنکه این عنصر <a> باشد و دارای کلاس specialClass نیز باشد را انتخاب میکند.
- p a.specialClass: تمام عناصر لینک (<a>) را که دارای کلاس specialClass باشند و درون یک عنصر پاراگراف (<p>) قرار گرفته باشند را انتخاب میکند.
این انتخاب کنندهها شاید ساده به نظر برسند، اما در بسیاری از مواقع پاسخگوی ما میباشند؛ به علاوه آنه که با ادغام این انتخاب کنندههای ساده، ما میتوانیم انتخاب کنندههای پیچیدهتر و تخصصیتر ایجاد کنیم.
نکته مثبت در مورد انتخاب کنندههای CSS این است که از همین انتخاب کنندهها میتوانیم در jQuery نیز استفاده کنیم. برای این کار تنها کافیست انتخاب کننده مورد نظر را به تابع ()$ ارسال کنیم. در زیر یک نمونه را مشاهده میکنید:
$("p a.specialClass")
2-1- استفاده از انتخاب کنندههای فرزند (Child) ، نگهدارنده (Container) و صفت (Attribute)
برای انتخاب کنندههای پیشرفته تر، jQuery از جدیدترین مرورگرهایی که CSS را پشتیبانی میکنند، استفاده میکند که میتوان به Mozilla Firefox, Internet Explorer 7, Safariو سایر مرورگرهای پیشرفته (مدرن) اشاره کرد. این انتخاب کنندههای پیشرفته شما را قادر میسازند تا مستقیما فرزند یک عنصر را انتخاب کنید و یا از ساختار سلسله مراتبی عناصر صفحه، مستقیما به عنصر مورد نظر دسترسی داشته باشید و یا حتی تمام عناصری که یک صفت خاص را شامل میشوند، انتخاب کنید. گاهی اوقات انتخاب فرزندی از یک شی برای ما مطلوب است. برای مثال ممکن است ما به چند مورد از یک لیست احتیاج داشته باشیم، نه یک زیر مجموعه ای از آن لیست. به قطعه کد زیر که از صفحه کارگاهی این پست گرفته شده است دقت نمایید:
<ul> <li><a href="http://jquery.com">jQuery supports</a> <ul> <li><a href="css1">CSS1</a></li> <li><a href="css2">CSS2</a></li> <li><a href="css3">CSS3</a></li> <li>Basic XPath</li> </ul> </li> <li>jQuery also supports <ul> <li>Custom selectors</li> <li>Form selectors</li> </ul> </li> </ul>
راه حل مناسب برای انتخاب چنین حالتی استفاده از انتخاب فرزند می باشد که به این منظور Parent (والد) و Child (فرزند)، به وسیله یک کاراکتر < از یکدیگر جدا میشوند:
p > a
ul.myList > li > a
انتخاب کنندههای صفت نیز بسیار قدرتمند میباشند و ما را تواناتر میسازند، فرض کنید برای منظوری خاص قصد دارید به تمام لینکهای موجود در صفحه که به مکانی خارج از این وب سایت اشاره دارند، رفتاری را اضافه کنید (مثلا مانند همین سایت به کنار آنها یک آیکن اضافه نمایید) . فرض کنید این کد (کد موجود در مثال کارگاهی) را در صفحه خود دارید:
<li><a href="http://jquery.com">jQuery supports</a> <ul> <li><a href="css1">CSS1</a></li> <li><a href="css2">CSS2</a></li> <li><a href="css3">CSS3</a></li> <li>Basic XPath</li> </ul> </li>
a[href^=http://]
می توانید این کد را در صفحه کار گاهی تست کنید.
راهای دیگری برای استفاده از انتخاب کنندههای صفت وجود دارد.
form[method]
input[type=text]
دستور زیر مثالی دیگر برای بررسی یک مقدار بر اساس کاراکترهای نخست آن میباشد:
div[title^=my]
اما اگر بخواهیم تنها بر اساس کاراکترهای انتهایی انتخابی انجام دهیم، دستور مناسب چه خواهد بود؟ برای چنین منظوری مانند زیر عمل میکنیم:
a[href$=.pdf]
a[href*=jquery.com]
فراتر از خصوصیات، بعضی مواقع ما میخواهیم بررسی کنیم که آیا یک عنصر شامل عنصر دیگری هست یا خیر. در مثالهای قبلی فرض کنید ما میخواهیم بدانیم که آیا یک li شامل a هست یا خیر، jQuery با استفاده از انتخاب کنندههای Containerها این را پشتیبانی میکند:
li:has(a)
تصویر زیر انتخاب گرهایی را نشان میدهد که ما میتوانیم در jQuery استفاده نماییم.
انشالله در پستهای بعدی ادامه مباحث را بررسی خواهد شد.