فکر میکنم در همین هفته به اتمام برسه.
ایشالا همین هفته وقت کنم حتما منتشر خواهم کرد.
بله، قسمت پنجم رو سعی میکنم تا یکی دو هفته دیگه منتشر کنم.
خیلی عالی بود ، اتفاقا همین مشکل هفته گذشته واسه من به وجود امده بود.
نظرات مطالب
BloggerToCHM 1.3
سلام
با تشکر. برای آخر هفته درستش میکنم.
با تشکر. برای آخر هفته درستش میکنم.
برای بسیاری از تازه کاران که پا به عرصهی برنامههای تحت وب میگذارند، اینکه چگونه، از کجا و چطور باید هاستی را انتخاب کنند، دچار سردرگمی هستند. دیدن پلنهای مختلف با قیمتهای مختلف، باعث افزایش سردرگمی آنها میشود. در این مقاله به بررسی اینکه چطور باید هاستی خریداری شود و اینکه اصلا خود برنامهی نوشته شده نیازش چقدر هست، صحبت میکنیم.
قبل از اینکه صحبت را آغاز کنیم باید این نکته اشاره کنیم که انواع هاست چیست؟
انواع هاست
هاستها بر سه نوع تقسیم میشوند:
هاست اشتراکی
در این حالت شرکت میزبان یک سرور را به چند سایت تقسیم کرده و به هر وب سایت، با توجه به پلنی که مشتری انتخاب کرده، مقداری از منابع را اختصاص میدهد که عموما این تخصیص منابع در زمان خرید توسط WHMCS به طور خودکار صورت میگیرد. در این حالت ممکن است بر روی یک سرور بیش از یکصد وب سایت در حال سرویس گرفتن باشند. مزیت این هاستها قیمت ارزان آنها میباشد . عموما وب سایتهای با بازدید کم و نیاز به منابع کمتر، از این دست هاستها استفاده میکنند. در این نوع هاستها در صورتیکه استفادهی از منابع به نهایت مقداری که برای آن مشخص شده است برسد، از قبیل ترافیک (که بیشتر مسئله مربوط به آن است) یا دیسک سخت و ... به حد مشخص شده برسند، سایت را متوقف یا suspend میکنند. پرداخت این نوع هاستها به خاطر قیمت پایین به صورت یکساله دریافت میشود. قیمست سالیانه آنها در بعضی جاها از 50 هزار تومان تا صدهزار تومان به عنوان مبلغ آغازین شروع میشود.
سرورهای مجازی VPS یا Virtual Private Server
اینها هم تقریبا مثل هاستهای اشتراکی هستند با این تفاوت که منابع بیشتر و دسترسی بیشتری به شما داده میشوند؛ به طوری که احساس میکنید به شما یک سرور واقعی را دادهاند و عموما تعداد سایت هایی که روی آن سرویس میگیرند، به مراتب کمتر از هاست اشتراکی است. اکثر وب سایتهایی که توانایی استفاده از هاستهای اشتراکی را ندارند، از این نوع هاستینگ بهره میبرند. قیمتش بالاتر از یک هاست اشتراکی است ولی به مراتب پایینتر از یک سرور اختصاصی است. پرداخت این نوع هاست عموما به دو صورت ماهیانه و یا سالانه است که احتمال زیادی دارد پرداخت سالیانه تخفیف خوبی را شامل شود. در صورت رسیدن به حد نهایت منابع همانند هاست اشتراکی با شما رفتار خواهد شد. این سرورهای مجازی در ایران عموما از مبلغ ماهیانه 20 هزار تومان به بالا آغاز میشوند.
سروهای اختصاصی یا Dedicate
اینها دیگر سروهای واقعی هستند و صاحب اول و آخرشان شمایید و دسترسی کامل به همه اجزا و منابع آن را دارید. این سرورهای عموما برای فعالیتهای بزرگ تجاری و بازدیدهای همزمان به شدت بالا استفاده میشوند. قیمت، بسته به مشخصات آن متفاوت است و ممکن است یکی ماهیانه 200 هزار تومان، یکی ماهیانه 500 هزار تومان و .. آغاز شود.
سرورهای ابری
اگر قصد انجام عملیات رایانش برای را دارید بهتر هست که دنبال چنین سرورهایی باشید که مورد بحث این مقاله نیست و صرفا جهت تکمیل معرفی انواع هاستها آمده است.
قبل از اینکه صحبت را آغاز کنیم باید این نکته اشاره کنیم که انواع هاست چیست؟
انواع هاست
هاستها بر سه نوع تقسیم میشوند:
- هاست اشتراکی
- سرورهای مجازی
- سرور اختصاصی
هاست اشتراکی
در این حالت شرکت میزبان یک سرور را به چند سایت تقسیم کرده و به هر وب سایت، با توجه به پلنی که مشتری انتخاب کرده، مقداری از منابع را اختصاص میدهد که عموما این تخصیص منابع در زمان خرید توسط WHMCS به طور خودکار صورت میگیرد. در این حالت ممکن است بر روی یک سرور بیش از یکصد وب سایت در حال سرویس گرفتن باشند. مزیت این هاستها قیمت ارزان آنها میباشد . عموما وب سایتهای با بازدید کم و نیاز به منابع کمتر، از این دست هاستها استفاده میکنند. در این نوع هاستها در صورتیکه استفادهی از منابع به نهایت مقداری که برای آن مشخص شده است برسد، از قبیل ترافیک (که بیشتر مسئله مربوط به آن است) یا دیسک سخت و ... به حد مشخص شده برسند، سایت را متوقف یا suspend میکنند. پرداخت این نوع هاستها به خاطر قیمت پایین به صورت یکساله دریافت میشود. قیمست سالیانه آنها در بعضی جاها از 50 هزار تومان تا صدهزار تومان به عنوان مبلغ آغازین شروع میشود.
سرورهای مجازی VPS یا Virtual Private Server
اینها هم تقریبا مثل هاستهای اشتراکی هستند با این تفاوت که منابع بیشتر و دسترسی بیشتری به شما داده میشوند؛ به طوری که احساس میکنید به شما یک سرور واقعی را دادهاند و عموما تعداد سایت هایی که روی آن سرویس میگیرند، به مراتب کمتر از هاست اشتراکی است. اکثر وب سایتهایی که توانایی استفاده از هاستهای اشتراکی را ندارند، از این نوع هاستینگ بهره میبرند. قیمتش بالاتر از یک هاست اشتراکی است ولی به مراتب پایینتر از یک سرور اختصاصی است. پرداخت این نوع هاست عموما به دو صورت ماهیانه و یا سالانه است که احتمال زیادی دارد پرداخت سالیانه تخفیف خوبی را شامل شود. در صورت رسیدن به حد نهایت منابع همانند هاست اشتراکی با شما رفتار خواهد شد. این سرورهای مجازی در ایران عموما از مبلغ ماهیانه 20 هزار تومان به بالا آغاز میشوند.
سروهای اختصاصی یا Dedicate
اینها دیگر سروهای واقعی هستند و صاحب اول و آخرشان شمایید و دسترسی کامل به همه اجزا و منابع آن را دارید. این سرورهای عموما برای فعالیتهای بزرگ تجاری و بازدیدهای همزمان به شدت بالا استفاده میشوند. قیمت، بسته به مشخصات آن متفاوت است و ممکن است یکی ماهیانه 200 هزار تومان، یکی ماهیانه 500 هزار تومان و .. آغاز شود.
نکته ای در مورد سرورهای اختصاصی و حتی گاها VPSها هست اینکه عموما پشتیبانی اینها توسط شما تامین میشود و یا باید یک مدیر سرور با حق ماهیانه اختیار کنید یا در صورت بروز مشکل به صورت ساعتی حق حل مشکل را بدهید. بهتر هست این مورد را از قبل توسط میزبان جویا شوید.
سرورهای ابری
اگر قصد انجام عملیات رایانش برای را دارید بهتر هست که دنبال چنین سرورهایی باشید که مورد بحث این مقاله نیست و صرفا جهت تکمیل معرفی انواع هاستها آمده است.
چک لیست
موقعی که شما نوع هاست خود را انتخاب میکنید به یک سری پلن یا پکیجهای مختلفی میرسید که مشخصات مختلفی دارند و هر شرکت مبلغ و پیکربندی مختلفی را در نظر میگیرد. نگاهی به چک لیست پایین و بررسی گام به گام آن میتواند شما را در رسیدن به انتخاب بهتر یاری کند.
فضای دیسک و پهنای باند (ترافیک)
اولین نکاتی که همیشه توسط میزبانها و خریداران مورد توجه قرار میگیرند، این دو مورد هست. در صورتیکه سایت شما اجازه آپلودی به کاربران نمیدهد، یا خودتان هم آپلود چندانی ندارید، میتوانید فضای دیسک سخت پایینتری را انتخاب کنید. مثلا اگر شما تعدادی تصویر دارید و مقدار زیادی فایل متنی و ... به نظر یک گیگ کفایت میکند و در صورتیکه بعدها دیدید این فضا رو به اتمام است، میتوانید به میزبان درخواست افزایش و ارتقاء آن را بدهید؛ اما برای بار اول یک گیگ به خوبی کفایت میکند. ولی اگر چنانچه با فایلهای حجیم سرو کله میزنید و یا تعداد فایلهایی چون تصاویر، صوت و ویدیو در آن زیاد دیده میشود، بهتر است به فکر فضایی بیشتر و حتی گاها unlimited باشید. برای سایتهایی که حجم به شدت بالایی دارند و یا مثلا نیاز به راه اندازی بخش دانلود دارند، بهتر هست از یک هاستینگ به نام هاست دانلود استفاده کنند. این نوع هاستها به شما اجازه میزبانی فایلهای وب سایتهایی چون aspx,mvc,php را نمیدهند و بیشتر پهنای باند و فضای دیسک سخت آن مدنظر است. در این حالت سایت خود را روی یک هاست معمولی به اشتراک میگذارید و فایلهای خود را روی هاست دانلود قرار میدهید و ارتباط آنها را لینک میکنید.
در مورد پهنای باند باید تعداد کاربر و همچنین نوع اطلاعاتی که جابجا میشوند را مورد بررسی قرار دهید. اگر تعداد کاربران پایین است عموما این مقدار کم است و یا اگر اطلاعات متنی فقط جابجا میشود این مقدار کمتر هم میشود.
در سناریوی اول یک انجمن VB را بررسی میکنیم: عموم انجمنها در زمینهی چند رسانهای مثل تصاویر و ...، چیزی جز تصاویر پوسته خود (که کش هم میشوند) را انتقال نمیدهند. به این دلیل که اجازهی آپلود را به کار نمیدهند و کاربرها بیشتر از سرویسهای ثالث برای تصاویر بهره میبرند و به غیر از پوستهی، سایت متون انجمن هستند که متن هم ترافیک پایینی را مصرف میکند. پس در این حالت ترافیک ماهیانهی 10 گیگ میتواند برای شروع کار تا مدتی که سایت بازدیدکنندههای خودش را پیدا کند، مناسب باشد. از نظر دیسک سخت هم به نظر من اگر شما نیاز به قرار دادن تصاویری چون اسلایدشوها و ... را دارید، به انضمام آواتار کاربران، 500 مگابایت میتواند کافی باشد. حجم آواتار کاربران در قسمت مدیریت، کنترل شده است و تنظیم دستی آن میتواند این مقدار حجم آواتارها را افزایش دهد.
در سناریوی دوم ما یک وب سرویس مثلا برای یک برنامهی اندرویدی را مثال میزنیم: عموما وب سرویسها چیزی جز یک فایل متنی را انتقال نمیدهند؛ مگر اینکه از تصاویر، صورت و ویدیو هم بهره برده باشید. در صورتیکه بیشتر همان متن را بخواهید انتقال دهید، ترافیکی جز همان متن مصرف نمیشود و حتی از یک انجمن هم مصرف پایینتری دارد و میتواند ماهیانه 10 گیگ یا حتی کمتر هم مورد استفاده قرار بگیرد. در صورت اضافه شدن تصویر و دیگر موارد چندرسانهای و کاربران در حال افزایش، این مقدار هم به همان تناسب بالا میرود.
سرعت Speed و توان سرویس دهی(Uptime)
بعد از ارزیابی موارد بالا نوبت به این دو مورد میرسد. اینکه هاست شما به قولی چقدر دوام و ایستادگی دارد، بسیار مهم است و در صورتیکه این امر محقق نگردد، میتواند موجب از دست دادن و شنیدن اعتراض کاربرها باشید. uptime به معنی این است که در یک دورهی زمانی چقدر وب سایت شما در دسترس بوده است؛ درست شنیدید! حتما پیش خودتان میگویید خوب، یک وب سایت همیشه در دسترس است. ولی واقعیت را بخواهید خیر، اینگونه نیست. اگر سرویس دهندهی خوبی را انتخاب نکنید، ممکن است در روز یا هفته یا در هرمقطع زمانی، با در دسترس نبودن وب سایت روبرو شوید؛ تقریبا مشابه زمانیکه adsl شما قطع میشود، چند لحظهای (طولانیتر از زمان عادی) مرورگر شما سعی کرده و میبیند که زورش نمیرسد و یک پیام عدم دسترسی را به شما نمایش میدهد.
به خوبی به یاد دارم که یک میزبان، هاستهای ارزان قیمتی را ارائه میکرد، ولی گاها در روز دو الی سه بار پنج دقیقهای سرور از دسترس خارج میشد و میگفتیم این هاست برای کسانی است که پول کافی ندارند و یا خیلی نمیخواهند خرج کنند و حالا پنج دقیقهای هم در روز در دسترس نباشد اهمیتی ندارد. در سایتها بیشتر این مورد را با اعدادی شبیه 99.9% مشخص میکنند و پیشنهاد میکنم هاستی را بخرید که این عدد را به شما نشان میدهد، یا حداقل دیگر 99.5% کمتر نباشد. البته تمامی هاستها همین را مینویسند، ولی واقعیت چیز دیگری است و بهتر از از دوستان و آشنایان و جستجوی در اینترنت و انجمنها به این مورد برسید.
فاکتور بعدی سرعت سرور است و کاربران به این مورد هم اهمیت ویژهای میدهند. به خوبی به یاد دارم، اولین هاستی که در کارم استفاده کردم و به پیشنهاد دوستم که در آن کار میکرد این هاست را خریداری کردم، اوایل خوب بود، ولی مشکل سرعت بعدها اضافه شد که حتی ورود به پنل مدیریتی چند دقیقهای طول میکشید. یا اینکه چند وقت پیش وب سایتی را مطالعه میکردم که فقط متن جابجا میکرد، ولی به طوری که کندتر از زمان اجرای یک سایت با گرافیک متوسط طول میکشید. (البته این نکته حائز اهمیت است خود وب سایت هم باید از این لحاظ مورد بررسی قرار گیرد و مشکل را سریعا گردن سرویس دهنده نیندازیم).
برای اینکه بدانیم که سرعت یک هاست چگونه باید ارزیابی شود باید سه مورد را بررسی کنیم :
مورد سوم نزدیکی سرور به کاربران هدف است. هر چقدر traceroute و پینگ زمانی کمتری به سرور داشته باشید، دسترسی به اطلاعات آن سریعتر میشود. در ایران عموما از کشورهایی چون آمریکا، کانادا ، انگلیس ، آلمان و استرالیا سرور خریده میشود و به مدت چندسالی است که در ایران هم این امکان فراهم شده است ولی خب مسائلی که این سرورها در ایران دارند باعث میشوند عدهی زیادی دور آنها را که هیچ روی آنها را هم خط بکشند.
مشکلاتی که این سرورها دارند بیشتر به سه مورد زیر بر میگردد:
هزینهی سنگین ترافیک : این مورد آن قدر به وضوح پیداست که شما را به کل برای هر نوع خریدی پشیمان میکند؛ مگر اینکه پولتان از جایی تامین میشود. ادارات دولتی عموما این نوع هاست را بر میدارند و یا سایتهای اشتراکی که منابع زیادی را مصرف نمیکنند و یا شرکتها یا اشخاصی که پول تامین را دارند.
عدم دسترسی به شبکههای اجتماعی یا هرسرویس دهندهی مسدود شده : من این را تست نکردم ولی با دو دوتا چهارتا کردن این حساب دستم آمد که وقتی سایتی مثل فیس بوک و توئیتر در ایران مسدود باشند، باید روی این سرورها هم مسدود باشند. در نتیجه اگر سایت شما مطالبش را در این سایتها معرفی میکند و میخواهید پست و توئیتی روی آنها داشته باشید، احتمالا توانایی این کار را نخواهید داشت. مگه اینکه خودتان دستی بروید در سایت مربوطه و اضافه کنید.
جایگاه پایینتر SEO : گفتیم که یکی از عوامل سرعت، نزدیک بودن سرور به کاربر هدف است. این مورد برای سرور موتورهای جستجویی مثل گوگل که در ایران سروری ندارند هم اتفاق میافتد و در نتیجه گوگل از لحاظ امتیاز بندی سرعت، امتیاز شما را کم میکند ولی این مورد همیشه چندان صدق نمیکند و مبحث ترافیک سایت بیشتر مورد ارزیابی قرار میگیرد.
امنیت
این مورد شامل دو بخش میشود یکی امنیت دسترسی به سرور و دیگر امنیت نگهداری اطلاعات. نصب فایروالها روی سرور و نظارت 24 ساعته روی سرورهای شرکت (که شامل بخش پشتیبانی میشود) میتواند این موردها را پوشش دهد. هر چند این مورد را زیاد نمیتوانید محک بزنید، ولی اگر اخباری چون «همکاری با یک شرکت امنیتی جهت تست سرور و همکاری با تعدادی هکر جهت تست سرور» و ... را شنیدید، میتوانید این اطمینان را کسب کنید که آنها به این مورد اهمیت میدهند.
امنیت اطلاعات چون پشتیبانهای روزانه و نگهداری اطلاعات تا مدتی معین پس از اتمام قرارداد و موارد این چنینی، میتواند شما را مطمئن سازد. در مورد یکی از مشتریانم به خاطر دارم که فراموش کرده بود هاست را تمدید کند و یک روز بعد از انقضاء ما درخواست دیتابیس را داشتیم که برای ما ارسال کنند و به گفتهی خودشان ما مسئولیتی در قبال نگه داری اطلاعات نداریم، ولی تا 5 روز نگه میداریم که البته گفتند هر چه به دنبال دیتابیس گشتند، چیزی پیدا نکردند که البته این مشکل را از راهکار دیگری حل کردیم و ماجرا به خوشی تمام شد. ولی جالب این بود که اینقدر که من حرص خوردم، چطوری به مدیر اینها بگوئیم که اطلاعات تیمشان پریده و اینها حرص نخوردند و بی خیال.
کنترل پنل
میگویند کنترل پنل هم چیز مهمی است ولی در اکثر اوقات اکثر هاستینگها از همان پنلهای معروف استفاده میکنند. برای مثال در لینوکس "سی پنل" و در ویندوز "وب سایت پنل" و "پلسک" میباشد . تا آخرین باری که من با آنها کار کردم، وب سایت پنل، یک پنل سبک بود که برای انجام عملیات ساده تا متوسط در زمینه کاری پنلها میباشد و پلسک نسبت به آن امکانات خیلی زیادتری دارد. اکثرا آنها حاوی امکانی چون نصب یک کلیکی CMS هایی چون وردپرس و جوملا و ... هستند.
اگر از یک سرور اختصاصی بهره ببرید خودتان مختار نصب هر نوع چیزی روی آن هستید و در مورد کنترل پنل هم صدق میکند ولی عموما شرکتها یک سری الگوهای پیش فرضی را برای سرویسهای خود دارند که در صورت تغییر باید به آنها، تغییرات را طلاع دهید.
فضای دیسک و پهنای باند (ترافیک)
اولین نکاتی که همیشه توسط میزبانها و خریداران مورد توجه قرار میگیرند، این دو مورد هست. در صورتیکه سایت شما اجازه آپلودی به کاربران نمیدهد، یا خودتان هم آپلود چندانی ندارید، میتوانید فضای دیسک سخت پایینتری را انتخاب کنید. مثلا اگر شما تعدادی تصویر دارید و مقدار زیادی فایل متنی و ... به نظر یک گیگ کفایت میکند و در صورتیکه بعدها دیدید این فضا رو به اتمام است، میتوانید به میزبان درخواست افزایش و ارتقاء آن را بدهید؛ اما برای بار اول یک گیگ به خوبی کفایت میکند. ولی اگر چنانچه با فایلهای حجیم سرو کله میزنید و یا تعداد فایلهایی چون تصاویر، صوت و ویدیو در آن زیاد دیده میشود، بهتر است به فکر فضایی بیشتر و حتی گاها unlimited باشید. برای سایتهایی که حجم به شدت بالایی دارند و یا مثلا نیاز به راه اندازی بخش دانلود دارند، بهتر هست از یک هاستینگ به نام هاست دانلود استفاده کنند. این نوع هاستها به شما اجازه میزبانی فایلهای وب سایتهایی چون aspx,mvc,php را نمیدهند و بیشتر پهنای باند و فضای دیسک سخت آن مدنظر است. در این حالت سایت خود را روی یک هاست معمولی به اشتراک میگذارید و فایلهای خود را روی هاست دانلود قرار میدهید و ارتباط آنها را لینک میکنید.
در مورد پهنای باند باید تعداد کاربر و همچنین نوع اطلاعاتی که جابجا میشوند را مورد بررسی قرار دهید. اگر تعداد کاربران پایین است عموما این مقدار کم است و یا اگر اطلاعات متنی فقط جابجا میشود این مقدار کمتر هم میشود.
در سناریوی اول یک انجمن VB را بررسی میکنیم: عموم انجمنها در زمینهی چند رسانهای مثل تصاویر و ...، چیزی جز تصاویر پوسته خود (که کش هم میشوند) را انتقال نمیدهند. به این دلیل که اجازهی آپلود را به کار نمیدهند و کاربرها بیشتر از سرویسهای ثالث برای تصاویر بهره میبرند و به غیر از پوستهی، سایت متون انجمن هستند که متن هم ترافیک پایینی را مصرف میکند. پس در این حالت ترافیک ماهیانهی 10 گیگ میتواند برای شروع کار تا مدتی که سایت بازدیدکنندههای خودش را پیدا کند، مناسب باشد. از نظر دیسک سخت هم به نظر من اگر شما نیاز به قرار دادن تصاویری چون اسلایدشوها و ... را دارید، به انضمام آواتار کاربران، 500 مگابایت میتواند کافی باشد. حجم آواتار کاربران در قسمت مدیریت، کنترل شده است و تنظیم دستی آن میتواند این مقدار حجم آواتارها را افزایش دهد.
در سناریوی دوم ما یک وب سرویس مثلا برای یک برنامهی اندرویدی را مثال میزنیم: عموما وب سرویسها چیزی جز یک فایل متنی را انتقال نمیدهند؛ مگر اینکه از تصاویر، صورت و ویدیو هم بهره برده باشید. در صورتیکه بیشتر همان متن را بخواهید انتقال دهید، ترافیکی جز همان متن مصرف نمیشود و حتی از یک انجمن هم مصرف پایینتری دارد و میتواند ماهیانه 10 گیگ یا حتی کمتر هم مورد استفاده قرار بگیرد. در صورت اضافه شدن تصویر و دیگر موارد چندرسانهای و کاربران در حال افزایش، این مقدار هم به همان تناسب بالا میرود.
سرعت Speed و توان سرویس دهی(Uptime)
بعد از ارزیابی موارد بالا نوبت به این دو مورد میرسد. اینکه هاست شما به قولی چقدر دوام و ایستادگی دارد، بسیار مهم است و در صورتیکه این امر محقق نگردد، میتواند موجب از دست دادن و شنیدن اعتراض کاربرها باشید. uptime به معنی این است که در یک دورهی زمانی چقدر وب سایت شما در دسترس بوده است؛ درست شنیدید! حتما پیش خودتان میگویید خوب، یک وب سایت همیشه در دسترس است. ولی واقعیت را بخواهید خیر، اینگونه نیست. اگر سرویس دهندهی خوبی را انتخاب نکنید، ممکن است در روز یا هفته یا در هرمقطع زمانی، با در دسترس نبودن وب سایت روبرو شوید؛ تقریبا مشابه زمانیکه adsl شما قطع میشود، چند لحظهای (طولانیتر از زمان عادی) مرورگر شما سعی کرده و میبیند که زورش نمیرسد و یک پیام عدم دسترسی را به شما نمایش میدهد.
به خوبی به یاد دارم که یک میزبان، هاستهای ارزان قیمتی را ارائه میکرد، ولی گاها در روز دو الی سه بار پنج دقیقهای سرور از دسترس خارج میشد و میگفتیم این هاست برای کسانی است که پول کافی ندارند و یا خیلی نمیخواهند خرج کنند و حالا پنج دقیقهای هم در روز در دسترس نباشد اهمیتی ندارد. در سایتها بیشتر این مورد را با اعدادی شبیه 99.9% مشخص میکنند و پیشنهاد میکنم هاستی را بخرید که این عدد را به شما نشان میدهد، یا حداقل دیگر 99.5% کمتر نباشد. البته تمامی هاستها همین را مینویسند، ولی واقعیت چیز دیگری است و بهتر از از دوستان و آشنایان و جستجوی در اینترنت و انجمنها به این مورد برسید.
فاکتور بعدی سرعت سرور است و کاربران به این مورد هم اهمیت ویژهای میدهند. به خوبی به یاد دارم، اولین هاستی که در کارم استفاده کردم و به پیشنهاد دوستم که در آن کار میکرد این هاست را خریداری کردم، اوایل خوب بود، ولی مشکل سرعت بعدها اضافه شد که حتی ورود به پنل مدیریتی چند دقیقهای طول میکشید. یا اینکه چند وقت پیش وب سایتی را مطالعه میکردم که فقط متن جابجا میکرد، ولی به طوری که کندتر از زمان اجرای یک سایت با گرافیک متوسط طول میکشید. (البته این نکته حائز اهمیت است خود وب سایت هم باید از این لحاظ مورد بررسی قرار گیرد و مشکل را سریعا گردن سرویس دهنده نیندازیم).
برای اینکه بدانیم که سرعت یک هاست چگونه باید ارزیابی شود باید سه مورد را بررسی کنیم :
- دیتاسنتر
- سرور
- نزدیکی سرور به محل زندگی کاربران هدف
مورد سوم نزدیکی سرور به کاربران هدف است. هر چقدر traceroute و پینگ زمانی کمتری به سرور داشته باشید، دسترسی به اطلاعات آن سریعتر میشود. در ایران عموما از کشورهایی چون آمریکا، کانادا ، انگلیس ، آلمان و استرالیا سرور خریده میشود و به مدت چندسالی است که در ایران هم این امکان فراهم شده است ولی خب مسائلی که این سرورها در ایران دارند باعث میشوند عدهی زیادی دور آنها را که هیچ روی آنها را هم خط بکشند.
مشکلاتی که این سرورها دارند بیشتر به سه مورد زیر بر میگردد:
هزینهی سنگین ترافیک : این مورد آن قدر به وضوح پیداست که شما را به کل برای هر نوع خریدی پشیمان میکند؛ مگر اینکه پولتان از جایی تامین میشود. ادارات دولتی عموما این نوع هاست را بر میدارند و یا سایتهای اشتراکی که منابع زیادی را مصرف نمیکنند و یا شرکتها یا اشخاصی که پول تامین را دارند.
عدم دسترسی به شبکههای اجتماعی یا هرسرویس دهندهی مسدود شده : من این را تست نکردم ولی با دو دوتا چهارتا کردن این حساب دستم آمد که وقتی سایتی مثل فیس بوک و توئیتر در ایران مسدود باشند، باید روی این سرورها هم مسدود باشند. در نتیجه اگر سایت شما مطالبش را در این سایتها معرفی میکند و میخواهید پست و توئیتی روی آنها داشته باشید، احتمالا توانایی این کار را نخواهید داشت. مگه اینکه خودتان دستی بروید در سایت مربوطه و اضافه کنید.
جایگاه پایینتر SEO : گفتیم که یکی از عوامل سرعت، نزدیک بودن سرور به کاربر هدف است. این مورد برای سرور موتورهای جستجویی مثل گوگل که در ایران سروری ندارند هم اتفاق میافتد و در نتیجه گوگل از لحاظ امتیاز بندی سرعت، امتیاز شما را کم میکند ولی این مورد همیشه چندان صدق نمیکند و مبحث ترافیک سایت بیشتر مورد ارزیابی قرار میگیرد.
امنیت
این مورد شامل دو بخش میشود یکی امنیت دسترسی به سرور و دیگر امنیت نگهداری اطلاعات. نصب فایروالها روی سرور و نظارت 24 ساعته روی سرورهای شرکت (که شامل بخش پشتیبانی میشود) میتواند این موردها را پوشش دهد. هر چند این مورد را زیاد نمیتوانید محک بزنید، ولی اگر اخباری چون «همکاری با یک شرکت امنیتی جهت تست سرور و همکاری با تعدادی هکر جهت تست سرور» و ... را شنیدید، میتوانید این اطمینان را کسب کنید که آنها به این مورد اهمیت میدهند.
امنیت اطلاعات چون پشتیبانهای روزانه و نگهداری اطلاعات تا مدتی معین پس از اتمام قرارداد و موارد این چنینی، میتواند شما را مطمئن سازد. در مورد یکی از مشتریانم به خاطر دارم که فراموش کرده بود هاست را تمدید کند و یک روز بعد از انقضاء ما درخواست دیتابیس را داشتیم که برای ما ارسال کنند و به گفتهی خودشان ما مسئولیتی در قبال نگه داری اطلاعات نداریم، ولی تا 5 روز نگه میداریم که البته گفتند هر چه به دنبال دیتابیس گشتند، چیزی پیدا نکردند که البته این مشکل را از راهکار دیگری حل کردیم و ماجرا به خوشی تمام شد. ولی جالب این بود که اینقدر که من حرص خوردم، چطوری به مدیر اینها بگوئیم که اطلاعات تیمشان پریده و اینها حرص نخوردند و بی خیال.
کنترل پنل
میگویند کنترل پنل هم چیز مهمی است ولی در اکثر اوقات اکثر هاستینگها از همان پنلهای معروف استفاده میکنند. برای مثال در لینوکس "سی پنل" و در ویندوز "وب سایت پنل" و "پلسک" میباشد . تا آخرین باری که من با آنها کار کردم، وب سایت پنل، یک پنل سبک بود که برای انجام عملیات ساده تا متوسط در زمینه کاری پنلها میباشد و پلسک نسبت به آن امکانات خیلی زیادتری دارد. اکثرا آنها حاوی امکانی چون نصب یک کلیکی CMS هایی چون وردپرس و جوملا و ... هستند.
اگر از یک سرور اختصاصی بهره ببرید خودتان مختار نصب هر نوع چیزی روی آن هستید و در مورد کنترل پنل هم صدق میکند ولی عموما شرکتها یک سری الگوهای پیش فرضی را برای سرویسهای خود دارند که در صورت تغییر باید به آنها، تغییرات را طلاع دهید.
پشتیبانی فنی
موردی را تصور کنید که ساعت 2 شب هست و سایت شما هم که در زمینهی مهم و حساسی فعالیت میکند، یک دفعه سرویس آن قطع میشود و برای سرور مشکلی پیش میآید. در این موقع چکاری را انجام میدهید؟ صبر میکنید تا صبح شود و سرویس شما راه بیفتد و کاربران هم از سایت شما نا امید شوند؟ اگر این مشکل به دفعات رخ بدهد چه؟ اگر چند روز پشت سرهم تعطیلی باشد چه؟
همهی این موارد مربوط به پشتیبانی میشود. این پشتیبانیها عموما به صورت چت و تلفنی (بیشتر مربوط به ساعات اداری) و ارسال تیکت در سامانه پشتیبانی میشود. البته تجربهی من میگوید در ایران زیاد به متون نوشتهی روی سایت میزبان توجهی نکنید و این را هم به موضوع تحقیق خود اضافه کنید. بعضیها میگویند 24 ساعته پشتیبانی تلفنی دارند ولی هر بار زنگ میزنی کسی پاسخی نمیدهد. یا تیکت میزنید و بارها و بارها پشت سر هم تیکت "چی شد؟" را برای جویا شدن ارسال میکنید. (البته انصاف هم داشته باشید و پنج دقیقه پنج دقیقه این تیکت را نفرستید).
پشتیبانی هر نوع سرور را مطلع شوید. بعضیها واقعا پشتیبانی دارند!؟ ولی وقتی زنگ میزنید میگویند این پشتیبانی مربوط به لینوکس است و بچههای ویندوز فقط ساعات اداری هستند. خلاصه حواستان را جمع کنید که بچههای آن بخش همیشه باشند.
بنابراین مطمئن شوید که یک پشتیبانی 24/7 واقعی داشته باشند.
امکانات اضافه
در کنار خود هاست یک سری ویژگیهایی چون FTP و تعدادی دامنههای پشتیبانی شده و زیر دامنهها و تعداد دیتابیسها و ایمیل و ... هم هستند که در قدیم یادم هست محدودیتهایی در این رابطه ایجاد کرده بودند که امروزه از این نظر در اکثر هاستها آن طور که دیدم این محدودیتها رفع شده است که البته خیلی هم خوب هست و این محدودیتها بیشتر شبیه سودجویی بود تا چیز دیگر.
هزینه
با همهی حرفهای بالا به نظر میرسد که هزینه، همه این معادلات را به هم میریزد. هاستینگهای مختلف و قیمتهای مختلف، تصمیم گیرنده شما هستید که چقدر به عوامل بالا اهمیت میدهید و چگونه آنها را در راستای قیمت اولویت بندی میکنید. اگر محدودیتی ندارید سعی کنید همهی عوامل را بررسی کنید. نکته اینکه اکثر هاستها طرحی به نام ضمانت برگشت پول را در هفت روز یا گاها یک ماه، دارند و در صورتیکه از سرویس خریداری شده راضی نبودید میتوانید پول خود را پس گرفته و سرویس معلق شود.
هاستینگ در قرارداد
هنگام عقد قرارداد با مشتری، در قرارداد در مورد هاستینگ که خودش تهیه میکند، این نکته ذکر شود که شما در مورد مسائلی که مربوط به هاست و دامنه میشود هیچ مسئولیتی ندارید و باید مشکلات را با مسئولان آن در میان بگذارد.
همچنین این نکته هم ذکر شود در مورد مسائلی چون عدم پشتیبانی مناسب هاست، از محصول یا سرویس شما، شما هیچگونه مسئولیتی در قبال آن ندارید و یا باید این مورد توسط شما دنبال شود یا خرید ایشان با مشاوره و تایید شما از هاست مربوطه باشد.
در صورتی که مشتری نمیتواند شخصا خرید هاستینگ را انجام دهد یا در شرکت مسئولین IT ندارند که این کار را انجام دهند و این مورد به شما محول میشود، در قرارداد ذکر شود که شما هیچ گونه مسئولیتی در قبال مشکلاتی که برای هاست و دامنه رخ میدهد ندارید و تنها میتوانید به عنوان یک واسط یا وکیل در ازای مبلغ توافق شدهای مشکل را با مسئولین هاست و دامنه در میان گذاشته و ماجرا را برای حل مشکل ایجاد شده دنبال کنید یا این کار را به عنوان هدیه ای از طرف خدمات طلایی شما به مشتریان به صورت رایگان انجام دهید.
موردی را تصور کنید که ساعت 2 شب هست و سایت شما هم که در زمینهی مهم و حساسی فعالیت میکند، یک دفعه سرویس آن قطع میشود و برای سرور مشکلی پیش میآید. در این موقع چکاری را انجام میدهید؟ صبر میکنید تا صبح شود و سرویس شما راه بیفتد و کاربران هم از سایت شما نا امید شوند؟ اگر این مشکل به دفعات رخ بدهد چه؟ اگر چند روز پشت سرهم تعطیلی باشد چه؟
همهی این موارد مربوط به پشتیبانی میشود. این پشتیبانیها عموما به صورت چت و تلفنی (بیشتر مربوط به ساعات اداری) و ارسال تیکت در سامانه پشتیبانی میشود. البته تجربهی من میگوید در ایران زیاد به متون نوشتهی روی سایت میزبان توجهی نکنید و این را هم به موضوع تحقیق خود اضافه کنید. بعضیها میگویند 24 ساعته پشتیبانی تلفنی دارند ولی هر بار زنگ میزنی کسی پاسخی نمیدهد. یا تیکت میزنید و بارها و بارها پشت سر هم تیکت "چی شد؟" را برای جویا شدن ارسال میکنید. (البته انصاف هم داشته باشید و پنج دقیقه پنج دقیقه این تیکت را نفرستید).
پشتیبانی هر نوع سرور را مطلع شوید. بعضیها واقعا پشتیبانی دارند!؟ ولی وقتی زنگ میزنید میگویند این پشتیبانی مربوط به لینوکس است و بچههای ویندوز فقط ساعات اداری هستند. خلاصه حواستان را جمع کنید که بچههای آن بخش همیشه باشند.
بنابراین مطمئن شوید که یک پشتیبانی 24/7 واقعی داشته باشند.
امکانات اضافه
در کنار خود هاست یک سری ویژگیهایی چون FTP و تعدادی دامنههای پشتیبانی شده و زیر دامنهها و تعداد دیتابیسها و ایمیل و ... هم هستند که در قدیم یادم هست محدودیتهایی در این رابطه ایجاد کرده بودند که امروزه از این نظر در اکثر هاستها آن طور که دیدم این محدودیتها رفع شده است که البته خیلی هم خوب هست و این محدودیتها بیشتر شبیه سودجویی بود تا چیز دیگر.
هزینه
با همهی حرفهای بالا به نظر میرسد که هزینه، همه این معادلات را به هم میریزد. هاستینگهای مختلف و قیمتهای مختلف، تصمیم گیرنده شما هستید که چقدر به عوامل بالا اهمیت میدهید و چگونه آنها را در راستای قیمت اولویت بندی میکنید. اگر محدودیتی ندارید سعی کنید همهی عوامل را بررسی کنید. نکته اینکه اکثر هاستها طرحی به نام ضمانت برگشت پول را در هفت روز یا گاها یک ماه، دارند و در صورتیکه از سرویس خریداری شده راضی نبودید میتوانید پول خود را پس گرفته و سرویس معلق شود.
کل مطالب گفته شده در چک لیست به طور خلاصه : اول از همه بررسی فضای ذخیره سازی و پهنای باند، بعد از آن بررسی توانایی سرویس دهی و سرعت سرورها، داشتن محیط امن و پشتیبانی مناسب بود.
هاستینگ در قرارداد
هنگام عقد قرارداد با مشتری، در قرارداد در مورد هاستینگ که خودش تهیه میکند، این نکته ذکر شود که شما در مورد مسائلی که مربوط به هاست و دامنه میشود هیچ مسئولیتی ندارید و باید مشکلات را با مسئولان آن در میان بگذارد.
همچنین این نکته هم ذکر شود در مورد مسائلی چون عدم پشتیبانی مناسب هاست، از محصول یا سرویس شما، شما هیچگونه مسئولیتی در قبال آن ندارید و یا باید این مورد توسط شما دنبال شود یا خرید ایشان با مشاوره و تایید شما از هاست مربوطه باشد.
در صورتی که مشتری نمیتواند شخصا خرید هاستینگ را انجام دهد یا در شرکت مسئولین IT ندارند که این کار را انجام دهند و این مورد به شما محول میشود، در قرارداد ذکر شود که شما هیچ گونه مسئولیتی در قبال مشکلاتی که برای هاست و دامنه رخ میدهد ندارید و تنها میتوانید به عنوان یک واسط یا وکیل در ازای مبلغ توافق شدهای مشکل را با مسئولین هاست و دامنه در میان گذاشته و ماجرا را برای حل مشکل ایجاد شده دنبال کنید یا این کار را به عنوان هدیه ای از طرف خدمات طلایی شما به مشتریان به صورت رایگان انجام دهید.
در مطلب پیشین برای نگهداری حالت شیء یا همان ویژگیهای آن Propertyها را در کلاس معرفی کردیم و پس از ایجاد شیء مقدار مناسبی را به پروپرتیها اختصاص دادیم.
اگرچه ایجاد شیء و مقداردهی به ویژگیهای آن ما را به هدفمان میرساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازندهها (Constructors) حل میشود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا میشود. وظیفه سازنده مقداردهی به ویژگیهای عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامهنویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی میکنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی میکنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی میکنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
چون در بخشی از یک پروژه نیاز پیدا کردیم با یک سری مثلث کار کنیم، کلاس بالا را طراحی کرده ایم. به نکات زیر توجه نمایید.
• در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگیها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز میتوان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت میکنیم.
• در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.
وقتی شیء ای از یک کلاس ایجاد میشود، بلافاصله سازنده آن فراخوانی میگردد. سازندهها هم نام کلاسی هستند که در آن تعریف میشوند و معمولاً اعضای داده ای شیء جدید را مقداردهی میکند. همانطور که میدانید وهله سازی از یک کلاس با عملگر new انجام میشود. سازنده کلاس بلافاصله پس از آنکه حافظه برای شیء در حال تولید اختصاص داده شد، توسط عملگر new فراخوانی میشود.
اگر برای کلاسی که طراحی میکنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتیها را با توجه به نوع آنها تنظیم مینماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتیها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای میسازد که وضعیت صحیحی ندارد و نمیتواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.
در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف میکنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
در این سازنده مقدار ۱ را برای متغیر خصوصی پشت (backing field یا backing store) هر یک از دو ویژگی قاعده و ارتفاع تنظیم نموده ایم.
کد بالا مقدار ۱ را برای قاعده و ارتفاع و مقدار ۰.۵ را برای مساحت چاپ مینماید. بنابراین مشخص است که سازنده اجرا شده و مقادیر مناسب را برای شیء تنظیم نموده به طوری که شیء از بدو تولید در وضعیت مناسبی است.
با توجه به اینکه مقادیر ارسالی به این سازنده توسط کد مشتری در نظر گرفته میشود و ممکن است در محدوده مجاز نباشد، به جای انتساب مستقیم این مقادیر به فیلد خصوصی پشت ویژگی قاعده و ارتفاع یعنی baseLength_ و height_ آنها را به پروپرتیها منتسب کردیم تا قانون اعتبارسنجی موجود در اکسسور set پروپرتیها از انتساب مقادیر غیر مجاز جلوگیری کند.
سازنده اخیر را میتوان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومانهای مورد نظر مورد استفاده قرار داد.
مقادیر چاپ شده برابر ۵ برای ارتفاع، ۸ برای قاعده و ۲۰ برای مساحت خواهد بود که نشان از اجرای صحیح سازنده دارد.
در مطالب بالا چندین بار از سازندهها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه میکنیم. این دو نکته را به خاطر داشته باشید:
در بخشهای بعدی مطالب بیشتری در مورد سازندهها و سایر اعضای کلاس خواهید آموخت.
اگرچه ایجاد شیء و مقداردهی به ویژگیهای آن ما را به هدفمان میرساند، اما بهترین روش نیست چرا که ممکن است مقداردهی به یک ویژگی فراموش شده و سبب شود شیء در وضعیت نادرستی قرار گیرد. این مشکل با استفاده از سازندهها (Constructors) حل میشود.
سازنده (Constructor) عضو ویژه ای از کلاس است بسیار شبیه به یک متد که در هنگام وهله سازی (ایجاد یک شیء از کلاس) به صورت خودکار فراخوانی و اجرا میشود. وظیفه سازنده مقداردهی به ویژگیهای عمومی و خصوصی شیء تازه ساخته شده و به طور کلی انجام هر کاری است که برنامهنویس در نظر دارد پیش از استفاده از شیء به انجام برساند.
مثالی که در این بخش بررسی میکنیم کلاس مثلث است. برای پیاده سازی این کلاس سه ویژگی در نظر گرفته ایم. قاعده، ارتفاع و مساحت. بله مساحت را این بار به جای متد به صورت یک پروپرتی پیاده سازی میکنیم. اگرچه در آینده بیشتر راجع به چگونگی انتخاب برای پیاده سازی یک عضو کلاس به صورت پروپرتی یا متد بحث خواهیم کرد اما به عنوان یک قانون کلی در نظر داشته باشید عضوی که به صورت منطقی به عنوان داده مطرح است را به صورت پروپرتی پیاده سازی کنید. مانند نام دانشجو. از طرفی اعضایی که دلالت بر انجام عملی دارند را به صورت متد پیاده سازی میکنیم. مانند متد تبدیل به نوع داده دیگر. (مثلاً ()Object.ToString)
public class Triangle { private int _height; private int _baseLength; public int Height { get { return _height; } set { if (value < 1 || value > 100) { // تولید خطا } _height = value; } } public int BaseLength { get { return _baseLength; } set { if (value < 1 || value > 100) { // تولید خطا } _baseLength = value; } } public double Area { get { return _height * _baseLength * 0.5; } } }
• در اکسسور set دو ویژگی قاعده و ارتفاع، محدوده مجاز مقادیر قابل انتساب را بررسی نموده ایم. در صورتی که مقداری خارج از محدوده یاد شده برای این ویژگیها تنظیم شود خطایی را ایجاد خواهیم کرد. شاید برای برنامه نویسانی که تجربه کمتری دارند زیاد روش مناسبی به نظر نرسد. اما این یک روش قابل توصیه است. مواجه شدن کد مشتری (کد استفاده کننده از کلاس) با یک خطای مهلک که علت رخ دادن خطا را نیز میتوان به همراه آن ارائه کرد بسیار بهتر از بروز خطاهای منطقی در برنامه است. چون رفع خطاهای منطقی بسیار دشوارتر است. در مطالب آینده راجع به تولید خطا و موارد مرتبط با آن بیشتر صحبت میکنیم.
• در مورد ویژگی مساحت، اکسسور set را پیاده سازی نکرده ایم تا این ویژگی را به صورت فقط خواندنی ایجاد کنیم.
وقتی شیء ای از یک کلاس ایجاد میشود، بلافاصله سازنده آن فراخوانی میگردد. سازندهها هم نام کلاسی هستند که در آن تعریف میشوند و معمولاً اعضای داده ای شیء جدید را مقداردهی میکند. همانطور که میدانید وهله سازی از یک کلاس با عملگر new انجام میشود. سازنده کلاس بلافاصله پس از آنکه حافظه برای شیء در حال تولید اختصاص داده شد، توسط عملگر new فراخوانی میشود.
سازنده پیش فرض
سازندهها مانند متدهای دیگر میتوانند پارامتر دریافت کنند. سازنده ای که هیچ پارامتری دریافت نمیکند سازنده پیش فرض (Default constructor) نامیده میشود. سازنده پیش فرض زمانی اجرا میشود که با استفاده از عملگر new شیء ای ایجاد میکنید اما هیچ آرگومانی را برای این عملگر در نظر نگرفته اید.اگر برای کلاسی که طراحی میکنید سازنده ای تعریف نکرده باشید کامپایلر سی شارپ یک سازنده پیش فرض (بدون پارامتر) خواهد ساخت. این سازنده هنگام ایجاد اشیاء فراخوانی شده و مقدار پیش فرض متغیرها و پروپرتیها را با توجه به نوع آنها تنظیم مینماید. مثلاً مقدار صفر برای متغیری از نوع int یا false برای نوع bool و null برای انواع ارجاعی که در آینده در این مورد بیشتر خواهید آموخت.
اگر مقادیر پیش فرض برای متغیرها و پروپرتیها مناسب نباشد، مانند مثال ما، سازنده پیش فرض ساخته شده توسط کامپایلر همواره شیء ای میسازد که وضعیت صحیحی ندارد و نمیتواند وظیفه خود را انجام دهد. در این گونه موارد باید این سازنده را جایگزین نمود.
جایگزینی سازنده پیش فرض ساخته شده توسط کامپایلر
افزودن یک سازنده صریح به کلاس بسیار شبیه به تعریف یک متد در کلاس است. با این تفاوت که:- سازنده هم نام کلاس است.
- برای سازنده نوع خروجی در نظر گرفته نمیشود.
در مثال ما محدوده مجاز برای قاعده و ارتفاع مثلث بین ۱ تا ۱۰۰ است در حالی که سازنده پیش فرض مقدار صفر را برای آنها تنظیم خواهد نمود. پس برای اینکه مطمئن شویم اشیاء مثلث ساخته شده از این کلاس در همان بدو تولید دارای قاعده و ارتفاع معتبری هستند سازنده زیر را به صورت صریح در کلاس تعریف میکنیم تا جایگزین سازنده پیش فرضی شود که کامپایلر خواهد ساخت و به جای آن فراخوانی گردد.
public Triangle() { _height = _baseLength = 1; }
اجرای سازنده
همانطور که گفته شد سازنده اضافه شده به کلاس جایگزین سازنده پیش فرض کامپایلر شده و در هنگام ایجاد یک شیء جدید از کلاس مثلث توسط عملگر new اجرا میشود. برای بررسی اجرا شدن سازنده به سادگی میتوان کدی مشابه مثال زیر را نوشت.Triangle triangle = new Triangle(); Console.WriteLine(triangle.Height); Console.WriteLine(triangle.BaseLength); Console.WriteLine(triangle.Area);
سازندههای پارامتر دار
در مثال قبل یک سازنده بدون پارامتر را به کلاس اضافه کردیم. این سازنده تنها مقادیر پیش فرض مناسبی را تنظیم میکند. بدیهی است پس از ایجاد شیء در صورت نیاز میتوان مقادیر مورد نظر دیگر را برای قاعده و ارتفاع تنظیم نمود. اما برای اینکه سازنده بهتر بتواند فرآیند وهله سازی را کنترل نماید میتوان پارامترهایی را به آن افزود. افزودن پارامتر به سازنده مانند افزودن پارامتر به متدهای دیگر صورت میگیرد. در مثال زیر سازنده دیگری تعریف میکنیم که دارای دو پارامتر است. یکی قاعده و دیگری ارتفاع. به این ترتیب در حین فرآیند وهله سازی میتوان مقادیر مورد نظر را منتسب نمود.public Triangle(int height, int baseLength) { Height = height; BaseLength = baseLength; }
سازنده اخیر را میتوان به صورت زیر با استفاده از عملگر new و فراهم کردن آرگومانهای مورد نظر مورد استفاده قرار داد.
Triangle triangle = new Triangle(5, 8); Console.WriteLine(triangle.Height); Console.WriteLine(triangle.BaseLength); Console.WriteLine(triangle.Area);
در مطالب بالا چندین بار از سازندهها صحبت کردیم و گفتیم سازنده دیگری به کلاس اضافه میکنیم. این دو نکته را به خاطر داشته باشید:
- یک کلاس میتواند دارای چندین سازنده باشد که بر اساس آرگومانهای فراهم شده هنگام وهله سازی، سازنده مورد نظر انتخاب و اجرا میشود.
- الزامی به تعریف یک سازنده پیش فرض (به معنای بدون پارامتر) نیست. یعنی یک کلاس میتواند هیچ سازنده بی پارامتری نداشته باشد.
در بخشهای بعدی مطالب بیشتری در مورد سازندهها و سایر اعضای کلاس خواهید آموخت.
برای ترسیم نمودار در برنامههای WPF، چندین کتابخانهی سورس باز مانند GraphIT ، Sparrow Toolkit ، Dynamic Data Display و ... OxyPlot وجود دارند. در بین اینها، کتابخانهی OxyPlot دارای این مزایا است:
- دارای مجوز MIT است. (مجاز هستید از آن در هر نوع برنامهای استفاده کنید)
- cross-platform است. به این معنا که دات نت، WinRT و Xamarin را به خوبی پشتیبانی میکند.
- WPF و همچنین WinForms تا Xamarin.Android را پوشش میدهد.
- بستههای اصلی NuGet آن تا به امروز نزدیک به 40 هزار بار دریافت شدهاند.
- انجمن فعالی دارد.
- بسیار بسیار غنی است. تا حدی که مرور سطحی مجموعه مثالهای آن شاید چند ساعت وقت را به خود اختصاص دهد.
- طراحی آن به نحوی است که با الگوی MVVM کاملا سازگاری دارد.
- به صورت متناوبی به روز شده و نگهداری میشود.
این برنامه (تصویر فوق) که حاوی مرورگر مثالهای آن است، در پوشهی Source\Examples\WPF\ExampleBrowser سورسهای آن قرار دارد.
در ادامه نگاهی خواهیم داشت به نحوهی استفاده از OxyPlot در برنامههای WPF جهت رسم نموداری بلادرنگ که اطلاعات آن در زمان اجرای برنامه تهیه شده و در همین حین نیز تغییر میکنند.
دریافت بستههای نیوگت OxyPlot
برای دریافت دو بستهی OxyPlot.Core و OxyPlot.Wpf تنها کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنیم:
افزودن تعاریف چارت به View
ابتدا باید فضای نام oxy اضافه شود. پس از آن oxy:PlotView به صفحه اضافه شده و سپس Model آن از ViewModel برنامه تعذیه میگردد.
ساختار کلی ViewModel برنامه
کار ViewModel متصل شده به View فوق، مقدار دهی PlotModel است.
یک نکتهی کاربردی
اگر هیچ ایدهای نداشتید که این PlotModel را چگونه باید مقدار دهی کرد، به همان برنامهی ExampleBrowser ابتدای مطلب مراجعه کنید.
مثالهای اجرای شدهی آن یک برگهی نمایشی و یک برگهی Code دارند. خروجی این متدها را اگر به خاصیت PlotModel فوق انتساب دهید ... یک چارت کامل خواهید داشت.
مراحل ساخت یک PlotModel
ابتدا نیاز است یک وهلهی جدید از PlotModel را ایجاد کنیم:
PlotModel در برگیرندهی محورها، نقاط و تمام ناحیهی چارت است. در اینجا عنوان و زیرعنوان نمودار، مقدار دهی شدهاند. همچنین در همین ViewModel بدون نیاز به مراجعه به View، میتوان به رخدادهای مختلف OxyPlot دسترسی داشت. برای مثال میخواهیم اگر کاربر دو بار بر روی چارت کلیک کرد، کلیه اعمال zoom و pan آن به حالت اول برگردانده شوند.
برای pan، کافی است دکمهی سمت راست ماوس را نگه داشته و بکشید. به این ترتیب میتوانید نمودار را بر روی محورهای X و Y حرکت دهید.
برای zoom نیاز است دکمهی وسط ماوس را نگه داشته و بکشید. ناحیهای که در این حالت نمایان میگردد، محل بزرگنمایی نهایی خواهد بود.
این دو قابلیت به صورت توکار در OxyPlot قرار دارند و نیازی به کدنویسی برای فعال سازی آنها نیست.
افزودن محورهای X و Y
محور X در مثال ما، از نوع LinearAxis است. بهتر است متغیر آنرا در سطح کلاس تعریف کرد تا بتوان از آن در سایر قسمتهای چارت نیز بهره گرفت:
در اینجا مقدار خاصیت Position، مشخص میکند که این محور در کجا باید قرار گیرد. اگر مقدار دهی نشود، محور Y را تشکیل خواهد داد.
مقدار دهی GridlineStyleها سبب ایجاد یک Grid خاکستری در نمودار میشوند.
در آخر نیاز است این محور به محورهای PlotModel اضافه شود.
تعریف محور Y نیز به همین نحو است. اگر مقدار خاصیت Position ذکر نشود، این محور در سمت چپ صفحه قرار میگیرد:
افزودن تعاریف سریهای خطوط
در تصویر فوق، دو سری خط را ملاحظه میکنید. تعاریف پایه سری اول آن به این صورت است:
مقدار خاصیت MarkerType، نحوهی نمایش نقاط اضافه شده را مشخص میکند. خاصیت Title، عنوان آنرا که در کنار صفحه نمایش داده شده، تعیین کرده و در آخر، این سری نیز باید به سریهای PlotModel اضافه گردد.
هر سری دارای خاصیت MouseDown نیز هست. برای مثال اگر علاقمندید که کلیک کاربر بر روی نقاط مختلف را دریافت کرده و سپس بر این اساس، اطلاعات خاصی را نمایش دهید، میتوانید از مقدار e.HitTestResult.Index استفاده کنید. در اینجا ایندکس نزدیکترین نقطه به محل کلیک کاربر یافت میشود.
تعاریف اولیه سری دوم نیز به همین ترتیب هستند:
به روز رسانی دستی OxyPlot
پس از نمایش اولیه OxyPlot، هر تغییری که در اطلاعات آن صورت گیرد، نمایش داده نخواهد شد. برای به روز رسانی آن فقط کافی است متد PlotModel.InvalidatePlot را فراخوانی نمائید. برای نمونه در متدهای فوق، کلیک ماوس، پس از رسم نمودار انجام میشود. بنابراین اگر نیاز است زیرعنوان نمودار تغییر کند، باید متد PlotModel.InvalidatePlot نیز فراخوانی گردد.
ایجاد یک تایمر برای افزودن نقاط به صورت پویا
در ادامه میخواهیم نقاطی را به صورت پویا به نمودار اضافه کنیم. نمایش یکباره نمودار، نکتهی خاصی ندارد. تنها کافی است توسط lineSeries1.Points.Add یک سری DataPoint را اضافه کنید. این نقاط در زمان نمایش View، به یکباره نمایش داده خواهند شد. اما در اینجا ابتدا یک چارت خالی نمایش داده میشود و سپس قرار است نقاطی به آن اضافه شوند.
چند نکته در اینجا حائز اهمیت هستند:
- افزودن نقاط جدید توسط متدهای lineSeries1.Points.Add انجام میشوند.
- مقادیر max محورهای x و y را نیز ذخیره میکنیم. اگر نقاط برنامه پویا نباشند، OxyPlot به صورت خودکار نمودار را با مقیاس درستی ترسیم میکند. اما اگر نقاط پویا باشند، نیاز است حداکثر محورهای x و y را به صورت دستی در آن تنظیم کنیم. به همین جهت متدهای updateXMax و updateYMax در اینجا فراخوانی شدهاند.
- به روز رسانی ظاهر چارت، توسط متد زیر انجام میشود:
کل کاری که در اینجا انجام شده، فراخوانی کنترل شدهی PlotModel.InvalidatePlot هر دو ثانیه یکبار است. CompositionTarget.Rendering بر اساس رندر View، عمل کرده و از آن میتوان برای به روز رسانی نمایشی چارت استفاده کرد. اگر متد PlotModel.InvalidatePlot را دقیقا در زمان افزودن نقاط فراخوانی کنیم به CPU Usage بالایی خواهیم رسید. به همین جهت نیاز است فراخوانی آن کنترل شده و در فواصل زمانی مشخصی باشد. همچنین اگر نقطهای اضافه نشده (بر اساس مقدار haveNewPoints)، به روز رسانی انجام نخواهد شد.
نکتهی دیگری که در متد updatePlot فوق درنظر گرفته شدهاست، تغییر مقدار Maximum محورهای x و y بر اساس حداکثرهای نقاط اضافه شدهاست. به این ترتیب نمودار به صورت خودکار جهت نمایش کل اطلاعات، تغییر اندازه خواهد داد.
البته همانطور که عنوان شد، تمام این تهمیدات برای نمایش نمودارهای بلادرنگ است. اگر کار مقدار دهی Points.Add را فقط یکبار در سازندهی ViewModel انجام میدهید، نیازی به این نکات نخواهید داشت.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
OxyPlotWpfTests.zip
- دارای مجوز MIT است. (مجاز هستید از آن در هر نوع برنامهای استفاده کنید)
- cross-platform است. به این معنا که دات نت، WinRT و Xamarin را به خوبی پشتیبانی میکند.
- WPF و همچنین WinForms تا Xamarin.Android را پوشش میدهد.
- بستههای اصلی NuGet آن تا به امروز نزدیک به 40 هزار بار دریافت شدهاند.
- انجمن فعالی دارد.
- بسیار بسیار غنی است. تا حدی که مرور سطحی مجموعه مثالهای آن شاید چند ساعت وقت را به خود اختصاص دهد.
- طراحی آن به نحوی است که با الگوی MVVM کاملا سازگاری دارد.
- به صورت متناوبی به روز شده و نگهداری میشود.
این برنامه (تصویر فوق) که حاوی مرورگر مثالهای آن است، در پوشهی Source\Examples\WPF\ExampleBrowser سورسهای آن قرار دارد.
در ادامه نگاهی خواهیم داشت به نحوهی استفاده از OxyPlot در برنامههای WPF جهت رسم نموداری بلادرنگ که اطلاعات آن در زمان اجرای برنامه تهیه شده و در همین حین نیز تغییر میکنند.
دریافت بستههای نیوگت OxyPlot
برای دریافت دو بستهی OxyPlot.Core و OxyPlot.Wpf تنها کافی است دستور ذیل را در کنسول پاورشل نیوگت اجرا کنیم:
PM> install-package OxyPlot.Wpf
افزودن تعاریف چارت به View
<Window x:Class="OxyPlotWpfTests.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:oxy="http://oxyplot.org/wpf" xmlns:oxyPlotWpfTests="clr-namespace:OxyPlotWpfTests" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <oxyPlotWpfTests:MainWindowViewModel x:Key="MainWindowViewModel" /> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource MainWindowViewModel}}"> <oxy:PlotView Model="{Binding PlotModel}"/> </Grid> </Window>
ساختار کلی ViewModel برنامه
کار ViewModel متصل شده به View فوق، مقدار دهی PlotModel است.
public class MainWindowViewModel { public PlotModel PlotModel { get; set; }
یک نکتهی کاربردی
اگر هیچ ایدهای نداشتید که این PlotModel را چگونه باید مقدار دهی کرد، به همان برنامهی ExampleBrowser ابتدای مطلب مراجعه کنید.
مثالهای اجرای شدهی آن یک برگهی نمایشی و یک برگهی Code دارند. خروجی این متدها را اگر به خاصیت PlotModel فوق انتساب دهید ... یک چارت کامل خواهید داشت.
مراحل ساخت یک PlotModel
ابتدا نیاز است یک وهلهی جدید از PlotModel را ایجاد کنیم:
private void createPlotModel() { PlotModel = new PlotModel { Title = "سری خطوط", Subtitle = "Pan (right click and drag)/Zoom (Middle click and drag)/Reset (double-click)" }; PlotModel.MouseDown += (sender, args) => { if (args.ChangedButton == OxyMouseButton.Left && args.ClickCount == 2) { foreach (var axis in PlotModel.Axes) axis.Reset(); PlotModel.InvalidatePlot(false); } }; }
برای pan، کافی است دکمهی سمت راست ماوس را نگه داشته و بکشید. به این ترتیب میتوانید نمودار را بر روی محورهای X و Y حرکت دهید.
برای zoom نیاز است دکمهی وسط ماوس را نگه داشته و بکشید. ناحیهای که در این حالت نمایان میگردد، محل بزرگنمایی نهایی خواهد بود.
این دو قابلیت به صورت توکار در OxyPlot قرار دارند و نیازی به کدنویسی برای فعال سازی آنها نیست.
افزودن محورهای X و Y
محور X در مثال ما، از نوع LinearAxis است. بهتر است متغیر آنرا در سطح کلاس تعریف کرد تا بتوان از آن در سایر قسمتهای چارت نیز بهره گرفت:
readonly LinearAxis _xAxis = new LinearAxis(); private void addXAxis() { _xAxis.Minimum = 0; _xAxis.MaximumPadding = 1; _xAxis.MinimumPadding = 1; _xAxis.Position = AxisPosition.Bottom; _xAxis.Title = "X axis"; _xAxis.MajorGridlineStyle = LineStyle.Solid; _xAxis.MinorGridlineStyle = LineStyle.Dot; PlotModel.Axes.Add(_xAxis); }
مقدار دهی GridlineStyleها سبب ایجاد یک Grid خاکستری در نمودار میشوند.
در آخر نیاز است این محور به محورهای PlotModel اضافه شود.
تعریف محور Y نیز به همین نحو است. اگر مقدار خاصیت Position ذکر نشود، این محور در سمت چپ صفحه قرار میگیرد:
readonly LinearAxis _yAxis = new LinearAxis(); private void addYAxis() { _yAxis.Minimum = 0; _yAxis.Title = "Y axis"; _yAxis.MaximumPadding = 1; _yAxis.MinimumPadding = 1; _yAxis.MajorGridlineStyle = LineStyle.Solid; _yAxis.MinorGridlineStyle = LineStyle.Dot; PlotModel.Axes.Add(_yAxis); }
افزودن تعاریف سریهای خطوط
در تصویر فوق، دو سری خط را ملاحظه میکنید. تعاریف پایه سری اول آن به این صورت است:
readonly LineSeries _lineSeries1 = new LineSeries(); private void addLineSeries1() { _lineSeries1.MarkerType = MarkerType.Circle; _lineSeries1.StrokeThickness = 2; _lineSeries1.MarkerSize = 3; _lineSeries1.Title = "Start"; _lineSeries1.MouseDown += (s, e) => { if (e.ChangedButton == OxyMouseButton.Left) { PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index); PlotModel.InvalidatePlot(false); } }; PlotModel.Series.Add(_lineSeries1); }
هر سری دارای خاصیت MouseDown نیز هست. برای مثال اگر علاقمندید که کلیک کاربر بر روی نقاط مختلف را دریافت کرده و سپس بر این اساس، اطلاعات خاصی را نمایش دهید، میتوانید از مقدار e.HitTestResult.Index استفاده کنید. در اینجا ایندکس نزدیکترین نقطه به محل کلیک کاربر یافت میشود.
تعاریف اولیه سری دوم نیز به همین ترتیب هستند:
readonly LineSeries _lineSeries2 = new LineSeries(); private void addLineSeries2() { _lineSeries2.MarkerType = MarkerType.Circle; _lineSeries2.Title = "End"; _lineSeries2.StrokeThickness = 2; _lineSeries2.MarkerSize = 3; _lineSeries2.MouseDown += (s, e) => { if (e.ChangedButton == OxyMouseButton.Left) { PlotModel.Subtitle = "Index of nearest point in LineSeries: " + Math.Round(e.HitTestResult.Index); PlotModel.InvalidatePlot(false); } }; PlotModel.Series.Add(_lineSeries2); }
به روز رسانی دستی OxyPlot
پس از نمایش اولیه OxyPlot، هر تغییری که در اطلاعات آن صورت گیرد، نمایش داده نخواهد شد. برای به روز رسانی آن فقط کافی است متد PlotModel.InvalidatePlot را فراخوانی نمائید. برای نمونه در متدهای فوق، کلیک ماوس، پس از رسم نمودار انجام میشود. بنابراین اگر نیاز است زیرعنوان نمودار تغییر کند، باید متد PlotModel.InvalidatePlot نیز فراخوانی گردد.
ایجاد یک تایمر برای افزودن نقاط به صورت پویا
در ادامه میخواهیم نقاطی را به صورت پویا به نمودار اضافه کنیم. نمایش یکباره نمودار، نکتهی خاصی ندارد. تنها کافی است توسط lineSeries1.Points.Add یک سری DataPoint را اضافه کنید. این نقاط در زمان نمایش View، به یکباره نمایش داده خواهند شد. اما در اینجا ابتدا یک چارت خالی نمایش داده میشود و سپس قرار است نقاطی به آن اضافه شوند.
private int _xMax; private int _yMax; private bool _haveNewPoints; private void addPoints() { var timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(1)}; var rnd = new Random(); var x = 1; updateXMax(x); timer.Tick += (sender, args) => { var y1 = rnd.Next(100); updateYMax(y1); _lineSeries1.Points.Add(new DataPoint(x, y1)); var y2 = rnd.Next(100); updateYMax(y2); _lineSeries2.Points.Add(new DataPoint(x, rnd.Next(y2))); x++; updateXMax(x); _haveNewPoints = true; }; timer.Start(); } private void updateXMax(int value) { if (value > _xMax) { _xMax = value; } } private void updateYMax(int value) { if (value > _yMax) { _yMax = value; } }
- افزودن نقاط جدید توسط متدهای lineSeries1.Points.Add انجام میشوند.
- مقادیر max محورهای x و y را نیز ذخیره میکنیم. اگر نقاط برنامه پویا نباشند، OxyPlot به صورت خودکار نمودار را با مقیاس درستی ترسیم میکند. اما اگر نقاط پویا باشند، نیاز است حداکثر محورهای x و y را به صورت دستی در آن تنظیم کنیم. به همین جهت متدهای updateXMax و updateYMax در اینجا فراخوانی شدهاند.
- به روز رسانی ظاهر چارت، توسط متد زیر انجام میشود:
private readonly Stopwatch _stopwatch = new Stopwatch(); private void updatePlot() { CompositionTarget.Rendering += (sender, args) => { if (_stopwatch.ElapsedMilliseconds > _lastUpdateMilliseconds + 2000 && _haveNewPoints) { if (_yMax > 0 && _xMax > 0) { _yAxis.Maximum = _yMax + 3; _xAxis.Maximum = _xMax + 1; } PlotModel.InvalidatePlot(false); _haveNewPoints = false; _lastUpdateMilliseconds = _stopwatch.ElapsedMilliseconds; } }; }
نکتهی دیگری که در متد updatePlot فوق درنظر گرفته شدهاست، تغییر مقدار Maximum محورهای x و y بر اساس حداکثرهای نقاط اضافه شدهاست. به این ترتیب نمودار به صورت خودکار جهت نمایش کل اطلاعات، تغییر اندازه خواهد داد.
البته همانطور که عنوان شد، تمام این تهمیدات برای نمایش نمودارهای بلادرنگ است. اگر کار مقدار دهی Points.Add را فقط یکبار در سازندهی ViewModel انجام میدهید، نیازی به این نکات نخواهید داشت.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
OxyPlotWpfTests.zip
ASP.NET Core با ذهنیت پشتیبانی و استفاده از تزریق وابستگیها ایجاد شدهاست. اپلیکیشنهای ASP.NET Core از سرویسهای ذاتی فریم ورک که داخل متدهای کلاس Startup پروژه تزریق شدهاند و همچنین سرویسهای اپلیکیشن که تنظیمات خاص آنها در پروژه انجام گرفته است، استفاده میکنند. سرویس کانتینر پیش فرض ارائه شده توسط ASP.NET Core، مجموعهای حداقلی از ویژگیها را ارائه میکند و هدف آن جایگزینی با دیگر فریم ورکهای تزریق وابستگی نمیباشد.
مشاهده یا دانلود کدهای مقاله
تزریق وابستگی چیست؟
تزریق وابستگی (DI) تکنیکی برای دستیابی به اتصال شل بین اشیاء و همکاران اشیاء و وابستگیهای بین آنها میباشد. یک شیء برای انجام وظایف خود، بجای اینکه اشیاء همکار خود را به صورت مستقیم نمونه سازی کند، یا از ارجاعات استاتیک استفاده نماید، میتواند از اشیائی که برایش تامین شدهاست، استفاده کند. در اغلب موارد کلاسها، وابستگیهای خود را از طریق سازندهی خود درخواست میکنند، که به آنها اجازه میدهد اصل وابستگی صریح را رعایت کنند (Explicit Dependencies Principle). این روش را «تزریق در سازنده» مینامند.
از آنجا که در طراحی کلاسها با استفاده از DI، نمونه سازی مستقیم، توسط کلاسها و به صورت Hard-coded انجام نمیگیرد، وابستگی بین اشیاء کم شده و پروژهای با اتصالات شل به دست میآید. با این کار اصل وابستگی معکوس (Dependency Inversion Principle) رعایت میشود. بر اساس این اصل، ماژولهای سطح بالا نباید به ماژولهای سطح پایین خود وابسته باشند؛ بلکه هر دو باید به کلاسهایی انتزاعی وابسته باشند. اشیاء بجای ارجاع به پیاده سازیهای خاص کلاسهای همکار خود، کلاسهای انتزاعی، معمولاٌ اینترفیس آنها را درخواست میکنند و هنگام نمونه سازی از آنها (داخل متد سازنده) کلاس پیاده سازی شده برایشان تامین میشود. خارج کردن وابستگیهای مستقیم از کلاسها و تامین پیاده سازیهای این اینترفیسها به صورت پارامترهایی برای کلاسها، یک مثال از الگوی طراحی استراتژی (Strategy design pattern) میباشد.
در حالتیکه کلاسها به تعداد زیادی کلاس وابستگی داشته باشند و برای اجرا شدن، نیاز به تامین وابستگیهایشان داشته باشند، بهتر است یک کلاس اختصاصی، برای نمونه سازی این کلاسها با وابستگیهای مورد نیاز آنها، در سیستم وجود داشته باشد. این کلاس نمونه ساز را کانتینرIoC، یا کانتینر DI یا به طور خلاصه کانتینر مینامند ( Inversion of Control (IoC) ). کانتینر در اصل یک کارخانه میباشد که وظیفهی تامین نمونههایی از کلاسهایی را که از آن درخواست میشود، انجام میدهد. اگر یک کلاس تعریف شده، وابستگی به کلاسهای دیگر داشته باشد و کانتینر برای ارائه وابستگیهای کلاس تعریف شده تنظیم شده باشد، هر موقع نیاز به یک نمونه از این کلاس وجود داشته باشد، به عنوان بخشی از کار نمونه سازی از کلاس مورد نظر، کلاسهای وابستهی آن نیز ایجاد میشوند (همهی کارهای مربوط به نمونه سازی کلاس خاص و کلاسهای وابسته به آن توسط کانتینر انجام میگیرد). به این ترتیب، میتوان وابستگیهای بسیار پیچیده و تو در توی موجود در سیستم را بدون نیاز به هیچگونه نمونه سازی hard-code شده، برای کلاسها فراهم کرد. کانتینرها علاوه بر ایجاد اشیاء و وابستگیهای موجود در آنها، معمولا طول عمر اشیاء در اپلیکیشن را نیز مدیریت میکنند.
ASP.NET Core یک کانتینر بسیار ساده را به نام اینترفیس IServiceProvider ارائه داده است که به صورت پیش فرض از تزریق وابستگی در سازندهی کلاسها پشتیبانی میکند و همچنین ASP.NET برخی از سرویسهای خود را از طریق DI در دسترس قرار داده است. کانتینرASP.NET، یک اشارهگر به کلاسهایی است که به عنوان سرویس عمل میکنند. در ادامهی این مقاله، سرویسها به کلاسهایی گفته میشود که به وسیلهی کانتینر ASP.NET Core مدیریت میشوند. شما میتوانید سرویس ConfigureServices کانتینر را در داخل کلاس Startup پروژه خود پیکربندی کنید.
تزریق وابستگی از طریق متد سازندهی کلاس
تزریق وابستگی از طریق متد سازنده، مستلزم آن است که سازندهی کلاس مورد نظر عمومی باشد. در غیر این صورت، اپلیکیشن شما استثنای InvalidOperationException را با پیام زیر نشان میدهد:
تزریق از طریق متد سازنده مستلزم آن است که تنها یک سازندهی مناسب وجود داشته باشد. البته Overload سازنده امکان پذیر است؛ ولی باید تنها یک متد سازنده وجود داشته باشد که آرگومانهای آن توسط DI قابل ارائه باشند. اگر بیش از یکی وجود داشته باشد، سیستم استثنای InvalidOperationException را با پیام زیر نشان میدهد:
سازندگان میتوانند آرگومانهایی را از طریق DI دریافت کنند. برای این منظور آرگومانهای این سازندهها باید مقدار پیش فرضی را داشته باشند. به مثال زیر توجه نمایید:
استفاده از سرویس ارائه شده توسط فریم ورک
متد ConfigureServices در کلاس Startup، مسئول تعریف سرویسهایی است که سیستم از آن استفاده میکند. از جملهی این سرویسها میتوان به ویژگیهای پلتفرم مانند EF Core و ASP.NET Core MVC اشاره کرد. IServiceCollection که به ConfigureServices ارائه میشود، سرویسهای زیر را تعریف میکند (که البته بستگی به نوع پیکربندی هاست دارد):
در زیر نمونه ای از نحوهی اضافه کردن سرویسهای مختلف را به کانتینر، با استفاده از متدهای الحاقی مانند AddDbContext، AddIdentity و AddMvc، مشاهده میکنید:
ویژگیها و میان افزارهای ارائه شده توسط ASP.NET، مانند MVC، از یک قرارداد، با استفاده از متد الحاقی AddServiceName برای ثبت تمام سرویسهای مورد نیاز این ویژگی پیروی میکنند.
ثبت سرویسهای اختصاصی
شما میتوانید سرویسهای اپلیکیشن خودتان را به ترتیبی که در تکه کد زیر مشاهده میکنید، ثبت نمایید. اولین نوع جنریک، نوعی است که از کانتینر درخواست خواهد شد و معمولا به شکل اینترفیس میباشد. نوع دوم، نوع پیاده سازی شدهای است که به وسیلهی کانتینر، نمونه سازی خواهد شد و کانتینر برای درخواستهای از نوع اول، این نمونه از تایپ را ارائه خواهد کرد:
نکته:
هر متد الحاقی <services.Add<ServiceName، سرویسهایی را اضافه و پیکربندی میکند. به عنوان مثال services.AddMvc نیازمندیهای سرویس MVC را اضافه میکند. توصیه میشود شما هم با افزودن متدهای الحاقی در فضای نام Microsoft.Extensions.DependencyInjection این قرارداد را رعایت نمائید. این کار باعث کپسوله شدن ثبت گروهی سرویسها میشود.
متد AddTransient، برای نگاشت نوعهای انتزاعی به سرویسهای واقعی که نیاز به نمونه سازی به ازای هر درخواست دارند، استفاده میشود. در اصطلاح، طول عمر سرویسها در اینجا مشخص میشوند. در ادامه گزینههای دیگری هم برای طول عمر سرویسها تعریف خواهند شد. خیلی مهم است که برای هر یک از سرویسهای ثبت شده، طول عمر مناسبی را انتخاب نمایید. آیا برای هر کلاس که سرویسی را درخواست میکند، باید یک نمونهی جدید ساخته شود؟ آیا فقط یک نمونه در طول یک درخواست وب مورد استفاده قرار میگیرد؟ یا باید از یک نمونهی واحد برای طول عمر کل اپلیکیشن استفاده شود؟
در مثال ارائه شدهی در این مقاله، یک کنترلر ساده به نام CharactersController وجود دارد که نام کاراکتری را نشان میدهد. متد Index، لیست کنونی کاراکترهایی را که در اپلیکیشن ذخیره شدهاند، نشان میدهد. در صورتیکه این لیست خالی باشد، تعدادی به آن اضافه میکند. توجه داشته باشید، اگرچه این اپلیکیشن از Entity Framework Core و ClassDataContext برای دادههای مانا استفاده میکند، هیچیکدام از آنها در کنترلر ظاهر نمیشوند. در عوض، مکانیزم دسترسی به دادههای خاص، در پشت یک اینترفیس (ICharacterRepository) مخفی شده است (طبق الگوی طراحی ریپازیتوری). یک نمونه از ICharacterRepository از طریق سازنده درخواست میشود و به یک فیلد خصوصی اختصاص داده میشود، سپس برای دسترسی به کاراکترها در صورت لزوم استفاده میشود:
ICharacterRepository دو متد مورد نیاز کنترلر برای کار با نمونههای Character را تعریف میکند:
این اینترفیس با نوع واقعی CharacterRepository پیاده سازی شده است که در زمان اجرا استفاده میشود:
توجه داشته باشید که CharacterRepository یک ApplicationDbContext را در سازندهی خود درخواست میکند. همانطور که مشاهده میشود هر وابستگی درخواست شده، به نوبه خود وابستگیهای دیگری را درخواست میکند. تزریق وابستگیهایی به شکل زنجیرهای، همانند این مثال غیر معمول نیست. کانتینر مسئول resolve (نمونه سازی) همهی وابستگیهای موجود در گراف وابستگی و بازگرداندن سرویس کاملا resolve شده میباشد.
نکته
ایجاد شیء درخواست شده و تمامی اشیاء مورد نیاز شیء درخواست شده را گراف شیء مینامند. به همین ترتیب مجموعهای از وابستگیهایی را که باید resolve شوند، به طور معمول، درخت وابستگی یا گراف وابستگی مینامند.
در مورد مثال مطرح شده، ICharacterRepository و به نوبه خود ApplicationDbContext باید با سرویسهای خود در کانتینر ConfigureServices و کلاس Startup ثبت شوند. ApplicationDbContext با فراخوانی متد <AddDbContext<T پیکربندی میشود. کد زیر ثبت کردن نوع CharacterRepository را نشان میدهد:
کانتکست انتیتی فریم ورک، با استفاده از متدهای کمکی که در تکه کد بالا نشان داده شده است، باید با طول عمر Scoped به کانتینر سرویسها افزوده شود. این کار میتواند به صورت اتوماتیک انجام گیرد. همهی ریپازیتوریهایی که از Entity Framework استفاده میکنند، باید از یک طول عمر مشابه استفاده کنند.
هشدار
خطر بزرگی را که باید در نظر گرفت، resolve کردن سرویس Scoped از طول عمر singleton میباشد. در صورت انجام این کار، احتمال دارد که سرویسها وارد حالت نادرستی شوند.
سرویسهایی که وابستگیهای دیگری هم دارند، باید آنها را در کانتینر ثبت کنند. اگر سازندهی سرویس نیاز به یک primitive به عنوان ورودی داشته باشد، میتوان با استفاده از الگوی گزینهها و پیکربندی (options pattern and configuration)، ورودیهای مناسبی را به سازندهها منتقل کرد.
طول عمر سرویسها و گزینههای ثبت
سرویسهای ASP.NET را میتوان با طول عمرهای زیر پیکربندی کرد:
Transient: سرویسهایی با طول عمر Transient، در هر زمان که درخواست میشوند، مجددا ایجاد میشوند. این طول عمر برای سرویسهای سبک و بدون حالت مناسب میباشند.
Scoped: سرویسهایی با طول عمر Scoped، تنها یکبار در طی هر درخواست ایجاد میشوند.
Singleton: سرویسهایی با طول عمر Singleton، برای اولین باری که درخواست میشوند (یا اگر در ConfigureServices نمونهای را مشخص کرده باشید) ایجاد میشوند و درخواستهای آتی برای این سرویسها از همان نمونهی ایجاد شده استفاده میکنند. اگر اپلیکیشن شما درخواست رفتار singleton را داشته باشد، پیشنهاد میشود که سرویس کانتینر را برای مدیریت طول عمر سرویس مورد نیاز پیکربندی کنید و خودتان الگوی طراحی singleton را پیاده سازی نکنید.
سرویسها به چندین روش میتوانند در کانتینر ثبت شوند. چگونگی ثبت کردن یک سرویس پیاده سازی شده برای یک نوع، در بخشهای پیشین توضیح داده شده است. علاوه بر این، یک کارخانه را میتوان مشخص کرد، که برای ایجاد نمونه بر اساس تقاضا استفاده شود. رویکرد سوم، ایجاد مستقیم نمونهای از نوع مورد نظر است که در این حالت کانتینر اقدام به ایجاد یا نابود کردن نمونه نمیکند.
به منظور مشخص کردن تفاوت بین این طول عمرها و گزینههای ثبت کردن، یک اینترفیس ساده را در نظر بگیرید که نشان دهندهی یک یا چند operation است و یک شناسهی منحصر به فرد operation را از طریق OperationId نشان میدهد. برای مشخص شدن انواع طول عمرهای درخواست شده، بسته به نحوهی پیکربندی طول عمر سرویس مثال زده شده، کانتینر، نمونهی یکسان یا متفاوتی را از سرویس، به کلاس درخواست کننده ارائه میدهد. ما برای هر طول عمر، یک نوع را ایجاد میکنیم:
ما این اینترفیسها را با استفاده از یک کلاس واحد به نام Operation پیاده سازی کردهایم. سازندهی این کلاس، یک Guid به عنوان ورودی میگیرد؛ یا اگر Guid برایش تامین نشد، خودش یک Guid جدید را میسازد.
سپس در ConfigureServices، هر نوع با توجه به طول عمر مورد نظر، به کانتینر افزوده میشود:
توجه داشته باشید که سرویس IOperationSingletonInstance، از یک نمونهی خاص، با شناسهی شناخته شدهی Guid.Empty استفاده میکند (این Guid فقط شامل اعداد صفر میباشد). بنابراین زمانیکه این تایپ مورد استفاده قرار میگیرد، کاملا واضح است. تمام این سرویسها وابستگیهای خود را به صورت پراپرتی نمایش میدهند. بنابراین میتوان آنها را در View نمایش داد.
برای نشان دادن طول عمر اشیاء، در بین درخواستهای جداگانهی یک اپلیکیشن، مثال ذکر شده شامل کنترلر OperationsController میباشد که هر کدام از انواع IOperation و همچنین OperationService را درخواست میکند. سپس اکشن Index تمام مقادیر OperationId کنترل کننده و سرویسها را نمایش میدهد:
حالا دو درخواست جداگانه برای این کنترلر ساخته شده است:
به تفاوتهای موجود در مقادیر OperationId در یک درخواست و بین درخواستها توجه کنید:
- OperationId اشیاء Transient همیشه متفاوت میباشند. چون یک نمونه جدید برای هر کنترلر و هر سرویس ایجاد شدهاست.
- اشیاء Scoped در یک درخواست، یکسان هستند؛ اما در درخواستهای مختلف متفاوت میباشند.
- اشیاء Singleton برای هر شیء و هر درخواست (صرف نظر از اینکه یک نمونه در ConfigureServices ارائه شده است) یکسان میباشند.
درخواست سرویس
در ASP.NET سرویسهای موجود در یک درخواست HttpContext از طریق مجموعه RequestServices قابل مشاهده میباشد.
RequestServices نشان دهندهی سرویسهایی است که شما به عنوان بخشی از اپلیکیشن خود، آنها را پیکربندی و درخواست میکنید. هنگامیکه اشیاء اپلیکیشن شما وابستگیهای خود را مشخص میکنند، این وابستگیها با استفاده از نوعهای موجود در RequestServices برآورده میشوند و نوعهای موجود در ApplicationServices در این مرحله مورد استفاده قرار نمیگیرد.
به طور کلی، شما نباید مستقیما از این خواص استفاده کنید و بجای آن، نوعهای کلاس خود را توسط سازندهی کلاس، درخواست کنید و اجازه دهید فریم ورک این وابستگیها را تزریق کند. این کار باعث بهوجود آمدن کلاسهایی با قابلیت آزمونپذیری بالاتر و اتصالات شلتر بین آنها میشود.
نکته
درخواست وابستگیها با استفاده از پارامترهای کلاس سازنده، بر روش کار با مجموعهی RequestServices ارجحیت دارد.
طراحی سرویسها برای تزریق وابستگیها
شما باید سرویسهای خود را طوری طراحی کنید که از تزریق وابستگیها برای ارتباطات خود استفاده نمایند. این کار باعث کاهش استفاده از فراخوانیهای متدهای استاتیک (متدهای استاتیک، حالت دار میباشند و استفادهی زیاد از آنها باعث به وجود آمدن بوی بد کدی به نام static cling، میشود) و همچنین از بین رفتن نیاز به نمونه سازی مستقیم کلاسهای وابسته داخل سرویسها، میشود. هر موقع بخواهید بین new کردن یک کلاس، یا درخواست دادن آن از طریق تزریق وابستگی، یکی را انتخاب کنید، این اصطلاح را به یاد بیاورید، New is Glue. با پیروی از اصول SOLID طراحی شیء گرا، به طور طبیعی کلاسهای شما تمایل به کوچک بودن، کارا و قابل تست بودن را دارند.
اگر متوجه شدید که کلاسهای شما تمایل دارند تا تعداد وابستگیهای زیادی به آنها تزریق شود، چه باید بکنید؟ به طور کلی این مشکل نشانهای است از نقض Single Responsibility Principle یا SRP است و احتمالا کلاسهای شما وظایف بیش از اندازهای را دارند. در این گونه موارد تلاش کنید مقداری از وظایف کلاس را به یک کلاس جدید منتقل کنید. در نظر داشته باشید که کلاسهای کنترلر باید به مسائل UI تمرکز کنند و قوانین کسب و کار و جزئیات دسترسی به دادهها باید در کلاسهایی جداگانه و مرتبط با خود قرار داشته باشند.
به طور خاص برای دسترسی به داده ، شما میتوانید DbContext را به کنترلرهای خود تزریق کنید (با فرض اینکه شما EF را به کانتینر سرویس ConfigureServices اضافه کردهاید). بعضی از توسعه دهندگان به جای تزریق مستقیم DbContext از یک اینترفیس ریپازیتوری استفاده مینمایند. میتوانید با استفاده از یک اینترفیس برای کپسوله کردن منطق دسترسی به دادهها در یک مکان، تعداد تغییرات مورد نیاز را در صورت تغییر دیتابیس، به حداقل برسانید.
تخریب سرویس ها
سرویس کانتینر برای نوعهای IDisposable که خودش ایجاد کردهاست، متد Dispose را فراخوانی خواهد کرد. با این حال، اگر شما خودتان نمونهای را به صورت دستی نمونه سازی و به کانتینر اضافه کرده باشید، سرویس کانتینر آنرا dispose نخواهد کرد.
مثال:
نکته:
در نسخه 1.0، کانتینر برای تمام اشیاء از نوع IDisposable از جمله اشیائی که خودش ایجاد نکرده بود، متد dispose را فراخوانی میکرد.
سرویسهای کانتینر جانشین
کانتینر موجود در net core. به منظور تامین نیازهای اساسی فریم ورک ایجاد شدهاست و تعداد زیادی از اپلیکیشنها از آن استفاده میکنند. با این حال، توسعه دهندگان میتوانند کانتینرهای مورد نظر خود را جایگزین آن کنند. متد ConfigureServices به طور معمول مقدار void را بر میگرداند. اما با تغییر امضای آن به نوع بازگشتیIServiceProvider، میتوان سرویس کانتینر متفاوتی را در اپلیکیشن پیکربندی کرد. سرویسهای کانتینر IOC مختلفی برای NET. وجود دارند؛ در مثال زیر، Autofac استفاده شده است.
در ابتدا بستههای زیر را نصب کنید:
سپس کانتینر را در ConfigureServices پیکربندی کنید و IServiceProvider را به عنوان خروجی بازگردانید:
توصیه ها
هنگام کار با تزریق وابستگیها، توصیههای ذیر را در نظر داشته باشید:
- DI برای اشیایی که دارای وابستگی پیچیده هستند، مناسب میباشد. کنترلرها، سرویسها، آداپتورها و ریپازیتوریها، نمونههایی از این اشیاء هستند که میتوانند به DI اضافه شوند.
- از ذخیرهی دادهها و پیکربندی مستقیم در DI اجتناب کنید. به عنوان مثال، معمولا سبد خرید کاربر نباید به سرویس کانتینر اضافه شود. پیکربندی باید از مدل گزینهها استفاده کند. همچنین از اشیاء "data holder"، که فقط برای دسترسی دادن به اشیاء دیگر ایجاد شدهاند، نیز اجتناب کنید. در صورت امکان بهتر است شیء واقعی مورد نیاز DI درخواست شود.
- از دسترسی استاتیک به سرویسها اجتناب شود.
- از نمونه سازی مستقیم سرویسها در کد برنامه خود اجتناب کنید.
- از دسترسی استاتیک به HttpContext اجتناب کنید.
توجه
مانند هر توصیهی دیگری، ممکن است شما با شرایطی مواجه شوید که مجبور به نقض هر یک از این توصیهها شوید. اما این موارد استثناء بسیار نادر میباشند و رعایت این نکات یک عادت برنامه نویسی خوب محسوب میشود.
مرجع: Introduction to Dependency Injection in ASP.NET Core
مشاهده یا دانلود کدهای مقاله
تزریق وابستگی چیست؟
تزریق وابستگی (DI) تکنیکی برای دستیابی به اتصال شل بین اشیاء و همکاران اشیاء و وابستگیهای بین آنها میباشد. یک شیء برای انجام وظایف خود، بجای اینکه اشیاء همکار خود را به صورت مستقیم نمونه سازی کند، یا از ارجاعات استاتیک استفاده نماید، میتواند از اشیائی که برایش تامین شدهاست، استفاده کند. در اغلب موارد کلاسها، وابستگیهای خود را از طریق سازندهی خود درخواست میکنند، که به آنها اجازه میدهد اصل وابستگی صریح را رعایت کنند (Explicit Dependencies Principle). این روش را «تزریق در سازنده» مینامند.
از آنجا که در طراحی کلاسها با استفاده از DI، نمونه سازی مستقیم، توسط کلاسها و به صورت Hard-coded انجام نمیگیرد، وابستگی بین اشیاء کم شده و پروژهای با اتصالات شل به دست میآید. با این کار اصل وابستگی معکوس (Dependency Inversion Principle) رعایت میشود. بر اساس این اصل، ماژولهای سطح بالا نباید به ماژولهای سطح پایین خود وابسته باشند؛ بلکه هر دو باید به کلاسهایی انتزاعی وابسته باشند. اشیاء بجای ارجاع به پیاده سازیهای خاص کلاسهای همکار خود، کلاسهای انتزاعی، معمولاٌ اینترفیس آنها را درخواست میکنند و هنگام نمونه سازی از آنها (داخل متد سازنده) کلاس پیاده سازی شده برایشان تامین میشود. خارج کردن وابستگیهای مستقیم از کلاسها و تامین پیاده سازیهای این اینترفیسها به صورت پارامترهایی برای کلاسها، یک مثال از الگوی طراحی استراتژی (Strategy design pattern) میباشد.
در حالتیکه کلاسها به تعداد زیادی کلاس وابستگی داشته باشند و برای اجرا شدن، نیاز به تامین وابستگیهایشان داشته باشند، بهتر است یک کلاس اختصاصی، برای نمونه سازی این کلاسها با وابستگیهای مورد نیاز آنها، در سیستم وجود داشته باشد. این کلاس نمونه ساز را کانتینرIoC، یا کانتینر DI یا به طور خلاصه کانتینر مینامند ( Inversion of Control (IoC) ). کانتینر در اصل یک کارخانه میباشد که وظیفهی تامین نمونههایی از کلاسهایی را که از آن درخواست میشود، انجام میدهد. اگر یک کلاس تعریف شده، وابستگی به کلاسهای دیگر داشته باشد و کانتینر برای ارائه وابستگیهای کلاس تعریف شده تنظیم شده باشد، هر موقع نیاز به یک نمونه از این کلاس وجود داشته باشد، به عنوان بخشی از کار نمونه سازی از کلاس مورد نظر، کلاسهای وابستهی آن نیز ایجاد میشوند (همهی کارهای مربوط به نمونه سازی کلاس خاص و کلاسهای وابسته به آن توسط کانتینر انجام میگیرد). به این ترتیب، میتوان وابستگیهای بسیار پیچیده و تو در توی موجود در سیستم را بدون نیاز به هیچگونه نمونه سازی hard-code شده، برای کلاسها فراهم کرد. کانتینرها علاوه بر ایجاد اشیاء و وابستگیهای موجود در آنها، معمولا طول عمر اشیاء در اپلیکیشن را نیز مدیریت میکنند.
ASP.NET Core یک کانتینر بسیار ساده را به نام اینترفیس IServiceProvider ارائه داده است که به صورت پیش فرض از تزریق وابستگی در سازندهی کلاسها پشتیبانی میکند و همچنین ASP.NET برخی از سرویسهای خود را از طریق DI در دسترس قرار داده است. کانتینرASP.NET، یک اشارهگر به کلاسهایی است که به عنوان سرویس عمل میکنند. در ادامهی این مقاله، سرویسها به کلاسهایی گفته میشود که به وسیلهی کانتینر ASP.NET Core مدیریت میشوند. شما میتوانید سرویس ConfigureServices کانتینر را در داخل کلاس Startup پروژه خود پیکربندی کنید.
تزریق وابستگی از طریق متد سازندهی کلاس
تزریق وابستگی از طریق متد سازنده، مستلزم آن است که سازندهی کلاس مورد نظر عمومی باشد. در غیر این صورت، اپلیکیشن شما استثنای InvalidOperationException را با پیام زیر نشان میدهد:
A suitable constructor for type 'YourType' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.
تزریق از طریق متد سازنده مستلزم آن است که تنها یک سازندهی مناسب وجود داشته باشد. البته Overload سازنده امکان پذیر است؛ ولی باید تنها یک متد سازنده وجود داشته باشد که آرگومانهای آن توسط DI قابل ارائه باشند. اگر بیش از یکی وجود داشته باشد، سیستم استثنای InvalidOperationException را با پیام زیر نشان میدهد:
Multiple constructors accepting all given argument types have been found in type 'YourType'. There should only be one applicable constructor.
سازندگان میتوانند آرگومانهایی را از طریق DI دریافت کنند. برای این منظور آرگومانهای این سازندهها باید مقدار پیش فرضی را داشته باشند. به مثال زیر توجه نمایید:
// throws InvalidOperationException: Unable to resolve service for type 'System.String'... public CharactersController(ICharacterRepository characterRepository, string title) { _characterRepository = characterRepository; _title = title; } // runs without error public CharactersController(ICharacterRepository characterRepository, string title = "Characters") { _characterRepository = characterRepository; _title = title; }
استفاده از سرویس ارائه شده توسط فریم ورک
متد ConfigureServices در کلاس Startup، مسئول تعریف سرویسهایی است که سیستم از آن استفاده میکند. از جملهی این سرویسها میتوان به ویژگیهای پلتفرم مانند EF Core و ASP.NET Core MVC اشاره کرد. IServiceCollection که به ConfigureServices ارائه میشود، سرویسهای زیر را تعریف میکند (که البته بستگی به نوع پیکربندی هاست دارد):
نوع سرویس | طول زندگی |
Microsoft.AspNetCore.Hosting.IHostingEnvironment | Singleton |
Microsoft.AspNetCore.Hosting.IApplicationLifetime | Singleton |
Microsoft.AspNetCore.Hosting.IStartup | Singleton |
Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
Microsoft.Extensions.Options.IConfigureOptions | Transient |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.AspNetCore.Hosting.IStartupFilter | Transient |
System.Diagnostics.DiagnosticListener | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
Microsoft.Extensions.Options.IOptions | Singleton |
Microsoft.AspNetCore.Http.IHttpContextFactory | Transient |
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Transient |
Microsoft.Extensions.Logging.ILogger | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
در زیر نمونه ای از نحوهی اضافه کردن سرویسهای مختلف را به کانتینر، با استفاده از متدهای الحاقی مانند AddDbContext، AddIdentity و AddMvc، مشاهده میکنید:
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); // Add application services. services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>(); }
ثبت سرویسهای اختصاصی
شما میتوانید سرویسهای اپلیکیشن خودتان را به ترتیبی که در تکه کد زیر مشاهده میکنید، ثبت نمایید. اولین نوع جنریک، نوعی است که از کانتینر درخواست خواهد شد و معمولا به شکل اینترفیس میباشد. نوع دوم، نوع پیاده سازی شدهای است که به وسیلهی کانتینر، نمونه سازی خواهد شد و کانتینر برای درخواستهای از نوع اول، این نمونه از تایپ را ارائه خواهد کرد:
services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<ISmsSender, AuthMessageSender>();
نکته:
هر متد الحاقی <services.Add<ServiceName، سرویسهایی را اضافه و پیکربندی میکند. به عنوان مثال services.AddMvc نیازمندیهای سرویس MVC را اضافه میکند. توصیه میشود شما هم با افزودن متدهای الحاقی در فضای نام Microsoft.Extensions.DependencyInjection این قرارداد را رعایت نمائید. این کار باعث کپسوله شدن ثبت گروهی سرویسها میشود.
متد AddTransient، برای نگاشت نوعهای انتزاعی به سرویسهای واقعی که نیاز به نمونه سازی به ازای هر درخواست دارند، استفاده میشود. در اصطلاح، طول عمر سرویسها در اینجا مشخص میشوند. در ادامه گزینههای دیگری هم برای طول عمر سرویسها تعریف خواهند شد. خیلی مهم است که برای هر یک از سرویسهای ثبت شده، طول عمر مناسبی را انتخاب نمایید. آیا برای هر کلاس که سرویسی را درخواست میکند، باید یک نمونهی جدید ساخته شود؟ آیا فقط یک نمونه در طول یک درخواست وب مورد استفاده قرار میگیرد؟ یا باید از یک نمونهی واحد برای طول عمر کل اپلیکیشن استفاده شود؟
در مثال ارائه شدهی در این مقاله، یک کنترلر ساده به نام CharactersController وجود دارد که نام کاراکتری را نشان میدهد. متد Index، لیست کنونی کاراکترهایی را که در اپلیکیشن ذخیره شدهاند، نشان میدهد. در صورتیکه این لیست خالی باشد، تعدادی به آن اضافه میکند. توجه داشته باشید، اگرچه این اپلیکیشن از Entity Framework Core و ClassDataContext برای دادههای مانا استفاده میکند، هیچیکدام از آنها در کنترلر ظاهر نمیشوند. در عوض، مکانیزم دسترسی به دادههای خاص، در پشت یک اینترفیس (ICharacterRepository) مخفی شده است (طبق الگوی طراحی ریپازیتوری). یک نمونه از ICharacterRepository از طریق سازنده درخواست میشود و به یک فیلد خصوصی اختصاص داده میشود، سپس برای دسترسی به کاراکترها در صورت لزوم استفاده میشود:
public class CharactersController : Controller { private readonly ICharacterRepository _characterRepository; public CharactersController(ICharacterRepository characterRepository) { _characterRepository = characterRepository; } // GET: /characters/ public IActionResult Index() { PopulateCharactersIfNoneExist(); var characters = _characterRepository.ListAll(); return View(characters); } private void PopulateCharactersIfNoneExist() { if (!_characterRepository.ListAll().Any()) { _characterRepository.Add(new Character("Darth Maul")); _characterRepository.Add(new Character("Darth Vader")); _characterRepository.Add(new Character("Yoda")); _characterRepository.Add(new Character("Mace Windu")); } } }
ICharacterRepository دو متد مورد نیاز کنترلر برای کار با نمونههای Character را تعریف میکند:
using System.Collections.Generic; using DependencyInjectionSample.Models; namespace DependencyInjectionSample.Interfaces { public interface ICharacterRepository { IEnumerable<Character> ListAll(); void Add(Character character); } }
using System.Collections.Generic; using System.Linq; using DependencyInjectionSample.Interfaces; namespace DependencyInjectionSample.Models { public class CharacterRepository : ICharacterRepository { private readonly ApplicationDbContext _dbContext; public CharacterRepository(ApplicationDbContext dbContext) { _dbContext = dbContext; } public IEnumerable<Character> ListAll() { return _dbContext.Characters.AsEnumerable(); } public void Add(Character character) { _dbContext.Characters.Add(character); _dbContext.SaveChanges(); } } }
نکته
ایجاد شیء درخواست شده و تمامی اشیاء مورد نیاز شیء درخواست شده را گراف شیء مینامند. به همین ترتیب مجموعهای از وابستگیهایی را که باید resolve شوند، به طور معمول، درخت وابستگی یا گراف وابستگی مینامند.
در مورد مثال مطرح شده، ICharacterRepository و به نوبه خود ApplicationDbContext باید با سرویسهای خود در کانتینر ConfigureServices و کلاس Startup ثبت شوند. ApplicationDbContext با فراخوانی متد <AddDbContext<T پیکربندی میشود. کد زیر ثبت کردن نوع CharacterRepository را نشان میدهد:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase() ); // Add framework services. services.AddMvc(); // Register application services. services.AddScoped<ICharacterRepository, CharacterRepository>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); services.AddTransient<OperationService, OperationService>(); }
هشدار
خطر بزرگی را که باید در نظر گرفت، resolve کردن سرویس Scoped از طول عمر singleton میباشد. در صورت انجام این کار، احتمال دارد که سرویسها وارد حالت نادرستی شوند.
سرویسهایی که وابستگیهای دیگری هم دارند، باید آنها را در کانتینر ثبت کنند. اگر سازندهی سرویس نیاز به یک primitive به عنوان ورودی داشته باشد، میتوان با استفاده از الگوی گزینهها و پیکربندی (options pattern and configuration)، ورودیهای مناسبی را به سازندهها منتقل کرد.
طول عمر سرویسها و گزینههای ثبت
سرویسهای ASP.NET را میتوان با طول عمرهای زیر پیکربندی کرد:
Transient: سرویسهایی با طول عمر Transient، در هر زمان که درخواست میشوند، مجددا ایجاد میشوند. این طول عمر برای سرویسهای سبک و بدون حالت مناسب میباشند.
Scoped: سرویسهایی با طول عمر Scoped، تنها یکبار در طی هر درخواست ایجاد میشوند.
Singleton: سرویسهایی با طول عمر Singleton، برای اولین باری که درخواست میشوند (یا اگر در ConfigureServices نمونهای را مشخص کرده باشید) ایجاد میشوند و درخواستهای آتی برای این سرویسها از همان نمونهی ایجاد شده استفاده میکنند. اگر اپلیکیشن شما درخواست رفتار singleton را داشته باشد، پیشنهاد میشود که سرویس کانتینر را برای مدیریت طول عمر سرویس مورد نیاز پیکربندی کنید و خودتان الگوی طراحی singleton را پیاده سازی نکنید.
سرویسها به چندین روش میتوانند در کانتینر ثبت شوند. چگونگی ثبت کردن یک سرویس پیاده سازی شده برای یک نوع، در بخشهای پیشین توضیح داده شده است. علاوه بر این، یک کارخانه را میتوان مشخص کرد، که برای ایجاد نمونه بر اساس تقاضا استفاده شود. رویکرد سوم، ایجاد مستقیم نمونهای از نوع مورد نظر است که در این حالت کانتینر اقدام به ایجاد یا نابود کردن نمونه نمیکند.
به منظور مشخص کردن تفاوت بین این طول عمرها و گزینههای ثبت کردن، یک اینترفیس ساده را در نظر بگیرید که نشان دهندهی یک یا چند operation است و یک شناسهی منحصر به فرد operation را از طریق OperationId نشان میدهد. برای مشخص شدن انواع طول عمرهای درخواست شده، بسته به نحوهی پیکربندی طول عمر سرویس مثال زده شده، کانتینر، نمونهی یکسان یا متفاوتی را از سرویس، به کلاس درخواست کننده ارائه میدهد. ما برای هر طول عمر، یک نوع را ایجاد میکنیم:
using System; namespace DependencyInjectionSample.Interfaces { public interface IOperation { Guid OperationId { get; } } public interface IOperationTransient : IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationSingletonInstance : IOperation { } }
سپس در ConfigureServices، هر نوع با توجه به طول عمر مورد نظر، به کانتینر افزوده میشود:
services.AddScoped<ICharacterRepository, CharacterRepository>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); services.AddTransient<OperationService, OperationService>();
using DependencyInjectionSample.Interfaces; namespace DependencyInjectionSample.Services { public class OperationService { public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance SingletonInstanceOperation { get; } public OperationService(IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance instanceOperation) { TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = instanceOperation; } } }
using DependencyInjectionSample.Interfaces; using DependencyInjectionSample.Services; using Microsoft.AspNetCore.Mvc; namespace DependencyInjectionSample.Controllers { public class OperationsController : Controller { private readonly OperationService _operationService; private readonly IOperationTransient _transientOperation; private readonly IOperationScoped _scopedOperation; private readonly IOperationSingleton _singletonOperation; private readonly IOperationSingletonInstance _singletonInstanceOperation; public OperationsController(OperationService operationService, IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance singletonInstanceOperation) { _operationService = operationService; _transientOperation = transientOperation; _scopedOperation = scopedOperation; _singletonOperation = singletonOperation; _singletonInstanceOperation = singletonInstanceOperation; } public IActionResult Index() { // viewbag contains controller-requested services ViewBag.Transient = _transientOperation; ViewBag.Scoped = _scopedOperation; ViewBag.Singleton = _singletonOperation; ViewBag.SingletonInstance = _singletonInstanceOperation; // operation service has its own requested services ViewBag.Service = _operationService; return View(); } } }
حالا دو درخواست جداگانه برای این کنترلر ساخته شده است:
به تفاوتهای موجود در مقادیر OperationId در یک درخواست و بین درخواستها توجه کنید:
- OperationId اشیاء Transient همیشه متفاوت میباشند. چون یک نمونه جدید برای هر کنترلر و هر سرویس ایجاد شدهاست.
- اشیاء Scoped در یک درخواست، یکسان هستند؛ اما در درخواستهای مختلف متفاوت میباشند.
- اشیاء Singleton برای هر شیء و هر درخواست (صرف نظر از اینکه یک نمونه در ConfigureServices ارائه شده است) یکسان میباشند.
درخواست سرویس
در ASP.NET سرویسهای موجود در یک درخواست HttpContext از طریق مجموعه RequestServices قابل مشاهده میباشد.
RequestServices نشان دهندهی سرویسهایی است که شما به عنوان بخشی از اپلیکیشن خود، آنها را پیکربندی و درخواست میکنید. هنگامیکه اشیاء اپلیکیشن شما وابستگیهای خود را مشخص میکنند، این وابستگیها با استفاده از نوعهای موجود در RequestServices برآورده میشوند و نوعهای موجود در ApplicationServices در این مرحله مورد استفاده قرار نمیگیرد.
به طور کلی، شما نباید مستقیما از این خواص استفاده کنید و بجای آن، نوعهای کلاس خود را توسط سازندهی کلاس، درخواست کنید و اجازه دهید فریم ورک این وابستگیها را تزریق کند. این کار باعث بهوجود آمدن کلاسهایی با قابلیت آزمونپذیری بالاتر و اتصالات شلتر بین آنها میشود.
نکته
درخواست وابستگیها با استفاده از پارامترهای کلاس سازنده، بر روش کار با مجموعهی RequestServices ارجحیت دارد.
طراحی سرویسها برای تزریق وابستگیها
شما باید سرویسهای خود را طوری طراحی کنید که از تزریق وابستگیها برای ارتباطات خود استفاده نمایند. این کار باعث کاهش استفاده از فراخوانیهای متدهای استاتیک (متدهای استاتیک، حالت دار میباشند و استفادهی زیاد از آنها باعث به وجود آمدن بوی بد کدی به نام static cling، میشود) و همچنین از بین رفتن نیاز به نمونه سازی مستقیم کلاسهای وابسته داخل سرویسها، میشود. هر موقع بخواهید بین new کردن یک کلاس، یا درخواست دادن آن از طریق تزریق وابستگی، یکی را انتخاب کنید، این اصطلاح را به یاد بیاورید، New is Glue. با پیروی از اصول SOLID طراحی شیء گرا، به طور طبیعی کلاسهای شما تمایل به کوچک بودن، کارا و قابل تست بودن را دارند.
اگر متوجه شدید که کلاسهای شما تمایل دارند تا تعداد وابستگیهای زیادی به آنها تزریق شود، چه باید بکنید؟ به طور کلی این مشکل نشانهای است از نقض Single Responsibility Principle یا SRP است و احتمالا کلاسهای شما وظایف بیش از اندازهای را دارند. در این گونه موارد تلاش کنید مقداری از وظایف کلاس را به یک کلاس جدید منتقل کنید. در نظر داشته باشید که کلاسهای کنترلر باید به مسائل UI تمرکز کنند و قوانین کسب و کار و جزئیات دسترسی به دادهها باید در کلاسهایی جداگانه و مرتبط با خود قرار داشته باشند.
به طور خاص برای دسترسی به داده ، شما میتوانید DbContext را به کنترلرهای خود تزریق کنید (با فرض اینکه شما EF را به کانتینر سرویس ConfigureServices اضافه کردهاید). بعضی از توسعه دهندگان به جای تزریق مستقیم DbContext از یک اینترفیس ریپازیتوری استفاده مینمایند. میتوانید با استفاده از یک اینترفیس برای کپسوله کردن منطق دسترسی به دادهها در یک مکان، تعداد تغییرات مورد نیاز را در صورت تغییر دیتابیس، به حداقل برسانید.
تخریب سرویس ها
سرویس کانتینر برای نوعهای IDisposable که خودش ایجاد کردهاست، متد Dispose را فراخوانی خواهد کرد. با این حال، اگر شما خودتان نمونهای را به صورت دستی نمونه سازی و به کانتینر اضافه کرده باشید، سرویس کانتینر آنرا dispose نخواهد کرد.
مثال:
// Services implement IDisposable: public class Service1 : IDisposable {} public class Service2 : IDisposable {} public class Service3 : IDisposable {} public void ConfigureServices(IServiceCollection services) { // container will create the instance(s) of these types and will dispose them services.AddScoped<Service1>(); services.AddSingleton<Service2>(); // container did not create instance so it will NOT dispose it services.AddSingleton<Service3>(new Service3()); services.AddSingleton(new Service3()); }
نکته:
در نسخه 1.0، کانتینر برای تمام اشیاء از نوع IDisposable از جمله اشیائی که خودش ایجاد نکرده بود، متد dispose را فراخوانی میکرد.
سرویسهای کانتینر جانشین
کانتینر موجود در net core. به منظور تامین نیازهای اساسی فریم ورک ایجاد شدهاست و تعداد زیادی از اپلیکیشنها از آن استفاده میکنند. با این حال، توسعه دهندگان میتوانند کانتینرهای مورد نظر خود را جایگزین آن کنند. متد ConfigureServices به طور معمول مقدار void را بر میگرداند. اما با تغییر امضای آن به نوع بازگشتیIServiceProvider، میتوان سرویس کانتینر متفاوتی را در اپلیکیشن پیکربندی کرد. سرویسهای کانتینر IOC مختلفی برای NET. وجود دارند؛ در مثال زیر، Autofac استفاده شده است.
در ابتدا بستههای زیر را نصب کنید:
Autofac Autofac.Extensions.DependencyInjection
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); // Add other framework services // Add Autofac var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterModule<DefaultModule>(); containerBuilder.Populate(services); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
توصیه ها
هنگام کار با تزریق وابستگیها، توصیههای ذیر را در نظر داشته باشید:
- DI برای اشیایی که دارای وابستگی پیچیده هستند، مناسب میباشد. کنترلرها، سرویسها، آداپتورها و ریپازیتوریها، نمونههایی از این اشیاء هستند که میتوانند به DI اضافه شوند.
- از ذخیرهی دادهها و پیکربندی مستقیم در DI اجتناب کنید. به عنوان مثال، معمولا سبد خرید کاربر نباید به سرویس کانتینر اضافه شود. پیکربندی باید از مدل گزینهها استفاده کند. همچنین از اشیاء "data holder"، که فقط برای دسترسی دادن به اشیاء دیگر ایجاد شدهاند، نیز اجتناب کنید. در صورت امکان بهتر است شیء واقعی مورد نیاز DI درخواست شود.
- از دسترسی استاتیک به سرویسها اجتناب شود.
- از نمونه سازی مستقیم سرویسها در کد برنامه خود اجتناب کنید.
- از دسترسی استاتیک به HttpContext اجتناب کنید.
توجه
مانند هر توصیهی دیگری، ممکن است شما با شرایطی مواجه شوید که مجبور به نقض هر یک از این توصیهها شوید. اما این موارد استثناء بسیار نادر میباشند و رعایت این نکات یک عادت برنامه نویسی خوب محسوب میشود.
مرجع: Introduction to Dependency Injection in ASP.NET Core
سلام
با استفاده از دستور زیر خروجی xml تمام commit های صورت گرفته به همراه کامنتها و زمان و غیره را میشود بدست آورد:
svn log --xml file:///C:/Repositories/project1/trunk > logs.xml
همچنین در فصل آخر جزوهای که نامبرده شد روش دسترسی به svn با برنامه نویسی هم ذکر شده که هر نوع خروجی که مایل بودید به این صورت میشود تهیه کرد.
با استفاده از دستور زیر خروجی xml تمام commit های صورت گرفته به همراه کامنتها و زمان و غیره را میشود بدست آورد:
svn log --xml file:///C:/Repositories/project1/trunk > logs.xml
همچنین در فصل آخر جزوهای که نامبرده شد روش دسترسی به svn با برنامه نویسی هم ذکر شده که هر نوع خروجی که مایل بودید به این صورت میشود تهیه کرد.