var entityMethod = typeof (DbModelBuilder).GetMethod("Entity");
- چطور یک اپلیکیشن وب ASP.NET MVC 5 بسازید و آن را روی یک وب سایت Windows Azure منتشر کنید.
- چگونه از OAuth، OpenID و سیستم عضویت ASP.NET برای ایمن سازی اپلیکیشن خود استفاده کنید.
- چگونه از API جدید سیستم عضویت برای مدیریت اعضا و نقشها استفاده کنید.
- چگونه از یک دیتابیس SQL برای ذخیره دادهها در Windows Azure استفاده کنید.
توجه: برای تمام کردن این مقاله به یک حساب کاربری Windows Azure نیاز دارید، که بصورت رایگان میتوانید آن را بسازید. برای اطلاعات بیشتر به Windows Azure Free Trial مراجعه کنید.
در این مقاله:
- برپایی محیط توسعه (development environment)
- برپایی محیط Windows Azure
- ایجاد یک اپلیکیشن ASP.NET MVC 5
- توزیع اپلیکیشن روی Windows Azure
- افزودن یک دیتابیس به اپلیکیشن
- افزودن یک OAuth Provider
- استفاده از Membership API
- توزیع اپلیکیشن روی Windows Azure
- قدمهای بعدی
برپایی محیط توسعه
هنگامی که این مرحله با موفقیت به اتمام رسید، تمام ابزار لازم برای شروع به کار را در اختیار دارید.
برپایی محیط Windows Azure
وب سایت Windows Azure شما در یک محیط اشتراکی (shared) میزبانی میشود، و این بدین معنا است که وب سایتهای شما روی ماشینهای مجازی (virtual machines) اجرا میشوند که با مشتریان دیگر Windows Azure به اشتراک گذاشته شده اند. یک محیط میزبانی اشتراکی گزینه ای کم هزینه برای شروع کار با رایانشهای ابری است. اگر در آینده ترافیک وب سایت شما رشد چشم گیری داشته باشد، میتوانید اپلیکیشن خود را طوری توسعه دهید که به نیازهای جدید پاسخگو باشد و آن را روی یک ماشین مجازی اختصاصی (dedicated VMs) میزبانی کنید. اگر معماری پیچیدهتری نیاز دارید، میتوانید به یک سرویس Windows Azure Cloud مهاجرت کنید. سرویسهای ابری روی ماشینهای مجازی اختصاصی اجرا میشوند که شما میتوانید تنظیمات آنها را بر اساس نیازهای خود پیکربندی کنید.
- در پرتال مدیریتی Windows Azure روی Web Sites در قسمت چپ صفحه کلیک کنید، و گزینه New را برگزینید.
- روی Web Site و سپس Custom Create کلیک کنید.
- در مرحله Create Web Site در قسمت URL یک رشته وارد کنید که آدرسی منحصر بفرد برای اپلیکیشن شما خواهد بود. آدرس کامل وب سایت شما، ترکیبی از مقدار این فیلد و مقدار روبروی آن است.
- در لیست Database گزینه Create a free 20 MB SQL Database را انتخاب کنید.
- در لیست Region همان مقداری را انتخاب کنید که برای وب سایت تان انتخاب کرده اید. تنظیمات این قسمت مشخص میکند که ماشین مجازی (VM) شما در کدام مرکز داده (data center) خواهد بود.
- در قسمت DB Connection String Name مقدار پیش فرض DefaultConnection را بپذیرید.
- دکمه فلش پایین صفحه را کلیک کنید تا به مرحله بعد، یعنی مرحله Specify Database Settings بروید.
- در قسمت Name مقدار ContactDB را وارد کنید (تصویر زیر).
- در قسمت Server گزینه New SQL Database Server را انتخاب کنید. اگر قبلا دیتابیس ساخته اید میتوانید آن را از کنترل dropdown انتخاب کنید.
- مقدار قسمت Region را به همان مقداری که برای ایجاد وب سایت تان تنظیم کرده اید تغییر دهید.
- یک Login Name و Password مدیر (administrator) وارد کنید. اگر گزینه New SQL Database server را انتخاب کرده اید، چنین کاربری وجود ندارد و در واقع اطلاعات یک حساب کاربری جدید را وارد میکنید تا بعدا هنگام دسترسی به دیتابیس از آن استفاده کنید. اگر دیتابیس دیگری را از لیست انتخاب کرده باشید، اطلاعات یک حساب کاربری موجود از شما دریافت خواهد شد. در مثال این مقاله ما گزینه Advanced را رها میکنیم. همچنین در نظر داشته باشید که برای دیتابیسهای رایگان تنها از یک Collation میتوانید استفاده کنید.
دکمه تایید پایین صفحه را کلیک کنید تا مراحل تمام شود.
تصویر زیر استفاده از یک SQL Server و حساب کاربری موجود (existing) را نشان میدهد.
پرتال مدیریتی پس از اتمام مراحل، به صفحه وب سایتها باز میگردد. ستون Status نشان میدهد که سایت شما در حال ساخته شدن است. پس از مدتی (معمولا کمتر از یک دقیقه) این ستون نشان میدهد که سایت شما با موفقیت ایجاد شده. در منوی پیمایش سمت چپ، تعداد سایت هایی که در اکانت خود دارید در کنار آیکون Web Sites نمایش داده شده است، تعداد دیتابیسها نیز در کنار آیکون SQL Databases نمایش داده میشود.
یک اپلیکیشن ASP.NET MVC 5 بسازید
نوع پروژه را ASP.NET Web Application انتخاب کنید.
نکته: در تصویر بالا نام پروژه "MyExample" است اما حتما نام پروژه خود را به "ContactManager" تغییر دهید. قطعه کدهایی که در ادامه مقاله خواهید دید نام پروژه را ContactManager فرض میکنند.
در دیالوگ جدید ASP.NET نوع اپلیکیشن را MVC انتخاب کنید و دکمه Change Authentication را کلیک کنید.
گزینه پیش فرض Individual User Accounts را بپذیرید. برای اطلاعات بیشتر درباره متدهای دیگر احراز هویت به این لینک مراجعه کنید. دکمههای OK را کلیک کنید تا تمام مراحل تمام شوند.
تنظیم تیتر و پاورقی سایت
- فایل Layout.cshtml_ را باز کنید. دو نمونه از متن "My ASP.NET MVC Application" را با عبارت "Contact Manager" جایگزین کنید.
- عبارت "Application name" را هم با "CM Demo" جایگزین کنید.
اپلیکیشن را بصورت محلی اجرا کنید
اپلیکیشن شما فعلا آماده است و میتوانید آن را روی Windows Azure توزیع کنید. بعدا دیتابیس و دسترسی داده نیز اضافه خواهد شد.
اپلیکیشن را روی Windows Azure منتشر کنید
حال دیالوگ Import Publish Profile نمایش داده میشود.
یکی از متدهای زیر را استفاده کنید تا ویژوال استودیو بتواند به اکانت Windows Azure شما متصل شود.
- روی Sign In کلیک کنید تا با وارد کردن اطلاعات حساب کاربری وارد Windows Azure شوید.
- روی Manage subscriptions کلیک کنید تا یک management certificate نصب کنید، که دسترسی به حساب کاربری شما را ممکن میسازد.
در دیالوگ باکس Publish Web روی Publish کلیک کنید.
اپلیکیشن شما حالا در فضای ابری اجرا میشود. دفعه بعد که اپلیکیشن را منتشر کنید تنها فایلهای تغییر کرده (یا جدید) آپلود خواهند شد.
یک دیتابیس به اپلیکیشن اضافه کنید
کلاسهای مدل Contacts را اضافه کنید
نام کلاس را به Contact.cs تغییر دهید و دکمه Add را کلیک کنید.
کد فایل Contact.cs را با قطعه کد زیر مطابقت دهید.
using System.ComponentModel.DataAnnotations; using System.Globalization; namespace ContactManager.Models { public class Contact { public int ContactId { get; set; } public string Name { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } [DataType(DataType.EmailAddress)] public string Email { get; set; } } }
این کلاس موجودیت Contact را در دیتابیس معرفی میکند. داده هایی که میخواهیم برای هر رکورد ذخیره کنیم تعریف شده اند، بعلاوه یک فیلد Primary Key که دیتابیس به آن نیاز دارد.
یک کنترلر و نما برای دادهها اضافه کنید
در دیالوگ باکس Add Scaffold گزینه MVC 5 Controller with views, using EF را انتخاب کنید.
در دیالوگ Add Controller نام "CmController" را برای کنترلر وارد کنید. (تصویر زیر.)
در لیست Model گزینه (Contact (ContactManager.Models را انتخاب کنید.
در قسمت Data context class گزینه (ApplicationDbContext (ContactManager.Models را انتخاب کنید. این ApplicationDbContext هم برای اطلاعات سیستم عضویت و هم برای دادههای Contacts استفاده خواهد شد.
روی Add کلیک کنید. ویژوال استودیو بصورت خودکار با استفاده از Scaffolding متدها و Viewهای لازم برای عملیات CRUD را فراهم میکند، که همگی از مدل Contact استفاده میکنند.
فعالسازی مهاجرت ها، ایجاد دیتابیس، افزودن داده نمونه و یک راه انداز
در پنجره باز شده فرمان زیر را وارد کنید.
enable-migrations
فرمان enable-migrations یک پوشه با نام Migrations می سازد و فایلی با نام Configuration.cs را به آن اضافه میکند. با استفاده از این کلاس میتوانید دادههای اولیه دیتابیس را وارد کنید و مهاجرتها را نیز پیکربندی کنید.
در پنجره Package Manager Console فرمان زیر را وارد کنید.
add-migration Initial
فرمان add-migration initial فایلی با نام data_stamp> initial> ساخته و آن را در پوشه Migrations ذخیره میکند. در این مرحله دیتابیس شما ایجاد میشود. در این فرمان، مقدار initial اختیاری است و صرفا برای نامگذاری فایل مهاجرت استفاده شده. فایلهای جدید را میتوانید در Solution Explorer مشاهده کنید.
در کلاس Initial متد Up جدول Contacts را میسازد. و متد Down (هنگامی که میخواهید به وضعیت قبلی بازگردید) آن را drop میکند.
حال فایل Migrations/Configuration.cs را باز کنید. فضای نام زیر را اضافه کنید.
using ContactManager.Models;
حال متد Seed را با قطعه کد زیر جایگزین کنید.
protected override void Seed(ContactManager.Models.ApplicationDbContext context) { context.Contacts.AddOrUpdate(p => p.Name, new Contact { Name = "Debra Garcia", Address = "1234 Main St", City = "Redmond", State = "WA", Zip = "10999", Email = "debra@example.com", }, new Contact { Name = "Thorsten Weinrich", Address = "5678 1st Ave W", City = "Redmond", State = "WA", Zip = "10999", Email = "thorsten@example.com", }, new Contact { Name = "Yuhong Li", Address = "9012 State st", City = "Redmond", State = "WA", Zip = "10999", Email = "yuhong@example.com", }, new Contact { Name = "Jon Orton", Address = "3456 Maple St", City = "Redmond", State = "WA", Zip = "10999", Email = "jon@example.com", }, new Contact { Name = "Diliana Alexieva-Bosseva", Address = "7890 2nd Ave E", City = "Redmond", State = "WA", Zip = "10999", Email = "diliana@example.com", } ); }
این متد دیتابیس را Seed میکند، یعنی دادههای پیش فرض و اولیه دیتابیس را تعریف میکند. برای اطلاعات بیشتر به Seeding and Debugging Entity Framework (EF) DBs مراجعه کنید.
در پنجره Package Manager Console فرمان زیر را وارد کنید.
update-database
فرمان update-database مهاجرت نخست را اجرا میکند، که دیتابیس را میسازد. بصورت پیش فرض این یک دیتابیس SQL Server Express LocalDB است.
حال پروژه را با CTRL + F5 اجرا کنید.
همانطور که مشاهده میکنید، اپلیکیشن دادههای اولیه (Seed) را نمایش میدهد، و لینک هایی هم برای ویرایش، حذف و مشاهده جزئیات رکوردها فراهم میکند. میتوانید دادهها را مشاهده کنید، رکورد جدید ثبت کنید و یا دادههای قبلی را ویرایش و حذف کنید.
یک تامین کننده OAuth2 و OpenID اضافه کنید
استفاده از Membership API
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework;
bool AddUserAndRole(ContactManager.Models.ApplicationDbContext context) { IdentityResult ir; var rm = new RoleManager<IdentityRole> (new RoleStore<IdentityRole>(context)); ir = rm.Create(new IdentityRole("canEdit")); var um = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>(context)); var user = new ApplicationUser() { UserName = "user1", }; ir = um.Create(user, "Passw0rd1"); if (ir.Succeeded == false) return ir.Succeeded; ir = um.AddToRole(user.Id, "canEdit"); return ir.Succeeded; }
protected override void Seed(ContactManager.Models.ApplicationDbContext context) { AddUserAndRole(context); context.Contacts.AddOrUpdate(p => p.Name, // Code removed for brevity }
کدی موقتی برای تخصیص نقش canEdit به کاربران جدید Social Provider ها
await UserManager.AddToRoleAsync(user.Id, "CanEdit");
در ادامه مقاله اپلیکیشن خود را روی Windows Azure منتشر خواهید کرد و با استفاده از Google و تامین کنندگان دیگر وارد سایت میشوید. هر فردی که به آدرس سایت شما دسترسی داشته باشد، و یک حساب کاربری Google هم در اختیار داشته باشد میتواند در سایت شما ثبت نام کند و سپس دیتابیس را ویرایش کند. برای جلوگیری از دسترسی دیگران، میتوانید وب سایت خود را متوقف (stop) کنید.
در پنجره Package Manager Console فرمان زیر را وارد کنید.
Update-Database
فرمان را اجرا کنید تا متد Seed را فراخوانی کند. حال AddUserAndRole شما نیز اجرا میشود. تا این مرحله نقش canEdit ساخته شده و کاربر جدیدی با نام user1 ایجاد و به آن افزوده شده است.
محافظت از اپلیکیشن توسط SSL و خاصیت Authorize
در این قسمت شما با استفاده از خاصیت Authorize دسترسی به اکشن متدها را محدود میکنید. کاربران ناشناس (Anonymous) تنها قادر به مشاهده متد Index در کنترلر home خواهند بود. کاربرانی که ثبت نام کرده اند به متدهای Index و Details در کنترلر Cm و صفحات About و Contact نیز دسترسی خواهند داشت. همچنین دسترسی به متدهایی که دادهها را تغییر میدهند تنها برای کاربرانی وجود دارد که در نقش canEdit هستند.
خاصیت Authorize و RequireHttps را به اپلیکیشن اضافه کنید. یک راه دیگر افزودن این خاصیتها به تمام کنترلرها است، اما تجارب امنیتی توصیه میکند که این خاصیتها روی کل اپلیکیشن اعمال شوند. با افزودن این خاصیتها بصورت global تمام کنترلرها و اکشن متدهایی که میسازید بصورت خودکار محافظت خواهند شد، و دیگر لازم نیست بیاد داشته باشید کدام کنترلرها و متدها را باید ایمن کنید.
برای اطلاعات بیشتر به Securing your ASP.NET MVC App and the new AllowAnonymous Attribute مراجعه کنید.
فایل App_Start/FilterConfig.cs را باز کنید و متد RegisterGlobalFilters را با کد زیر مطابقت دهید.
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new System.Web.Mvc.AuthorizeAttribute()); filters.Add(new RequireHttpsAttribute()); }
خاصیت Authorize در کد بالا از دسترسی کاربران ناشناس به تمام متدهای اپلیکیشن جلوگیری میکند. شما برای اعطای دسترسی به متدهایی خاص از خاصیت AllowAnonymous استفاده خواهید کرد. در آخر خاصیت RequireHTTPS باعث میشود تا تمام دسترسیها به اپلیکیشن وب شما از طریق HTTPS صورت گیرد.
حالا خاصیت AllowAnonymous را به متد Index در کنترلر Home اضافه کنید. از این خاصیت برای اعطای دسترسی به تمامی کاربران سایت استفاده کنید. قسمتی از کد کنترلر Home را در زیر میبینید.
namespace ContactManager.Controllers { public class HomeController : Controller { [AllowAnonymous] public ActionResult Index() { return View(); }
یک جستجوی عمومی برای عبارت AllowAnonymous انجام دهید. همانطور که مشاهده میکنید این خاصیت توسط متدهای ورود و ثبت نام در کنترلر Account نیز استفاده شده است.
در کنترلر CmController خاصیت [("Authorize(Roles="canEdit] را به تمام متدهایی که با داده سر و کار دارند اضافه کنید، به غیر از متدهای Index و Details. قسمتی از کد کامل شده در زیر آمده است.
فعال سازی SSL برای پروژه
روی نام پروژه کلیک راست کنید و Properties را انتخاب کنید. در قسمت چپ گزینه Web را انتخاب کنید. حالا مقدار Project Url را به آدرسی که کپی کرده اید تغییر دهید. نهایتا تغییرات را ذخیره کنید و پنجره را ببندید.
حال پروژه را اجرا کنید. مرورگر شما باید یک پیام خطای اعتبارسنجی به شما بدهد. دلیلش این است که اپلیکیشن شما از یک Valid Certificate استفاده نمیکند. هنگامی که پروژه را روی Windows Azure منتشر کنید دیگر این پیغام را نخواهید دید. چرا که سرورهای مایکروسافت همگی لایسنسهای معتبری دارند. برای اپلیکیشن ما میتوانید روی Continue to this website را انتخاب کنید.
حال مرورگر پیش فرض شما باید صفحه Index از کنترلر home را به شما نمایش دهد.
اگر از یک نشست قبلی هنوز در سایت هستید (logged-in) روی لینک Log out کلیک کنید و از سایت خارج شوید.
روی لینکهای About و Contact کلیک کنید. باید به صفحه ورود به سایت هدایت شوید چرا که کاربران ناشناس اجازه دسترسی به این صفحات را ندارند.
روی لینک Register کلیک کنید و یک کاربر محلی با نام Joe بسازید. حال مطمئن شوید که این کاربر به صفحات Home, About و Contact دسترسی دارد.
روی لینک CM Demo کلیک کنید و مطمئن شوید که دادهها را مشاهده میکنید.
حال روی یکی از لینکهای ویرایش (Edit) کلیک کنید. این درخواست باید شما را به صفحه ورود به سایت هدایت کند، چرا که کاربران محلی جدید به نقش canEdit تعلق ندارند.
با کاربر user1 که قبلا ساختید وارد سایت شوید. حال به صفحه ویرایشی که قبلا درخواست کرده بودید هدایت میشوید.
اگر نتوانستید با این کاربر به سایت وارد شوید، کلمه عبور را از سورس کد کپی کنید و مجددا امتحان کنید. اگر همچنان نتوانستید به سایت وارد شوید، جدول AspNetUsers را بررسی کنید تا مطمئن شوید کاربر user1 ساخته شده است. این مراحل را در ادامه مقاله خواهید دید.
در آخر اطمینان حاصل کنید که میتوانید دادهها را تغییر دهید.
اپلیکیشن را روی Windows Azure منتشر کنید
در دیالوگ باز شده روی قسمت Settings کلیک کنید. روی File Publish Options کلیک کنید تا بتوانید Remote connection string را برای ApplicationDbContext و دیتابیس ContactDB انتخاب کنید.
اگر ویژوال استودیو را پس از ساخت Publish profile بسته و دوباره باز کرده اید، ممکن است رشته اتصال را در لیست موجود نبینید. در چنین صورتی، بجای ویرایش پروفایل انتشار، یک پروفایل جدید بسازید. درست مانند مراحلی که پیشتر دنبال کردید.
زیر قسمت ContactManagerContext گزینه Execute Code First Migrations را انتخاب کنید.
حال Publish را کلیک کنید تا اپلیکیشن شما منتشر شود. با کاربر user1 وارد سایت شوید و بررسی کنید که میتوانید دادهها را ویرایش کنید یا خیر.
حال از سایت خارج شوید و توسط یک اکانت Google یا Facebook وارد سایت شوید، که در این صورت نقش canEdit نیز به شما تعلق میگیرد.
برای جلوگیری از دسترسی دیگران، وب سایت را متوقف کنید
یک راه دیگر متوقف کردن وب سایت از طریق پرتال مدیریت Windows Azure است.
فراخوانی AddToRoleAsync را حذف و اپلیکیشن را منتشر و تست کنید
await UserManager.AddToRoleAsync(user.Id, "CanEdit");
دکمه Start Preview را فشار دهید. در این مرحله تنها فایل هایی که نیاز به بروز رسانی دارند آپلود خواهند شد.
وب سایت را راه اندازی کنید. سادهترین راه از طریق پرتال مدیریت Windows Azure است. توجه داشته باشید که تا هنگامی که وب سایت شما متوقف شده، نمیتوانید اپلیکیشن خود را منتشر کنید.
حال به ویژوال استودیو بازگردید و اپلیکیشن را منتشر کنید. اپلیکیشن Windows Azure شما باید در مرورگر پیش فرض تان باز شود. حال شما در حال مشاهده صفحه اصلی سایت بعنوان یک کاربر ناشناس هستید.
روی لینک About کلیک کنید، که شما را به صفحه ورود هدایت میکند.
روی لینک Register در صفحه ورود کلیک کنید و یک حساب کاربری محلی بسازید. از این حساب کاربری برای این استفاده میکنیم که ببینیم شما به صفحات فقط خواندنی (read-only) و نه صفحاتی که دادهها را تغییر میدهند دسترسی دارید یا خیر. بعدا در ادامه مقاله، دسترسی حسابهای کاربری محلی (local) را حذف میکنیم.
مطمئن شوید که به صفحات About و Contact دسترسی دارید.
لینک CM Demo را کلیک کنید تا به کنترلر CmController هدایت شوید.
روی یکی از لینکهای Edit کلیک کنید. این کار شما را به صفحه ورود به سایت هدایت میکند. در زیر قسمت User another service to log in یکی از گزینههای Google یا Facebook را انتخاب کنید و توسط حساب کاربری ای که قبلا ساختید وارد شوید.
حال بررسی کنید که امکان ویرایش اطلاعات را دارید یا خیر.
نکته: شما نمیتوانید در این اپلیکیشن از اکانت گوگل خود خارج شده، و با همان مرورگر با اکانت گوگل دیگری وارد اپلیکیشن شوید. اگر دارید از یک مرورگر استفاده میکنید، باید به سایت گوگل رفته و از آنجا خارج شوید. برای وارد شدن به اپلیکیشن توسط یک اکانت دیگر میتوانید از یک مرورگر دیگر استفاده کنید.
دیتابیس SQL Azure را بررسی کنید
توجه: اگر نمیتوانید گره SQL Databases را باز کنید و یا ContactDB را در ویژوال استودیو نمیبینید، باید مراحلی را طی کنید تا یک پورت یا یکسری پورت را به فایروال خود اضافه کنید. دقت داشته باشید که در صورت اضافه کردن Port Rangeها ممکن است چند دقیقه زمان نیاز باشد تا بتوانید به دیتابیس دسترسی پیدا کنید.
روی جدول AspNetUsers کلیک راست کرده و View Data را انتخاب کنید.
حالا روی AspNetUserRoles کلیک راست کنید و View Data را انتخاب کنید.
اگر شناسه کاربران (User ID) را بررسی کنید، مشاهده میکنید که تنها دو کاربر user1 و اکانت گوگل شما به نقش canEdit تعلق دارند.
Cannot open server login error
شما باید آدرس IP خود را به لیست آدرسهای مجاز (Allowed IPs) اضافه کنید. در پرتال مدیریتی Windows Azure در قسمت چپ صفحه، گزینه SQL Databases را انتخاب کنید.
دیتابیس مورد نظر را انتخاب کنید. حالا روی لینک Set up Windows Azure firewall rules for this IP address کلیک کنید.
هنگامی که با پیغام "?The current IP address xxx.xxx.xxx.xxx is not included in existing firewall rules. Do you want to update the firewall rules" مواجه شدید Yes را کلیک کنید. افزودن یک آدرس IP بدین روش معمولا کافی نیست و در فایروالهای سازمانی و بزرگ باید Range بیشتری را تعریف کنید.
مرحله بعد اضافه کردن محدوده آدرسهای مجاز است.
مجددا در پرتال مدیریتی Windows Azure روی SQL Databases کلیک کنید. سروری که دیتابیس شما را میزبانی میکند انتخاب کنید.
در بالای صفحه لینک Configure را کلیک کنید. حالا نام rule جدید، آدرس شروع و پایان را وارد کنید.
در پایین صفحه Save را کلیک کنید.
در آخر میتوانید توسط SSOX به دیتابیس خود متصل شوید. از منوی View گزینه SQL Server Object Explorer را انتخاب کنید. روی SQL Server کلیک راست کرده و Add SQL Server را انتخاب کنید.
در دیالوگ Connect to Server متد احراز هویت را به SQL Server Authentication تغییر دهید. این کار نام سرور و اطلاعات ورود پرتال Windows Azure را به شما میدهد.
در مرورگر خود به پرتال مدیریتی بروید و SQL Databases را انتخاب کنید. دیتابیس ContactDB را انتخاب کرده و روی View SQL Database connection strings کلیک کنید. در صفحه Connection Strings مقادیر Server و User ID را کپی کنید. حالا مقادیر را در دیالوگ مذکور در ویژوال استودیو بچسبانید. مقدار فیلد User ID در قسمت Login وارد میشود. در آخر هم کلمه عبوری که هنگام ساختن دیتابیس تنظیم کردید را وارد کنید.
حالا میتوانید با مراحلی که پیشتر توضیح داده شد به دیتابیس Contact DB مراجعه کنید.
افزودن کاربران به نقش canEdit با ویرایش جداول دیتابیس
حالا RoleId را کپی کنید و در ردیف جدید بچسبانید.
شناسه کاربر مورد نظر را از جدول AspNetUsers پیدا کنید و مقدار آن را در ردیف جدید کپی کنید. همین! کاربر جدید شما به نقش canEdit اضافه شد.
نکاتی درباره ثبت نام محلی (Local Registration)
- در کنترلر Account متدهای Register را ویرایش کنید و خاصیت AllowAnonymous را از آنها حذف کنید (هر دو متد GET و POST). این کار ثبت نام کاربران ناشناس و بدافزارها (bots) را غیر ممکن میکند.
- در پوشه Views/Shared فایل LoginPartial.cshtml_ را باز کنید و لینک Register را از آن حذف کنید.
- در فایل Views/Account/Login.cshtml نیز لینک Register را حذف کنید.
- اپلیکیشن را دوباره منتشر کنید.
قدمهای بعدی
در قسمت قبل به معرفی postgresql پرداختیم; در این قسمت قصد ایجاد و راه اندازی یک api با استفاده از دیتابیس postgresql و استفاده از تکنولوژیهای آن را با استفاده از docker داریم.
ابتدا با استفاده از دستور زیر یک پروژهی جدید asp.net core را ایجاد کنید:
dotnet new webapi --minimal -o YourDirectoryPath:\YourFolderName
سپس فایل docker-compose.yaml را به روت پروژه اضافه کنید که شامل کانفیگهای زیر میباشد:
version: '3.1' services: db: image: postgres container_name: db restart: always environment: POSTGRES_PASSWORD: postgres POSTGRES_USERNAME: postgres POSTGRES_DB: BloggingDb ports: - "5432:5432" volumes: - postgres_data:/data/db adminer: image: adminer restart: always ports: - 8080:8080 pgadmin4: image: dpage/pgadmin4 restart: always environment: PGADMIN_DEFAULT_EMAIL: pgadmin4@pgadmin.org PGADMIN_DEFAULT_PASSWORD: admin PGADMIN_CONFIG_SERVER_MODE: 'False' ports: - 5050:80 volumes: - pgadmin:/var/lib/pgadmin depends_on: - db volumes: postgres_data: pgadmin:
سپس با اجرای دستور زیر در روت پروژه، سرویسها را راه اندازی کنید:
docker compose up -d
معرفی سرویسهای استفاده شده در تنظیمات فایل بالا:
سرویس db :
نمونه ایمیج اصلی، volume، تنظیمات connection string در آن استفاده شده است.
سرویس adminer :
https://hub.docker.com/_/adminer /
Adminer - Database management in a single PHP file
یک برنامه تحت وب مدیریت پایگاه داده ساده میباشد که ویژگیها MySql را در کنار سرعت و امنیت ارائه میدهد و در آدرس http://localhost:8080 / اجرا خواهد شد.
سرویس pgadmin4 :
dpage/pgadmin4 - Docker Image | Docker Hub
در حال حاضر این برنامه محبوبترین برنامه مدیریت پایگاه داده میباشد که ویژگیهای پیشرفتهای را نیز پوشش میدهد و در آدرس http://localhost:5050 / اجرا خواهد شد.
اکنون نوبت نوشتن کدها میباشد.
- تنظیم connection string در فایل appsettings.json:
"ConnectionStrings": { "BloggingContext": "Username=postgres;Password=postgres;Server=localhost;Database=BloggingDb” }
- و همینطور پکیجهای زیر را به برنامه خود رفرنس دهید:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design
- مدلهای برنامه را در مسیر /Models ایجاد کنید:
namespace NpgsqlAPI.Models; public class Post { public int PostId { get; set; } public string Title { get; set; } = null!; public string Content { get; set; } = null!; public int BlogId { get; set; } public Blog Blog { get; set; } = null!; } namespace NpgsqlAPI.Models; public class Blog { public int BlogId { get; set; } public string? Url { get; set; } public List<Post>? Posts { get; set; } }
- سپس BloggingContext را در مسیر /Data ایجاد کنید:
using Microsoft.EntityFrameworkCore; using NpgsqlAPI.Models; namespace NpgsqlAPI.Data; public class BloggingContext : DbContext { public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { } public DbSet<Blog> Blogs => Set<Blog>(); public DbSet<Post
- سپس اینترفیس IBlogServices را در مسیر /Servicec/Blogs ایجاد کنید:
using NpgsqlAPI.Models; namespace NpgsqlAPI.Services.Blogs; public interface IBlogServices { Task<IEnumerable<Blog>> GetList(); Task<Blog?> Get(uint id); Task<uint> Add(Blog obj); Task AddRange(Blog[] obj); Task Update(Blog obj); Task UpdateRange(Blog[] obj); Task Remove(uint id); }
- و سپس پیاده سازی آن را در فایل BlogEFServices و در کنار اینترفیس آن قرار دهید:
using Microsoft.EntityFrameworkCore; using NpgsqlAPI.Data; using NpgsqlAPI.Models; namespace NpgsqlAPI.Services.Blogs; public sealed class BlogEFServices : IBlogServices { private readonly BloggingContext _context; public BlogEFServices(BloggingContext context) { _context = context; } public async Task<uint> Add(Blog obj) { await _context.Blogs.AddAsync(obj); return (uint)await SaveChangesAsync(); } public async Task AddRange(Blog[] obj) { await _context.Blogs.AddRangeAsync(obj); await SaveChangesAsync(); } public async Task<Blog?> Get(uint id) { return await _context.Blogs.FirstOrDefaultAsync(x=>x.BlogId == id); } public async Task<IEnumerable<Blog>> GetList() { return await _context.Blogs.ToListAsync(); } public async Task Remove(uint id) { var entity = await Get(id); _context.Blogs.Remove(entity!); await SaveChangesAsync(); } public async Task Update(Blog obj) { _context.Blogs.Update(obj); await SaveChangesAsync(); } public async Task UpdateRange(Blog[] obj) { _context.Blogs.UpdateRange(obj); await SaveChangesAsync(); } private async Task<int> SaveChangesAsync() { return await _context.SaveChangesAsync(); } }
- اکنون endpointهای api را در فایل program.cs ایجاد کنید:
using System.Data; using Microsoft.EntityFrameworkCore; using Npgsql; using NpgsqlAPI.Services.Blogs; using NpgsqlAPI.Data; using NpgsqlAPI.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); string connectionString = builder.Configuration.GetConnectionString("BloggingContext")!; builder.Services.AddDbContext<BloggingContext>(options => options.UseNpgsql(connectionString)); builder.Services.AddTransient<IDbConnection>(_ => new NpgsqlConnection(connectionString)); // builder.Services.AddScoped<IBlogServices, BlogDapperServices>(); // builder.Services.AddScoped<IBlogServices, BlogEFRawQueryServices>(); builder.Services.AddScoped<IBlogServices, BlogEFServices>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapGet("/blogs", async (IBlogServices service) => await service.GetList()) .WithName("GetBlogs") .WithOpenApi(); app.MapGet("/blogs/{id}", async (IBlogServices service, uint id) => await service.Get(id)) .WithName("GetBlog") .WithOpenApi(); app.MapPost("/blogs", async (IBlogServices service, Blog blog) => await service.Add(blog)) .WithName("AddBlog") .WithOpenApi(); app.MapDelete("/blogs/{id}", async (IBlogServices service, uint id) => await service.Remove(id)) .WithName("RemoveBlog") .WithOpenApi(); app.MapPut("/blogs", async (IBlogServices service, Blog blog) => await service.Update(blog)) .WithName("UpdateBlog") .WithOpenApi(); app.MapPut("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) => await service.UpdateRange(blogs)) .WithName("UpdateBulkBlog") .WithOpenApi(); app.MapPost("/blogs/Bulk", async (IBlogServices service, Blog[] blogs) => await service.AddRange(blogs)) .WithName("AddBulkBlog") .WithOpenApi(); app.Run();
تمامی کدهای برنامه تا به اینجا نوشته شدهاند. اکنون migration را پس از اطمینان از اجرا بودن داکر اجرا کنید
dotnet ef migrations add Init dotnet ef database update
و برنامه را اجرا و تست کنید.
- Create Database
- Delete Database
- Migrate Database
- Seed Database
services.AddParbad() .ConfigureDatabase(builder => { // Choose your database provider (SQL Server, MySql, Sqlite, etc.) builder.Use.... }) .ConfigureDatabaseInitializer(builder => { builder.UseInitializer(async context => { await context.Database.EnsureDeletedAsync(); // OR await context.Database.EnsureCreatedAsync(); // OR await context.Database.MigrateAsync(); }); });
services.AddParbad() .ConfigureDatabase(builder => { // Choose your Entity Framework provider (SQL Server, MySql, Sqlite, etc.) builder.Use.... }) .ConfigureDatabaseInitializer(builder => { builder.CreateDatabase(); // OR builder.DeleteAndCreateDatabase(); // OR builder.CreateAndMigrateDatabase(); });
services.AddParbad() .ConfigureDatabase(builder => { // SQL Server builder.UseSqlServer("Connection String", options => options.UseParbadMigrations()); }) .ConfigureDatabaseInitializer(builder => { builder.CreateAndMigrateDatabase(); });
builder.UseSqlServer("Connection String", options => options.MigrationsAssembly("Parbad"));
نمونه مثالها را همچنین میتوانید در صفحه GitHub پروژه مشاهده کنید.
در این بین اگر به قالب پیشفرض پروژههای MVC تولید شدهی توسط ASP.NET Core 2.1 نیز دقت کنید، پشتیبانی توکار از پیشنیازهای GDPR در آن لحاظ شدهاست؛ چه از لحاظ گوشزد کردن شرایط حریم خصوصی و پذیرش آن و چه از لحاظ «پاک کردن» و «پس گرفتن» اطلاعات شخصی.
قالب و کوکی پذیرش شرایط حریم خصوصی سایت (Cookie Consent)
اگر قالب پیشفرض یک پروژهی ASP.NET Core 2.1 را اجرا کنید، تصویر فوق را که در آن نوار پذیرش شرایط حریم خصوصی سایت در بالای صفحه درج شدهاست، مشاهده خواهید کرد.
قالب جدید نوار پذیرش شرایط حریم خصوصی در مسیر Views\Shared\_CookieConsentPartial.cshtml واقع شدهاست و در فایل layout برنامه توسط tag helper جدید Partial، رندر و نمایش داده میشود:
<partial name="_CookieConsentPartial" />
@using Microsoft.AspNetCore.Http.Features @{ var consentFeature = Context.Features.Get<ITrackingConsentFeature>(); var showBanner = !consentFeature?.CanTrack ?? false; var cookieString = consentFeature?.CreateConsentCookie(); }
الف) تنظیم نیاز به دریافت پذیرش
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; });
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... app.UseCookiePolicy();
علاوه بر Cookie Consent فوق که در یک قالب ابتدایی MVC نیز درج شدهاست، در قالب پروژههای ASP.NET Core Identity، دو گزینهی جدید دریافت اطلاعات شخصی و همچنین حذف اکانت (دادن حق فراموشی به کاربران) نیز پیشبینی شدهاست: PersonalData.cshtml
البته این صفحه جزو بستهی جدید Microsoft.AspNetCore.Identity.UI است که به همراه ASP.NET Core 2.1 ارائه میشود:
dotnet add package Microsoft.AspNetCore.Identity.UI --version 2.1.0-rc1-final
HKLM { %REGROOTBEGIN% 'DataProviders' { '{6085DDE2-2EE1-4768-82C3-5425D9B98DAD}' = s 'IBM DB2 Provider' { val 'DisplayName' = s 'Provider_DisplayName, IBM.DB2.Resources' val 'ShortDisplayName' = s 'Provider_ShortDisplayName, IBM.DB2.Resources' val 'Description' = s 'Provider_Description, IBM.DB2.Resources' val 'FactoryService'= s'{45E1413D-896C-4a2a-A75C-1CBCA51C80CB}' val 'Technology' = s '{6565551F-A496-45f3-AFFB-D1AECA082824}' val 'InvariantName' = s 'IBM.DB2' val 'PlatformVersion' = s '2.0' 'SupportedObjects' { 'IVsDataViewSupport' 'IVsDataObjectSupport' 'IVsDataConnectionUIControl' 'IVsDataConnectionProperties' 'IVsDataConnectionSupport' } } } 'Services' { '{45E1413D-896C-4a2a-A75C-1CBCA51C80CB}' = s '{7B7F1923-D8F9-430f-9FA7-7919677E5EAC}' { val 'Name' = 'IBM DB2 Provider Object Factory' } } 'Packages' { '{7B7F1923-D8F9-430f-9FA7-7919677E5EAC}' = 'DB2 Package' { val 'InProcServer32' = s 'mscoree.dll' val 'Class' = s 'IBM.DB2.DB2Package' val 'Codebase' = s '%MODULE%' 'SatelliteDll' { val 'Path' = s '%PATH%' val 'DllName' = s 'IBM.DB2UI.DLL' } } %REGROOTEND% }
اگر مایل به بازگشت و کار بر روی نسخه جدید Sql Server Compact 5 هستید اینجا در Visual Studio UserVoice رای دهید.
نصب و راه اندازی مقدماتی Full Text Search
بررسیهای مقدماتی
ابتدای کار نیاز است بررسی کنیم آیا افزونهی Full Text Search، به همراه SQL Server نصب شدهاست یا خیر. برای این منظور کوئری ذیل را اجرا کنید:
select SERVERPROPERTY('IsFullTextInstalled');
instance features -> database engine services -> Full Text
راه اندازی سرویس Full Text Search
پیش از ادامهی بحث، به کنسول سرویسهای ویندوز مراجعه کرده و مطمئن شوید که سرویس SQL Full-text Filter Daemon Launcher MSSQLSERVER نیز در حال اجرا است. در غیراینصورت با خطای ذیل مواجه خواهید شد:
SQL Server encountered error 0x80070422 while communicating with full-text filter daemon host (FDHost) process.
sp_fulltext_service 'restart_all_fdhosts'
چه نوع دادههایی را میتوان توسط FTS ایندکس کرد؟
با استفاده از امکانات FTS میتوان کلیه ستونهایی را که دارای نوعهای ذیل باشند، ایندکس کرد:
char, nchar, varchar, nvarchar, text, ntext, image, xml, varbinary(max)
همچنین FTS برای پردازش این فایلهای باینری و ایندکس کردن اطلاعات آنها، نیاز به افزونههایی به نام IFilters دارد. کار این فیلترها استخراج متن بدون فرمت، از فایلهای باینری مرتبط و ارائهی آنها به موتور FTS میباشد.
نصب فیلترهای مخصوص FTS آفیس
اگر علاقمند هستید که بدانید در حال حاضر چه تعداد فیلترهای FTS بر روی سیستم شما نصب شدهاست، کوئری ذیل را اجرا نمائید:
exec sys.sp_help_fulltext_system_components 'filter';
فیلترهای آفیس را جداگانه نیز میتوانید دریافت و نصب کنید (بدون نیاز به نصب کامل آفیس بر روی سرور):
این فیلترها تا نگارش 2013 آفیس را نیز پشتیبانی میکنند و آگر آپدیت ویندوز نیز روشن باشد، سرویس پک 2 آن را نیز دریافت خواهید کرد.
پس از اینکه فیلترها را نصب کردید، باید آنها را در وهلهی جاری SQL Server ثبت کرد:
exec sys.sp_fulltext_service 'load_os_resources', 1; EXEC sp_fulltext_service 'update_languages'; EXEC sp_fulltext_service 'restart_all_fdhosts';
select * from sys.fulltext_document_types;
فیلترهای فوق علاوه بر اینکه امکان FTS را بر روی کلیه فایلهای مجموعه آفیس میسر میکنند، امکان جستجو FTS را بر روی خواص ویژه اضافی آنها، مانند نام نویسنده، واژههای کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.
FTS چگونه کار میکند؟
زبانهای پشتیبانی شده توسط FTS را توسط کوئری ذیل میتوانید مشاهده کنید:
select lcid, name from sys.fulltext_languages order by name;
word-breakers تک تک کلمات را (که به آنها token نیز گفته میشود) تشخیص داده و سپس FTS آنها را با فرمتی فشرده شده، درون ایندکسهای مخصوص خود ذخیره میکند.کار stemmers تولید حالات inflectional (صرفی) یک کلمه بر اساس دستور زبانی مشخص است.
اهمیت آنالیز inflectional، در اینجا است که برای مثال اگر در متنی واژهی jumps وجود داشت و کاربر در حین جستجو، jumped را وارد کرد، FTS بر اساس دستور زبان مورد استفاده، پیشتر، حالات مختلف صرفی jump را ذخیره کردهاست و امکان انجام یک چنین کوئری پیشرفتهای را پیدا میکند.
نصب و فعال سازی Semantic Language Database
کار TFS تنها به خرد کردن واژهها و آنالیز صرفی آنها خلاصه نمیشود. در مرحلهی بعد، انجام Statistical semantic search میسر میشود. در اینجا SQL Server بر اساس آمار واژههای کلیدی استخراج شده، توانایی یافتن متونی مشابه و یا مرتبط را پیدا میکند. Semantic Search جزو تازههای SQL Server 2012 است.
برای اینکار نیاز است بانک اطلاعاتی Semantic language statistics نیز نصب شود. برای اطمینان از نصب بودن آن، کوئری ذیل را اجرا کنید:
select * from sys.fulltext_semantic_language_statistics_database;
x64\Setup\SemanticLanguageDatabase.msi x86\Setup\SemanticLanguageDatabase.msi
پس از آن نیاز است این بانک اطلاعاتی را Attach و همچنین ثبت کرد:
CREATE DATABASE semanticsdb ON ( FILENAME = 'D:\SQL_Data\SemanticLanguageDatabase\semanticsdb.mdf' ) LOG ON ( FILENAME = 'D:\SQL_Data\SemanticLanguageDatabase\semanticsdb_log.ldf' ) FOR ATTACH GO EXEC sp_fulltext_semantic_register_language_statistics_db @dbname = N'semanticsdb' GO
پس از مراحل فوق، اگر مجددا کوئری یاد شده بر روی sys.fulltext_semantic_language_statistics_database را اجرا کنید، یک سطر خروجی خواهد داشت.
- «با 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" },
پس از اطمینان حاصل کردن از نصب بستهی نیوگت 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 دومی را نیز با متد الحاقی Run مشاهده میکنید. به این نوع middlewareهای خاص، اصطلاحا terminal middleware میگویند. از این جهت که درخواستی را دریافت و یک response را تولید میکنند و کار همینجا خاتمه مییابد و زنجیرهی پردازشی middlewareها ادامه نخواهد یافت. در اینجا پارامتر context آن از نوع HttpContext است و باید دقت داشت، زمانیکه کار نوشتن در Response، در اینجا انجام شد، اگر پس از متد Run یک Middleware دیگر را ثبت کنید، هیچگاه اجرا نخواهد شد.
در این حالت اگر برنامه را اجرا کنید، خروجی ذیل را مشاهده خواهید کرد:
welcome page نیز یک terminal middleware است. به همین جهت middleware بعدی ثبت شدهی در اینجا یا همان متد Run، دیگر اجرا نخواهد شد.
در قسمتهای بعدی، تعداد بیشتری از Middlewareهای توکار ASP.NET Core 1.0 را بررسی خواهیم کرد.
افزودن سرویس httpService.js به برنامه
تا این قسمت، تمام اطلاعات نمایش داده شدهی در لیست فیلمها، از سرویس درون حافظهای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین میشوند. اکنون در ادامه میخواهیم این سرویسها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامهی نمایش لیست فیلمها نیز اضافه میکنیم (فایل آنرا از پروژهی قبلی کپی کرده و در اینجا paste میکنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگیهای خارجی react-toastify و axios است. به همین جهت برای افزودن آنها مراحل زیر را طی میکنیم:
- نصب کتابخانههای react-toastify و axios از طریق خط فرمان (با فشردن دکمههای ctrl+back-tick در VSCode):
> npm i axios --save > npm i react-toastify --save
import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css";
render() { return ( <React.Fragment> <ToastContainer />
دریافت اطلاعات لیست نمایش ژانرها از سرویس backend
با فراخوانی آدرس https://localhost:5001/api/Genres، میتوان لیست ژانرهای سینمایی تعریف شدهی در سرویسهای backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال میکنیم:
چون نمیخواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت میکند، جایگزین میکنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد میکنیم. سپس آنرا طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json"; import http from "./httpService"; export function getGenres() { return http.get(apiUrl + "/genres"); }
{ "apiUrl": "https://localhost:5001/api" }
پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
import { getGenres } from "../services/genreService";
Uncaught TypeError: Object is not a function or its return value is not iterable
async componentDidMount() { const { data } = await getGenres(); const genres = [{ _id: "", name: "All Genres" }, ...data]; this.setState({ movies: getMovies(), genres }); }
دریافت اطلاعات لیست فیلمها از سرویس backend
پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونهی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آنرا به صورت زیر تکمیل میکنیم:
import { apiUrl } from "../config.json"; import http from "./httpService"; const apiEndpoint = apiUrl + "/movies"; function movieUrl(id) { return `${apiEndpoint}/${id}`; } export function getMovies() { return http.get(apiEndpoint); } export function getMovie(movieId) { return http.get(movieUrl(movieId)); } export function saveMovie(movie) { if (movie.id) { return http.put(movieUrl(movie.id), movie); } return http.post(apiEndpoint, movie); } export function deleteMovie(movieId) { return http.delete(movieUrl(movieId)); }
ابتدا دو متد دریافت لیست فیلمها و حذف یک فیلم را که در این کامپوننت استفاده شدهاند، import میکنیم:
import { getMovies, deleteMovie } from "../services/movieService";
async componentDidMount() { const { data } = await getGenres(); const genres = [{ id: "", name: "All Genres" }, ...data]; const { data: movies } = await getMovies(); this.setState({ movies, genres }); }
handleDelete = async movie => { const originalMovies = this.state.movies; const movies = originalMovies.filter(m => m.id !== movie.id); this.setState({ movies }); try { await deleteMovie(movie.id); } catch (ex) { if (ex.response && ex.response.status === 404) { console.log(ex); toast.error("This movie has already been deleted."); } this.setState({ movies: originalMovies }); //undo changes } };
import { toast } from "react-toastify";
اتصال فرم ثبت و ویرایش یک فیلم به backend server
تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شدهی در صفحه، به صفحهی not-found هدایت میشویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService"; import { getMovie, saveMovie } from "../services/fakeMovieService";
import { getGenres } from "../services/genreService"; import { getMovie, saveMovie } from "../services/movieService";
async componentDidMount() { const { data: genres } = await getGenres(); this.setState({ genres }); const movieId = this.props.match.params.id; if (movieId === "new") return; const { data: movie } = await getMovie(movieId); if (!movie) return this.props.history.replace("/not-found"); this.setState({ data: this.mapToViewModel(movie) }); }
async populateGenres() { const { data: genres } = await getGenres(); this.setState({ genres }); } async populateMovie() { try { const movieId = this.props.match.params.id; if (movieId === "new") return; const { data: movie } = await getMovie(movieId); this.setState({ data: this.mapToViewModel(movie) }); } catch (ex) { if (ex.response && ex.response.status === 404) this.props.history.replace("/not-found"); } } async componentDidMount() { await this.populateGenres(); await this.populateMovie(); }
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت میداد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت میگرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.
مرحلهی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام میشود:
doSubmit = async () => { await saveMovie(this.state.data); this.props.history.push("/movies"); };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip