مدت نسبتا زیادی است که ذهن من مشغول جزییات این مطلب بوده و متاسفانه به دلایل زیادی امکان پرداختن به آن میسر نشد.
در این فرصت، به بهانه بررسی ساختار پیش فرض یک پروژه ASP .NET MVC سعی خواهم کرد که نقش اجزای مختلف این پروژه را مشخص کرده و با توجه به بضاعت اندک خود، در مورد ارتباط این اجزا توضیحاتی را تقدیم علاقمندان نمایم. پیشاپیش از توجه دوستان و همکاران محترم سپاسگذارم.
به منظور کالبد شکافی یک پروژه ساده MVC و بررسی ارتباط بین اجزای مختلف موجود در Template پیش فرض این پروژه ها به نظر میرسد که توجه به نقش و عملکرد فایل Global.asax به عنوان نقطه شروع این بررسی ، بسیار مفید و آموزنده می باشد.
در واقع فایل Global.asax که به نام ASP .NET Application File نیز شناخته میشود از همان ابتدای تولد تکنولوژی Active Server Page که امروزه به نام ASP کلاسیک شناخته میشود به صورت فایلی با نام Global.asa و بعدها در سال 2003 با ظهور تکنولوژی ASP .NET Web Form به صورت Global.asax به عنوان یک ویژگی Optional و اختیاری به همراه پروژه های ASP .NET مورد استفاده قرار گرفته است. قبل از توضیحاتی در مورد نقش این فایل لازم به ذکر است که تقریبا مقارن با معرفی .NET Framework 3.5 مایکروسافت در ی: اقدام غافلگیرانه به طور کلی معماری Internet Information Server را تغییر داده و با معرفی Integrated Mode در IIS عملا در ساده ترین شکل ممکن این امکان را فراهم کرد که برنامه نویسان نرم افزارهای کاربردی وب با استفاده از رویدادها و Event های تدارک دیده شده در Global.asax و یا در سطوح بالاتر با ایجاد Module های اختصاصی در مراحل مختلف پردازش IIS نظارت و یا در صورت نیاز دخالت نمایند.
خود تکنولوژی MVC محصوصل چنین نگرش متفاوتی میباشد که امکان تغییرات بنیادی در معماری برنامه های مبتنی بر وب را پدید آورد . موضوع رویدادها و Stage های متعددی که در طول مسیر پردازش IIS وجود دارد (این مسیر معمولا به اختصار IIS Processing Pipeline نامیده می شود) موضوع بسیار جالبی برای برنامه نویسانی است که نیاز به کنترل بیشتر در برنامه های مبتنی بر وب (به عنوان مثال ایجاد ماژول های سفارشی Authenticate و Authorize) دارند و در اینجا مجال پرداختن به آن وجود ندارد.
در یک جمع بندی خلاصه می توان چنین گفت که Global.asax امکان مشارکت یا اصطلاحا Subscribe در مراحل مختلف IIS Processing Pipeline که همان مسیر یا مقاطع متعدد و متنوع پردازش در تکنولوژی ASP .NET است ) از مرحله دریافت Request تا مرحله پایانی ارسال Response) را فراهم میکند. در زیر لیستی از رویداد های Synchronous را مشاهده میکنید:
1. BeginRequest
2. AuthenticateRequest
3. PostAuthenticateRequest
4. AuthorizeRequest
5. PostAuthorizeRequest
6. ResolveRequestCache
7. PostResolveRequestCache
8. MapRequestHandler
9. PostMapRequestHandler
10. AcquireRequestState
11. PostAcquireRequestState
12. PreRequestHandlerExecute
اینجا مرحله ایست که پردازش های Page Handler در ASP .NET آغاز میشود
13. PostRequestHandlerExecute
14. ReleaseRequestState
15. PostReleaseRequestState
16. UpdateRequestCache
17. PostUpdateRequestCache
18. LogRequest
19. PostLogRequest
20. EndRequest
دلیل اصلی اینکه ما Global.asax را به عنوان نقطه شروع کالبد شکافی پروژه های MVC انتخاب کردیم این است که طراحان MVC جزییات مربوط به منطق Config و تنظیمات پایه و عمل ثبت و Register کردن آنها را در رویداد Application_Start فایل Global.asax قرار داده اند که چنانچه از نام این رویداد نیز مشخص است یک رویداد Application Level محسوب شده و به ازای هر بار اجرای Application (فراموش نکنیم که رایج ترین روش اجرای یک Application مبتنی بر وب ، درخواست یا Request می باشد که به طور معمول با تایپ آدرس یا URL برنامه در مرورگر انجام میشود) متد وابسته به این رویداد اجرا خواهد شد.
protected void Application_Start(){
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
در میان مواردی که در کد مشاهده میکنید در حال حاضر فراخوانی متد استاتیک RegisterRoutes کلاس RouteConfig بیشتر از سایرین مورد توجه ما می باشد .
اما در یک نگاه اجمالی واضح است که منشاء بسیاری از تنظیمات و Config اولیه MVC از همینجا آغاز میشود. به عنوان مثال RegisterBundles عملیات بسته بندی بخشهای استاتیک سایت ، مواردی نظیر Stylesheet و کد های Client-side و کتابخانه های JQuery را بر عهده دارد. هدف از این بسته بندی در درجه اول این است که اطلاعات این فایلهای استاتیک را ضمن نوعی فشرده سازی در یک بسته واحد متمرکز نموده و استفاده از آنها در مراحل مختلف پروژه با سرعت و کیفیت بالاتری میسر گردد. همچنین متد استاتیک RegisterAuth وظیفه الحاق کتابخانه های مورد نیاز برای استفاده از روش OAuth را در MVC فراهم میسازد .
نکته : همینطور که در مسیر این بررسی جلوتر می رویم، بهانه های متعددی برای انحراف از مسیر اصلی وجود دارد که من عمدا بدون توجه به آنها فقط به مواردی که جهت ایجاد یک دیدگاه کلی از پروژه های MVC الزامی هستند توجه خواهم کرد .
چنانچه اشاره شد فراخوانی متد استاتیک RegisterRoute نقطه آغاز تنظیمات Routhing در MVC می باشد که در حال حاضر موضوع اصلی بحث ما خواهد بود.
یقین دارم که ارائه یک معادل فارسی برای Routhing هیچ کمکی به درک نقش آن نخواهد کرد پس بنابراین من در سرتاسر این مطلب صرفا از همین اصطلاح استفاده خواهم کرد و در اینجا فقط به وظایف و نقش بسیار کلیدی آن خواهم پرداخت .
مفهوم Routhing در MVC به طور مشخص برای تفسیر URL و قسمت های مختلف آن که Segment یا قطعه نامیده میشوند به کار میرود. چنانچه از قبل مطلع هستیم به طور معمول یک URL معتبر معمولا دارای Segment های مختلفی است که هر Segment با استفاده از علامت / از سایرین متمایز میشود .
تا قبل از MVC و مخصوصا در ASP .NET Web Form با ملاحظه یک URL نمونه مثل :
http://example.com/albums/list.aspx?catid=17313&genreid=33723
اطلاعات بسیار مفیدی در آن مشاهده میکنیم که صرفنظراز بسیاری از جزییات موجود در آن، توجه به این نکته مهم ضروری است که تا قبل از MVC یک تناظر مشخص بین URL و ساختار فایلها و فولدرهای Application وجود داشته و در URL مثال قبل بدون هیچ تردیدی می توان دریافت که قطعا فایلی به نام List.aspx در فولدری به نام Albums در یک Domain ثبت شده به نام Example.com وجود دارد که کاربر با تایپ URL فوق ، با استفاده از پروتکل http قصد دریافت سرویس (یا اجرای برنامه) موجود در آدرس مذکور را داشته است. مجددا آنچه از ظواهر امر پیداست، این است که کاربر با ارسال اطلاعاتی که بعد از علامت ? قرار دارد اصطلاحا با ارسال یک Query String قصد دارد لیستی از آلبومهای خاصی از یک موضوع یا ژانر خاص را مشاهده نماید.
در MVC این تناظر ساختار فایلها با فولدرها دیگر معتبر نبوده (یا حداقل به شدت قبل نیست) و همه چیز وابسته به تفسیری است که Routhing انجام داده و حاصل تفسیر این اطلاعات در این تکنولوژِی (یعنی MVC) مستقیما در اختیار Controller قرار داده میشود که به نحوی که به آن اشاره خواهیم کرد، مسیر پردازش و نحوه ارسال پاسخ یا Response را مشخص نماید .
پس در یک جمع بندی در ابتدا فایل Global.asax در رویداد Application_OnStart متد استاتیک RegisterRoutes را فراخوانی میکند . پارامتر این متد یعنی RouteTable.Routes یک نمونه یا Instance از کلاس RouteCollection می باشد که در یک تعبیر ساده، کلکسیونی از مسیر های قابل درک برنامه ما را در خود ذخیره میکند. این کلکسیون شامل آیتم هایی است که به طور معمول در MVC ما برای پردازش آنها به ازای هر یک از آنها متدی از کلاس Controller را در نظر گرفته ایم . البته باید توجه داشت که در MVC کماکان امکان پردازش فایلهای استاتیک پیش بینی شده است و ما در مورد این موضوع در مباحث مربوط به View بیشتر صحبت خواهیم کرد .
در فولدر App_Start کلاس RouteConfig وظیفه ثبت مسیرهای مورد نظر در برنامه ما را به عهده دارد در پیاده سازی پیش فرض این کلاس ما بوضوح مشاهده خواهیم کرد که در طول فراخوانی متد RegisterRoute که در Global.asax انجام میشود چه عملیاتی انجام میشود:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
در ابتدا و در سطر اول، ملاحظه میکنید که فراخوانی متد IgnoreRoute ظاهرا از هر گونه دسترسی مستقیم کاربر به فایلهای Resource مشخص شده با پسوند .axd جلوگیری میکند. شاید جالب باشد که بدانید در واقع عملا هیچ فایلی با پسوند axd در پروژه ما وجود ندارد!!!! اما در ASP .NET با استفاده از یک ترفند ویژه به طور معمول برخی از اطلاعات استاتیک پروژه نظیر کد های Javascript و حتی Image ها که اغلب آنها را Resource یا منابع پروژه می نامیم به صورت Dynamic در قالب فایلهایی با پسوند .axd تبدیل شده و برای تکمیل پروسه Render و نمایش در Browser نقش آفرینی میکنند .
اما اولین و مهمترین مسیر مجاز با فراخوانی MapRoute در RouteCollection ثبت میگردد که اصطلاحا به آن مسیر پیش فرض یا Default Route گفته میشود . این وضعیت تا حد بسیار زیادی مشابه با تعیین Defaullt Document در ASP .NET Web Form می باشد که خوانندگان محترم قطعا با این مفهوم آشنا می باشند .
آنچه به طور دقیق توسط فراخوانی متد MapRoute در سطر دوم انجام میشود این است که ما به عنوان طراح اصلی برنامه مشخص میکنیم که URL مورد نظر برای دستیابی به برنامه ما می تواند حداکثر شامل سه Segment یا قطعه باشد که به ترتیب با استفاده از سه Placeholder (نام جایگزین) با نامهای Controller و action و id آنها را مشخص کرده ایم. اسامی جایگزین یا همان Placeholder ها در داخل یک جفت {} تایپ شده و هر Segment با یک علامت / از دیگری جدا شده است .
همچنین در ادامه برای هر یک از این سه قطعه قواعدی مشخص نموده ایم که مشخصات این قواعد را به طور دقیق در defaults به طور واضح بیان میکنیم. ما برای controller مقدار پیش فرض home و برای action (که در واقع متد های Public موجود در کلاس Controller به عنوان جزء اصلی پردازش هر مسیر هستند) مقدار پیش فرض Index را در نظر گرفته ایم. با ذکر UrlParameter.Optional ، صریحا ذکر id را اختیاری یا Optional تعیین کرده ایم .
اگر فرض کنیم که نام پروژه ما MVCPreview باشد در این صورت مسیر های زیر برای برنامه ما مجاز محسوب خواهند شد :
1) http://MVCPreview/Home/Index/210
2) http://MVCPreview/Home/Index/
3) http://MVCPreview/Home
4) http://MVCPreview/
در حالت اول نتیجه پردازش Routhing این است که برای Placeholder های id و action و controller به ترتیب مقادیر 210 و index و Home تعیین شده است . در واقع URL مذکور شامل سه Segment می باشد که با شرط ذکر شده در فراخوانی متد MapRoute (یعنی "{controller}/{action}/{id}") کاملا منطبق است . به عبارت ساده تر در URL مذکور ما صریحا مشخص کرد ه ایم که مسیر پردازش به متد Index (که نقش action را عهده دار است) در کلاس Home (که نقش Controller را ایفا میکند) هدایت شده و مقدار 210 به عنوان پارامتر و جایگزین id در اختیار این متد قرار بگیرد .
در حالت دوم مقدار جایگزین برای id ذکر نشده که با توجه به UrlParameter.Optional توسط Routhing همچنان به عنوان یک مسیر معتبر تفسیر شده و مسیر پردازش به متد Index در Controller هدایت خواهد شد.
در حالت سوم از ذکر نام action خود داری شده ولی باز به دلیل ارائه مقدار Default در فراخوانی MapRoute یعنی درست با ذکر action = "Index" مفسر Routhing به صورت خودکار جای خالی action را با مقدار index تامین خواهد کرد. بنابراین مجددا این URL نیز توسط Routhing معتبر و مجاز تشخیص داده شده و مسیر پردازش درست به مانند دو حالت قبل ادامه خواهد یافت.
در حالت چهارم هر سه Segment غایب هستند و این بار هم مجددا عدم وجود Controller توسط controller = "Home" جبران شده و مقدار Home در اینجا مشخص میکند که عدم ذکر نام Controller به معنی انتخاب Controller ی به نام Home خواهد بود و نتیجه آنکه مجددا این URL نیز معتبر و مجاز تشخیص داده شده و مسیر پردازش به متد Index در کلاس Controller ما یعنی Home هدایت میشود .
....ادامه دارد