دریافت کتاب از Google books
آپلود شد:
http://rapidshare.com/files/328924251/GBDownloader-0.6.9.33532.rar
آشنایی با مدل برنامه نویسی TAP
سیستم ASP.NET Membership بهمراه ASP.NET 2.0 در سال 2005 معرفی شد، و از آن زمان تا بحال تغییرات زیادی در چگونگی مدیریت احزار هویت و اختیارات کاربران توسط اپلیکیشنهای وب بوجود آمده است. ASP.NET Identity نگاهی تازه است به آنچه که سیستم Membership هنگام تولید اپلیکیشنهای مدرن برای وب، موبایل و تبلت باید باشد.
پیش زمینه: سیستم عضویت در ASP.NET
- الگوی پایگاه داده آن برای SQL Server طراحی شده است، و قادر به تغییرش هم نیستید. میتوانید اطلاعات پروفایل را اضافه کنید، اما تمام دادهها در یک جدول دیگر ذخیره میشوند، که دسترسی به آنها نیز مشکلتر است، تنها راه دسترسی Profile Provider API خواهد بود.
- سیستم تامین کننده (Provider System) امکان تغییر منبع دادهها را به شما میدهد، مثلا میتوانید از بانکهای اطلاعاتی MySQL یا Oracle استفاده کنید. اما تمام سیستم بر اساس پیش فرض هایی طراحی شده است که تنها برای بانکهای اطلاعاتی relational درست هستند. میتوانید تامین کننده (Provider) ای بنویسید که دادههای سیستم عضویت را در منبعی به غیر از دیتابیسهای relational ذخیره میکند؛ مثلا Windows Azure Storage Tables. اما در این صورت باید مقادیر زیادی کد بنویسید. مقادیر زیادی هم System.NotImplementedException باید بنویسید، برای متد هایی که به دیتابیسهای NoSQL مربوط نیستند.
- از آنجایی که سیستم ورود/خروج سایت بر اساس مدل Forms Authentication کار میکند، سیستم عضویت نمیتواند از OWIN استفاده کند. OWIN شامل کامپوننت هایی برای احراز هویت است که شامل سرویسهای خارجی هم میشود (مانند Microsoft Accounts, Facebook, Google, Twitter). همچنین امکان ورود به سیستم توسط حسابهای کاربری سازمانی (Organizational Accounts) نیز وجود دارد مانند Active Directory و Windows Azure Active Directory. این کتابخانه از OAuth 2.0، JWT و CORS نیز پشتیبانی میکند.
- ذخیره دادههای سیستم عضویت در بانکهای اطلاعاتی non-relational مشکل است.
- نمی توانید از آن در کنار OWIN استفاده کنید.
- با فراهم کنندههای موجود ASP.NET Membership بخوبی کار نمیکند. توسعه پذیر هم نیست.
ASP.NET Universal Providers
ASP.NET Universal Providers برای ذخیره سازی اطلاعات سیستم عضویت در Windows Azure SQL Database توسعه پیدا کردند. با SQL Server Compact هم بخوبی کار میکنند. این تامین کنندهها بر اساس Entity Framework Code First ساخته شده بودند و بدین معنا بود که دادههای سیستم عضویت را میتوان در هر منبع داده ای که توسط EF پشتیبانی میشود ذخیره کرد. با انتشار این تامین کنندهها الگوی دیتابیس سیستم عضویت نیز بسیار سبکتر و بهتر شد. اما این سیستم بر پایه زیر ساخت ASP.NET Membership نوشته شده است، بنابراین محدودیتهای پیشین مانند محدودیتهای SqlMembershipProvider هنوز وجود دارند. به بیان دیگر، این سیستمها همچنان برای بانکهای اطلاعاتی relational طراحی شده اند، پس سفارشی سازی اطلاعات کاربران و پروفایلها هنوز مشکل است. در آخر آنکه این تامین کنندهها هنوز از مدل احراز هویت فرم استفاده میکنند.
ASP.NET Identity
- یک سیستم هویت واحد (One ASP.NET Identity system)
- سیستم ASP.NET Identity میتواند در تمام فریم ورکهای مشتق از ASP.NET استفاده شود. مانند ASP.NET MVC, Web Forms, Web Pages, Web API و SignalR
- از این سیستم میتوانید در تولید اپلیکیشنهای وب، موبایل، استور (Store) و یا اپلیکیشنهای ترکیبی استفاده کنید.
- سادگی تزریق دادههای پروفایل درباره کاربران
- روی الگوی دیتابیس برای اطلاعات کاربران و پروفایلها کنترل کامل دارید. مثلا میتوانید به سادگی یک فیلد، برای تاریخ تولد در نظر بگیرید که کاربران هنگام ثبت نام در سایت باید آن را وارد کنند.
- کنترل ذخیره سازی/واکشی اطلاعات
- بصورت پیش فرض ASP.NET Identity تمام اطلاعات کاربران را در یک دیتابیس ذخیره میکند. تمام مکانیزمهای دسترسی به دادهها توسط EF Code First کار میکنند.
- از آنجا که روی الگوی دیتابیس، کنترل کامل دارید، تغییر نام جداول و یا نوع داده فیلدهای کلیدی و غیره ساده است.
- استفاده از مکانیزمهای دیگر برای مدیریت دادههای آن ساده است، مانند SharePoint, Windows Azure Storage Table و دیتابیسهای NoSQL.
- تست پذیری
- ASP.NET Identity تست پذیری اپلیکیشن وب شما را بیشتر میکند. میتوانید برای تمام قسمت هایی که از ASP.NET Identity استفاده میکنند تست بنویسید.
- تامین کننده نقش (Role Provider)
- تامین کننده ای وجود دارد که به شما امکان محدود کردن سطوح دسترسی بر اساس نقوش را میدهد. بسادگی میتوانید نقشهای جدید مانند "Admin" بسازید و بخشهای مختلف اپلیکیشن خود را محدود کنید.
- Claims Based
- ASP.NET Identity از امکان احراز هویت بر اساس Claims نیز پشتیبانی میکند. در این مدل، هویت کاربر بر اساس دسته ای از اختیارات او شناسایی میشود. با استفاده از این روش توسعه دهندگان برای تعریف هویت کاربران، آزادی عمل بیشتری نسبت به مدل Roles دارند. مدل نقشها تنها یک مقدار منطقی (bool) است؛ یا عضو یک نقش هستید یا خیر، در حالیکه با استفاده از روش Claims میتوانید اطلاعات بسیار ریز و دقیقی از هویت کاربر در دست داشته باشید.
- تامین کنندگان اجتماعی
- به راحتی میتوانید از تامین کنندگان دیگری مانند Microsoft, Facebook, Twitter, Google و غیره استفاده کنید و اطلاعات مربوط به کاربران را در اپلیکیشن خود ذخیره کنید.
- Windows Azure Active Directory
- برای اطلاعات بیشتر به این لینک مراجعه کنید.
- یکپارچگی با OWIN
- ASP.NET Identity بر اساس OWIN توسعه پیدا کرده است، بنابراین از هر میزبانی که از OWIN پشتیبانی میکند میتوانید استفاده کنید. همچنین هیچ وابستگی ای به System.Web وجود ندارد. ASP.NET Identity یک فریم ورک کامل و مستقل برای OWIN است و میتواند در هر اپلیکیشنی که روی OWIN میزبانی شده استفاده شود.
- ASP.NET Identity از OWIN برای ورود/خروج کاربران در سایت استفاده میکند. این بدین معنا است که بجای استفاده از Forms Authentication برای تولید یک کوکی، از OWIN CookieAuthentication استفاده میشود.
- پکیج NuGet
- ASP.NET Identity در قالب یک بسته NuGet توزیع میشود. این بسته در قالب پروژههای ASP.NET MVC, Web Forms و Web API که با Visual Studio 2013 منتشر شدند گنجانده شده است.
- توزیع این فریم ورک در قالب یک بسته NuGet این امکان را به تیم ASP.NET میدهد تا امکانات جدیدی توسعه دهند، باگها را برطرف کنند و نتیجه را بصورت چابک به توسعه دهندگان عرضه کنند.
ASP.NET Identity در قالب پروژههای ASP.NET MVC, Web Forms, Web API و SPA که بهمراه Visual Studio 2013 منتشر شده اند استفاده میشود. در ادامه به اختصار خواهیم دید که چگونه ASP.NET Identity کار میکند.
- یک پروژه جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
-
پروژه ایجاد شده شامل سه بسته میشود که مربوط به ASP.NET Identity هستند:
- Microsoft.AspNet.Identity.EntityFramework این بسته شامل پیاده سازی ASP.NET Identity با Entity Framework میشود، که تمام دادههای مربوطه را در یک دیتابیس SQL Server ذخیره میکند.
- Microsoft.AspNet.Identity.Core این بسته محتوی تمام interfaceهای ASP.NET Identity است. با استفاده از این بسته میتوانید پیاده سازی دیگری از ASP.NET Identity بسازید که منبع داده متفاوتی را هدف قرار میدهد. مثلا Windows Azure Storage Table و دیتابیسهای NoSQL.
- Microsoft.AspNet.Identity.OWIN این بسته امکان استفاده از احراز هویت OWIN را در اپلیکیشنهای ASP.NET فراهم میکند. هنگام تولید کوکیها از OWIN Cookie Authentication استفاده خواهد شد.
هنگامیکه بر روی دکمهی Register کلیک شود، کنترلر Account، اکشن متد Register را فراخوانی میکند تا حساب کاربری جدیدی با استفاده از ASP.NET Identity API ساخته شود.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
اگر حساب کاربری با موفقیت ایجاد شود، کاربر توسط فراخوانی متد SignInAsync به سایت وارد میشود.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
private async Task SignInAsync(ApplicationUser user, bool isPersistent) { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); var identity = await UserManager.CreateIdentityAsync( user, DefaultAuthenticationTypes.ApplicationCookie); AuthenticationManager.SignIn( new AuthenticationProperties() { IsPersistent = isPersistent }, identity); }
از آنجا که ASP.NET Identity و OWIN Cookie Authentication هر دو Claims-based هستند، فریم ورک، انتظار آبجکتی از نوع ClaimsIdentity را خواهد داشت. این آبجکت تمامی اطلاعات لازم برای تشخیص هویت کاربر را در بر دارد. مثلا اینکه کاربر مورد نظر به چه نقش هایی تعلق دارد؟ و اطلاعاتی از این قبیل. در این مرحله میتوانید Claimهای بیشتری را به کاربر بیافزایید.
کلیک کردن روی لینک Log off در سایت، اکشن متد LogOff در کنترلر Account را اجرا میکند.
// POST: /Account/LogOff [HttpPost] [ValidateAntiForgeryToken] public ActionResult LogOff() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); }
همانطور که مشاهده میکنید برای ورود/خروج کاربران از AuthenticationManager استفاده میشود که متعلق به OWIN است. متد SignOut همتای متد FormsAuthentication.SignOut است.
کامپوننتهای ASP.NET Identity
تصویر زیر اجزای تشکیل دهنده ASP.NET Identity را نمایش میدهد. بسته هایی که با رنگ سبز نشان داده شده اند سیستم کلی ASP.NET Identity را میسازند. مابقی بستهها وابستگی هایی هستند که برای استفاده از ASP.NET Identity در اپلیکیشنهای ASP.NET لازم اند.
دو پکیج دیگر نیز وجود دارند که به آنها اشاره نشد:
- Microsoft.Security.Owin.Cookies این بسته امکان استفاده از مدل احراز هویت مبتنی بر کوکی (Cookie-based Authentication) را فراهم میکند. مدلی مانند سیستم ASP.NET Forms Authentication.
- EntityFramework که نیازی به معرفی ندارد.
مهاجرت از Membership به ASP.NET Identity
قدمهای بعدی
چرا در C# 9.0 تا این اندازه بر روی سادگی ایجاد اشیاء Immutable تمرکز شدهاست؟
به شیءای Immutable گفته میشود که پس از وهله سازی ابتدایی آن، وضعیت آن دیگر قابل تغییر نباشد. همچنین به کلاسی Immutable گفته میشود که تمام وهلههای ساخته شدهی از آن نیز Immutable باشند. نمونهی یک چنین شیءای را از نگارش 1 دات نت در حال استفاده هستیم: رشتهها. رشتهها در دات نت غیرقابل تغییر هستند و هرگونه تغییری بر روی آنها، سبب ایجاد یک رشتهی جدید (یک شیء جدید) میشود. نوع جدید record نیز به همین صورت عمل میکند.
مزایای وجود Immutability:
- اشیاء Immutable یا غیرقابل تغییر، thread-safe هستند که در نتیجه، برنامه نویسی همزمان و موازی را بسیار ساده میکنند؛ چون چندین thread میتوانند با شیءای کار کنند که دسترسی به آن، تنها read-only است.
- اشیاء Immutable از اثرات جانبی، مانند تغییرات آنها در متدهای مختلف در امان هستند. میتوانید آنها را به هر متدی ارسال کنید و مطمئن باشید که پس از پایان کار، این شیء تغییری نکردهاست.
- کار با اشیاء Immutable، امکان بهینه سازی حافظه را میسر میکنند. برای مثال NET runtime.، هش رشتههای تعریف شدهی در برنامه را در پشت صحنه نگهداری میکند تا مطمئن شود که تخصیص حافظهی اضافی، برای رشتههای تکراری صورت نمیگیرد. نمونهی دیگر آن نمایش حرف "a" در یک ادیتور یا نمایشگر است. زمانیکه یک شیء Immutable حاوی اطلاعات حرف "a"، ایجاد شود، به سادگی میتوان این تک وهله را جهت نمایش هزاران حرف "a" مورد استفادهی مجدد قرار داد، بدون اینکه نگران مصرف حافظهی بالای برنامه باشیم.
- کار با اشیاء Immutable به باگهای کمتری ختم میشود؛ چون همواره امکان تغییر حالت درونی یک شیء، توسط قسمتهای مختلف برنامه، میتواند به باگهای ناخواستهای منتهی شوند.
- Hash listها که در جهت بهبود کارآیی برنامهها بسیار مورد استفاده قرار میگیرند، بر اساس کلیدهایی Immutable قابل تشکیل هستند.
روش تعریف نوعهای جدید record
کلاس سادهی زیر را در نظر بگیرید:
public class User { public string Name { set; get; } }
public record User { public string Name { set; get; } }
var user = new User(); user.Name = "User 1";
روش تعریف دومی نیز در اینجا میسر است (به آن positional record هم میگویند):
public record User(string Name);
برای کار با رکورد دومی که تعریف کردیم باید سازندهی این record را مقدار دهی کرد:
var user = new User("User 1"); // Error: Init-only property or indexer 'User.Name' can only be assigned // in an object initializer, or on 'this' or 'base' in an instance constructor // or an 'init' accessor. [CS9Features]csharp(CS8852) user.Name = "User 1";
نوع جدید record چه اطلاعاتی را به صورت خودکار تولید میکند؟
روش دوم تعریف recordها اگر در نظر بگیریم:
public record User(string Name);
using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; using CS9Features; public class User : IEquatable<User> { protected virtual Type EqualityContract { [System.Runtime.CompilerServices.NullableContext(1)] [CompilerGenerated] get { return typeof(User); } } public string Name { get; set/*init*/; } public User(string Name) { this.Name = Name; base..ctor(); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("User"); stringBuilder.Append(" { "); if (PrintMembers(stringBuilder)) { stringBuilder.Append(" "); } stringBuilder.Append("}"); return stringBuilder.ToString(); } protected virtual bool PrintMembers(StringBuilder builder) { builder.Append("Name"); builder.Append(" = "); builder.Append((object?)Name); return true; } [System.Runtime.CompilerServices.NullableContext(2)] public static bool operator !=(User? r1, User? r2) { return !(r1 == r2); } [System.Runtime.CompilerServices.NullableContext(2)] public static bool operator ==(User? r1, User? r2) { return (object)r1 == r2 || (r1?.Equals(r2) ?? false); } public override int GetHashCode() { return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name); } public override bool Equals(object? obj) { return Equals(obj as User); } public virtual bool Equals(User? other) { return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name); } public virtual User <Clone>$() { return new User(this); } protected User(User original) { Name = original.Name; } public void Deconstruct(out string Name) { Name = this.Name; } }
- recordها هنوز هم در اصل همان classهای استاندارد #C هستند (یعنی در اصل reference type هستند).
- این کلاس به همراه یک سازنده و یک خاصیت init-only است (بر اساس تعاریف ما).
- متد ToString آن بازنویسی شدهاست تا اگر آنرا بر روی شیء حاصل، فراخوانی کردیم، به صورت خودکار نمایش زیبایی را از محتوای آن ارائه دهد.
- این کلاس از نوع <IEquatable<User است که امکان مقایسهی اشیاء record را به سادگی میسر میکند. برای این منظور متدهای GetHashCode و Equals آن به صورت خودکار بازنویسی و تکمیل شدهاند (یعنی مقایسهی آن شبیه به value-type است).
- این کلاس امکان clone کردن اطلاعات جاری را مهیا میکند.
- همچنین به همراه یک متد Deconstruct هم هست که جهت انتساب خواص تعریف شدهی در آن، به یک tuple مفید است.
بنابراین یک رکورد به همراه قابلیتهایی است که سالها در زبان #C وجود داشتهاند و شاید ما به سادگی حاضر به تشکیل و تکمیل آنها نمیشدیم؛ اما اکنون کامپایلر زحمت کدنویسی خودکار آنها را متقبل میشود!
ساخت یک وهلهی جدید از یک record با clone کردن آن
اگر به کدهای حاصل از دیکامپایل فوق دقت کنید، یک قسمت جدید clone هم با syntax خاصی در آن ظاهر شدهاست:
public virtual User <Clone>$() { return new User(this); }
public record User(string Name, int Age);
var user1 = new User("User 1", 21);
var user2 = user1 with { Age = 31 };
مقایسهی نوعهای record
در کدهای حاصل از دیکامپایل فوق، قسمت عمدهای از آن به تکمیل اینترفیس <IEquatable<User پرداخته شده بود. به همین جهت اکنون دو رکورد با مقادیر خواص یکسانی را ایجاد میکنیم:
var user1 = new User("User 1", 21); var user2 = new User("User 1", 21);
Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2)); Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
user1.Equals(user2) -> True user1 == user2 -> True
- زمانیکه عملگر == را بر روی شیء user1 و user2 اعمال میکنیم، اگر User، از نوع کلاس معمولی باشد، حاصل آن false خواهد بود؛ چون این دو، به یک مکان از حافظه اشاره نمیکنند، حتی با اینکه مقادیر خواص هر دو شیء یکی است.
- اما اگر به قطعه کد دیکامپایل شده دقت کنید، در یک رکورد که هر چند در اصل یک کلاس است، حتی عملگر == نیز بازنویسی شدهاست تا در پشت صحنه همان متد Equals را فراخوانی کند و این متد با توجه به پیاده سازی اینترفیس <IEquatable<User، اینبار دقیقا مقادیر خواص رکورد را یک به یک مقایسه کرده و نتیجهی حاصل را باز میگرداند:
public virtual bool Equals(User? other) { return (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name) && EqualityComparer<int>.Default.Equals(Age, other!.Age); }
یک نکته: بازنویسی عملگر == در SDK نگارش rc2 فعلی رخدادهاست و در نگارشهای قبلی preview، اینگونه نبود.
امکان ارثبری در recordها
دو رکورد زیر را در نظر بگیرید که اولی به همراه Name است و نمونهی مشتق شدهی از آن، خاصیت init-only سن را نیز به همراه دارد:
public record User { public string Name { get; init; } public User(string name) { Name = name; } } public record UserWithAge : User { public int Age { get; init; } public UserWithAge(string name, int age) : base(name) { Age = age; } }
var user1 = new User("User 1"); var user2 = new UserWithAge("User 1", 21); Console.WriteLine("user1.Equals(user2) -> {0}", user1.Equals(user2)); Console.WriteLine("user1 == user2 -> {0}", user1 == user2);
user1.Equals(user2) -> False user1 == user2 -> False
امکان تعریف ارثبری رکوردها به صورت زیر نیز وجود دارد و الزاما نیازی به روش تعریف کلاس مانند آنها، مانند مثال فوق نیست:
public abstract record Food(int Calories); public record Milk(int C, double FatPercentage) : Food(C);
رکوردها متد ToString را بازنویسی میکنند
در مثال قبلی اگر یک ToString را بر روی اشیاء تشکیل شده فراخوانی کنیم:
Console.WriteLine(user1.ToString()); Console.WriteLine(user2.ToString());
User { Name = User 1 } UserWithAge { Name = User 1, Age = 21 }
امکان استفادهی از Deconstruct در رکوردها
دو روش برای تعریف رکوردها وجود دارند؛ یکی شبیه به تعریف کلاسها است و دیگری تعریف یک سطری، که positional record نیز نامیده میشود:
public record Person(string Name, int Age);
public void Deconstruct(out string Name, out int Age) { Name = this.Name; Age = this.Age; }
var (name, age) = new Person("User 1", 21);
امکان استفادهی از نوعهای record در ASP.NET Core 5x
سیستم model binding در ASP.NET Core 5x، از نوعهای record نیز پشتیبانی میکند؛ یک مثال:
public record Person([Required] string Name, [Range(0, 150)] int Age); public class PersonController { public IActionResult Index() => View(); [HttpPost] public IActionResult Index(Person person) { // ... } }
پرسش و پاسخ
آیا نوعهای record به صورت value type معرفی میشوند؟
پاسخ: خیر. رکوردها در اصل reference type هستند؛ اما از لحاظ مقایسه، شبیه به value types عمل میکنند.
آیا میتوان در یک کلاس، خاصیتی از نوع رکورد را تعریف کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.
آیا میتوان در رکوردها، از struct و یا کلاسها جهت تعریف خواص استفاده کرد؟
پاسخ: بله. از این لحاظ محدودیتی وجود ندارد.
آیا میتوان از واژهی کلیدی with با کلاسها و یا structها استفاده کرد؟
پاسخ: خیر. این واژهی کلیدی در C# 9.0 مختص به رکوردها است.
آیا رکوردها به صورت پیشفرض Immutable هستند؟
پاسخ: اگر آنها را به صورت positional records تعریف کنید، بله. چون در این حالت خواص تشکیل شدهی توسط آنها از نوع init-only هستند. در غیراینصورت، میتوان خواص غیر init-only را نیز به تعریف رکوردها اضافه کرد.
froala/wysiwyg-editor 1.2 منتشر شد
تشکر از زحمات شما
برای آپلود فایل شبیه آپلود تصویر پارامتر همون HttpPostedFileBase file یا اینجا تفاوت داره؟
ممنون میشم راهنمایی کنید
فایل php هست متاسفانه سر در نیاوردم
در ادامه روشهای مختلف ارسال درخواستهای Ajax را توسط jQuery و همچنین معادلهای XMLHttpRequest و Fetch API آنها بررسی میکنیم.
ارسال درخواستهای GET
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد |
توسط jQuery |
fetch('/my/name').then(function(response) { if (response.ok) { return response.text(); } else { throw new Error(); } }).then( function success(name) { console.log('my name is ' + name); }, function failure() { console.error('Name request failed!'); } ); | var xhr = new XMLHttpRequest(); xhr.open('GET', '/my/name'); xhr.onload = function() { if (xhr.status >= 400) { console.error('Name request failed!'); } else { console.log('my name is ' + xhr.responseText); } }; xhr.onerror = function() { console.error('Name request failed!'); }; xhr.send(); | $.get('/my/name').then( function success(name) { console.log('my name is ' + name); }, function failure() { console.error('Name request failed!'); } ); |
jQuery برای پشتیبانی از درخواستهای Ajax، متد ویژهای را به نام ()ajax ارائه میکند که برای ارسال درخواستهایی از هر نوع، مانندGET، POST و غیره کاربرد دارد. همچنین برای بعضی از نوعها، متدهای کوتاهتری را مانند ()get و ()post نیز در اختیار برنامه نویس قرار میدهد. جاوا اسکریپت خالص و Web API مرورگرها نیز دو شیء XMLHttpRequest و fetch را برای ارسال درخواستهای غیرهمزمان، ارائه میکند. XMLHttpRequest در تمام مرورگرهای قدیمی و جدید پشتیبانی میشود، اما fetch API مدتی است که در غالب مرورگرهای امروزی در دسترس است. در جدول فوق روش ارسال درخواستهای Ajax از نوع GET را توسط این سه روش مشاهده میکنید.
در این مثالها درخواستی به آدرس my/name سمت سرور ارسال شده و انتظار میرود که یک plaintext حاوی متن کاربر بازگشت داده شود که در نهایت در console لاگ میشود.
- در حالت استفادهی از jQuery در صورت بازگشت موفقیت آمیز پاسخی از طرف سرور، متد success و در غیراینصورت متد failure اجرا میشود. باید درنظر داشت که متد ajax جیکوئری، چیزی بیشتر از یک محصور کنندهی اشیاء XMLHttpRequest نیست.
- در حالت کار با XMLHttpRequest باید اندکی بیشتر تایپ کرد؛ اما اصول کار یکی است. در اینجا onload زمانی فراخوانی میشود که پاسخی از سرور دریافت شده و عملیات خاتمه یافتهاست؛ هرچند این پاسخ میتواند یک خطا نیز باشد. به همین جهت باید status آنرا بررسی کرد. اگر رخداد onerror فراخوانی شد، یعنی درخواست، در سطوح بسیار پایین آن مانند بروز یک خطای CORS با شکست مواجه شدهاست.
همانطور که مشاهده میکنید در حالت کار با XMLHttpRequest جاوا اسکریپت از اشیاء Promise پشتیبانی نمیکند که این کمبود با معرفی fetch API برطرف شدهاست که نمونهای از آنرا با متد then متصل به fetch مشاهده میکنید؛ دقیقا مشابه متد ajax جیکوئری که آن نیز یک Promise را بازگشت میدهد.
تفاوت Promise جیکوئری با fetch API در این است که جیکوئری در صورتیکه یک status code بیانگر خطا را دریافت کند، قسمت failure را اجرا میکند؛ اما fetch API مانند اشیاء XMLHttpRequest تنها در صورت بروز خطاهای سطح پایین درخواست، این متد را فراخوانی خواهد کرد. هرچند اگر در اینجا response.ok نبود، میتوان با صدور یک استثناء به رفتاری شبیه به jQuery رسید و قسمت then failure را به صورت خودکار اجرا کرد.
ارسال درخواستهای Ajax از نوع POST ، PUT و DELETE
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('/user/name', { method: 'POST', body: 'some data' }); | var xhr = new XMLHttpRequest(); xhr.open('POST', '/user/name'); xhr.send('some data'); | $.ajax({ method: 'POST', url: '/user/name', contentType: 'text/plain', data: 'some data' }); |
اگر از شیء XMLHttpRequest استفاده شود، Content-Type آن به صورت پیشفرض به text/plain تنظیم شدهاست. در اینجا بدنهی درخواست به متد send ارسال میشود.
متد fetch نیز همانند شیء XMLHttpRequest دارای Content-Type پیشفرض از نوع text/plain است. در اینجا بدنهی درخواست به خاصیت body انتساب داده میشود.
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('/user/1', { method: 'PUT', body: //record including new mobile number }); | var xhr = new XMLHttpRequest(); xhr.open('PUT', '/user/1'); xhr.send(/* record including new data */); | $.ajax({ method: 'PUT', url: '/user/1', contentType: 'text/plain', data: //record including new data }); |
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('/user/1', {method: 'DELETE'}); | var xhr = new XMLHttpRequest(); xhr.open('DELETE', '/user/1'); xhr.send(); | $.ajax('/user/1', {method: 'DELETE'}); |
مدیریت Encoding درخواستهای Ajax
در مثالهای قبل، اطلاعاتی از نوع text/plain را به سمت سرور ارسال کردیم که به آن encoding type نیز گفته میشود. برای تکمیل بحث میتوان حالتهای دیگری مانند application/x-www-form-urlencoded، application/json و یا multipart/form-data را که در برنامههای کاربردی زیاد مورد استفاده قرار میگیرند، بررسی کرد.
کار با URL Encoding
عموما URL encoding در دو قسمت آدرس درخواستی به سرور و یا حتی بدنهی درخواست ارسالی تنظیم میشود. MIME type آن نیز application/x-www-form-urlencoded است و اطلاعات آن شامل یکسری جفت کلید/مقدار است. برای متدهای ارسال از نوع GET و DELETE، اطلاعات آن در انتهای آدرس درخواستی و برای سایر حالات در بدنهی درخواست ذکر میشوند.
در جیکوئری با استفاده از متد param آن میتوان یک شیء جاوا اسکریپتی را به معادل URL-encoded string آنها تبدیل کرد:
$.param({ key1: 'some value', 'key 2': 'another value' });
key1=some+value&key+2=another+value
$.ajax({ method: 'POST', url: '/user', data: { name: 'VahidN', address: 'Address 1', phone: '555-555-5555' } });
برخلاف جیکوئری در حین کار با روشهای جاوا اسکریپتی خالص این encoding باید به صورت دستی و صریحی انجام شود. برای این منظور دو متد استاندارد encodeURI و encodeURIComponent در جاوا اسکریپت مورد استفاده قرار میگیرند. هدف متد encodeURI اعمال آن بر روی یک URL کامل است و یا کلید/مقدارهای جدا شدهی توسط & مانند first=Vahid&last=N. اما متد encodeURIComponent صرفا جهت اعمال بر روی یک تک مقدار طراحی شدهاست.
به این ترتیب معادل جاوا اسکریپتی قطعه کد جیکوئری فوق به صورت زیر است:
var xhr = new XMLHttpRequest(); var data = encodeURI('name=VahidN&address=Address 1&phone=555-555-5555'); xhr.open('POST', '/user'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(data);
روش انجام اینکار توسط fetch API به صورت زیر است:
var data = encodeURI('name=VahidN&address=Address 1&phone=555-555-5555'); fetch('/user', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: data });
function param(object) { var encodedString = ''; for (var prop in object) { if (object.hasOwnProperty(prop)) { if (encodedString.length > 0) { encodedString += '&'; } encodedString += encodeURI(prop + '=' + object[prop]); } } return encodedString; }
کار با JSON Encoding
در عمل JSON نمایش رشتهای یک شیء جاوا اسکریپتی است و هدف آن سهولت نقل و انتقالات این اشیاء به سرور و برعکس است. برخلاف حالت application/x-www-form-urlencoded که اطلاعات آن مسطح است، حالت application/json امکان ارسال اطلاعات سلسله مراتبی را نیز میسر میکند (مانند مثال زیر که phone آن دیگر مسطح نیست و خود آن نیز یک شیء جاوا اسکریپتی است).
در جیکوئری برای ارسال اشیاء جاوا اسکریپتی JSON Encoded به سمت سرور از روش زیر استفاده میشود:
$.ajax({ method: 'POST', url: '/user', contentType: 'application/json', data: JSON.stringify({ name: 'VahidN', address: 'Address 1', phone: { home: '555-555-5555', mobile: '444-444-4444' } }); });
پیاده سازی همین مثال با جاوا اسکریپت خالص و XMLHttpRequest استاندارد به صورت زیر است:
var xhr = new XMLHttpRequest(); var data = JSON.stringify({ name: 'VahidN', address: 'Address 1', phone: { home: '555-555-5555', mobile: '444-444-4444' } }); xhr.open('POST', '/user'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(data);
در این حالت اگر خروجی سرور نیز JSON باشد، روش دریافت و پردازش آن به صورت زیر است:
var xhr = new XMLHttpRequest(); xhr.open('GET', '/user/1'); xhr.onload = function() { var user = JSON.parse(xhr.responseText); // do something with this user JavaScript object }; xhr.send();
اگر از مرورگر IE صرفنظر کنیم، تمام مرورگرهای دیگر دارای خاصیتی به نام xhr.response نیز هستند که نیاز به تبدیل و Parse دستی رشتهی دریافتی را حذف میکند؛ از این جهت که این خاصیت حاوی شیء جاوا اسکریپتی معادل بدنهی response دریافتی از سمت سرور است. البته با این شرط که سرور، Content-Type مساوی application/json را برای response تنظیم کرده باشد.
و روش انجام این عملیات توسط fetch API به صورت زیر است:
fetch('/user', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ name: 'VahidN', address: 'Address 1', phone: { home: '555-555-5555', mobile: '444-444-4444' } }); });
و یا اگر بخواهیم اطلاعات JSON دریافتی از سمت سرور را در اینجا پردازش کنیم، روش کار به صورت زیر است:
fetch('/user/1').then(function(response) { return response.json(); }).then(function(userRecord) { // do something with this user JavaScript object });
همین مثال را اگر بخواهیم توسط ECMAScript 2016 و Arrow functions آن بازنویسی کنیم، به قطعه کد زیر میرسیم:
fetch('/user/1') .then(response => response.json()) // Transform the data into json .then(userRecord => { // do something with this userRecord object });
کار با Multipart Encoding
نوع دیگری از encoding که بیشتر با فرمهای HTML بکار میرود، multipart/form-data نام دارد:
<form action="my/server" method="POST" enctype="multipart/form-data"> <label>First Name: <input name="first"> </label> <label>Last Name: <input name="last"> </label> <button>Submit</button> </form>
-----------------------------1686536745986416462127721994 Content-Disposition: form-data; name="first" Vahid -----------------------------1686536745986416462127721994 Content-Disposition: form-data; name="last" N -----------------------------1686536745986416462127721994--
روش ارسال اطلاعات با این نوع encoding خاص به سمت سرور توسط متد ajax جیکوئری به صورت زیر است:
var formData = new FormData(); formData.append('name', 'VahidN'); formData.append('address', 'Address 1'); formData.append('phone', '555-555-5555'); $.ajax({ method: 'POST', url: '/user', contentType: false, processData: false, data: formData });
در اینجا ذکر processData: false ضروری است. در غیراینصورت jQuery این اطلاعات را به یک URL-encoded string تبدیل میکند. همچنین با اعلام contentType: false، جیکوئری در کار مرورگر دخالت نمیکند. از این جهت که هدر ویژهی این نوع درخواستها توسط خود مرورگر تنظیم میشود و برای مثال یک چنین شکلی را دارد:
multipart/form-data; boundary=—————————1686536745986416462127721994
روش انجام اینکار با XMLHttpRequest و همچنین fetch API به صورت زیر است:
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد |
var formData = new FormData(); formData.append('name', 'VahidN'); formData.append('address', 'Address 1'); formData.append('phone', '555-555-5555'); fetch('/user', { method: 'POST', body: formData }); | var formData = new FormData(), xhr = new XMLHttpRequest(); formData.append('name', 'VahidN'); formData.append('address', 'Address 1'); formData.append('phone', '555-555-5555'); xhr.open('POST', '/user'); xhr.send(formData); |
آپلود فایلها توسط درخواستهای Ajax ایی
تنها راه آپلود فایلها در مرورگرهای قدیمی که شامل IE 9.0 هم میشود، تعریف المان <"input type="file> در داخل المان <form> و سپس submit مستقیم آن فرم است. برای رفع این مشکل در مرورگرهای پس از IE 9.0 و پشتیبانی از Ajax، جهت آپلود فایلها، استاندارد XMLHttpRequest Level 2 معرفی شدهاست. در این حالت اگر المان <input type=file> در صفحه وجود داشته باشد، روش ارسال Ajax ایی آن به سمت سرور به صورت زیر است:
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
var file = document.querySelector( 'INPUT[type="file"]').files[0]; fetch('/uploads', { method: 'POST', body: file }); } | var file = document.querySelector( 'INPUT[type="file"]').files[0], xhr = new XMLHttpRequest(); xhr.open('POST', '/uploads'); xhr.send(file); | var file = $('INPUT[type="file"]')[0].files[0]; $.ajax({ method: 'POST', url: '/uploads', contentType: false, processData: false, data: file }); |
یک نکته: اگر نیاز به آپلود بیش از یک فایل را داشتید و همچنین در اینجا نیاز به اطلاعات دیگری مانند سایر فیلدهای فرم نیز وجود داشت، از همان روش تعریف new FormData و افزودن اطلاعات مورد نیاز به آن استفاده کنید. امکان افزودن شیء file نیز به FormData پیش بینی شدهاست.
دانلود فایلها توسط درخواستهای Ajax ایی
پیشتر در حین بررسی JSON encoding توسط fetch API از متد ()json برای تبدیل اطلاعات دریافتی از سرور به json و بازگشت آن به صورت یک Promise استفاده کردیم:
fetch(url) .then((resp) => resp.json()) // Transform the data into json .then(function(data) { // use data object }) })
- ()clone یک کپی از response را تهیه میکند.
- ()redirect یک response جدید را با URL دیگری ایجاد میکند.
- ()arrayBuffer یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء ArrayBuffer تبدیل میکند.
- ()formData یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء FormData تبدیل میکند.
- ()blob یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء Blob تبدیل میکند.
- ()text یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به string تبدیل میکند.
- ()json یک Promise را بازگشت میدهد که پس از پایان درخواست، response را به یک شیء جاوا اسکریپتی تبدیل میکند.
در اینجا متدی که میتواند برای تبدیل یک byte array بازگشتی از سرور به فایل قابل دریافت در سمت کلاینت مورد استفاده قرار گیرد، متد blob است:
function downloadBlob() { fetch('/Home/InMemoryReport') .then(function(response) { return response.blob(); }) .then(function(xlsxBlob) { var a = document.createElement("a"); document.body.appendChild(a); a.style = "display: none"; let url = window.URL.createObjectURL(xlsxBlob); a.href = url; a.download = "report.xlsx"; a.click(); window.URL.revokeObjectURL(url); }); }
ارسال درخواستهای Ajax به دومینهای دیگر (CORS)
گاهی از اوقات نیاز است اطلاعاتی را توسط درخواستهای Ajax، به سروری دیگر در دومینی دیگر ارسال و یا دریافت کرد. هرچند انجام اینکار به صورت مستقیم و خارج از مرورگر بدون مشکل قابل انجام است، اما مرورگرها برای درخواستهای جاوا اسکریپتی محدودیت «same-origin policy» را اعمال میکنند. به این معنا که XMLHttpRequest بین دومینها به صورت پیشفرض ممنوع است. برای ارسال درخواستهای مجاز و از پیش مشخص شدهی Ajax بین دومینها، تاکنون دو روش پیش بینی شدهاست:
الف) روش JSONP
«same-origin policy» از شروع ارسال درخواستی به خارج از دومین جاری، جلوگیری میکند. هرچند این مورد به درخواستهای XMLHttpRequest اعمال میشود، اما در مورد المانهایی از نوع <a>، <img> و <script> صادق نیست و آنها محدود به این سیاست امنیتی نیستند. روش «JavaScript Object Notation with Padding» و یا به اختصار JSONP از یکی از همین استثناءها جهت ارسال درخواستهایی به سایر دومینها استفاده میکند. البته نام این روش کمی غلط انداز است؛ از این جهت که در این فرآیند اصلا JSON ایی مورد استفاده قرار نمیگیرد؛ خروجی سرور در این حالت یک تابع جاوا اسکریپتی است و نه JSON.
روش انجام این نوع درخواستها را توسط جیکوئری در ذیل مشاهده میکنید:
$.ajax('http://jsonp-aware-endpoint.com/user/1', { jsonp: 'callback', dataType: 'jsonp' }).then(function(response) { // handle user info from server });
انجام اینکار بدون jQuery و در حقیقت کاری که jQuery در پشت صحنه برای ایجاد تگ script انجام میدهد، چنین چیزی است:
window.myJsonpCallback = function(data) { // handle user info from server }; var scriptEl = document.createElement('script'); scriptEl.setAttribute('src', 'http://jsonp-aware-endpoint.com/user/1?callback=myJsonpCallback'); document.body.appendChild(scriptEl);
ب) روش CORS
CORS و یا Cross Origin Resource Sharing روش مدرن و پذیرفته شدهی ارسال درخواستهای Ajax در بین دومینها است و دارای دو نوع ساده و غیرساده است. نوع سادهی آن به همراه هدر مخصوص Origin است که جهت بیان دومین ارسال کنندهی درخواست بکار میرود و تنها از encodingهای “text/plain” و “application/x-www-form-urlencoded” پشتیبانی میکند. نوع غیرسادهی آن که این روزها بیشتر بکار میرود، از نوع «preflight» است. Preflight در اینجا به این معنا است که زمانیکه درخواست Ajax ایی را به دومین دیگری ارسال کردید، پیش از ارسال، مرورگر یک درخواست از نوع OPTIONS را به سمت سرور مقصد ارسال میکند. در این حالت اگر سرور مجوز مناسبی را صادر کرد، آنگاه مرورگر اصل درخواست را به سمت آن سرور ارسال میکند. به همین جهت در این حالت به ازای هر درخواستی که در برنامه ارسال میشود، در برگهی network مرورگر، دو درخواست را مشاهده خواهید کرد. درخواست preflight از نوع OPTIONS به صورت خودکار توسط مرورگر مدیریت میشود و نیازی به کدنویسی خاصی ندارد.
مدیریت کوکیها در درخواستهای Ajax
اگر درخواست Ajax ایی را به دومین دیگری ارسال کنید، به صورت پیشفرض به همراه کوکیهای مرتبط نخواهد بود. برای رفع این مشکل نیاز است خاصیت withCredentials را به true تنظیم کنید:
توسط استاندارد جدید Fetch API | توسط XMLHttpRequest استاندارد | توسط jQuery |
fetch('http://someotherdomain.com', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, credentials: 'include' }); | var xhr = new XMLHttpRequest(); xhr.open('POST', 'http://someotherdomain.com'); xhr.withCredentials = true; xhr.setRequestHeader('Content-Type', 'text/plain'); xhr.send('sometext'); | $.ajax('http://someotherdomain.com', { method: 'POST', contentType: 'text/plain', data: 'sometext', beforeSend: function(xmlHttpRequest) { xmlHttpRequest.withCredentials = true; } }); |
یک نکتهی مهم: در fetch API حتی برای درخواستهای ساده نیز کوکیها ارسال نمیشوند. در این حالت برای کار با دومین جاری و ارسال کوکیهای کاربر به سمت سرور، باید از تنظیم 'credentials: 'same-origin استفاده کرد؛ زیرا مقدار پیشفرض آن omit است.
از متدهای همزمان متداول برای انجام امور ذیل استفاده نمائید:
- جهت پردازش اعمالی ساده و سریع
- اعمال مدنظر بیشتر قرار است بر روی CPU اجرا شوند و از مرزهای IO سیستم عبور نمیکنند.
و از متدهای غیرهمزمان برای پردازش موارد زیر کمک بگیرید:
- از وب سرویسهایی استفاده میکنید که متدهای نگارش async را نیز ارائه دادهاند.
- عمل مدنظر network-bound و یا I/O-bound است بجای CPU-bound. یعنی از مرزهای IO سیستم عبور میکند.
- نیاز است چندین عملیات را به موازات هم اجرا کرد.
- نیاز است مکانیزمی را جهت لغو یک عملیات طولانی ارائه دهید.
مزایای استفاده از متدهای async در ASP.NET
استفاده از await در ASP.NET، ساختار ذاتی پروتکل HTTP را که اساسا یک synchronous protocol، تغییر نمیدهد. کلاینت، درخواستی را ارسال میکند و باید تا زمان آماده شدن نتیجه و بازگشت آن از طرف سرور، صبر کند. نحوهی تهیهی این نتیجه، خواه async باشد و یا حتی همزمان، از دید مصرف کننده کاملا مخفی است. اکنون سؤال اینجا است که چرا باید از متدهای async استفاده کرد؟
- پردازش موازی: میتوان چند Task را مثلا توسط Task.WhenAll به صورت موازی با هم پردازش کرده و در نهایت نتیجه را سریعتر به مصرف کننده بازگشت داد. اما باید دقت داشت که این Taskها اگر I/O bound باشند، ارزش پردازش موازی را دارند و اگر compute bound باشند (اعمال محاسباتی)، صرفا یک سری ترد را ایجاد و مصرف کردهاید که میتوانستهاند به سایر درخواستهای رسیده پاسخ دهند.
- خالی کردن تردهای در حال انتظار: در اعمالی که disk I/O و یا network I/O دارند، پردازش موازی و اعمال async به شدت مقیاس پذیری سیستم را بالا میبرند. به این ترتیب worker thread جاری (که تعداد آنها محدود است)، سریعتر آزاد شده و به worker pool بازگشت داده میشود تا بتواند به یک درخواست دیگر رسیده سرویس دهد. در این حالت میتوان با منابع کمتری، درخواستهای بیشتری را پردازش کرد.
ایجاد Asynchronous HTTP Handlers در ASP.Net 4.5
در نگارشهای پیش از دات نت 4.5، برای نوشتن فایلهای ashx غیرهمزمان میبایستی اینترفیس IHttpAsynchHandler پیاده سازی میشد که نحوهی کار با آن از مدل APM پیروی میکرد؛ نیاز به استفاده از یک سری callback داشت و این عملیات باید طی دو متد پردازش میشد. اما در دات نت 4.5 و با معرفی امکانات async و await، نگارش سازگاری با پیاده سازی کلاس پایه HttpTaskAsyncHandler فراهم شده است.
برای آزمایش آن، یک برنامهی جدید ASP.NET Web forms نگارش 4.5 یا بالاتر را ایجاد کنید. سپس از منوی پروژه، گزینهی Add new item یک Generic handler به نام LogRequestHandler.ashx را به پروژه اضافه نمائید.
زمانیکه این فایل به پروژه اضافه میشود، یک چنین امضایی را دارد:
public class LogRequestHandler : IHttpHandler
using System; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web; namespace Async14 { public class LogRequestHandler : HttpTaskAsyncHandler { public override async Task ProcessRequestAsync(HttpContext context) { string url = context.Request.QueryString["rssfeedURL"]; if (string.IsNullOrWhiteSpace(url)) { context.Response.Write("Rss feed URL is not provided"); } using (var webClient = new WebClient {Encoding = Encoding.UTF8}) { webClient.Headers.Add("User-Agent", "LogRequestHandler 1.0"); var rssfeed = await webClient.DownloadStringTaskAsync(url); context.Response.Write(rssfeed); } } public override bool IsReusable { get { return true; } } public override void ProcessRequest(HttpContext context) { throw new Exception("The ProcessRequest method has no implementation."); } } }
در این مثال آدرس یک فید RSS از طریق کوئری استرینگ rssfeedURL دریافت شده و سپس محتوای آن به کمک متد DownloadStringTaskAsync دریافت و بازگشت داده میشود.
برای آزمایش آن، مسیر ذیل را درخواست دهید:
http://localhost:4207/LogRequestHandler.ashx?rssfeedURL=https://www.dntips.ir/feed/latestchanges
صفحات async در ASP.NET 4.5
در قسمتهای قبل مشاهده کردیم که در برنامههای دسکتاپ، به سادگی میتوان امضای روالهای رخداد گردان را به async تغییر داد و ... برنامه کار میکند. به علاوه از مزیت استفاده از واژه کلیدی await نیز در آنها برخوردار خواهیم شد. اما ... هرچند این روش در وب فرمها نیز صادق است (مثلا public void Page_Load را به public async void Page_Load میتوان تبدیل کرد) اما اعضای تیم ASP.NET آنرا در مورد برنامههای وب فرم توصیه نمیکنند:
Async void event handlers تنها در مورد تعداد کمی از روالهای رخدادگردان ASP.NET Web forms کار میکنند و از آنها تنها برای تدارک پردازشهای ساده میتوان استفاده کرد. اگر کار در حال انجام اندکی پیچیدگی دارد، «باید» از PageAsyncTask استفاده نمائید. علت اینجا است که Async void یعنی fire and forget (کاری را شروع کرده و فراموشش کنید). این روش در برنامههای دسکتاپ کار میکند، زیرا این برنامهها مدل طول عمر متفاوتی داشته و تا زمانیکه برنامه از طرف OS خاتمه نیابد، مشکلی نخواهند داشت. اما برنامههای بدون حالت وب متفاوتند. اگر عملیات async پس از خاتمهی طول عمر صفحه پایان یابد، دیگر نمیتوان اطلاعات صحیحی را به کاربر ارائه داد. بنابراین تا حد ممکن از تعاریف async void در برنامههای وب خودداری کنید.
تبدیل روالهای رخدادگردان متداول وب فرمها به نسخهی async شامل دو مرحله است:
الف) از متد جدید RegisterAsyncTask که در کلاس پایه Page قرار دارد برای تعریف یک PageAsyncTask استفاده کنید:
using System; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web.UI; namespace Async14 { public partial class _default : Page { protected void Page_Load(object sender, EventArgs e) { RegisterAsyncTask(new PageAsyncTask(LoadSomeData)); } public async Task LoadSomeData() { using (var webClient = new WebClient { Encoding = Encoding.UTF8 }) { webClient.Headers.Add("User-Agent", "LogRequest 1.0"); var rssfeed = await webClient.DownloadStringTaskAsync("url"); //listcontacts.DataSource = rssfeed; } } } }
ب) سپس در کدهای فایل aspx، نیاز است خاصیت async را نیز true نمائید:
<%@ Page Language="C#" AutoEventWireup="true" Async="true" CodeBehind="default.aspx.cs" Inherits="Async14._default" %>
تغییر تنظیمات IIS برای بهره بردن از پردازشهای Async
اگر از ویندوزهای 7، ویستا و یا 8 استفاده میکنید، IIS آنها به صورت پیش فرض به 10 درخواست همزمان محدود است.
بنابراین تنظیمات ذیل مرتبط است به یک ویندوز سرور و نه یک work station :
به IIS manager مراجعه کنید. سپس برگهی Application Pools آنرا باز کرده و بر روی Application pool برنامه خود کلیک راست نمائید. در اینجا گزینهی Advanced Settings را انتخاب کنید. در آن Queue Length را به مثلا عدد 5000 تغییر دهید. همچنین در دات نت 4.5 عدد 5000 برای MaxConcurrentRequestsPerCPU نیز مناسب است. به علاوه عدد connectionManagement/maxconnection را نیز به 12 برابر تعداد هستههای موجود تغییر دهید.
Subversion 1.6.0
ممنون از پاسخ و کمک شما