طی آخرین پروژه ای اجرا کردم، در ابتدا به خاطر ساختار کاری که داشتم مشکلات فراوانی داشتم که با استفاده از http module ها و http handler ها این کارها رو خیلی راحت تر از اونی که فکرش رو میکردم انجام دادم. اما در طول اجرای این پروژه یه نکته ای توجه منو جلب کرد و اون این بود که خیلی از برنامه نویس های ما از وجود ماژول ها و هندلر ها بی خبر هستن و ازش استفاده نمکنن و یا اگر مطلع هستن، فقط از اونها در حد سمپل های موجود استفاده کردن و در مورد عملکرد اونها خیلی کم مطلع هستن. برای همین تصمیم گرفتم طی چند مقاله به معرفی این امکانات و استفاده از اون ها بپردازم و امیدوارم که به دردتون بخوره.
در قسمت اول به معرفی کلی HTTP Modules و HTTP Handlers میپردازم. در قسمت های بعد تعدادی از ماژول ها و هندلرهایی که در طول کار خودم بهشون نیاز پیدا کردم رو به صورت عملی با هم بررسی میکنیم.
معرفی HTTP Modules و HTTP Handlers:
هرگاه یک درخواست به یک برنامه ASP.NET ارسال میشود، این درخواست به وسیله HTTP Handlerدریافت میگردد و پردازش لازم صورت میگیرد و پاسخ برای کاربر ارسال میگردد. عمومی ترین HTTP Handler موجود، مربوط به پردازش صفحات aspx میباشد. هنگامی که درخواستی به یک صفحه ارسال میگردد این درخواست به وسیله HTTP Handler پردازش میشود. در صورت نیاز میتوانید HTTP Handler شخصی خود را توسعه دهید تا خروجی مورد نیاز خود را ایجاد کنید.
HTTP Module یک اسمبلی است که هرگار درخواستی به سایت شما ارسال شود فراخوانی میگردد. HTTP Module به شما این اجازه را میدهد تا روی ورودی و خروجی ایجاد شده در صورت نیاز تغییرات خود را اعمال کنید.
مثال های عملی:
HTTP Handlers:
rss feed: میتوانید یک HTTP Handler ایجاد کنید که درخواست هایی که پسوند rssدارند را پردازش نماید و در آن HTTP Handler فید مربوط به سایت خود را به کاربر نمایش دهید.
image server: اگر میخاهید تصاویر سایت خود را در سایز های مختلفی به کاربر نمایش، در این صورت میتوانید یک HTTP Handler ایجاد کنید که مسئولیت تغییر سایز و نمایش تصاویر را به عهده داشته باشد.
HTTP Modules:
امنیت: به خاطر اینکه میتوانید قبل از هر کاری به یک درخواست ورودی دسترسی داشته باشید، میتوانید قبل از ورود درخواست به برنامه خود، چک های امنیتی خود را انجام دهید و اعمال مناسب را پیاده سازی کنید.
لاگ کردن: همانطور که قبلا گفتیم در هر درخواستی این ماژول ها اجرا میشوند، پس میتوانید برای اعمالی که روی هر درخواستی باید انجام شود مانند لاگ کردن و بررسی و ثبت وضعیت ها، یک httpmodule بنویسید و این کار را به صورت مرکزی انجام دهید به جای اینکه در تک تک قسمت های برنامه این کارها را پیاده سازی کنید.
قابلیت ها و ویژگی ها:
قابلیت ها و خواص HTTP Modules و HTTP Handlers در زیر آمده است
1- برای توسعه HTTP Modules و HTTP Handlers باید از اینترفیس های IHttpHandler و IHttpModule استفاده کنیم.
2- برای توسعه یک http handler غیر همزمان از IHttpAsyncHandler استفاده میکنیم.
3- سور کد ماژول ها و هندلر هایی که توصعه میدهیم میتواند در فولدر App_Code قرار گیرد یا میتوانیم آنها را در یک پروژه دیگر توسعه دهیم و به نرم افزار خود اضافه کنیم.
4- ماژول ها و هندلر های توسعه داده شده برای IIS 6 میتوانند با اندک تغییراتی یا بدون تغییر در iis7 مورد استفاده قرار گیرند.
5- در IIS7 ماژول ها نه تنها میتوانند با درخواست های برنامه مورد استفاده قرار گیرند. بلکه میتوانند با تمامی درخواست های ارسال شده به IIS استفاده شوند.
پیشینه:
HTTP Handlers
یک HTTP Handler پردازشی است که در پاسخ به یک درخواست که برای یک برنامه ASP.NET ارسال میشود اجرا میگردد. عمومی ترین هندلر موجود مربوط به پاسخ به درخواست هایی است که برای یک صفحه aspx ارسال میگردد. هنگامی که کاربر درخواستی برای یک صفحه ارسال میکند این درخواست توسط یک page handler پاسخ داده میشود.
ASP.NET شامل چندین نوع مختلف handler است. هر کدام از این handler ها میتوانند به یک درخواست خواص یا گروهی از درخواست های ورودی پاسخ دهند. در ذیل لیستی از انواع handler های موجود را میبینید.
ASP.NET page handler (*.aspx): یک هندلر عمومی است که مسئولیت پردازش درخواست های مربوط به صفحات را بر عهده دارد.
Web service handler (*.asmx): هندلر عمومی برای پردازش درخواست هایی که به وب سرویس ها ارسال میگردد.
Generic Web handler (*.ashx): یک هندلر عمومی است که تمامی درخواست هایی که هیچ UI ندارند را پردازش میکند.
Trace handler (trace.axd): هندلری است که اطلاعات تریس را نمایش میدهد.
ایجاد یک HTTP Handler:
برای ایجاد یک HTTP Handler کافی است کلاسی ایجاد کنید و یکی از اینتر فیس های IHttpHandler یا IHttpAsyncHandler را پیاده سازی کنید. این اینترفیس ها شامل 2 متد میباشند. IsReusable که اگر مقدار آن برابر با true باشد این هندلر را در Pool قرار میدهد و در دفعات بعدی از آن استفاده میکند و باعث افزایش سرعت میشود و در غیر این صورت با هر درخواست یک شی جدید از این هندلر ایجاد میشود. متد ProcessRequest برای پاسخ به درخواست کاربر استفاده میشود و هنگام ارسال درخواست این متد اجرا میگردد.
در یک http handler به application context دسترسی داریم و میتوانیم از خواص application context استفاده کنیم.
مپ کردن پسوند فایل ها:
هنگامی که یک http hanler ایجاد میکنید، این هندلر میتواند به هر درخواستی با هر پسوندی پاسخ دهد. برای مثال اگر هندلری بسازید که بخواهید تنها به درخواست هایی که پسوند rss دارند پاسخ دهد باید در IIS این پسوند را تنظیم کنید که به برنامه ASP.NET شما ارسال شود و در فایل web.config نیز این پسوند را به handler مخصوص خود ارسال کنید.
در صورتی که یک generic handler با پسوند ashx ایجاد کنید این هندلر به صورت اتوماتیک به وسیله IIS و ASP.NET شناخته خواهد شد.
برای ثبت هندلر های خود در IIS میتوانید از به اینجا و اینجا مراجعه کنید.
http handler های همزمان و غیر همزمان:
هندلرهای همزمان یا Synchronous آنهایی هستند که وقتی درخواستی به آنها ارسال میگردد تا زمانی که پردازش آن درخواست پایان یابد هیچ بازگشتی نخواهند داشت. اما هندلرهای غیرهمزمان آنهایی هستند که به پاسخ ارسال شده به کاربر وابستگی ندارند. این دسته از هندلرها زمانی مورد استفاده قرارمیگرند که بعد از ارسال یک درخواست که زمان پردازش آن نیز زیاد است، لزومی ندارد کاربر در انتظار پایان پردازش آن درخواست بماند و به سادگی میتواند به کار خود ادامه دهد.
HTTP Moduleها:
HTTP module یک اسمبلی است که با هر درخواستی که به برنامه ارسال میشود، اجرا میگردد. HTTP module به عنوان بخشی از خط لوله درخواست(منظور همون request pipeline هست D:) اجرا میگردد و به تمامی رخداد هایی که در طول عمر درخواست اتفاق می افتد درسترسی دارد. به همین دلیل ماژول ها به شما این امکان را میدهند که درخواست های ورودی را مدیریت کنید و یا قبل از ارسال پاسخ به کاربر تغییراتی در پاسخ ایجاد کنید.
HTTP modules از جهاتی شبیه به ISAPI فیلتر ها هستند چون با هر درخواستی اجرا میشوند با این تفاوت که در یک محیط مدیریت شده پیاده سازی شده اند و با چرخه حیات درخواست های برنامه های ASP.NETکاملا منطبق هستند.
ASP.NET برای اهداف مختلفی از این ماژول ها استفاده میکند مانندforms authentication, caching, session state و client script services. در هر شرایطی در صورتی که یکی از این امکانات فعال شوند، بدون اینکه نیاز باشد در هر صفحه ای این امکانات را فراخوانی کنیم، به صورت اتوماتیک این امکانات برای ما فعال میشود. ماژول ها میتوانند به رخ دادهای برنامه دسترسی داشته باشند و یا میتوانند رخ دادهایی ایجاد کنند و در Global.asax میتوان به این رخ داد ها رسیدگی کرد.
نکته: ماژول ها و handler ها با هم تفاوت های اساسی دارند. یک هندلر برای یک نوع خاص درخواست ایجاد میشود و به آن پاسخ میدهد ولی یک ماژول با هر درخواستی که به سایت میآید اجرا میشود. به طور کلی در مواردی از ماژول ها استفاده میکنیم که بخواهیم یک کار عمومی را در کل برنامه اجرا کنیم.
HTTP Module ها چگونه کار میکنند:
برای اینکه ماژول ها شروع به کار کنند باید آن ها را تنظیم و فعال کنیم. عمومی ترین روش به کارگیری ماژول ها استفاده از web.config جهت تنظیم آنها است.
هنگامی که یک نسخه از HttpApplication ایجاد میشود یک نمونه از تمامی ماژول های ثبت شده نیز ایجاد میگردد. هنگامی که یک ماژول ایجاد میشود متد Init آن فراخوانی شده و ماژول خودش را آماده به کار میکند. برای اطلاعات بیشتر به اینجا و اینجا مراجعه کنید. در متد Init شما میتوانید رخ داد های مختلفی را که در برنامه اتفاق می افتد به متدهایی در ماژول خود مقید کنید و با اتفاق افتادن آن رخ دادها، عملی دلخواه انجام دهید مانند چک کردن دسترسی ها یا لاگ کردن موارد دلخواه. یک ماژول به Context درخواست دسترسی دارد و این به شما کمک میکند تا تغییرات دلخواه خود را وارد کنید، مثلا درخواست را به جای دیگری Redirect کنید.
HTTP Module ها در برابر Global.asax
بسیاری از قابلیت هایی را که شما در ماژول ها در اختیار دارید در Global.asax نیز قابل دسترسی است و به شما این قابلیت را میدهد که اعمالی در سطح برنامه اجرا کنید. اما ماژول ها مزایایی در اختیار شما میگذارند که شما را ترغیب میکند به جای استفاده از Global.asax از آنها استفاده کنید.
شما میتواندی یک ماژول را ایجاد کنید و در برنامه های مختلفی استفاده کنید. البته در مقابل Global.asax نیز به شما این قابلیت را میدهد که از رخ دادهایی مانند Session_Start و Session_End باخبر شوید و یا یک شی را ایجاد کنید که در کل برنامه مورد استفاده قرار گیرد.
در شرایطی که نیاز به قابلیتی در سطح برنامه دارید و موارد زیر نیز محقق شده است میتوانید از ماژول ها استفاده کنید:
1- میخاهید از این کد در سایر برنامه ها نیز استفاده کنید.
2-نمیخاهید کدهای پیچیده ای درون فایل Global.asax قرار دهید.
3-این امکانات در تمامی درخواست های برنامه نیاز میباشد.
اگر شما نیاز به کدی داشتید که میخاهید با رخ دادهای مختلفی برنامه فعال شود و نیاز به استفاده از آن در سایر برنامه ها نیست و یا میخاهید با رخدادی کار کنید که در ماژول ها در دسترسی نیستند میتوانید از Global.asax استفاده کنید.
ایجاد یک HTTP Module:
رویه ایجاد یک Http Module به شرح زیر میباشد.
1- یک کلاس ایجاد کنید که IHttpModule را پیاده سازی کند.
2- متد Init را ایجاد کنید.
3- متدهایی که برای رخدادهای مختلف ثبت کرده اید را پیاده سازی کنید.
4- در صورت نیاز متد Dispose را پیاده سازی کنید.
5- ماژول را در فایل web.Config ثبت کنید.
منبع HTTP Handlers and HTTP Modules Overview
پایان قسمت اول.
در قسمت اول به معرفی کلی HTTP Modules و HTTP Handlers میپردازم. در قسمت های بعد تعدادی از ماژول ها و هندلرهایی که در طول کار خودم بهشون نیاز پیدا کردم رو به صورت عملی با هم بررسی میکنیم.
معرفی HTTP Modules و HTTP Handlers:
هرگاه یک درخواست به یک برنامه ASP.NET ارسال میشود، این درخواست به وسیله HTTP Handlerدریافت میگردد و پردازش لازم صورت میگیرد و پاسخ برای کاربر ارسال میگردد. عمومی ترین HTTP Handler موجود، مربوط به پردازش صفحات aspx میباشد. هنگامی که درخواستی به یک صفحه ارسال میگردد این درخواست به وسیله HTTP Handler پردازش میشود. در صورت نیاز میتوانید HTTP Handler شخصی خود را توسعه دهید تا خروجی مورد نیاز خود را ایجاد کنید.
HTTP Module یک اسمبلی است که هرگار درخواستی به سایت شما ارسال شود فراخوانی میگردد. HTTP Module به شما این اجازه را میدهد تا روی ورودی و خروجی ایجاد شده در صورت نیاز تغییرات خود را اعمال کنید.
مثال های عملی:
HTTP Handlers:
rss feed: میتوانید یک HTTP Handler ایجاد کنید که درخواست هایی که پسوند rssدارند را پردازش نماید و در آن HTTP Handler فید مربوط به سایت خود را به کاربر نمایش دهید.
image server: اگر میخاهید تصاویر سایت خود را در سایز های مختلفی به کاربر نمایش، در این صورت میتوانید یک HTTP Handler ایجاد کنید که مسئولیت تغییر سایز و نمایش تصاویر را به عهده داشته باشد.
HTTP Modules:
امنیت: به خاطر اینکه میتوانید قبل از هر کاری به یک درخواست ورودی دسترسی داشته باشید، میتوانید قبل از ورود درخواست به برنامه خود، چک های امنیتی خود را انجام دهید و اعمال مناسب را پیاده سازی کنید.
لاگ کردن: همانطور که قبلا گفتیم در هر درخواستی این ماژول ها اجرا میشوند، پس میتوانید برای اعمالی که روی هر درخواستی باید انجام شود مانند لاگ کردن و بررسی و ثبت وضعیت ها، یک httpmodule بنویسید و این کار را به صورت مرکزی انجام دهید به جای اینکه در تک تک قسمت های برنامه این کارها را پیاده سازی کنید.
قابلیت ها و ویژگی ها:
قابلیت ها و خواص HTTP Modules و HTTP Handlers در زیر آمده است
1- برای توسعه HTTP Modules و HTTP Handlers باید از اینترفیس های IHttpHandler و IHttpModule استفاده کنیم.
2- برای توسعه یک http handler غیر همزمان از IHttpAsyncHandler استفاده میکنیم.
3- سور کد ماژول ها و هندلر هایی که توصعه میدهیم میتواند در فولدر App_Code قرار گیرد یا میتوانیم آنها را در یک پروژه دیگر توسعه دهیم و به نرم افزار خود اضافه کنیم.
4- ماژول ها و هندلر های توسعه داده شده برای IIS 6 میتوانند با اندک تغییراتی یا بدون تغییر در iis7 مورد استفاده قرار گیرند.
5- در IIS7 ماژول ها نه تنها میتوانند با درخواست های برنامه مورد استفاده قرار گیرند. بلکه میتوانند با تمامی درخواست های ارسال شده به IIS استفاده شوند.
پیشینه:
HTTP Handlers
یک HTTP Handler پردازشی است که در پاسخ به یک درخواست که برای یک برنامه ASP.NET ارسال میشود اجرا میگردد. عمومی ترین هندلر موجود مربوط به پاسخ به درخواست هایی است که برای یک صفحه aspx ارسال میگردد. هنگامی که کاربر درخواستی برای یک صفحه ارسال میکند این درخواست توسط یک page handler پاسخ داده میشود.
ASP.NET شامل چندین نوع مختلف handler است. هر کدام از این handler ها میتوانند به یک درخواست خواص یا گروهی از درخواست های ورودی پاسخ دهند. در ذیل لیستی از انواع handler های موجود را میبینید.
ASP.NET page handler (*.aspx): یک هندلر عمومی است که مسئولیت پردازش درخواست های مربوط به صفحات را بر عهده دارد.
Web service handler (*.asmx): هندلر عمومی برای پردازش درخواست هایی که به وب سرویس ها ارسال میگردد.
Generic Web handler (*.ashx): یک هندلر عمومی است که تمامی درخواست هایی که هیچ UI ندارند را پردازش میکند.
Trace handler (trace.axd): هندلری است که اطلاعات تریس را نمایش میدهد.
ایجاد یک HTTP Handler:
برای ایجاد یک HTTP Handler کافی است کلاسی ایجاد کنید و یکی از اینتر فیس های IHttpHandler یا IHttpAsyncHandler را پیاده سازی کنید. این اینترفیس ها شامل 2 متد میباشند. IsReusable که اگر مقدار آن برابر با true باشد این هندلر را در Pool قرار میدهد و در دفعات بعدی از آن استفاده میکند و باعث افزایش سرعت میشود و در غیر این صورت با هر درخواست یک شی جدید از این هندلر ایجاد میشود. متد ProcessRequest برای پاسخ به درخواست کاربر استفاده میشود و هنگام ارسال درخواست این متد اجرا میگردد.
در یک http handler به application context دسترسی داریم و میتوانیم از خواص application context استفاده کنیم.
مپ کردن پسوند فایل ها:
هنگامی که یک http hanler ایجاد میکنید، این هندلر میتواند به هر درخواستی با هر پسوندی پاسخ دهد. برای مثال اگر هندلری بسازید که بخواهید تنها به درخواست هایی که پسوند rss دارند پاسخ دهد باید در IIS این پسوند را تنظیم کنید که به برنامه ASP.NET شما ارسال شود و در فایل web.config نیز این پسوند را به handler مخصوص خود ارسال کنید.
در صورتی که یک generic handler با پسوند ashx ایجاد کنید این هندلر به صورت اتوماتیک به وسیله IIS و ASP.NET شناخته خواهد شد.
برای ثبت هندلر های خود در IIS میتوانید از به اینجا و اینجا مراجعه کنید.
http handler های همزمان و غیر همزمان:
هندلرهای همزمان یا Synchronous آنهایی هستند که وقتی درخواستی به آنها ارسال میگردد تا زمانی که پردازش آن درخواست پایان یابد هیچ بازگشتی نخواهند داشت. اما هندلرهای غیرهمزمان آنهایی هستند که به پاسخ ارسال شده به کاربر وابستگی ندارند. این دسته از هندلرها زمانی مورد استفاده قرارمیگرند که بعد از ارسال یک درخواست که زمان پردازش آن نیز زیاد است، لزومی ندارد کاربر در انتظار پایان پردازش آن درخواست بماند و به سادگی میتواند به کار خود ادامه دهد.
HTTP Moduleها:
HTTP module یک اسمبلی است که با هر درخواستی که به برنامه ارسال میشود، اجرا میگردد. HTTP module به عنوان بخشی از خط لوله درخواست(منظور همون request pipeline هست D:) اجرا میگردد و به تمامی رخداد هایی که در طول عمر درخواست اتفاق می افتد درسترسی دارد. به همین دلیل ماژول ها به شما این امکان را میدهند که درخواست های ورودی را مدیریت کنید و یا قبل از ارسال پاسخ به کاربر تغییراتی در پاسخ ایجاد کنید.
HTTP modules از جهاتی شبیه به ISAPI فیلتر ها هستند چون با هر درخواستی اجرا میشوند با این تفاوت که در یک محیط مدیریت شده پیاده سازی شده اند و با چرخه حیات درخواست های برنامه های ASP.NETکاملا منطبق هستند.
ASP.NET برای اهداف مختلفی از این ماژول ها استفاده میکند مانندforms authentication, caching, session state و client script services. در هر شرایطی در صورتی که یکی از این امکانات فعال شوند، بدون اینکه نیاز باشد در هر صفحه ای این امکانات را فراخوانی کنیم، به صورت اتوماتیک این امکانات برای ما فعال میشود. ماژول ها میتوانند به رخ دادهای برنامه دسترسی داشته باشند و یا میتوانند رخ دادهایی ایجاد کنند و در Global.asax میتوان به این رخ داد ها رسیدگی کرد.
نکته: ماژول ها و handler ها با هم تفاوت های اساسی دارند. یک هندلر برای یک نوع خاص درخواست ایجاد میشود و به آن پاسخ میدهد ولی یک ماژول با هر درخواستی که به سایت میآید اجرا میشود. به طور کلی در مواردی از ماژول ها استفاده میکنیم که بخواهیم یک کار عمومی را در کل برنامه اجرا کنیم.
HTTP Module ها چگونه کار میکنند:
برای اینکه ماژول ها شروع به کار کنند باید آن ها را تنظیم و فعال کنیم. عمومی ترین روش به کارگیری ماژول ها استفاده از web.config جهت تنظیم آنها است.
هنگامی که یک نسخه از HttpApplication ایجاد میشود یک نمونه از تمامی ماژول های ثبت شده نیز ایجاد میگردد. هنگامی که یک ماژول ایجاد میشود متد Init آن فراخوانی شده و ماژول خودش را آماده به کار میکند. برای اطلاعات بیشتر به اینجا و اینجا مراجعه کنید. در متد Init شما میتوانید رخ داد های مختلفی را که در برنامه اتفاق می افتد به متدهایی در ماژول خود مقید کنید و با اتفاق افتادن آن رخ دادها، عملی دلخواه انجام دهید مانند چک کردن دسترسی ها یا لاگ کردن موارد دلخواه. یک ماژول به Context درخواست دسترسی دارد و این به شما کمک میکند تا تغییرات دلخواه خود را وارد کنید، مثلا درخواست را به جای دیگری Redirect کنید.
HTTP Module ها در برابر Global.asax
بسیاری از قابلیت هایی را که شما در ماژول ها در اختیار دارید در Global.asax نیز قابل دسترسی است و به شما این قابلیت را میدهد که اعمالی در سطح برنامه اجرا کنید. اما ماژول ها مزایایی در اختیار شما میگذارند که شما را ترغیب میکند به جای استفاده از Global.asax از آنها استفاده کنید.
شما میتواندی یک ماژول را ایجاد کنید و در برنامه های مختلفی استفاده کنید. البته در مقابل Global.asax نیز به شما این قابلیت را میدهد که از رخ دادهایی مانند Session_Start و Session_End باخبر شوید و یا یک شی را ایجاد کنید که در کل برنامه مورد استفاده قرار گیرد.
در شرایطی که نیاز به قابلیتی در سطح برنامه دارید و موارد زیر نیز محقق شده است میتوانید از ماژول ها استفاده کنید:
1- میخاهید از این کد در سایر برنامه ها نیز استفاده کنید.
2-نمیخاهید کدهای پیچیده ای درون فایل Global.asax قرار دهید.
3-این امکانات در تمامی درخواست های برنامه نیاز میباشد.
اگر شما نیاز به کدی داشتید که میخاهید با رخ دادهای مختلفی برنامه فعال شود و نیاز به استفاده از آن در سایر برنامه ها نیست و یا میخاهید با رخدادی کار کنید که در ماژول ها در دسترسی نیستند میتوانید از Global.asax استفاده کنید.
ایجاد یک HTTP Module:
رویه ایجاد یک Http Module به شرح زیر میباشد.
1- یک کلاس ایجاد کنید که IHttpModule را پیاده سازی کند.
2- متد Init را ایجاد کنید.
3- متدهایی که برای رخدادهای مختلف ثبت کرده اید را پیاده سازی کنید.
4- در صورت نیاز متد Dispose را پیاده سازی کنید.
5- ماژول را در فایل web.Config ثبت کنید.
منبع HTTP Handlers and HTTP Modules Overview
پایان قسمت اول.
2 نظرات:
جمله هندلرهای همزمان یا Synchronous آنهایی هستند که وقتی درخواستی به آنها ارسال میگردد تا زمانی که پردازش آن درخواست پایان یابد هیچ بازگشتی نخواهند داشت ممکن است موجب سوءبرداشت شود که HttpAsyncHandler بلافاصله به کلاینت برمیگردد در حالی که این طور نیست.
ممنون.مفید و جالب بود!
ارسال یک نظر