- از سال 2014، گوگل شروع به پردازش جاوا اسکریپت موجود در صفحات وب هم کردهاست. بنابراین مشکلی با این نوع برنامهها ندارد.
دریافت اطلاعات از سرور، توسط Axios
- ابتدا به پوشهی sample-22-backend ای که در قسمت قبل ایجاد کردیم، مراجعه کرده و فایل dotnet_run.bat آنرا اجرا کنید، تا endpointهای REST Api آن، قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts در مرورگر دسترسی یافت (و یا همانطور که عنوان شد، از آدرس https://jsonplaceholder.typicode.com/posts نیز میتوانید استفاده کنید؛ چون ساختار یکسانی دارند).
-سپس در برنامهی React ای که در قسمت قبل ایجاد کردیم، فایل app.js آنرا گشوده و ابتدا کتابخانهی Axios را import میکنیم:
import axios from "axios";
componentDidMount() { const promise = axios.get("https://localhost:5001/api/posts"); console.log(promise); }
تنظیمات CORS مخصوص React در برنامههای ASP.NET Core 3x
همانطور که مشاهده میکنید، پس از ذخیره سازی تغییرات، با اجرای برنامه، این Promise در حالت pending قرار گرفته و همچنین پس از پایان آن، حاوی نتیجهی عملیات نیز میباشد که در اینجا rejected است. علت شکست عملیات را در سطر بعدی آن ملاحظه میکنید که عنوان کردهاست «CORS policy» مناسبی در سمت سرور، برای این درخواست وجود ندارد؛ چرا؟ چون برنامهی React ما در مسیر http://localhost:3000/ اجرا میشود و برنامهی Web API در مسیر دیگری https://localhost:5001/ که شمارهی پورت ایندو یکی نیست. به همین جهت عنوان میکند که نیاز است در سمت سرور، هدرهای خاصی برای پردازش این نوع درخواستهای با Origin متفاوت وجود داشته باشد، تا مرورگر اجازهی دسترسی به آنرا بدهد. برای رفع این مشکل، برنامهی sample-22-backend را گشوده و تغییرات زیر را اعمال میکنیم:
ابتدا تنظیمات AddCors را با تعریف یک CORS policy جدید مخصوص آدرس http://localhost:3000، به متد ConfigureServices کلاس آغازین برنامه اضافه میکنیم:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("ReactCorsPolicy", builder => builder .AllowAnyMethod() .AllowAnyHeader() .WithOrigins("http://localhost:3000") .AllowCredentials() .Build()); }); services.AddSingleton<IPostsDataSource, PostsDataSource>(); services.AddControllers(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); //app.UseAuthentication(); //app.UseAuthorization(); app.UseCors("ReactCorsPolicy"); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
اینبار Promise بازگشت داده شده، در حالت resolved قرار گرفتهاست که به معنای موفقیت آمیز بودن عملیات async است. وجود [[PromiseStatus]] به معنای یک internal property است که توسط dot notation قابل دسترسی نیست. در اینجا [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است که نتیجهی عملیات (response دریافتی از سرور) در آن قرار میگیرد. برای مثال در data آن، آرایهی مطالب دریافتی از سرور، قابل مشاهدهاست و یا status=200 به معنای موفقیت آمیز بودن پردازش درخواست، از سمت سرور است.
البته زمانیکه درخواست افزودن رکورد جدیدی را به سمت سرور ارسال میکنیم، میتوان دو درخواست را در برگهی network ابزارهای توسعه دهندگان مرورگر، مشاهده کرد:
در اولین درخواست، Request Method: OPTIONS را داریم که دقیقا مرتبط است با بررسی CORS توسط مرورگر.
دریافت اطلاعات شیء response از یک Promise و نمایش آن
همانطور که عنوان شد، [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است. بنابراین اکنون این سؤال مطرح میشود که چگونه میتوان به اطلاعات آن دسترسی یافت؟
این شیء Promise، دارای متدی است به نام then است که نتیجهی عملیات async را بازگشت میدهد. البته این روش قدیمی کار کردن با Promiseها است و ما از آن در اینجا استفاده نخواهیم کرد. در جاوا اسکریپت مدرن، میتوان از واژهی کلیدی await برای دسترسی به شیء response دریافتی از سرور، استفاده کرد:
async componentDidMount() { const promise = axios.get("https://localhost:5001/api/posts"); console.log(promise); const response = await promise; console.log(response); }
البته قطعه کد نوشته شده، صرفا جهت توضیح مراحل مختلف عملیات، به این صورت چند مرحلهای نوشته شد، وگرنه میتوان واژهی کلیدی await را پیش از فراخوانی متدهای Axios نیز قرار داد:
async componentDidMount() { const response = await axios.get("https://localhost:5001/api/posts"); console.log(response); }
class App extends Component { state = { posts: [] }; async componentDidMount() { const { data: posts } = await axios.get("https://localhost:5001/api/posts"); this.setState({ posts }); // = { posts: posts } }
ایجاد یک مطلب جدید توسط Axios
در برنامهی React ای ایجاد شده، یک دکمهی Add نیز برای افزودن مطلبی جدید درنظر گرفته شدهاست. در یک برنامهی واقعیتر، معمولا فرمی وجود دارد و نتیجهی آن در حین submit، به سمت سرور ارسال میشود. در اینجا این سناریو را شبیه سازی خواهیم کرد:
const apiEndpoint = "https://localhost:5001/api/posts"; class App extends Component { state = { posts: [] }; async componentDidMount() { const { data: posts } = await axios.get(apiEndpoint); this.setState({ posts }); } handleAdd = async () => { const newPost = { title: "new Title ...", body: "new Body ...", userId: 1 }; const { data: post } = await axios.post(apiEndpoint, newPost); console.log(post); const posts = [post, ...this.state.posts]; this.setState({ posts }); };
- چون قرار است از آدرس https://localhost:5001/api/posts در قسمتهای مختلف برنامه استفاده کنیم، فعلا آنرا به صورت یک ثابت تعریف کرده و در متدهای get و post استفاده کردیم.
- در متد منتسب به خاصیت handleAdd، یک شیء جدید post را با ساختاری مشابه آن ایجاد کردهایم. این شیء جدید، دارای Id نیست؛ چون قرار است از سمت سرور پس از ثبت در بانک اطلاعاتی دریافت شود.
- سپس این شیء جدید را توسط متد post کتابخانهی Axios، به سمت سرور ارسال کردهایم. این متد نیز یک Promise را باز میگرداند. به همین جهت از واژهی کلیدی await برای دریافت نتیجهی واقعی آن استفاده شدهاست. همچنین هر زمانیکه await داریم، نیاز به ذکر واژهی کلیدی async نیز هست. اینبار این واژه باید پیش از قسمت تعریف پارامتر متد قرار گیرد و نه پیش از نام handleAdd؛ چون handleAdd در واقع یک خاصیت است که متدی به آن انتساب داده شدهاست.
- نتیجهی دریافتی از متد axios.post را اینبار به post، بجای posts تغییر نام دادهایم و همانطور که در تصویر زیر مشاهده میکنید، خاصیت id آن در سمت سرور مقدار دهی شدهاست:
- در آخر برای افزودن این رکورد، به مجموعهی رکوردهای موجود، از روش spread operator استفاده کردهایم تا ابتدا شیء post دریافتی از سمت سرور درج شود و سپس مابقی اعضای آرایهی posts موجود در state، در این آرایه گسترده شده و یک آرایهی جدید را تشکیل دهند. سپس این آرایهی جدید را جهت به روز رسانی state و در نتیجهی آن، به روز رسانی UI، به متد setState ارسال کردهایم، که نتیجهی آن درج این رکورد جدید، در ابتدای لیست است:
به روز رسانی اطلاعات در سمت سرور
در اینجا پیاده سازی متد put را مشاهده میکنید:
handleUpdate = async post => { post.title = "Updated"; const { data: updatedPost } = await axios.put( `${apiEndpoint}/${post.id}`, post ); console.log(updatedPost); const posts = [...this.state.posts]; const index = posts.indexOf(post); posts[index] = { ...post }; this.setState({ posts }); };
- اکنون امضای متد axios.put هرچند مانند متد post است، اما متد Update تعریف شدهی در سمت API سرور، یک چنین مسیری را نیاز دارد api/Posts/{id}. به همین جهت ذکر id مطلب، در URL نهایی نیز ضروری است.
- در اینجا نیز از واژههای await و async برای دریافت نتیجهی واقعی عملیات put و همچنین عملیات گذاری این متد به صورت async، استفاده شدهاست.
- در آخر، ابتدا آرایهی posts موجود در state را clone میکنیم. چون میخواهیم در آن، در ایندکسی که شیء post جاری قرار دارد، مقدار به روز رسانی شدهی آنرا قرار دهیم. سپس این آرایهی جدید را جهت به روز رسانی state و در نتیجهی آن، به روز رسانی UI، به متد setState ارسال کردهایم:
حذف اطلاعات در سمت سرور
برای حذف اطلاعات در سمت سرور، نیاز است یک HTTP Delete را به آن ارسال کنیم که اینکار را میتوان توسط متد axios.delete انجام داد. URL ای را که دریافت میکند، شبیه به URL ای است که برای حالت put ایجاد کردیم:
handleDelete = async post => { await axios.delete(`${apiEndpoint}/${post.id}`); const posts = this.state.posts.filter(item => item.id !== post.id); this.setState({ posts }); };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-22-backend-part-02.zip و sample-22-frontend-part-02.zip
با توجه به امکانات جدید Razor Components، آیا در آینده از SPA frameworks استفاده میکنید؟
Cookie - قسمت سوم
Cookie - قسمت اول: مقدمه، تاریخچه، معرفی، و شرح کامل
Cookie - قسمت دوم: کوکی در جاوا اسکریپت
نکته مهم: خواندن قسمتهای قبلی این سری (مخصوصا قسمت اول) برای درک بهتر مطالب پیشنهاد میشود.
.
کوکی در ASP.NET - بخش اول
در قسمتهای قبلی مقدمات و مباحث کلی راجع به کوکیها و انواع آن، شرح کامل خواص، نحوه رفتار مرورگرها با انواع کوکیها و درنهایت نحوه کار کردن با کوکیها در سمت کلاینت با استفاده از زبان محبوب جاوا اسکریپت پرداخته شد.
در ادامه این سری مطالب به نحوه برخورد ASP.NET با کوکیها و چگونگی کار کردن با کوکی در سمت سرور آشنا خواهیم شد. در بخش اول این قسمت مباحث ابتدایی و اولیه برای کار با کوکیها در ASP.NET ارائه میشود. در بخش دوم مباحث پیشرفتهتر همچون SubCookieها در ASP.NET و نیز سایر نکات ریز کار با کوکیها در ASP.NET بحث خواهد شد.
.
.
Response و Request در ASP.NET
در قسمت اول این سری به مفاهیم Http Response و Http Request اشاره کوتاهی شده بود. بهصورت خلاصه، درخواستی که از سمت یک کلاینت به یک وب سرور ارسال میشود Request و پاسخی که وب سرور به آن درخواست میدهد Response نامیده میشود.
در ASP.NET، کلیه اطلاعات مرتبط با درخواست رسیده از سمت یک کلاینت در نمونهای منحصر به فرد از کلاس HttpRequest نگهداری میشود. محل اصلی نگهداری این نمونه در پراپرتی Request از نمونه جاری کلاس System.Web.HttpContext (قابل دسترسی ازطریق HttpContext.Current) است. البته کلاس Page هم یک پراپرتی با نام Request دارد که دقیقا از همین پراپرتی کلاس HttpContext استفاده میکند.
همچنین کلیه اطلاعات مرتبط با پاسخ ارسالی وب سرور به سمت کلاینت مربوطه در نمونهای از کلاس HttpResponse ذخیره میشود. محل اصلی نگهداری این نمونه نیز در پراپرتی Response از نمونه جاری کلاس HttpContext است. همانند Request، کلاس Page یک پراپرتی با نام Response برای نگهداری این نمونه دارد که این هم دقیقا از پراپرتی متناظر در کلاس HttpContext استفاده میکند.
.
.
کوکیها در Response و Request
هر دو کلاس HttpResponse و HttpRequest یک پراپرتی با عنوان Cookies (^ و ^) دارند که مخصوص نگهداری کوکیهای مربوطه هستند. این پراپرتی از نوع System.Web.HttpCookieCollection است که یک کالکشن مخصوص برای ذخیره کوکیهاست.
- این پراپرتی (Cookies) در کلاس HttpRequest محل نگهداری کوکیهای ارسالی توسط مرورگر در درخواست متناظر آن است. کوکیهایی که مرورگر با توجه به شرایط جاری و تنظیمات کوکیها اجازه ارسال به سمت سرور را به آنها داده و در درخواست ارسالی ضمیمه کرده است (با استفاده از هدر :Cookie که در قسمت اول شرح داده شد) و ASP.NET پس از پردازش و Parse دادهها، درون این پراپرتی اضافه کرده است.
- این پراپرتی (Cookies) در کلاس HttpResponse محل ذخیره کوکیهای ارسالی از وب سرور به سمت مرورگر کلاینت در پاسخ به درخواست متناظر است. کوکیهای درون این پراپرتی پس از بررسی و استخراج دادههای موردنیاز توسط ASP.NET در هدر پاسخ ارسالی ضمیمه خواهند شد (با استفاده از هدر :Set-Cookie که در قسمت اول توضیح داده شد).
.
.
ایجاد و بهروزرسانی کوکی در ASP.NET
برای ایجاد یک کوکی و ارسال آن به سمت کلاینت همانطور که در بالا نیز اشاره شد، باید از پراپرتی Response.Cookies از کلاس HttpContext استفاده کرد. برای ایجاد یک کوکی روشهای مختلفی وجود دارد.
- در روش اول با استفاده از ویژگی مخصوص ایندکسر کلاس HttpCookieCollection عملیات تولید کوکی انجام میشود. در این روش، ابتدا بررسی میشود که کوکی موردنظر در لیست کوکیهای جاری وجود دارد یا خیر. درصورتیکه با این نام قبلا یک کوکی ثبت شده باشد، مقدار کوکی موجود بروزرسانی خواهد شد. اما اگر این نام وجود نداشته باشد یک کوکی جدید با این نام به لیست افزوده شده و مقدار آن ثبت میشود. مثال:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myCookieValue";
- روش بعدی استفاده از متد Add در کلاس HttpCookieCollection است. در این روش ابتدا یک نمونه از کلاس HttpCookie ایجاد شده و سپس این نمونه به لیست کوکیها اضافه میشود. کد زیر چگونگی استفاده از این روش را نشان میدهد:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); HttpContext.Current.Response.Cookies.Add(myCookie);
- روش دیگر استفاده از متد Set کلاس HttpCookieCollection است. تفاوت این متد با متد Add در این است که متد Set ابتدا سعی میکند عملیات update انجام دهد. یعنی عملیات افزودن تنها وقتیکه نام کوکی موردنظر در لیست کوکیها یافته نشود انجام خواهد شد. برای مثال:
HttpContext.Current.Response.Cookies.Set(new HttpCookie("myCookie", "myCookieValue"));
نکته: باتوجه به توضیحات بالا، متد Set اجازه افزودن دو کوکی با یک نام را نمیدهد. برای اینکار باید از متد Add استفاده کرد. درباره این موضوع در قسمت بعدی بیشتر توضیح داده خواهد شد.
- روش دیگری که برای ایجاد یکی کوکی میتوان از آن استفاده کرد، بکارگیری متد AppnedCookie از کلاس HttpResponse است. در این روش نیز ابتدا باید یک نمونه از کلاس HttpCookie تولید شود. این روش همانند استفاده از متد Add از کلاس HttpCookieCollection است. کد زیر مثالی از این روش را نشان میدهد:
HttpContext.Current.Response.AppendCookie(new HttpCookie("myCookie", "myCookieValue"));
- روش بعدی استفاده از متد SetCookie از کلاس HttpResponse است. فرق این متد با متد AppendCookie در این است که در متد SetCookie ابتدا وجود یک کوکی با نام ارائه شده بررسی میشود و درصورت وجود، مقدار این کوکی بروزرسانی میشود. درصورتیکه قبلا یک کوکی با این نام وجود نداشته باشد، یک کوکی جدید به لیست کوکیها اضافه میشود. این روش همانند استفاده از متد Set از کلاس HttpCookieCollection است. نمونهای از نحوه استفاده از این متد در زیر آورده شده است:
HttpContext.Current.Response.SetCookie(new HttpCookie("myCookie", "myCookieValue"));
نکته: تمامی فرایندهای نشان داده شده در بالا تنها موجب تغییر محتویات کالکشن کوکیها درون HttpContext میشود و تا زمانیکه توسط وب سرور با استفاده از دستور Set-Cookie به سمت مرورگر ارسال نشوند تغییری در کلاینت بوجود نخواهند آورد.
برای آشنایی بیشتر با این روند کد زیر را برای تعریف یک کوکی جدید درنظر بگیرید:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myValue";
همانطور که مشاهده میکنید دستور ایجاد یک کوکی با نام و مقدار وارده در هدر پاسخ تولیدی توسط وب سرور گنجانیده شده است.
نکته: در ASP.NET به صورت پیش فرض از مقدار "/" برای پراپرتی Path استفاده میشود.
.
خواص کوکی در ASP.NET
برای تعیین یا تغییر خواص یک کوکی در ASP.NET باید به نمونه HttpCookie مربوطه دست یافت. سپس با استفاده از پراپرتیهای این کلاس میتوان خواص موردنظر را تعیین کرد. برای مثال:
var myCookie = new HttpCookie(string.Empty); myCookie.Name = "myCookie"; myCookie.Value = "myCookieValue"; myCookie.Domain = "dotnettip.info"; myCookie.Path = "/post"; myCookie.Expires = new DateTime(2015, 1, 1); myCookie.Secure = true; myCookie.HttpOnly = true;
نکته مهم: امکان تغییر خواص یک کوکی به صورت مستقیم در سمت سرور وجود ندارد. درواقع برای اعمال این تغییرات در سمت کلاینت باید به ازای هر کوکی موردنظر یک کوکی جدید با مقادیر جدید ایجاد و به کالکشن کوکیها در Http Response مربوطه اضافه شود تا پس از قرار دادن دستور Set-Cookie متناظر در هدر پاسخ ارسالی به سمت کلاینت و اجرای آن توسط مرورگر، مقادیر خواص مورنظر در سمت کلاینت بروزرسانی شوند. دقت کنید که تمامی نکات مرتبط با هویت یک کوکی که در قسمت اول شرح داده شد در اینجا نیز کاملا صادق است.
روش دیگری نیز برای تعیین برخی خواص کوکیها به صورت کلی در فایل وب کانفیگ وجود دارد. برای اینکار از تگ httpCookies در قسمت system.web استفاده میشود. برای مثال:
<httpCookies domain="www.example.com" httpOnlyCookies="true" requireSSL="true" />
این امکان از ASP.NET 2.0 به بعد اضافه شده است. با استفاده از این تگ، تنظیمات اعمال شده برای تمامی کوکیها درنظر گرفته میشود. البته درصورتیکه تنظیم موردنظر برای کوکی به صورت صریح آورده نشده باشد. برای نمونه به کد زیر دقت کنید:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); myCookie.Domain = "test.com"; HttpContext.Current.Response.Cookies.Add(myCookie); var myCookie2 = new HttpCookie("myCookie2", "myCookieValue2"); myCookie2.HttpOnly = false; myCookie2.Secure = false; HttpContext.Current.Response.Cookies.Add(myCookie2);
با استفاده از تنظیمات تگ httpCookies که در بالا نشان داده شده است، هدر پاسخ تولیدی توسط وب سرور به صورت زیر خواهد بود:
همانطور که میبینید تنها مقادیر پراپرتیهایی که صراحتا برای کوکی آورده نشده است از تنظیمات وب کانفیگ خوانده میشود.
.
.
حذف کوکی در ASP.NET
برای حذف یک کوکی در ASP.NET یک روش کلی وجود دارد که در قسمتهای قبلی نیز شرح داده شده است، یعنی تغییر خاصیت Expires کوکی به تاریخی در گذشته. برای نمونه داریم:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); myCookie.Expires = DateTime.Now.AddYears(-1);
نکته مهم: در کلاس HttpCookieCollection یک متد با نام Remove وجود دارد. از این متد برای حذف یک کوکی از لیست موجود در این کلاس استفاده میشود. دقت کنید که حذف یک کوکی از لیست کوکیها با استفاده از این متد تاثیری بر موجودیت آن کوکی در سمت کلاینت نخواهد گذاشت و تنها روش موجود برای حذف یک کوکی در سمت کلاینت همان تنظیم مقدار خاصیت Expires است.
.
خواندن کوکی در ASP.NET
برای خواندن مقدار یک کوکی ارسالی از مرورگر کلاینت در ASP.NET، باتوجه به توضیحات ابتدای این مطلب، طبیعی است که باید از پراپرتی Request.Cookies در نمونه جاری از کلاس HttpContext استفاده کرد. برای این کار نیز چند روش وجود دارد.
- روش اول استفاده از ایندکسر کلاس HttpCookieCollection است. برای اینکار نیاز به نام یا ایندکس کوکی موردنظر در لیست مربوطه داریم. برای مثال:
var myCookie = HttpContext.Current.Request.Cookies["myCookie"];
- یا این نمونه با استفاده از ایندکسر عددی:
var myCookie = HttpContext.Current.Request.Cookies[0];
- روش دیگری که برای خواند مقدار یک کوکی میتوان بکار برد، استفاده از متد Get از کلاس HttpCookieCollection است. این متد همانند ایندکسر این کلاس نیاز به نام یا ایندکس کوکی موردنظر دارد. برای نمونه:
var myCookie = HttpContext.Current.Request.Cookies.Get("myCookie");
- یا استفاده از ایندکس کوکی:
var myCookie = HttpContext.Current.Request.Cookies.Get(0);
.
.
بحث و نتیجه گیری
تا اینجا با مفاهیم اولیه درباره نحوه برخورد ASP.NET با کوکیها آشنا شدیم. روشهای مختلف ایجاد و یا بهروزرسانی کوکیها نشان داده شد. با تعیین انواع خواص کوکیها آشنا شدیم. نحوه حذف یک کوکی در ASP.NET را دیدیم. روشهای خواندن مقادیر کوکیها را نیز مشاهده کردیم.
باز هم تاکید میکنم که تمامی تغییرات اعمالی در سمت سرور تا زمانیکه بهصورت دستورات Set-Cookie در هدر پاسخ وب سرور قرار نگیرند هیچ کاری در سمت کلاینت انجام نمیدهند.
در قسمت بعدی این سری مطالب به مباحث پیشرفتهتری چون SubCookieها در ASP.NET و هویت منحصر به فرد کوکیها در سمت سرور پرداخته میشود.
.
.
منابع
در طول روند تولید یک برنامه، چه به صورت تیمی و یا حتی انفرادی، بارها برای برنامه نویسان این نیاز پیش میآید که به نسخههای قدیمیتر فایلهای خود دسترسی داشته باشند تا بتوانند آنچه را که در قبل نوشتهاند مورد بازبینی قرار دهند. شاید کسانی که با سیستمهای مدیریت نسخه آشنایی ندارند، این کار را با استفاده از copy و paste کردن فایلها در پوشههای جداگانه انجام دهند؛ اما روند توسعه یک برنامه در محیط عملی، امکان استفاده از چنین روشی را به ما نمیدهد. زیرا مدیریت این فایلها علی الخصوص در پروژههای تیمی، بعد از مدتی بسیار دشوار خواهد شد. بنابراین نیاز به سیستمی احساس میشود که بتواند این کار را به صورت خودکار انجام دهد.
Git توسط سازنده سیستم عامل لینوکس یعنی آقای Linus Torvalds و برای مدیریت کدهای آن ساخته شد که بعدها توسط Linux-BitKeeper ارتقا یافت. BitKeeper یک سیستم مدیریت کد توزیع شده است که البته رایگان نیست. تیم BitKeeper در ابتدا پروژه لینوکس را به صورت رایگان پشتیبانی میکرد اما در سال 2005 این حمایت را قطع کرد. در این هنگام تیم توسعه لینوکس تصمیم گرفت که خود یک سیستم مدیریت کد توزیع شده ایجاد کند. آنها این سیستم را با Perl و C نوشتند و آن را برای اجرا شدن بر روی انواع سیستم عاملها نظیر لینوکس ویندوز و حتی مک آماده کردند اهداف اصلی Git عبارتند از:
سیستمهای کنترل نسخه را میتوان بر اساس خصوصیات مختلف در دستههای متفاوتی قرار داد اما از نظر معماری سیستم, به دو دستهی زیر تقسیم میشوند :
مخزن یا همان Repository محلی است که یک سیستم مدیریت نسخه از آن برای نگهداری تغییرات فایلها استفاده میکند. در سیستمهای VCS این مخزن به صورت متمرکز یا اصطلاحا Centralized Repository میباشد. به این معنا که یک Repository بر روی یک ماشین، خواه سیستم خود برنامه نویس(در پروژههای انفرادی) و خواه یک سرور قرار دارد (در پروژههای تیمی) و برنامه نویسان تغییرات فایلهای خود را به سمت این سرور میفرستند و این سرور وظیفه نگهداری تمامی نسخهها و اطلاعات مربوطه از برنامه نویسان مختلف را به عهده دارد. اشکال این روش در این است که برنامه نویس تنها به نسخه جاری که بر روی سیستم خود است دسترسی دارد و اگر بنا به دلیلی بخواهد از نسخههای پیشین استفاده کند باید آن را از سرور بخواهد که این کار مشکل دیگری ایجاد میکند و آن این است که ممکن است برنامه نویس همیشه در موقعیتی نباشد که بتواند به سرور دسترسی داشته باشد. به همین دلیل این روش وابستگی زیادی برای برنامه نویس ایجاد میکند اما پیاده سازی این روش آسانتر از مدل توزیع شده است.
پاسخ به این سوال بسیار ساده است: هر آنچه برای ما مهم است که این شامل فایلهای کد, فایلهای پیکربندی, خروجیهای نظیر dll و غیره است. البته در این بین استثنائاتی نظیر فایلهای EXE و یا پکیجهای نصب شده وجود دارد که در بسیاری از موارد نیازی به پیگیری نسخههای آنها نیست اما تمامی اینها وابسته به نظر برنامه نویس است.
روش اول: دریافت اطلاعات سمت سرور به کمک درخواستهای Ajax
استفاده از Ajax یکی از روشهای کلاسیک دریافت اطلاعات سمت سرور در کدهای جاوا اسکریپتی است.
<script type="text/javascript"> var products = []; $(function() { $.getJSON("/home/products", function(response) { products = response.products; }); }); </script>
- مزایا: استفاده از Ajax، روشی بسیار متداول و شناخته شدهاست و به کمک انواع و اقسام روشهای بازگشت JSON از سرور، میتوان با آن کار کرد.
- معایب: درخواست Ajax، صرفا پس از بارگذاری اولیهی صفحه به سمت سرور ارسال خواهد شد و در این بین، کاربر وقفهای را مشاهده خواهد کرد. همچنین در اینجا بجای یک درخواست از سرور، حداقل دو درخواست باید ارسال شوند؛ یکی برای بارگذاری صفحهی اصلی و دیگری برای دریافت اطلاعات Ajax ایی از سرور به صورت غیرهمزمان.
روش دوم: دریافت اطلاعات از یک فایل جاوا اسکریپتی خارجی
اطلاعات سمت کاربر را از یک فایل جاوا اسکریپتی خارجی الحاق شدهی به صفحهی جاری نیز میتوان تهیه کرد:
<script src="/file.js"></script>
این روش نیز تقریبا مانند حالت یک درخواست Ajax ایی کار میکند و اطلاعات مورد نیاز را در طی یک درخواست جداگانه، پس از بارگذاری صفحهی اصلی، از سرور دریافت خواهد کرد. البته در حالت کار با Ajax، میتوان در طی یک callback، نتیجه را دریافت کرد و سپس عکس العمل نشان داد؛ اما در اینجا callback ایی وجود ندارد.
روش سوم: استفاده از SignalR
در SignalR ابتدا سعی میشود تا با استفاده از Web Sockets ارتباطی ماندگار بین کلاینت و سرور برقرار شود و سپس در این حالت، سرور میتواند مدام اطلاعاتی، مانند تغییرات دادههای خود را به سمت کاربر، جهت نمایش و یا محاسبات خاص خود ارسال کند. اگر حالت Web Socket میسر نباشد (توسط سرور یا کلاینت پشتیبانی نشود)، به حالتهای دیگری مانند server events, forever frames, long polling سوئیچ خواهد کرد. اطلاعات بیشتر
روش چهارم: قرار دادن اطلاعات سمت سرور در کدهای HTML صفحه
روش متداول دیگری جهت تامین اطلاعات جاوا اسکریپتی سمت کاربر، قرار دادن آنها در ویژگیهای data-* ارائه شده در HTML5 است.
<ul> @foreach (var product in products) { <li id="product@product.Id" data-rank="@product.Rank">@product.Name</li> } </ul>
اکنون برای دسترسی به مقدار data-rank سطری مانند product1، در کدهای جاوا اسکریپتی صفحه میتوان نوشت:
<script type="text/javascript"> var product1Rank = $("#product1").data("rank"); </script>
روش پنجم: قرار دادن اطلاعات سمت سرور در کدهای جاوا اسکریپتی صفحه
این روش همانند روش چهارم است، با این تفاوت که اینبار اطلاعات مورد نیاز، مستقیما به یک متغیر جاوا اسکریپتی انتساب داده شدهاست:
<script type="text/javascript"> var product1Name = "@product1.Name"; </script>
روش ششم: انتساب یک شیء دات نتی به یک متغیر جاوا اسکریپتی
این روش همانند روش پنجم است، با این تفاوت که اینبار قصد داریم بجای یک مقدار ثابت رشتهای یا عددی، برای مثال، آرایهای از اشیاء را به یک متغیر جاوا اسکریپتی انتساب دهیم. در اینجا ابتدا اطلاعات مورد نظر را به فرمت JSON تبدیل میکنیم:
//سمت سرور [HttpGet] public ActionResult Index() { var array = new[] { "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and/or Barbuda" }; ViewBag.JsonString = new JavaScriptSerializer().Serialize(array); return View(); }
//سمت کلاینت <script type="text/javascript"> var jsonArray = @Html.Raw(@ViewBag.JsonString); </script>
و یا اینکار را به صورت خلاصه به شکل زیر نیز میتوان در سمت کاربر انجام داد:
<script type="text/javascript"> var model = @Html.Raw(Json.Encode(Model)); // your js code here </script>
- اگر هاست شما تمام سایتها را با یک Application pool مدیریت میکند، کرش یکی از چند ده سایت دیگر میتواند سبب ریاستارت شدن سایت شما هم بشود؛ چون برنامهها از همه ایزوله نشدهاند. راه حل آن ایجاد یک Application pool مجزا به ازای هر سایت هست (توسط هاستدار).
زمانیکه تمام سایتها با یک Application pool واحد مدیریت میشوند، تمام آنها توسط یک وهله از w3wp.exe اجرا خواهند شد. با تعریف Application poolهای مجزا، هر سایت، یک وهلهی مجزا از w3wp.exe را به خود اختصاص خواهد داد. یعنی اگر Task manager سرور را بررسی کنید، به ازای هر سایت، یک w3wp.exe با pid مجزا قابل مشاهده است. به این ترتیب اگر pid=1234 کرش کرد، تاثیری روی pid=4321 نخواهد داشت.
- یک برنامهی ASP.NET پس از مدتی بیکاری (قابل تنظیم در Application pool برنامه)، به صورت خودکار توسط IIS از حافظه خارج میشود. با درخواست بعدی که به آن برنامه میرسد (مثلا گشودن یک صفحهی آن توسط یک کاربر)، مجددا از صفر اجرا خواهد شد. این مورد نیز به معنای ریاستارت کامل برنامه است.
- در تنظیمات Application pool موارد زیادی را میتوان تنظیم کرد که سبب ریاستارت شدن برنامه میشوند. برای مثال اگر مصرف CPU و یا حافظهی برنامه به حد مشخصی رسید، برنامه ریاستارت شود و امثال آن.
فعال سازی پردازش فایلهای استاتیک در برنامههای ASP.NET Core 1.0
در مورد پوشهی جدید wwwroot در «قسمت 2 - بررسی ساختار جدید Solution» مطالبی عنوان شدند. جهت یادآوری:
اگر فایل Program.cs را بررسی کنید، یک چنین تعاریفی را مشاهده خواهید کرد:
public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); } }
یک مثال: زمانیکه فایل استاتیک images/banner3.svg در پوشهی wwwroot قرار میگیرد، با آدرس http://localhost:9189/images/banner3.svg توسط عموم قابل دسترسی خواهد بود.
یک نکتهی امنیتی مهم
در برنامههای ASP.NET Core، هنوز فایل web.config را نیز مشاهده میکنید. این فایل تنها کاربردی که در اینجا دارد، تنظیم ماژول AspNetCoreModule برای IIS است تا IIS static file handler آن، راسا اقدام به توزیع فایلهای یک برنامهی ASP.NET Core نکند. بنابراین توزیع این فایل را بر روی سرورهای IIS فراموش نکنید. همچنین بهتر است در ویندوزهای سرور، به قسمت Modules feature مراجعه کرده و StaticFileModule را از لیست ویژگیهای موجود حذف کرد.
نصب Middleware مخصوص پردازش فایلهای استاتیک
در قسمت قبل با نحوهی نصب و فعال سازی middleware مخصوص WelcomePage آشنا شدیم. روال کار در اینجا نیز دقیقا به همان صورت است:
الف) نصب بستهی نیوگت Microsoft.AspNetCore.StaticFiles
برای اینکار میتوان بر روی گرهی references کلیک راست کرده و سپس از منوی ظاهر شده،گزینهی manage nuget packages را انتخاب کرد. سپس ابتدا برگهی browse را انتخاب کنید و در اینجا نام Microsoft.AspNetCore.StaticFiles را جستجو کرده و سپس نصب کنید.
انجام این کارها معادل افزودن یک سطر ذیل به فایل project.json است و سپس ذخیرهی آن که کار بازیابی بستهها را به صورت خودکار آغاز میکند:
"dependencies": { // same as before "Microsoft.AspNetCore.StaticFiles": "1.0.0" },
برای اینکار به فایل Startup.cs مراجعه کرده و سطر UseStaticFiles را به متد Configure اضافه کنید (به UseWelcomePage هم دیگر نیازی نداریم):
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); //app.UseWelcomePage(); app.Run(async context => { await context.Response.WriteAsync("Hello DNT!"); }); } }
یک مثال: بر روی پوشهی wwwroot کلیک راست کرده و گزینهی add->new item را انتخاب کنید. سپس یک HTML page جدید را به نام index.html به این پوشه اضافه کنید.
با این محتوا:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hello World</title> </head> <body> Hello World! </body> </html>
که این خروجی دقیقا خروجی app.Run برنامه است و نه محتوای فایل index.html ایی که اضافه کردیم.
در ادامه اگر مسیر کامل این فایل را (http://localhost:7742/index.html) درخواست دهیم، آنگاه میتوان خروجی این فایل استاتیک را مشاهده کرد:
این رفتار اندکی متفاوت است نسبت به نگارشهای قبلی ASP.NET که فایل index.html را به عنوان فایل پیش فرض، درنظر میگرفت و محتوای آنرا نمایش میداد. منظور از فایل پیش فرض، فایلی است که با درخواست ریشهی یک مسیر، به کاربر ارائه داده میشود و index.html یکی از آنها است.
برای رفع این مشکل، نیاز است Middleware مخصوص آنرا به نام Default Files نیز به برنامه معرفی کرد:
public void Configure(IApplicationBuilder app) { app.UseDefaultFiles(); app.UseStaticFiles();
فعال سازی Default Files، سبب جستجوی یکی از 4 فایل ذیل به صورت پیش فرض میشود (اگر تنها ریشهی پوشهای درخواست شود):
default.htm
default.html
index.htm
index.html
اگر خواستید فایل سفارشی خاص دیگری را معرفی کنید، نیاز است پارامتر DefaultFilesOptions آنرا مقدار دهی نمائید:
// Serve my app-specific default file, if present. DefaultFilesOptions options = new DefaultFilesOptions(); options.DefaultFileNames.Clear(); options.DefaultFileNames.Add("mydefault.html"); app.UseDefaultFiles(options);
ترتیب معرفی Middlewares مهم است
در قسمت قبل، در حین معرفی تفاوتهای Middlewareها با HTTP Modules، عنوان شد که اینبار برنامه نویس میتواند بر روی ترتیب اجرای Middlewareها کنترل کاملی داشته باشد و این ترتیب معادل است با ترتیب معرفی آنها در متد Configure، به نحوی که مشاهده میکنید. برای آزمایش این مطلب، متد معرفی middleware فایلهای پیش فرض را پس از متد معرفی فایلهای استاتیک قرار دهید:
public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); app.UseDefaultFiles();
بله. اینبار تعریف فایلهای پیش فرض، هیچ تاثیری نداشته و درخواست ریشهی سایت، بدون ذکر صریح نام فایلی، مجددا به app.Run ختم شدهاست.
توزیع فایلهای استاتیک خارج از wwwroot
همانطور که در ابتدای بحث عنوان شد، با فعال سازی UseStaticFiles به صورت پیش فرض مسیر content root/wwwroot در معرض دید دنیای خارج قرار میگیرد و توسط وب سرور قابل توزیع خواهد شد:
○ wwwroot § css § images § ... ○ MyStaticFiles § test.png
برای این منظور میتوان از پارامتر StaticFileOptions متد UseStaticFiles به نحو ذیل جهت معرفی پوشهی MyStaticFiles استفاده کرد:
app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles") });
فعال سازی مشاهدهی مرور فایلهای استاتیک بر روی سرور
فرض کنید پوشهی تصاویر را به پوشهی عمومی wwwroot اضافه کردهایم. برای فعال سازی مرور محتوای این پوشه میتوان از Middleware دیگری به نام DirectoryBrowser استفاده کرد:
app.UseDirectoryBrowser(new DirectoryBrowserOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") });
Unable to resolve service for type 'System.Text.Encodings.Web.HtmlEncoder' while attempting to activate 'Microsoft.AspNetCore.StaticFiles.DirectoryBrowserMiddleware'.
public void ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); }
مشکل! در این حالت که DirectoryBrowser را فعال کردهایم، اگر بر روی لینک فایل تصویر نمایش داده شده کلیک کنیم، باز پیام Hello DNT یا اجرای app.Run را شاهد خواهیم بود.
به این دلیل که UseStaticFiles پیش فرض، مسیر درخواستی MyImages را که بر روی file system وجود ندارد، نمیشناسد. برای رفع این مشکل تنها کافی است مسیریابی این Request Path خاص را نیز فعال کنیم:
app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") });
بررسی خلاصهی تنظیماتی که به فایل آغازین برنامه اضافه شدند
تا اینجا اگر توضیحات را قدم به قدم دنبال و اجرا کرده باشید، یک چنین تنظیماتی را خواهید داشت:
using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; namespace Core1RtmEmptyTest { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); } public void Configure(IApplicationBuilder app) { app.UseDefaultFiles(); app.UseStaticFiles(); // For the wwwroot folder // For the files outside of the wwwroot app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles") }); // For DirectoryBrowser app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") }); app.UseDirectoryBrowser(new DirectoryBrowserOptions { FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")), RequestPath = new PathString("/MyImages") }); //app.UseWelcomePage(); app.Run(async context => { await context.Response.WriteAsync("Hello DNT!"); }); } } }
UseDefaultFiles کار فعال سازی شناسایی فایلهای پیش فرضی مانند index.html را در صورت ذکر نام ریشهی یک پوشه، انجام میدهد.
اولین UseStaticFiles تعریف شده، تمام مسیرهای فیزیکی ذیل wwwroot را عمومی میکند.
دومین UseStaticFiles تعریف شده، پوشهی MyStaticFiles واقع در خارج از wwwroot را عمومی میکند.
سومین UseStaticFiles تعریف شده، پوشهی فیزیکی wwwroot\images را به مسیر درخواستهای MyImages نگاشت میکند (http://localhost:7742/myimages) تا توسط DirectoryBrowser تعریف شده، قابل استفاده شود.
در آخر هم DirectoryBrowser تعریف شدهاست.
یک نکتهی امنیتی مهم
یک چنین قابلیتی (مرور فایلهای درون یک پوشه) به صورت پیش فرض بر روی تمام IISها به دلایل امنیتی غیرفعال است. به همین جهت بهتر است Middleware فوق را هیچگاه استفاده نکنید و به این قسمت صرفا از دیدگاه اطلاعات عمومی نگاه کنید.
ساده سازی تعاریف توزیع فایلهای استاتیک
Middleware دیگری به نام FileServer کار تعریف توزیع فایلهای استاتیک را ساده میکند. اگر آنرا تعریف کنید:
app.UseFileServer();
اگر خواستید DirectoryBrowsing آنرا نیز فعال کنید، پارامتر ورودی آنرا به true مقدار دهی کنید (که به صورت پیش فرض غیرفعال است):
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseFileServer(new FileServerOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")), RequestPath = new PathString("/StaticFiles"), EnableDirectoryBrowsing = false });
توزیع فایلهای ناشناخته
اگر به سورس ASP.NET Core 1.0 دقت کنید، کلاسی را به نام FileExtensionContentTypeProvider خواهید یافت. اینها پسوندها و mime typeهای متناظری هستند که توسط ASP.NET Core شناخته شده و توزیع میشوند. برای مثال اگر فایلی را به نام test.xyz به پوشهی wwwroot اضافه کنید، درخواست آن توسط کاربر، به Hello DNT ختم میشود؛ چون در این کلاس پایه، پسوند xyz تعریف نشدهاست.
برای رفع این مشکل و تکمیل این لیست میتوان به نحو ذیل عمل کرد:
// Set up custom content types -associating file extension to MIME type var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".xyz"] = "text/html"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider }) ; // For the wwwroot folder
و یا اگر خواستید کمی تمیزتر کار کنید، بهتر است از کلاس پایه FileExtensionContentTypeProvider ارث بری کرده و سپس در سازندهی این کلاس، خاصیت Mappings را ویرایش نمود:
public class XyzContentTypeProvider : FileExtensionContentTypeProvider { public XyzContentTypeProvider() { this.Mappings.Add(".xyz", "text/html"); } }
app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = new XyzContentTypeProvider() }) ; // For the wwwroot folder
روش دیگر مدیریت این مساله، تنظیم مقدار خاصیت ServeUnknownFileTypes به true است:
app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true, DefaultContentType = "image/png" });
ASP.NET MVC #1
چرا ASP.NET MVC ؟
با وجود فریم ورک پختهای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح میشود این است: «برای چی؟». بنابراین تا به این سؤال پاسخ داده نشود، هر نوع بحث فنی در این مورد بی فایده است.
مزایای ASP.NET MVC نسبت به ASP.NET web forms
1) سادگی نوشتن آزمونهای واحد
مهمترین دلیل استفاده از ASP.NET MVC صرفنظر از تمام دلایل دیگر، بحث طراحی ویژه آن جهت ساده سازی تهیه آزمونهای واحد است. مشکل اصلی نوشتن آزمونهای واحد برای برنامههای ASP.NET web forms، درگیر شدن مستقیم با تمام جزئیات طول عمر یک صفحه است. به علاوه فایلهای code behind هر چند به ظاهر کدهای منطق یک صفحه را از کدهای HTML مانند آن جدا میکنند اما در عمل حاوی ارجاعات مستقیمی به تک تک عناصر بصری موجود در صفحه هستند (حس غلط جدا سازی کدها از اجزای یک فرم). اگر قرار باشد برای این وب فرمها و صفحات، آزمون واحد بنویسیم باید علاوه بر شبیه سازی چرخه طول عمر صفحه و همچنین رخدادهای رسیده، کار وهله سازی تک تک عناصر بصری را نیز عهده دار شویم. اینجا است که ASP.NET web forms گزینهی مطلوبی برای این منظور نخواهد بود و اگر نوشتن آزمون واحد برای آن غیرممکن نباشد، به همین دلایل آنچنان مرسوم هم نیست.
البته شاید بپرسید که این مساله چه اهمیتی دارد؟ امکان نوشتن سادهتر آزمونهای واحد مساوی است با امکان سادهتر اعمال تغییرات به یک پروژه بزرگ. تغییرات در پروژههای بزرگی که آزمون واحد ندارند واقعا مشکل است. یک قسمت را تغییر میدهید، 10 قسمت دیگر به هم میریزند. اینجا است که مدام باید به کارفرما گفت: «نه!»، «نمیشه!» یا به عبارتی «نمیتونم پروژه رو جمع کنم!» چون نمیتونم سریع برآورد کنم که این تغییرات کدام قسمتها را تحت تاثیر قرار میدهند، کجا به هم ریخت. من باید خودم سریع بتونم مشخص کنم با این تغییر جدید چه قسمتهایی به هم ریخته تا اینکه دو روز بعد زنگ بزنند: «باز جایی رو تغییر دادی، یکجای دیگر کار نمیکنه!»
2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک
در طراحی ASP.NET MVC همهجا interface ها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویسهای مستقل برای ASP.NET MVC طراحی شدهاند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمیآید؟ عوضش کنید! سیستم اعتبار سنجی توکار آنرا دوست ندارید؟ آنرا با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interface ها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی mocking (تقلید) آنها است جهت ساده سازی نوشتن آزمونهای واحد.
3) سرعت بیشتر اجرا
ASP.NET MVC یک سری از قابلیتهای ذاتی ASP.NET web forms را مانند ViewState حذف کرده است. اگر وب را جستجو کنید، برنامه نویسهای ASP.NET web forms مدام از این مساله شکایت دارند و راه حلهای مختلفی را جهت حذف یا فشرده سازی آن ارائه میدهند. ViewState در ابتدای امر جهت شبیه سازی محیط دسکتاپ در وب درنظر گرفته شده بود و مهاجرت سادهتر برنامه نویسهای VB6 به وب، اما واقعیت این است که اگر یک برنامه نویس ASP.NET web forms به اندازه آن توجهی نداشته باشد، ممکن است حجم آن در یک صفحه پیچیده تا 500 کیلوبایت یا بیشتر هم برسد. همین مساله بر روی سرعت دریافت و اجرا تاثیر گذار خواهد بود.
4) کنترلهای ASP.NET web forms آنچنان آش دهنسوزی هم نیستند!
خوب، ViewState حذف شده، بنابراین اکثر کنترلهای ASP.NET web forms هم کاربرد آنچنانی در ASP.NET MVC نخواهند داشت؛ اما واقعیت این است که اکثر اوقات اگر شروع به سفارشی سازی یک کنترل توکار ASP.NET web forms کنید تا مطابق نیازهای کاری شما رفتار کند، پس از مدتی به یک کنترل کاملا از نو بازنویسی شده خواهید رسید! بنابراین در ابتدای امر تا 80 درصد کار اینطور به نظر میرسد که به عجب سرعت بالایی در توسعه دست یافتهایم، اما هنگامیکه قرار است این 20 درصد پایانی را پر کنیم، به این نتیجه خواهیم رسید که این کنترلها با این وضع ابتدایی که دارند قابل استفاده نیستند و نیاز به دستکاری قابل ملاحظهای دارند تا نیازهای واقعی کاری را برآورده کنند.
5) کنترل کامل بر روی HTML نهایی تولیدی
اگر علاقمند به کار با jQuery باشید، مدام نیاز خواهید تا با ID کنترلها و عناصر صفحه کار کنید. پیشتر ASP.NET web forms این ID را یک طرفه و به صورت مقدار منحصربفردی تولید میکرد که جهت کار با فریم ورکهای جاوا اسکریپتی عموما مشکل ساز بود. البته ASP.NET web forms در نگارشهای جدید خود مشکل عدم امکان مقدار دهی ClientId سفارشی را برای کنترلهای وب خود برطرف کرده است و این مورد را میتوان دستی هم تنظیم کرد ولی در کل باز هم آنچنان کنترلی رو خروجی HTML نهایی کنترلهای تولیدی نیست مگر اینکه مانند مورد چهارم یاد شده یک کنترل را از صفر بازنویسی کنید!
همچنین اگر باز هم بیشتر با jQuery و ASP.NET web forms کار کرده باشید میدانید که jQuery آنچنان سنخیتی با ViewState و Postback وب فرمها ندارد و همین مساله عموما مشکلزا است. علاوه بر آن اخیرا مایکروسافت توسعه ASP.NET Ajax خود را تقریبا در حالت تعلیق و واگذار شده به شرکتهای ثالث درآورده است و توصیه آنها استفاده از jQuery Ajax است. اینجا است که مدل ASP.NET MVC سازگاری کاملی را با jQuery Ajax دارد هم از لحاظ نبود ViewState و هم از جنبهی کنترل کامل بر روی markup نهایی تولیدی.
یا برای مثال خروجی پیش فرض یک GridView، جدول HTML ایی است که این روزها همهجا علیه آن صحبت میشود. البته یک سری آداپتور CSS friendly برای اکثر این کنترلها موجود است و ... باز هم دستکاری بیش از حد کنترلهای پیش فرض جهت رسیدن به خروجی دلخواه. تمام اینها را در ASP.NET MVC میشود با معادلهای بسیار باکیفیت افزونههای jQuery جایگزین کرد و از همه مهمتر چون ViewState و مفاهیمی مانند PostBack حذف شده، استفاده از این افزونهها مشکل ساز نخواهد بود.
6) استفاده از امکانات جدید زبانهای دات نتی
طراحی اصلی ASP.NET web forms مربوط است به دوران دات نت یک؛ زمانیکه نه Generics وجود داشت، نه LINQ و نه آنچنان مباحث TDD یا استفاده از ORMs متداول بود. برای مثال شاید ایجاد یک strongly typed web form الان کمی دور از ذهن به نظر برسد، زمانیکه اصل آن بر مبنای بکارگیری گسترده datatable و dataset بوده است (با توجه به امکانات زبانهای دات نتی در آن دوران). بنابراین اگر علاقمند هستید که این امکانات جدید را بکاربگیرید، ASP.NET MVC برای استفاده از آنها طراحی شده است!
7) از ASP.NET web forms سادهتر است
طراحی ASP.NET MVC بر اساس ایده Convention over configuration است. به این معنا که اجزای آن بر اساس یک سری قرار داد در کنار هم مشغول به کار هستند. مشخص است View باید کجا باشد، نام کنترلرها چگونه باید تعیین شوند و قرار داد مرتبط به آن چیست، مدل باید کجا قرار گیرد، قرار داد پردازش آدرسهای صفحات سایت به چه نحوی است و الی آخر. خلاصه در بدو امر با یک فریم ورک حساب شده که شما را در مورد نحوه استفاده صحیح از آن راهنمایی میکند، مواجه هستید.
به همین ترتیب هر پروژه MVC دیگری را هم که مشاهده کنید، سریع میتوانید تشخیص دهد قراردادهای بکارگرفته شده در آن چیست. بنابراین اگر قرار است ASP.NET را امروز شروع کنید و هیچ سابقهای هم از وب فرمها ندارید، یک راست با ASP.NET MVC شروع کنید.
8) محدود به پیاده سازی مایکروسافت نیست
پیاده سازیهای مستقلی هم از ASP.NET MVC توسط اشخاص و گروههای خارج از مایکروسافت وجود دارد: ^، ^، ^، ^ و ...
و در پایان یکی دیگر از دلایل سوئیچ به ASP.NET MVC ، «یاد گرفتن یک چیز جدید است» یا به عبارتی فرا گرفتن یک روش دیگر برای حل مسایل، هیچگاه ضرری را به همراه نخواهد داشت که هیچ، بلکه باعث بازتر شدن میدان دید نیز خواهد گردید.
یک دیدگاه دیگر
ASP.NET MVC برای شما مناسب نخواهد بود اگر ...
1) با پلیمرفیزم مشکل دارید.
ASP.NET MVC پر است از interfaces، abstract classes، virtual methods و امثال آن. بنابراین اگر تازه کار هستید، ابتدا باید مفاهیم شیءگرایی را تکمیل کنید.
2) اگر نمیتوانید فریم ورک خودتون رو بر پایه ASP.NET MVC بنا کنید!
ASP.NET MVC برخلاف وب فرمها به همراه آنچنان تعداد بالایی کنترل و افزونه از پیش مهیا شده نیست. در بدو امر شما فقط یک سری url helper، html helper و ajax helper ساده را خواهید دید؛ این نقطه ضعف ASP.NET MVC نیست. عمدا به این نحو طراحی شده است. همانطور که عنوان شد اکثر اجزای این فریم ورک قابل تعویض است. بنابراین دست شما را باز گذاشته است تا با پیاده سازی این اینترفیسها، امکانات جدیدی را خلق کنید. البته پس از این چندین و چند سال که از ارائه آن میگذرد، به اندازه کافی افزونه برای ASP.NET MVC طراحی شده است که به هیچ عنوان احساس کمبود نکنید یا اینکه نیازی هم نداشته باشید تا آنچنان فریم ورک خاصی را بر پایه ASP.NET MVC تهیه کنید. برای مثال پروژه MvcContrib موجود است یا شرکت telerik یک مجموعه سورس باز کامل مخصوص ASP.NET MVC را ارائه داده است و الی آخر.
3) اگر نمیتوانید از کتابخانههای سورس باز استفاده کنید.
همانطور که عنوان شد ASP.NET MVC به همراه کوهی از کنترلها ارائه نشده است. اکثر افزونههای آن سورس باز هستند و کار با آنها هم دنیای خاص خودش را دارد. چگونه باید کتابخانههای مناسب را پیدا کرد، کجا سؤال پرسید، کجا باگ گزارش داد، چگونه مشارکت کرد و غیره. خلاصه منتظر یک بسته شکیل حاضر و آماده نباید بود. خود ASP.NET MVC هم تحت مجوز MSPL به صورت سورس باز در دسترس است.
و یک نکته تکمیلی
مایکروسافت مدتی است شروع کرده به پرورش و زمزمه ایده «یک ASP.NET واحد». به عبارتی قصد دارند در یکی دو نگارش بعد، این دو (وب فرم و MVC) را یکی کنند. هم اکنون اگر مطالب وبلاگها را مطالعه کنید زیرساخت آن به نام ASP.NET Web API آماده شده است و در مرحله بتا است. نکته جالب اینجا است که این Web API امکان تعریف یکپارچه و مستقیم کنترلرهای MVC را در وب فرمها میسر میکند. ولی باز هم نام آن Controller است یعنی جزئی از ASP.NET MVC و کسی میتواند از آن استفاده کند که با MVC مشکلی نداشته باشد. بنابراین یادگیری MVC هیچ ضرری نخواهد داشت و جای دوری نخواهد رفت!