اشتراکها
اشتراکها
فریم ورک سئو برای ASP.NET Core
Following my last post on my ASP.NET MVC SEO-Framework I started looking at adding support also for ASP.NET Core MVC, with its superior Dependency Injection and Tag Helpers.
نظرات مطالب
ساخت ربات تلگرامی با #C
درود؛ وقت بخیر؛ من یک ربات ساختم. میخواهم وقتی کاربر قراره یک متنی وارد کند، یک مقداری خودم تو input نشون بدم و کاربر ادامش رو تکمیل کنه. امکان چنین کاری هست؟ مثلا : من یک مسیج به کاربر میدم و مینویسم لطفا عدد مورد نظر را وارد کنید. بعد کاربر این پیام رو که میبینه، توی input هم مقدار زیر رو ببینه و ادامش عدد مورد نظرش رو وارد کنه. تعداد :
نظرات مطالب
بررسی Bad code smell ها: دلال یا Middle Man
View Model در MVVM شبیه به کنترلرها در MVC هستند. اینها بالاترین لایه برنامه هستند که با تزریق امکانات دیگر سرویسهای برنامه به آنها امکان استفادهی نهایی از آنها فراهم میشود. بنابراین اینها بله ... دلال سرویسهای برنامه به رابط کاربری آن هستند، اما با مفهوم فوق یکی نیستند. کار کنترلرها یا ViewModelها مدیریت و سامان دهی درخواستها و هدایت آنها به سرویسهای متناظر، گاهی از اوقات ترکیب چند سرویس با هم و ارائهی یک پاسخ به مصرف کننده نهایی است. بنابراین اینها واسط بین View برنامه و Model هستند و مدیریت کنندهی تعاملات با برنامه.
بر فرض مثال ما میخواهیم زمانی که از گوگل شخصی به سایت ما هدایت میشود با کلمه کلیدی ای که وارد سایت شده به طور خودکار در سایت جستجو کنیم.
کلا چطور میشه متوجه شد که کاربری از گوگل به سایت ما وارد شده و با چه جستجویی؟
عموما در اکثر مطالب مقایسهای بین وب فرمها و ASP.NET MVC به جداسازی بهتر منطق کدها از فرمها و قابلیت بهتر تهیه آزمونهای واحد اشاره میشود. در این مطلب از دیدگاهی دیگر به این مساله خواهیم پرداخت؛ از لحاظ فنی و جدای از مسایل یاد شده، چه مزایای دیگری را میتوان با استفاده از ASP.NET MVC نسبت به وب فرمها به دست آورد؟
1- آدرسهای تمیزتر
در ASP.NET MVC به صورت پیش فرض از سیستم Routing موجود در زیر ساخت ASP.NET برای نمایش Urlهایی بدون پسوند استفاده میشود. همچنین این سیستم امکان تهیه آدرسهایی با سازگاری بهتر با موتورهای جستجو را نیز از ابتدا مدنظر داشته است.
بله. این زیر ساخت در اختیار وب فرمها نیز هست؛ اما فرق است بین حالتی که از ابتدا مجبور شویم تمیزتر کار کنیم با زمانیکه این انتخاب را داریم و ... عموما هم از آن در وب فرمها استفاده نمیشود.
2- عدم وابستگی الزامی به فایلهای فیزیکی موجود در سیستم
کلیه درخواستها در MVC برخلاف وب فرمها در بدو امر به فایلهای موجود در سیستم منتقل نمیشوند. درخواستها به متدهای موجود در کنترلرها منتقل میشوند. همین مساله سبب میشود که آدرسها الزاما به یک فایل فیزیکی موجود در سیستم اشاره نکنند. به این ترتیب میتوان درخواستها را بر اساس شرایط، به Viewهای مختلف هدایت کرد و نه اینکه هر درخواست ابتدا به یک view رسیده و سپس به متدی ارجاع داده شود.
این مساله از لحاظ امنیتی نیز مهم است. درخواستهای رسیده به MVC سبب اجرای هیچ فرمی نخواهند شد. درخواستها حتما باید از فیلتر یک کنترلر عبور کنند تا اجرایی شوند.
3- امکان مدیریت بهتر قسمتهای مختلف سایت در پوشههای جداگانه
اگر به سورس اکثر سایتهای مبتنی بر ASP.NET Web forms توجه کنیم، تمام فایلهای آنها در ریشه سایت قرار دارند منهای فایلهای CSS و JS و تصاویر. در ASP.NET MVC از ابتدای کار، هر قسمت از سایت در پوشههای جداگانهای قرار میگیرد و به این ترتیب مدیریت فایلها و نظم دهی به آنها سادهتر خواهد بود.
4- امکان تعریف تمام اجزای یک فرم یا view به صورت strongly typed
در ASP.NET MVC میتوان یک کلاس را به یک فرم یا View نسبت داد. به این ترتیب کنترلهای web forms تبدیل به خواص این کلاس در MVC خواهند شد. مزیت این امر امکان کنترل تمام اجزای فرمهای سایت توسط کامپایلر است.
به این ترتیب اگر در طی یک حلقه، جدولی را ایجاد کنیم، تمام عناصر تشکیل دهنده این حلقه (چه کدهای آن و چه المانهایی که اطلاعات را در صفحه نمایش میدهند) نیز توسط کامپایلر قابل بررسی و خطایابی هستند.
5- مقدار دهی خودکار مدل متناظر با یک فرم یا View در ASP.NET MVC
روال متداول کار با وب فرمها، قرار دادن تعدادی کنترل در صفحه و سپس دریافت دستی مقادیر آنها در فایل code behind است. در MVC دیگر نیازی نیست تا این کارها را دستی انجام دهید. به یک فرم یا View کلاسی را انتساب خواهید داد. فریم ورک خواص آنرا به صورت خودکار در حین ارسال به سرور مقدار دهی خواهد کرد. این مورد حتی در حین کار با jQuery Ajax نیز صادق است.
این مساله کار با ORMها را نیز سادهتر میکند. از این جهت که تمام آنها نهایتا با یک سری مدل و کلاس کار خواهند کرد و تمام فیلدها و جداول به تعدادی کلاس و خاصیت تعریف شده در آنها نگاشت میشوند.
به این ترتیب چون دیگر نیازی به ارجاع مستقیم به اشیاء بصری در فایلهای code behind که در اینجا کنترلر نام گرفتهاند نیست، نوشتن آزمون واحد برای این کلاسها نیز به شدت سادهتر شده است.
6- ASP.NET MVC به همراه یک فرم ساز توکار ارائه میشود
اگر کسی به شما گفته است که سرعت کار با ASP.NET MVC پایین است به طور قطع دو فصل اول یک کتاب MVC را بیشتر مطالعه نکرده است. در MVC یک کلاس متناظر با فرمی را طراحی میکنید. توسط ابزار scaffolding همراه با VS.NET از روی این کلاس و مدل، با چند کلیک یک فرم تولید خواهد شد. فرمی که حتی مقدار دهی و انتساب عناصر بصری آن به کلاس متناظر با آن نیز خودکار است.
سرعت پیاده سازی یک برنامه با ASP.NET MVC به مراتب بیشتر است از کار با وب فرمها.
7- حذف View State در MVC
از آنجائیکه فرمهای ASP.NET Web forms از نوع strongly typed نیستند (در دات نت 4 و نیم اندکی بهبود در حد گریدهای آن حاصل شده البته)، برای اینکه پس از ارسال یک فرم به سرور باز هم کنترلهای نمایش داده شده در صفحه همان مقادیر قبلی را نمایش دهند، مکانیزم View State به همراه ذخیره سازی اطلاعات فرم در فیلدهای مخفی فرم جاری طراحی شد.
در MVC نیازی به این مکانیزم نیست. زیرا فقط کافی است که اطلاعات مدل را مجددا به View ارسال کنیم. نمایش و انتساب نهایی آن در اینجا خودکار است. بنابراین نیازی به View State وجود ندارد.
8- کنترل بهتر بر روی اعتبار سنجی اطلاعات دریافتی
در وب فرمها اگر بخواهیم سیستم اعتبارسنجی آنرا غیرفعال کنیم، مثلا برای دریافت html از کاربر، نیاز است کلا آنرا از کار بیندازیم (یا در سطح فرم یا در سطح کل برنامه). در MVC میتوان این اعتبار سنجی را تنها در سطح یک خاصیت که قرار است اطلاعات HTML ایی را دریافت کند، غیرفعال کرد؛ نه برای کل فرم و نه در سطح کل برنامه.
9- امکان استفاده از فرمهای و Viewهای Razor بجای موتور وب فرمها
در وب فرمها تا این زمان فقط محدود به تنها موتور نمایشی مخصوص به آن هستیم. اما در MVC این محدودیت برداشته شده و تا به حال چندین و چند View engine در این بین توسط مایکروسافت و سایر برنامه نویسها طراحی شده است. مهمترین آنها Razor است که تمام برنامه نویسهای MVC پس از مدتی به روان بودن و طراحی طبیعی و عالی آن اعتراف دارند.
10- امکان تعریف بیش از یک فرم در صفحه
طراحی ASP.NET Web forms از روز اول آن محدود به یک فرم در صفحه بوده است. این محدودیت در MVC برداشته شده و مزیت آن امکان ارسال اطلاعات قسمتهای مختلف یک فرم به کنترلرهای مختلف و جداسازی بهتر کدهای قسمتهای مختلف برنامه است.
11- امکان Refactoring بهتر کدهای تکراری در ASP.NET MVC به کمک مفهوم فیلترها
فیلترها در MVC یک سری attribute هستند که میتوان آنها را به متدهای کنترلرها اعمال کرد و به صورت خودکار توسط فریم ورک پیش یا پس از اجرای یک متد اجرا میشوند. به این ترتیب حجم قابل ملاحظهای از if و elseها را میتوان در این فیلترها کپسوله کرد و کدهای متدهای کنترلرها را تمیزتر و زیباتر نمود.
12- سازگاری کامل با jQuery و jQuery Ajax و کلا انواع و اقسام فریمورکهای جاوا اسکریپتی
در MVC وب کنترلها کنار گذاشته شدهاند و سعی شده است با وب به همان نحو که هست برخورد شود. به این ترتیب اگر نیاز داشتید، کل دکمههای فرم را با spanها جایگزین کرده و توسط فریم ورکهای CSS ایی تزئین کنید، بدون نیاز به نگارش جدیدی از ASP.NET MVC، باز هم برنامه کار خواهد کرد.
یا برای کار با اجزای مختلف فرم از دهها و صدها افزونه موجود برای jQuery به سادگی میتوان استفاده کرد. برای نمونه کل سیستم اعتبار سنجی توکار MVC با اعتبار سنجی jQuery یکپارچه و جایگزین شده است.
یا برای کار با jQuery Ajax نیازی نیست تا متدی را static تعریف کنید و به این ترتیب از مزایای امنیتی توکار ASP.NET محروم شوید (مثلا دسترسی به شیء User اعتبار سنجی مبتنی بر فرمها). یا اگر فرم شما 10 فیلد دارد، کل این فیلدها به صورت خودکار به خواص متناظر با آنها نگاشت خواهد شد و نیازی نیست برای این مورد کد بنویسید. به علاوه باید درنظر داشت که jQuery Ajax نسبت به فریم ورک Ajax همراه با ASP.NET web forms بسیار سبکتر و سریعتر عمل میکند چون نیازی ندارد تا هر بار View state را نیز به سرور ارسال کند.
همچنین در اینجا دیگر ID کنترلهای مورد استفاده در اسکریپتها به صورت خودکار تولید نمیشوند و برنامه نویس از ابتدای امر کنترل کاملی را روی این مساله دارد.
13- امکانات فشرده سازی css و js بهتر
در MVC 4 سیستم bundling آن از نمونه مشابه موجود در وب فرمها کاملتر است و جهت فشرده سازی و یکی کردن هر دو مورد فایلهای css و js میتواند بکارگرفته شود؛ به همراه تنظیمات کش مرورگر و gzip خودکار حاصل. به علاوه این سیستم را سفارشی سازی نیز میتوان ساخت و بهینه سازی عملکرد آن مطابق نیاز میسر است.
14- یکپارچگی بهتر EF Code first با MVC
عنوان شد که وجود مدلها و فرمهای strongly typed یکی از مزایای کار با MVC است و ORMها نیز نهایتا با همین کلاسها هستند که کار میکنند. در MVC سیستم کد سازی به نام scaffolding وجود دارد (تهیه شده توسط خود مایکروسافت) که میتواند بر اساس مدلهای EF code first شما، قسمت عمدهای از کدهای یک برنامه ASP.NET MVC را تولید کند. سپس میتوانید به سفارشی سازی آن مشغول شوید.
15- تزریق وابستگیها در MVC سادهتر است
در هر دو فریم ورک وب فرمها و MVC امکان تزریق وابستگیها وجود دارد. اما در MVC میتوان در میانه کار وهله سازی کنترلرها، دخالت کرد و کنترل آن را کاملا در دست گرفت. همین امر سبب میشود حین کار با کتابخانههای تزریق وابستگیها در ASP.NET MVC حجم کد نویسی به شدت کاهش پیدا کند.
16- امکانات امنیتی MVC بیشتر است
عنوان شد که در MVC میتوان اعتبار سنجی را تنها در حد یک خاصیت غیرفعال کرد. فیلتر مبارزه با حملات CSRF جزئی از فریم ورک MVC است. به همراه فیلتر Authorize آن که باز هم اعمال سفارشی سیستم اعتبار سنجی مبتنی بر فرمها را سادهتر میکند با امکان یکپارچگی بهتر با Role providerهای سفارشی.
و یا برای نمونه Razor به صورت پیش فرض امن طراحی شده است. خروجی Razor همواره و در بدو امر، html encoded است مگر اینکه برنامه نویس آگاهانه آنرا تغییر دهد. این مورد مقاومت در برابر حملات XSS را بالا خواهد برد.
امکان استفاده از فیلترهای سفارشی که عنوان شد، جهت مسایل امنیتی بسیار کاربرد دارند. برای مثال بررسی referrer فرم ارسال به سرور را درنظر بگیرید. در وب فرمها میتوان اینکار را با یک http module که روی کل برنامه تاثیر گذار است انجام داد. اما در MVC این فیلتر را تنها میتوان بر روی یک فرم خاص عمومی برای مثال اعمال کرد و نه کل برنامه.
1- آدرسهای تمیزتر
در ASP.NET MVC به صورت پیش فرض از سیستم Routing موجود در زیر ساخت ASP.NET برای نمایش Urlهایی بدون پسوند استفاده میشود. همچنین این سیستم امکان تهیه آدرسهایی با سازگاری بهتر با موتورهای جستجو را نیز از ابتدا مدنظر داشته است.
بله. این زیر ساخت در اختیار وب فرمها نیز هست؛ اما فرق است بین حالتی که از ابتدا مجبور شویم تمیزتر کار کنیم با زمانیکه این انتخاب را داریم و ... عموما هم از آن در وب فرمها استفاده نمیشود.
2- عدم وابستگی الزامی به فایلهای فیزیکی موجود در سیستم
کلیه درخواستها در MVC برخلاف وب فرمها در بدو امر به فایلهای موجود در سیستم منتقل نمیشوند. درخواستها به متدهای موجود در کنترلرها منتقل میشوند. همین مساله سبب میشود که آدرسها الزاما به یک فایل فیزیکی موجود در سیستم اشاره نکنند. به این ترتیب میتوان درخواستها را بر اساس شرایط، به Viewهای مختلف هدایت کرد و نه اینکه هر درخواست ابتدا به یک view رسیده و سپس به متدی ارجاع داده شود.
این مساله از لحاظ امنیتی نیز مهم است. درخواستهای رسیده به MVC سبب اجرای هیچ فرمی نخواهند شد. درخواستها حتما باید از فیلتر یک کنترلر عبور کنند تا اجرایی شوند.
3- امکان مدیریت بهتر قسمتهای مختلف سایت در پوشههای جداگانه
اگر به سورس اکثر سایتهای مبتنی بر ASP.NET Web forms توجه کنیم، تمام فایلهای آنها در ریشه سایت قرار دارند منهای فایلهای CSS و JS و تصاویر. در ASP.NET MVC از ابتدای کار، هر قسمت از سایت در پوشههای جداگانهای قرار میگیرد و به این ترتیب مدیریت فایلها و نظم دهی به آنها سادهتر خواهد بود.
4- امکان تعریف تمام اجزای یک فرم یا view به صورت strongly typed
در ASP.NET MVC میتوان یک کلاس را به یک فرم یا View نسبت داد. به این ترتیب کنترلهای web forms تبدیل به خواص این کلاس در MVC خواهند شد. مزیت این امر امکان کنترل تمام اجزای فرمهای سایت توسط کامپایلر است.
به این ترتیب اگر در طی یک حلقه، جدولی را ایجاد کنیم، تمام عناصر تشکیل دهنده این حلقه (چه کدهای آن و چه المانهایی که اطلاعات را در صفحه نمایش میدهند) نیز توسط کامپایلر قابل بررسی و خطایابی هستند.
5- مقدار دهی خودکار مدل متناظر با یک فرم یا View در ASP.NET MVC
روال متداول کار با وب فرمها، قرار دادن تعدادی کنترل در صفحه و سپس دریافت دستی مقادیر آنها در فایل code behind است. در MVC دیگر نیازی نیست تا این کارها را دستی انجام دهید. به یک فرم یا View کلاسی را انتساب خواهید داد. فریم ورک خواص آنرا به صورت خودکار در حین ارسال به سرور مقدار دهی خواهد کرد. این مورد حتی در حین کار با jQuery Ajax نیز صادق است.
این مساله کار با ORMها را نیز سادهتر میکند. از این جهت که تمام آنها نهایتا با یک سری مدل و کلاس کار خواهند کرد و تمام فیلدها و جداول به تعدادی کلاس و خاصیت تعریف شده در آنها نگاشت میشوند.
به این ترتیب چون دیگر نیازی به ارجاع مستقیم به اشیاء بصری در فایلهای code behind که در اینجا کنترلر نام گرفتهاند نیست، نوشتن آزمون واحد برای این کلاسها نیز به شدت سادهتر شده است.
6- ASP.NET MVC به همراه یک فرم ساز توکار ارائه میشود
اگر کسی به شما گفته است که سرعت کار با ASP.NET MVC پایین است به طور قطع دو فصل اول یک کتاب MVC را بیشتر مطالعه نکرده است. در MVC یک کلاس متناظر با فرمی را طراحی میکنید. توسط ابزار scaffolding همراه با VS.NET از روی این کلاس و مدل، با چند کلیک یک فرم تولید خواهد شد. فرمی که حتی مقدار دهی و انتساب عناصر بصری آن به کلاس متناظر با آن نیز خودکار است.
سرعت پیاده سازی یک برنامه با ASP.NET MVC به مراتب بیشتر است از کار با وب فرمها.
7- حذف View State در MVC
از آنجائیکه فرمهای ASP.NET Web forms از نوع strongly typed نیستند (در دات نت 4 و نیم اندکی بهبود در حد گریدهای آن حاصل شده البته)، برای اینکه پس از ارسال یک فرم به سرور باز هم کنترلهای نمایش داده شده در صفحه همان مقادیر قبلی را نمایش دهند، مکانیزم View State به همراه ذخیره سازی اطلاعات فرم در فیلدهای مخفی فرم جاری طراحی شد.
در MVC نیازی به این مکانیزم نیست. زیرا فقط کافی است که اطلاعات مدل را مجددا به View ارسال کنیم. نمایش و انتساب نهایی آن در اینجا خودکار است. بنابراین نیازی به View State وجود ندارد.
8- کنترل بهتر بر روی اعتبار سنجی اطلاعات دریافتی
در وب فرمها اگر بخواهیم سیستم اعتبارسنجی آنرا غیرفعال کنیم، مثلا برای دریافت html از کاربر، نیاز است کلا آنرا از کار بیندازیم (یا در سطح فرم یا در سطح کل برنامه). در MVC میتوان این اعتبار سنجی را تنها در سطح یک خاصیت که قرار است اطلاعات HTML ایی را دریافت کند، غیرفعال کرد؛ نه برای کل فرم و نه در سطح کل برنامه.
9- امکان استفاده از فرمهای و Viewهای Razor بجای موتور وب فرمها
در وب فرمها تا این زمان فقط محدود به تنها موتور نمایشی مخصوص به آن هستیم. اما در MVC این محدودیت برداشته شده و تا به حال چندین و چند View engine در این بین توسط مایکروسافت و سایر برنامه نویسها طراحی شده است. مهمترین آنها Razor است که تمام برنامه نویسهای MVC پس از مدتی به روان بودن و طراحی طبیعی و عالی آن اعتراف دارند.
10- امکان تعریف بیش از یک فرم در صفحه
طراحی ASP.NET Web forms از روز اول آن محدود به یک فرم در صفحه بوده است. این محدودیت در MVC برداشته شده و مزیت آن امکان ارسال اطلاعات قسمتهای مختلف یک فرم به کنترلرهای مختلف و جداسازی بهتر کدهای قسمتهای مختلف برنامه است.
11- امکان Refactoring بهتر کدهای تکراری در ASP.NET MVC به کمک مفهوم فیلترها
فیلترها در MVC یک سری attribute هستند که میتوان آنها را به متدهای کنترلرها اعمال کرد و به صورت خودکار توسط فریم ورک پیش یا پس از اجرای یک متد اجرا میشوند. به این ترتیب حجم قابل ملاحظهای از if و elseها را میتوان در این فیلترها کپسوله کرد و کدهای متدهای کنترلرها را تمیزتر و زیباتر نمود.
12- سازگاری کامل با jQuery و jQuery Ajax و کلا انواع و اقسام فریمورکهای جاوا اسکریپتی
در MVC وب کنترلها کنار گذاشته شدهاند و سعی شده است با وب به همان نحو که هست برخورد شود. به این ترتیب اگر نیاز داشتید، کل دکمههای فرم را با spanها جایگزین کرده و توسط فریم ورکهای CSS ایی تزئین کنید، بدون نیاز به نگارش جدیدی از ASP.NET MVC، باز هم برنامه کار خواهد کرد.
یا برای کار با اجزای مختلف فرم از دهها و صدها افزونه موجود برای jQuery به سادگی میتوان استفاده کرد. برای نمونه کل سیستم اعتبار سنجی توکار MVC با اعتبار سنجی jQuery یکپارچه و جایگزین شده است.
یا برای کار با jQuery Ajax نیازی نیست تا متدی را static تعریف کنید و به این ترتیب از مزایای امنیتی توکار ASP.NET محروم شوید (مثلا دسترسی به شیء User اعتبار سنجی مبتنی بر فرمها). یا اگر فرم شما 10 فیلد دارد، کل این فیلدها به صورت خودکار به خواص متناظر با آنها نگاشت خواهد شد و نیازی نیست برای این مورد کد بنویسید. به علاوه باید درنظر داشت که jQuery Ajax نسبت به فریم ورک Ajax همراه با ASP.NET web forms بسیار سبکتر و سریعتر عمل میکند چون نیازی ندارد تا هر بار View state را نیز به سرور ارسال کند.
همچنین در اینجا دیگر ID کنترلهای مورد استفاده در اسکریپتها به صورت خودکار تولید نمیشوند و برنامه نویس از ابتدای امر کنترل کاملی را روی این مساله دارد.
13- امکانات فشرده سازی css و js بهتر
در MVC 4 سیستم bundling آن از نمونه مشابه موجود در وب فرمها کاملتر است و جهت فشرده سازی و یکی کردن هر دو مورد فایلهای css و js میتواند بکارگرفته شود؛ به همراه تنظیمات کش مرورگر و gzip خودکار حاصل. به علاوه این سیستم را سفارشی سازی نیز میتوان ساخت و بهینه سازی عملکرد آن مطابق نیاز میسر است.
14- یکپارچگی بهتر EF Code first با MVC
عنوان شد که وجود مدلها و فرمهای strongly typed یکی از مزایای کار با MVC است و ORMها نیز نهایتا با همین کلاسها هستند که کار میکنند. در MVC سیستم کد سازی به نام scaffolding وجود دارد (تهیه شده توسط خود مایکروسافت) که میتواند بر اساس مدلهای EF code first شما، قسمت عمدهای از کدهای یک برنامه ASP.NET MVC را تولید کند. سپس میتوانید به سفارشی سازی آن مشغول شوید.
15- تزریق وابستگیها در MVC سادهتر است
در هر دو فریم ورک وب فرمها و MVC امکان تزریق وابستگیها وجود دارد. اما در MVC میتوان در میانه کار وهله سازی کنترلرها، دخالت کرد و کنترل آن را کاملا در دست گرفت. همین امر سبب میشود حین کار با کتابخانههای تزریق وابستگیها در ASP.NET MVC حجم کد نویسی به شدت کاهش پیدا کند.
16- امکانات امنیتی MVC بیشتر است
عنوان شد که در MVC میتوان اعتبار سنجی را تنها در حد یک خاصیت غیرفعال کرد. فیلتر مبارزه با حملات CSRF جزئی از فریم ورک MVC است. به همراه فیلتر Authorize آن که باز هم اعمال سفارشی سیستم اعتبار سنجی مبتنی بر فرمها را سادهتر میکند با امکان یکپارچگی بهتر با Role providerهای سفارشی.
و یا برای نمونه Razor به صورت پیش فرض امن طراحی شده است. خروجی Razor همواره و در بدو امر، html encoded است مگر اینکه برنامه نویس آگاهانه آنرا تغییر دهد. این مورد مقاومت در برابر حملات XSS را بالا خواهد برد.
امکان استفاده از فیلترهای سفارشی که عنوان شد، جهت مسایل امنیتی بسیار کاربرد دارند. برای مثال بررسی referrer فرم ارسال به سرور را درنظر بگیرید. در وب فرمها میتوان اینکار را با یک http module که روی کل برنامه تاثیر گذار است انجام داد. اما در MVC این فیلتر را تنها میتوان بر روی یک فرم خاص عمومی برای مثال اعمال کرد و نه کل برنامه.
پس از آشنایی با نوشتن یک سری کوئریهای ساده در EF Core، در این قسمت به نحوهی گزارشگیری از اطلاعات چندین جدول مرتبط به هم توسط Joinها خواهیم پرداخت.
مثال 1: یافتن زمانهای شروع رزرو کردن امکانات مختلف، توسط یک کاربر مشخص.
چگونه میتوان زمانهای شروع رزروهای کاربری به نام «David Farrell» را یافت؟
همانطور که در دیاگرام فوق مشاهده میکنید، به ازای هر ID کاربری در جدول کاربران، به دنبال ردیفهایی در جدول Bookings هستیم که این ID در آنها درج شدهاست. اما ... در EF-Core برخلاف SQL نویسی معمولی، ما کاری به ذکر قسمت اتصالی ON [Bookings].[MemId] = [Members].[MemId] نداریم. همینقدر که در کوئری نوشته شده به یک سر دیگر رابطه و خاصیت راهبری (navigation property) دیگری اشاره شود، خود EF-Core جوینی را به صورت خودکار تشکیل خواهد داد و شرط یاد شده را نیز برقرار میکند.
در قسمت اول این سری، در حین طراحی موجودیت کاربر، برای تشکیل سر دیگر رابطهی one-to-many آن، به جدول Bookings، خاصیت Member را نیز که بیانگر کلید خارجی به جدول کاربران است، اضافه کردیم:
خاصیت عددی MemId، کلید خارجی است که در بانک اطلاعاتی رابطهای ثبت خواهد شد و خاصیت Member، خاصیت راهبری است که جوین نویسی به جدول کاربران را بدون ذکر صریح جوین میسر میکند:
در این کوئری همینقدر که در قسمت Where آن booking.Member ذکر شده، جوینی به جدول کاربران را به صورت خودکار تشکیل میدهد:
مثال 2: یافتن زمانهای شروع به رزرو شدن یک امکان خاص در مجموعه.
لیست زمانهای شروع به رزرو شدن زمین(های) تنیس را برای روز 2012-09-21 تولید کنید. خروجی آن باید به همراه ستونهای StartTime, FacilityName باشد.
طراحی موجودیت Booking، به همراه یک کلید خارجی به Facility نیز هست:
خاصیت عددی FacId، کلید خارجی Facility است که در بانک اطلاعاتی رابطهای ثبت خواهد شد و خاصیت Facility، خاصیت راهبری است که جوین نویسی به جدول Facilities را بدون ذکر صریح جوین میسر میکند:
- زمینهای تنیس این مجموعه، دارای دو Id مساوی 0 و 1 هستند که در اینجا به صورت صریحی مشخص شدهاند تا مانند مثال 6 قسمت قبل عمل شود. روش دیگر یافتن آنها میتواند مانند مثال 5 قسمت قبل باشد که به صورت «Name.Contains("Tennis")» نوشته شد.
- در قسمت Where این کوئری چون booking.Facility ذکر شده، سبب ایجاد جوین خودکاری به جدول Facilities خواهد شد.
- علت استفادهی از دو تاریخ در اینجا برای یافتن اطلاعات تنها یک روز، ثبت زمان، به همراه تاریخ رزرو است. ستون تاریخ شروع، به صورت «2012-09-21 18:00:00.0000000» مقدار دهی شدهاست و نه به صورت «2012-09-21». البته در EF-Core راه دیگری هم برای حل این مساله وجود دارد. هر خاصیت از نوع DateTime، به همراه خاصیت Date نیز هست. برای مثال اگر بجای booking.StartTime نوشته شود booking.StartTime.Date (به خاصیت Date اضافه شده دقت کنید)، کد SQL حاصل، به همراه «CONVERT(date, [b].[StartTime])» خواهد بود که سبب حذف خودکار قسمت زمان این ستون میشود.
مثال 3: تولید لیست کاربرانی که کاربر دیگری را توصیه کردهاند.
چگونه میتوان لیست کاربرانی را یافت که کاربر دیگری را توصیه کردهاند؟ این لیست نباید به همراه ردیفهای تکراری باشد و همچنین باید بر اساس surname, firstname مرتب شود.
در اینجا به مفهوم جوین کردن یک جدول با خودش رسیدهایم. جدول کاربران، یک جدول خود ارجاع دهندهاست:
که در اینجا RecommendedBy، یک کلید خارجی نال پذیر است که به Id همین جدول اشاره میکند. دو خاصیت دیگر تعریف شده، مکمل این خاصیت عددی، جهت سهولت کوئری نویسیهای EF-Core هستند. برای مثال اگر در کوئری Recommender != null ذکر شود، سبب تشکیل جوینی به همین جدول شده و لیست کاربرانی را ارائه میدهد که کاربر دیگری را توصیه کردهاند:
وجود Distinct سبب بازگشت ردیفهایی غیرتکراری میشود (چون دو خاصیت نام و نام خانوادگی انتخاب شدهاند، ردیف غیرتکراری، ردیفی خواهد بود که هر دوی این ستونها در آن وجود نداشته باشد) و روش مرتب سازی بر اساس دو خاصیت را نیز مشاهده میکنید. در اینجا نباید دوبار OrderBy را پشت سر هم ذکر کرد. بار اول OrderBy است و بار دوم ThenBy تعریف میشود:
مثال 4: تولید لیست کاربران به همراه توصیه کنندهی آنها.
چگونه میتوان لیست کاربران را به همراه توصیه کنندهی آنها تولید کرد؟ این لیست باید بر اساس surname, firstname مرتب شود.
در اینجا نیز میتوان با ذکر member.Recommender سبب تولید یک جوین خودکار شد. همچنین همانطور که در مثال 7 قسمت قبل نیز بررسی کردیم، میتوان بر روی خواص ذکر شدهی در Select، محاسباتی را نیز انجام داد. برای مثال در اینجا بجای درج مقدار null برای کاربرانی که کاربر دیگری را توصیه نکردهاند، ترجیح دادهایم که یک رشتهی خالی بازگشت داده شود که به صورت «COALESCE ([m0].[FirstName], N'')» ترجمه میشود:
همانطور که ملاحظه میکنید، نوع جوین خودکار تشکیل شده، Left join است و دیگر مانند جوینهای مثالهای ابتدای بحث، inner join نیست. در inner join، جدول سمت راست و چپ بر اساس شرط ON آنها با هم مقایسه شده و ردیفهای کاملا تطابق یافتهای بازگشت داده میشوند. کار Left join نیز مشابه است، با این تفاوت که در اینجا ممکن است برای جدول سمت چپ، هیچ ردیف تطابق یافتهای در جدول سمت راست وجود نداشته باشد (نوع آن بر اساس نال پذیری خاصیت RecommendedBy تشخیص داده شدهاست)؛ برای مثال یک کاربر ممکن است توسط کاربر دیگری توصیه نشده باشد (و RecommendedBy او نال باشد)، اما علاقمندیم که نام او در لیست نهایی حضور داشته باشد و حذف نشود.
یک نکته: در SQL Server تفاوتی بین left join و left outer join وجود ندارد و ذکر واژهی کلیدی outer کاملا اختیاری است. جدول موارد مشابهی در SQL Server که به یک معنا هستند، صورت زیر است:
مثال 5: تولید لیست کاربرانی که از زمین تنیس استفاده کردهاند.
چگونه میتوان لیست کاربرانی را تولید کرد که از زمین(های) تنیس استفاده کردهاند؟ خروجی این گزارش باید به همراه یک ستون جمع نام و نام خانوادگی و ستون نام زمین باشد. این گزارش نباید دارای ردیفهای تکراری باشد و همچنین باید بر اساس حاصل جمع نام و نام خانوادگی، مرتب شده باشد.
جدول Bookings به همراه دو کلید خارجی به جداول Facilities و Members است:
بنابراین برای تولید گزارشی که اطلاعات هر دوی اینها را به همراه دارد (اطلاعات کاربر و اطلاعات امکاناتی که استفاده کرده)، نیاز است دو جوین به دو جدول یاد شده نوشته شود. برای اینکار نیاز است در کوئری خود به booking.Member و booking.Facility برسیم. به همین جهت از جدول کاربران که دارای خاصیت از نوع ICollection اشاره کنندهی به Bookings کاربران است شروع میکنیم:
سپس بر روی این خاصیت مجموعهای، اینبار یک SelectMany را فراخوانی میکنیم تا خروجی آن، تک تک رکوردهای booking متناظر باشد. اکنون که به هر رکورد booking کاربران دسترسی یافتهایم، میتوانیم از طریق خواص راهبری booking.Member و booking.Facility هر ردیف، اطلاعات نهایی گزارش را تولید کنیم:
ID زمینهای تنیس مشخص هستند که توسط tennisCourts.Contains به FacIdهای موجود اعمال شدهاند. همچنین در قسمت Select نیز خاصیت Member آن به جمع دو خاصیت از booking.Member اشاره میکند و چون نتیجهی حاصل یک ستون از پیش تعریف شده نیست، نیاز است تا برای آن نام صریحی انتخاب شود.
پس از آن برای حذف ردیفهای تکراری حاصل از گزارش، از متد Distinct استفاده شده و OrderBy نیز بر اساس خاصیت جدید Member، قابل تعریف است:
مثال 6: تولید لیست رزروهای گران قیمت
لیست رزروهای روز 2012-09-14 را تولید کنید که هزینهی آنها بیشتر از 30 دلار باشد. باید بخاطر داشت که هزینههای کاربران با مهمانها متفاوت است و هزینهها بر اساس Slotهای نیم ساعته محاسبه میشوند و ID کاربر مهمان همیشه صفر است. خروجی این گزارش باید به همراه نام کامل کاربر، نام امکانات مورد استفاده و هزینهی نهایی باشد. همچنین باید بر اساس هزینههای نهایی به صورت نزولی مرتب شود.
در اینجا نیز چون نیاز است خروجی نهایی به همراه نام کاربر و نام امکانات مورد استفاده باشد، همانند مثال قبلی، به حداقل دو جوین نیاز است. به همین جهت از جدول Members به همراه SelectMany بر روی تک تک Bookings آن شروع میکنیم.
سپس بر اساس صفر بودن یا نبودن booking.MemId (کاربر مهمان بودن یا خیر)، شرط هزینهی بیشتر از 30 دلار اعمال شدهاست.
در آخر Select گزارش مورد نیاز، به همراه جمع نام و نام خانوادگی، نام امکانات استفاده شده و خاصیت محاسباتی Cost است که بر اساس مهمان بودن یا نبودن کاربر، متفاوت است.
متد Distinct ردیفهای تکراری حاصل از این گزارش را حذف میکند (محل درج آن مهم است) و متد OrderByDescending، مرتب سازی نزولی بر اساس خاصیت محاسباتی Cost را انجام میدهد.
مثال 7: تولید لیست کاربران به همراه توصیه کنندهی آنها، بدون استفاده از جوین.
در اینجا میخواهیم همان مثال 4 را بدون استفاده از جوین بررسی کنیم. بدون استفاده از جوین در اینجا به معنای استفاده از sub-query است (نوشتن یک کوئری داخل کوئری اصلی).
این کوئری به صورت متداولی بر روی جدول Members اعمال شدهاست، با این تفاوت که در حین Select نهایی آن، یکبار دیگر کوئری جدید شروع شدهی با context.Members را مشاهده میکنید که سبب تولید یک sub-query، زمانیکه ToList نهایی فراخوانی میشود، خواهد شد. این sub-query در حقیقت یک outer join را با ذکر recommender.MemId == member.RecommendedBy (بیان صریح روش اتصال IDهای دو سر رابطه) شبیه سازی میکند.
مثال 8: تولید لیست رزروهای گران قیمت با استفاده از یک sub-query.
هدف از این مثال، ارائهی روش حل دیگری برای مثال 6، به نحو تمیزتری است. در مثال 6، هزینهی رزرو را دوبار، یکبار در متد Where و یکبار در متد Select محاسبه کردیم. اینبار میخواهیم با استفاده از sub-queryها این محاسبه را یکبار انجام دهیم.
اینبار یک Select نوشته شده که در آن Cost، در ابتدا محاسبه شده و سپس Where دومی ذکر شده که از این Cost استفاده میکند.
هرچند کوئری SQL نهایی تولید شدهی توسط EF-Core آن، تفاوتی چندانی با نگارش قبلی ندارد:
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.
مثال 1: یافتن زمانهای شروع رزرو کردن امکانات مختلف، توسط یک کاربر مشخص.
چگونه میتوان زمانهای شروع رزروهای کاربری به نام «David Farrell» را یافت؟
همانطور که در دیاگرام فوق مشاهده میکنید، به ازای هر ID کاربری در جدول کاربران، به دنبال ردیفهایی در جدول Bookings هستیم که این ID در آنها درج شدهاست. اما ... در EF-Core برخلاف SQL نویسی معمولی، ما کاری به ذکر قسمت اتصالی ON [Bookings].[MemId] = [Members].[MemId] نداریم. همینقدر که در کوئری نوشته شده به یک سر دیگر رابطه و خاصیت راهبری (navigation property) دیگری اشاره شود، خود EF-Core جوینی را به صورت خودکار تشکیل خواهد داد و شرط یاد شده را نیز برقرار میکند.
در قسمت اول این سری، در حین طراحی موجودیت کاربر، برای تشکیل سر دیگر رابطهی one-to-many آن، به جدول Bookings، خاصیت Member را نیز که بیانگر کلید خارجی به جدول کاربران است، اضافه کردیم:
namespace EFCorePgExercises.Entities { public class Booking { // ... public int MemId { set; get; } public virtual Member Member { set; get; } // ... } }
var startTimes = context.Bookings .Where(booking => booking.Member.FirstName == "David" && booking.Member.Surname == "Farrell") .Select(booking => new { booking.StartTime }) .ToList();
مثال 2: یافتن زمانهای شروع به رزرو شدن یک امکان خاص در مجموعه.
لیست زمانهای شروع به رزرو شدن زمین(های) تنیس را برای روز 2012-09-21 تولید کنید. خروجی آن باید به همراه ستونهای StartTime, FacilityName باشد.
طراحی موجودیت Booking، به همراه یک کلید خارجی به Facility نیز هست:
namespace EFCorePgExercises.Entities { public class Booking { // ... public int FacId { set; get; } public virtual Facility Facility { set; get; } // ... } }
int[] tennisCourts = { 0, 1 }; var date1 = new DateTime(2012, 09, 21); var date2 = new DateTime(2012, 09, 22); var startTimes = context.Bookings .Where(booking => tennisCourts.Contains(booking.Facility.FacId) && booking.StartTime >= date1 && booking.StartTime < date2) .Select(booking => new { booking.StartTime, booking.Facility.Name }) .ToList();
- در قسمت Where این کوئری چون booking.Facility ذکر شده، سبب ایجاد جوین خودکاری به جدول Facilities خواهد شد.
- علت استفادهی از دو تاریخ در اینجا برای یافتن اطلاعات تنها یک روز، ثبت زمان، به همراه تاریخ رزرو است. ستون تاریخ شروع، به صورت «2012-09-21 18:00:00.0000000» مقدار دهی شدهاست و نه به صورت «2012-09-21». البته در EF-Core راه دیگری هم برای حل این مساله وجود دارد. هر خاصیت از نوع DateTime، به همراه خاصیت Date نیز هست. برای مثال اگر بجای booking.StartTime نوشته شود booking.StartTime.Date (به خاصیت Date اضافه شده دقت کنید)، کد SQL حاصل، به همراه «CONVERT(date, [b].[StartTime])» خواهد بود که سبب حذف خودکار قسمت زمان این ستون میشود.
مثال 3: تولید لیست کاربرانی که کاربر دیگری را توصیه کردهاند.
چگونه میتوان لیست کاربرانی را یافت که کاربر دیگری را توصیه کردهاند؟ این لیست نباید به همراه ردیفهای تکراری باشد و همچنین باید بر اساس surname, firstname مرتب شود.
در اینجا به مفهوم جوین کردن یک جدول با خودش رسیدهایم. جدول کاربران، یک جدول خود ارجاع دهندهاست:
namespace EFCorePgExercises.Entities { public class Member { // ... public virtual ICollection<Member> Children { get; set; } public virtual Member Recommender { set; get; } public int? RecommendedBy { set; get; } // ... } }
var members = context.Members .Where(member => member.Recommender != null) .Select(member => new { member.Recommender.FirstName, member.Recommender.Surname }) .Distinct() .OrderBy(member => member.Surname).ThenBy(member => member.FirstName) .ToList();
مثال 4: تولید لیست کاربران به همراه توصیه کنندهی آنها.
چگونه میتوان لیست کاربران را به همراه توصیه کنندهی آنها تولید کرد؟ این لیست باید بر اساس surname, firstname مرتب شود.
var members = context.Members .Select(member => new { memFName = member.FirstName, memSName = member.Surname, recFName = member.Recommender.FirstName ?? "", recSName = member.Recommender.Surname ?? "" }) .OrderBy(member => member.memSName).ThenBy(member => member.memFName) .ToList();
همانطور که ملاحظه میکنید، نوع جوین خودکار تشکیل شده، Left join است و دیگر مانند جوینهای مثالهای ابتدای بحث، inner join نیست. در inner join، جدول سمت راست و چپ بر اساس شرط ON آنها با هم مقایسه شده و ردیفهای کاملا تطابق یافتهای بازگشت داده میشوند. کار Left join نیز مشابه است، با این تفاوت که در اینجا ممکن است برای جدول سمت چپ، هیچ ردیف تطابق یافتهای در جدول سمت راست وجود نداشته باشد (نوع آن بر اساس نال پذیری خاصیت RecommendedBy تشخیص داده شدهاست)؛ برای مثال یک کاربر ممکن است توسط کاربر دیگری توصیه نشده باشد (و RecommendedBy او نال باشد)، اما علاقمندیم که نام او در لیست نهایی حضور داشته باشد و حذف نشود.
یک نکته: در SQL Server تفاوتی بین left join و left outer join وجود ندارد و ذکر واژهی کلیدی outer کاملا اختیاری است. جدول موارد مشابهی در SQL Server که به یک معنا هستند، صورت زیر است:
A LEFT JOIN B A LEFT OUTER JOIN B A RIGHT JOIN B A RIGHT OUTER JOIN B A FULL JOIN B A FULL OUTER JOIN B A INNER JOIN B A JOIN B
مثال 5: تولید لیست کاربرانی که از زمین تنیس استفاده کردهاند.
چگونه میتوان لیست کاربرانی را تولید کرد که از زمین(های) تنیس استفاده کردهاند؟ خروجی این گزارش باید به همراه یک ستون جمع نام و نام خانوادگی و ستون نام زمین باشد. این گزارش نباید دارای ردیفهای تکراری باشد و همچنین باید بر اساس حاصل جمع نام و نام خانوادگی، مرتب شده باشد.
جدول Bookings به همراه دو کلید خارجی به جداول Facilities و Members است:
namespace EFCorePgExercises.Entities { public class Booking { // ... public int FacId { set; get; } public virtual Facility Facility { set; get; } public int MemId { set; get; } public virtual Member Member { set; get; } // ... } }
namespace EFCorePgExercises.Entities { public class Member { // ... public virtual ICollection<Booking> Bookings { set; get; } } }
int[] tennisCourts = { 0, 1 }; var members = context.Members .SelectMany(x => x.Bookings) .Where(booking => tennisCourts.Contains(booking.Facility.FacId)) .Select(booking => new { Member = booking.Member.FirstName + " " + booking.Member.Surname, Facility = booking.Facility.Name }) .Distinct() .OrderBy(x => x.Member) .ToList();
پس از آن برای حذف ردیفهای تکراری حاصل از گزارش، از متد Distinct استفاده شده و OrderBy نیز بر اساس خاصیت جدید Member، قابل تعریف است:
مثال 6: تولید لیست رزروهای گران قیمت
لیست رزروهای روز 2012-09-14 را تولید کنید که هزینهی آنها بیشتر از 30 دلار باشد. باید بخاطر داشت که هزینههای کاربران با مهمانها متفاوت است و هزینهها بر اساس Slotهای نیم ساعته محاسبه میشوند و ID کاربر مهمان همیشه صفر است. خروجی این گزارش باید به همراه نام کامل کاربر، نام امکانات مورد استفاده و هزینهی نهایی باشد. همچنین باید بر اساس هزینههای نهایی به صورت نزولی مرتب شود.
var date1 = new DateTime(2012, 09, 14); var date2 = new DateTime(2012, 09, 15); var items = context.Members .SelectMany(x => x.Bookings) .Where(booking => booking.StartTime >= date1 && booking.StartTime < date2 && ( (((booking.Slots * booking.Facility.GuestCost) > 30) && (booking.MemId == 0)) || (((booking.Slots * booking.Facility.MemberCost) > 30) && (booking.MemId != 0)) )) .Select(booking => new { Member = booking.Member.FirstName + " " + booking.Member.Surname, Facility = booking.Facility.Name, Cost = booking.MemId == 0 ? booking.Slots * booking.Facility.GuestCost : booking.Slots * booking.Facility.MemberCost }) .Distinct() .OrderByDescending(x => x.Cost) .ToList();
سپس بر اساس صفر بودن یا نبودن booking.MemId (کاربر مهمان بودن یا خیر)، شرط هزینهی بیشتر از 30 دلار اعمال شدهاست.
در آخر Select گزارش مورد نیاز، به همراه جمع نام و نام خانوادگی، نام امکانات استفاده شده و خاصیت محاسباتی Cost است که بر اساس مهمان بودن یا نبودن کاربر، متفاوت است.
متد Distinct ردیفهای تکراری حاصل از این گزارش را حذف میکند (محل درج آن مهم است) و متد OrderByDescending، مرتب سازی نزولی بر اساس خاصیت محاسباتی Cost را انجام میدهد.
مثال 7: تولید لیست کاربران به همراه توصیه کنندهی آنها، بدون استفاده از جوین.
در اینجا میخواهیم همان مثال 4 را بدون استفاده از جوین بررسی کنیم. بدون استفاده از جوین در اینجا به معنای استفاده از sub-query است (نوشتن یک کوئری داخل کوئری اصلی).
var members = context.Members .Select(member => new { Member = member.FirstName + " " + member.Surname, Recommender = context.Members .Where(recommender => recommender.MemId == member.RecommendedBy) .Select(recommender => recommender.FirstName + " " + recommender.Surname) .FirstOrDefault() ?? "" }) .Distinct() .OrderBy(member => member.Member) .ToList();
مثال 8: تولید لیست رزروهای گران قیمت با استفاده از یک sub-query.
هدف از این مثال، ارائهی روش حل دیگری برای مثال 6، به نحو تمیزتری است. در مثال 6، هزینهی رزرو را دوبار، یکبار در متد Where و یکبار در متد Select محاسبه کردیم. اینبار میخواهیم با استفاده از sub-queryها این محاسبه را یکبار انجام دهیم.
var date1 = new DateTime(2012, 09, 14); var date2 = new DateTime(2012, 09, 15); var items = context.Members .SelectMany(x => x.Bookings) .Where(booking => booking.StartTime >= date1 && booking.StartTime < date2) .Select(booking => new { Member = booking.Member.FirstName + " " + booking.Member.Surname, Facility = booking.Facility.Name, Cost = booking.MemId == 0 ? booking.Slots * booking.Facility.GuestCost : booking.Slots * booking.Facility.MemberCost }) .Where(x => x.Cost > 30) .Distinct() .OrderByDescending(x => x.Cost) .ToList();
هرچند کوئری SQL نهایی تولید شدهی توسط EF-Core آن، تفاوتی چندانی با نگارش قبلی ندارد:
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.
اشتراکها
بررسی ویژگیهای جدید ECMAScript 2020
بازخوردهای دوره
بایدها و نبایدهای استفاده از IoC Containers
وقت این سایت رو نگیر آقا یا خانم. 2 ساله داریم بحث میکنیم. هنوز باید بیام به اینها جواب پس بدم. تمومش کن.
نظرات مطالب
خلاصه اشتراکهای روز یک شنبه 13 آذر 1390
سلام؛ پیشنهادات خوبیه. در اولین فرصت اینها رو اضافه میکنم. نظرات دیگری هم داشتید لطفا از همان امکانات سایت کدپلکس برای ارسال استفاده کنید: http://bap.codeplex.com/discussions