استفاده از StructureMap به عنوان یک IoC Container
دریافت StructureMap
برای دریافت آن نیاز است دستور پاورشل ذیل را در کنسول نیوگت ویژوال استودیو فراخوانی کنید:
PM> Install-Package structuremap
آشنایی با ساختار برنامه
ابتدا یک برنامه کنسول را آغاز کرده و سپس یک Class library جدید را به نام Services نیز به آن اضافه کنید. در ادامه کلاسها و اینترفیسهای زیر را به Class library ایجاد شده، اضافه کنید. سپس از طریق نیوگت به روشی که گفته شد، StructureMap را به پروژه اصلی (ونه پروژه Class library) اضافه نمائید و Target framework آنرا نیز در حالت Full قرار دهید بجای حالت Client profile.
namespace DI03.Services { public interface IUsersService { string GetUserEmail(int userId); } } namespace DI03.Services { public interface IEmailsService { void SendEmailToUser(int userId, string subject, string body); } } using System; namespace DI03.Services { public class UsersService : IUsersService { public UsersService() { //هدف صرفا نمایش وهله سازی خودکار این وابستگی است Console.WriteLine("UsersService ctor."); } public string GetUserEmail(int userId) { //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه return "name@site.com"; } } } using System; namespace DI03.Services { public class EmailsService: IEmailsService { private readonly IUsersService _usersService; public EmailsService(IUsersService usersService) { Console.WriteLine("EmailsService ctor."); _usersService = usersService; } public void SendEmailToUser(int userId, string subject, string body) { var email = _usersService.GetUserEmail(userId); Console.WriteLine("SendEmailTo({0})", email); } } }
سرویس کاربران بر اساس آی دی یک کاربر، برای مثال از بانک اطلاعاتی ایمیل او را بازگشت میدهد. سرویس ارسال ایمیل، نیاز به ایمیل کاربری برای ارسال ایمیلی به او دارد. بنابراین وابستگی مورد نیاز خود را از طریق تزریق وابستگیها در سازنده کلاس و وهله سازی شده در خارج از آن (معکوس سازی کنترل)، دریافت میکند.
در سازندههای هر دو کلاس سرویس نیز از Console.WriteLine استفاده شدهاست تا زمان وهله سازی خودکار آنها را بتوان بهتر مشاهده کرد.
نکته مهمی که در اینجا وجود دارد، بیخبری لایه سرویس از وجود IoC Container مورد استفاده است.
استفاده از لایه سرویس و تزریق وابستگیها به کمک StructureMap
using DI03.Services; using StructureMap; namespace DI03 { class Program { static void Main(string[] args) { // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود ObjectFactory.Initialize(x => { x.For<IEmailsService>().Use<EmailsService>(); x.For<IUsersService>().Use<UsersService>(); }); //نمونهای از نحوه استفاده از تزریق وابستگیهای خودکار var emailsService = ObjectFactory.GetInstance<IEmailsService>(); emailsService.SendEmailToUser(userId: 1, subject: "Test", body: "Hello!"); } } }
به این ترتیب IoC Container ما زمانیکه قرار است object graph مربوط به IEmailsService درخواستی را تشکیل دهد، خواهد دانست ابتدا به سازندهی کلاس EmailsService میرسد. در اینجا برای وهله سازی این کلاس به صورت خودکار، باید وابستگیهای آنرا نیز وهله سازی کند. بنابراین بر اساس تنظیمات آغازین برنامه میداند که باید از کلاس UsersService برای تزریق خودکار وابستگیها در سازنده کلاس ارسال ایمیل استفاده نماید.
در این حالت اگر برنامه را اجرا کنیم، به خروجی زیر خواهیم رسید:
UsersService ctor. EmailsService ctor. SendEmailTo(name@site.com)
ابتداییترین مزیت استفاده از تزریق وابستگیها امکان تعویض آنها است؛ خصوصا در حین Unit testing. اگر کلاسی برای مثال قرار است با شبکه کار کند، میتوان پیاده سازی آنرا با یک نمونه اصطلاحا Fake جایگزین کرد و در این نمونه تنها نتیجهی کار را بازگشت داد. کلاسهای لایه سرویس ما تنها با اینترفیسها کار میکنند. این تنظیمات قابل تغییر اولیه IoC container مورد استفاده هستند که مشخص میکنند چه کلاسهایی باید در سازندههای کلاسها تزریق شوند.
تعیین طول عمر اشیاء در StructureMap
برای اینکه بتوان طول عمر اشیاء را بهتر توضیح داد، کلاس سرویس کاربران را به نحو زیر تغییر دهید:
using System; namespace DI03.Services { public class UsersService : IUsersService { private int _i; public UsersService() { //هدف صرفا نمایش وهله سازی خودکار این وابستگی است Console.WriteLine("UsersService ctor."); } public string GetUserEmail(int userId) { _i++; Console.WriteLine("i:{0}", _i); //برای مثال دریافت از بانک اطلاعاتی و بازگشت یک نمونه جهت آزمایش برنامه return "name@site.com"; } } }
//نمونهای از نحوه استفاده از تزریق وابستگیهای خودکار var emailsService1 = ObjectFactory.GetInstance<IEmailsService>(); emailsService1.SendEmailToUser(userId: 1, subject: "Test1", body: "Hello!"); var emailsService2 = ObjectFactory.GetInstance<IEmailsService>(); emailsService2.SendEmailToUser(userId: 1, subject: "Test2", body: "Hello!");
UsersService ctor. EmailsService ctor. i:1 SendEmailTo(name@site.com) UsersService ctor. EmailsService ctor. i:1 SendEmailTo(name@site.com)
اگر به هر دلیلی نیاز بود تا این رویه تغییر کند، میتوان بر روی طول عمر اشیاء تشکیل شده نیز تاثیر گذار بود. برای مثال تنظیمات آغازین برنامه را به نحو ذیل تغییر دهید:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود ObjectFactory.Initialize(x => { x.For<IEmailsService>().Use<EmailsService>(); x.For<IUsersService>().Singleton().Use<UsersService>(); });
UsersService ctor. EmailsService ctor. i:1 SendEmailTo(name@site.com) EmailsService ctor. i:2 SendEmailTo(name@site.com)
حالتهای دیگر تعیین طول عمر مطابق متدهای زیر هستند:
Singleton() HttpContextScoped() HybridHttpOrThreadLocalScoped()
در حالت ThreadLocal، به ازای هر Thread، وهلهای متفاوت در اختیار مصرف کننده قرار میگیرد.
حالت Hybrid ترکیبی است از حالتهای HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ میکند.
شاید بپرسید که کاربرد مثلا HttpContextScoped در کجا است؟
در یک برنامه وب نیاز است تا یک وهله از DbContext (مثلا Entity framework) را در اختیار کلاسهای مختلف لایه سرویس قرار داد. به این ترتیب چون هربار new Context صورت نمیگیرد، هربار هم اتصال جداگانهای به بانک اطلاعاتی باز نخواهد شد. نتیجه آن رسیدن به یک برنامه سریع، با سربار کم و همچنین کار کردن در یک تراکنش واحد است. چون هربار فراخوانی new Context به معنای ایجاد یک تراکنش جدید است.
همچنین در این برنامه وب قصد نداریم از حالت طول عمر Singleton استفاده کنیم، چون در این حالت یک وهله از Context در اختیار تمام کاربران سایت قرار خواهد گرفت (و DbContext به صورت Thread safe طراحی نشده است). نیاز است به ازای هر کاربر و به ازای طول عمر هر درخواست، تنها یکبار این وهله سازی صورت گیرد. بنابراین در این حالت استفاده از HttpContextScoped توصیه میشود. به این ترتیب در طول عمر کوتاه Object graphهای تشکیل شده، فقط یک وهله از DbContext ایجاد و استفاده خواهد شد که بسیار مقرون به صرفه است.
مزیت دیگر مشخص سازی طول عمر به نحو HttpContextScoped، امکان Dispose خودکار آن به صورت زیر است:
protected void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); }
تنظیمات خودکار اولیه در StructureMap
اگر نام اینترفیسهای شما فقط یک I در ابتدا بیشتر از نام کلاسهای متناظر با آنها دارد، مثلا مانند ITest و کلاس Test هستند؛ فقط کافی است از قراردادهای پیش فرض StructureMap برای اسکن یک یا چند اسمبلی استفاده کنیم:
// تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود ObjectFactory.Initialize(x => { //x.For<IEmailsService>().Use<EmailsService>(); //x.For<IUsersService>().Singleton().Use<UsersService>(); x.Scan(scan => { scan.AssemblyContainingType<IEmailsService>(); scan.WithDefaultConventions(); }); });
دریافت مثال قسمت جاری
DI03.zip
به روز شدهی این مثالها را بر اساس آخرین تغییرات وابستگیهای آنها از مخزن کد ذیل میتوانید دریافت کنید:
Dependency-Injection-Samples
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
تنظیمات امنیتی دسترسی به سرور RavenDB
حالت پیش فرض دسترسی به سرور RavenDB
اگر فایل Raven.Server.exe.config را در یک ویرایشگر متنی باز کنید، یک چنین تنظیماتی در آن قابل مشاهده هستند:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="Raven/Port" value="*"/> <add key="Raven/DataDir" value="~\Database\System"/> <add key="Raven/AnonymousAccess" value="Admin"/> </appSettings> <runtime> <loadFromRemoteSources enabled="true"/> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="Analyzers;Plugins"/> </assemblyBinding> </runtime> </configuration>
حالت پیش فرض دسترسی به RavenDB برای کاربران اعتبارسنجی نشده، حالت Get است (خواندن اطلاعات) و هیچگونه دسترسی تغییر اطلاعات آنرا ندارند (حالت Read only). اگر این کلید به All تنظیم شود، کلیه کاربران، قابلیت Read و Write را خواهند داشت. حالت None به این معنا است که تنها کاربران اعتبارسنجی شده میتوانند به دیتابیس دسترسی پیدا کنند.
اگر علاقمند هستید که مجوزهای یک کاربر متصل را مشاهده کنید، از فرمان ذیل استفاده نمائید:
var json = ((ServerClient) store.DatabaseCommands).CreateRequest("GET", "/debug/user-info").ReadResponseJson();
نکته بسیار مهم
اگر مجوز RavenDB را نخریده باشید، مقدار Admin تنها مقداری است که در اینجا میتوانید تنظیم کنید. به این معنا که کلیه کاربران، دسترسی Admin را به سرور خواهند داشت. (و بدیهی است فقط برای آزمایش سیستم مناسب است)
سعی در تنظیم حالت اعتبار سنجی زمانیکه از مجوز AGPL استفاده میکنید، با یک استثناء از طرف سرور متوقف خواهد شد.
Windows authentication
اعتبار سنجی پیش فرض مورد استفاده نیز Windows authentication است. به این معنا که تنها کاربری با دارا بودن اکانت معتبری بر روی سیستم و یا دومین ویندوزی، امکان کار با RavenDB را خواهد داشت. در این حالت کلیه کاربران دومین به سرور دسترسی خواهند داشت. اگر این حالت مطلوب شما نیست، میتوان از گروههای ویژه کاربران تعریف شده بر روی سیستم و یا بر روی دومین ویندوزی استفاده کرد.
این تنظیمات باید بر روی دیتابیس System صورت گیرند، در قسمت Settings و حالت Windows authentication :
اعتبارسنجی OAuth
شاید دسترسی به سرور RavenDB همیشه از طریق Windows authentication مطلوب نباشد. برای این حالت از روش اعتبارسنجی سفارشی خاصی به نام OAuth نیز پشتیبانی میشود. این حالت به صورت توکار در سرور RavenDB پیاده سازی شده است و یا میتوان با پیاده سازی اینترفیس IAuthenticateClient کنترل بیشتری را اعمال کرد. البته با دریافت افزونه Raven.Bundles.Authentication به یک نمونه پیاده سازی شده آن دسترسی خواهید داشت. پس از دریافت آن، فایل اسمبلی مربوطه را به درون پوشه افزونههای سرور کپی کنید تا آماده استفاده شود.
PM> Install-Package RavenDB.Bundles.Authentication -Pre
فایل کانفیگ سرور را برای افزودن سطر ذیل ویرایش کنید:
<add key="Raven/AuthenticationMode" value="OAuth"/>
var documentStore = new DocumentStore { ApiKey = "sample/ThisIsMySecret", Url = "http://localhost:8080/" };
اگر استاندارد 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
اولین کاری را که در اینجا انجام خواهیم داد، مراجعه به فایل 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(); }
- متد الحاقی 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()); }
افزودن میان افزار 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'))
\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 این حالت، جزو تنظیمات پیشفرض است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
- با نصب و اجرای 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 کلیک کنید.
CREATE PROCEDURE CreateNewAuthor @name nvarchar(50) AS BEGIN INSERT INTO Authors values(@name) END GO
Add-Migration StoredProcedureForCreateNewAuthor
public partial class StoredProcedureForCreateNewAuthor : Migration { protected override void Up(MigrationBuilder migrationBuilder) { } protected override void Down(MigrationBuilder migrationBuilder) { } }
protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.Sql(@"CREATE PROCEDURE CreateNewAuthor @name nvarchar(50) AS BEGIN INSERT INTO Authors values(@name) END GO"); }
protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.Sql("DROP PROCEDURE CreateNewAuthor"); }
سایت CodePlex و SVN
اگر سورس پروژه Google book downloader را بخواهید از طریق سایت CodePlex دریافت کنید چند صد مگابایت خواهد شد. علت هم این است که نویسنده پروژه تمام نگارشهای قبلی را نیز در این مکان نگهداری میکند و وب سایت CodePlex هم اجازهی انتخاب فقط trunk را جهت دریافت نمیدهد. یا همه را باید دریافت کنید یا هیچ.
راه میانبری برای دریافت سادهتر این پروژه نیز وجود دارد. مدتها است که امکان اتصال به این سایت از طریق کلاینتهای SVN مانند TortoiseSVN نیز فراهم شده است. فقط کافی است برای دریافت سورس کامل یک پروژه در CodePlex ، مسیر زیر را Checkout کرد:
برای مثال جهت دریافت فقط trunk این پروژه، میتوان مسیر زیر را checkout کرد:
سؤالات متداول
- میخواهم برنامهام را بر روی کامپیوتر مشتری بدون دردسر نصب کنم؛ با حداقل حجم و دردسر توزیع. به علاوه بدون نیاز به وصله پینه کردن اسمبلیهای تجاری دریافت شده.
پاسخ: PdfReport از چند اسمبلی دات نتی تشکیل شده است که نیاز به نصب خاصی ندارد. همچنین حجم فشرده شده آن نیز زیر 2 مگابایت است. بنابراین از جهت توزیع مشکل خاصی نخواهید داشت. همچنین کل این مجموعه سورس باز است و مشکلات متداول همراه با گزارش سازهای تجاری را به همراه ندارد.
- میخواهم فایل گزارش، به همراه برنامه و فایل exe آن و نه جدای از آن توزیع شود.
پاسخ: روش کار با PdfReport اصطلاحا code-first است. یعنی یک یا چند کلاس تهیه شده توسط شما، کار تهیه گزارش را انجام میدهند و تمام اینها کامپایل شده و به همراه فایل اجرایی یا اسمبلیهای خاصی که برای آن درنظر میگیرید، کامپایل و توزیع خواهند شد. همچنین با توجه به code-first بودن آن و عدم وابستگی به فناوری خاصی، این گزارشات در برنامههای وب و ویندوز نیز میتوانند بدون نیاز به تغییری در کدهای شما مورد استفاده قرار گیرند.
- برای کار با PdfReport از کجا باید شروع کرد؟
پاسخ: لطفا برچسب PdfReport را در سایت جاری دنبال نمائید. نحوه استفاده از قابلیتهای آن قدم به قدم توضیح داده شدهاند.
- میخواهم برای صرفه جویی در کاغذ چاپی، گزارش چند ستونهای را تهیه کنم.
پاسخ: لطفا مراجعه کنید به مثال ایجاد قالبهای سفارشی ستونها.
- میخواهم گزارشی را تولید کنم که حجم متن فیلدهای آن مشخص نیست. یکی ممکن است نصف صفحه باشد دیگری دو صفحه و یا بعضی تنها یک سطر.
پاسخ: در PdfReport ارتفاع هر سطر به صورت خودکار بر مبنای حجم وارد شده محاسبه و تنظیم میگردد. به عبارتی این تنظیم ثابت نیست و سبب حذف محتوای ارائه شده در آنها یا محو شدن ردیفهای دیگر نمیگردد.
- نیاز به گزارش سازی دارم که بدون مشکل با ORMها کار کند. برای مثال در حین کار با Entity framework مستقیما بتواند با اشیاء و لیستهای حاصل از آن کار کند.
پاسخ: یکی از انواع منابع داده تعریف شده در PdfReport جهت کار با ORMها طراحی شده است که مثالی از نحوه استفاده از آنرا در مثال EF Code first همراه با مجموعه مثالهای این کتابخانه میتوانید ملاحظه نمائید.
- آیا این کتابخانه میتواند از فایل سیستم (و نه صرفا بانک اطلاعاتی) هم تصاویر را دریافت و در گزارشات قرار دهد؟
پاسخ: بلی. لطفا به مثال ImageFilePath مراجعه نمائید.
- میخواهم یک گزارش ساز پویا در برنامه داشته باشم. فقط کوئری غیر مشخصی را به آن بدهم و حاصل آن یک گزارش باشد.
پاسخ: امکان تهیه گزارشهای پویا نیز در PdfReport پیش بینی شده است. توضیحات بیشتر همچنین این امکان در حین کار با ORMها نیز وجود دارد.
- میخواهم بدون استفاده از بانک اطلاعاتی نیز بتوانم گزارشی را تهیه کنم. برای مثال یک لیست جنریک تشکیل شده در حافظه دارم.
پاسخ: برای این منظور تنها کافی است از منبع داده صحیحی استفاده نمائید. برای اطلاعات بیشتر به مثال IList مراجعه کنید.
- میخواهم از بانکهای اطلاعاتی دیگری بجز SQL Server استفاده کنم.
پاسخ: میتوانید یک نمونه مثال استفاده از PdfReport را با بانک اطلاعاتی SQLite، در اینجا مشاهده کنید.
پیاده سازی جستجو با Elastic search در ASP.NET Core
اگر از ویندوز استفاده میکنید.
1) فایل را Unzip کنید.
2) در پوشه bin پروژه از طریق PowerShell دستور bin\elasticsearch.bat را اجرا کنید.
پروژه به صورت پیش فرض روی پورت http://localhost:9200 قابل دسترسی میباشد.
- اگر جهت ایندکس کردن مطالب و جستجو و یا لاگ زدن استفاده میکنید نیاز به لایسنس نیست .