@BundleConfig.AddStyles("~/Content/css", "~/Content/bootstrap.css", "~/Content/bootstrap-responsive.css", "~/Content/Site.css" )
سپس اگر صفحه Layout.cshtml_ باز کنید، با متا تگی به فرم زیر روبرو میشوید که توسط آن میتوانیم اندازه صفحه نمایش را مشخص کنیم:
زمانیکه Request ای صادر میشود، موارد ذیل تشکیل خواهند شد:
اگر دقت کنید در قسمت User-Agent یکسری مشخصات از سیستم نمایان است که ما طبق آن میتوانیم صفحه مربوطه را بارگذاری کنیم.
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); InitializeDisplayModeProvider(); } protected void InitializeDisplayModeProvider() { var phone = new DefaultDisplayMode("Phone") { ContextCondition = ctx => ctx.GetOverriddenUserAgent() != null && ctx.GetOverriddenUserAgent().Contains("iPhone") }; var mobile = new DefaultDisplayMode("Tablet") { ContextCondition = ctx => ctx.GetOverriddenUserAgent() != null && ctx.GetOverriddenUserAgent().Contains("iPad") }; DisplayModeProvider.Instance.Modes.Insert(0, phone); DisplayModeProvider.Instance.Modes.Insert(1, mobile); }
اگر Extension ای را برای تست شبیه سازی محیط موبایل یا تبلت، نصب کنید، میتوانید آن را مشاهده کنید.
معرفی Xamarin و مزیتهای استفاده از آن
قبل از اینکه صحبت را آغاز کنیم باید این نکته اشاره کنیم که انواع هاست چیست؟
انواع هاست
هاستها بر سه نوع تقسیم میشوند:
- هاست اشتراکی
- سرورهای مجازی
- سرور اختصاصی
هاست اشتراکی
در این حالت شرکت میزبان یک سرور را به چند سایت تقسیم کرده و به هر وب سایت، با توجه به پلنی که مشتری انتخاب کرده، مقداری از منابع را اختصاص میدهد که عموما این تخصیص منابع در زمان خرید توسط 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 هایی چون وردپرس و جوملا و ... هستند.
اگر از یک سرور اختصاصی بهره ببرید خودتان مختار نصب هر نوع چیزی روی آن هستید و در مورد کنترل پنل هم صدق میکند ولی عموما شرکتها یک سری الگوهای پیش فرضی را برای سرویسهای خود دارند که در صورت تغییر باید به آنها، تغییرات را طلاع دهید.
موردی را تصور کنید که ساعت 2 شب هست و سایت شما هم که در زمینهی مهم و حساسی فعالیت میکند، یک دفعه سرویس آن قطع میشود و برای سرور مشکلی پیش میآید. در این موقع چکاری را انجام میدهید؟ صبر میکنید تا صبح شود و سرویس شما راه بیفتد و کاربران هم از سایت شما نا امید شوند؟ اگر این مشکل به دفعات رخ بدهد چه؟ اگر چند روز پشت سرهم تعطیلی باشد چه؟
همهی این موارد مربوط به پشتیبانی میشود. این پشتیبانیها عموما به صورت چت و تلفنی (بیشتر مربوط به ساعات اداری) و ارسال تیکت در سامانه پشتیبانی میشود. البته تجربهی من میگوید در ایران زیاد به متون نوشتهی روی سایت میزبان توجهی نکنید و این را هم به موضوع تحقیق خود اضافه کنید. بعضیها میگویند 24 ساعته پشتیبانی تلفنی دارند ولی هر بار زنگ میزنی کسی پاسخی نمیدهد. یا تیکت میزنید و بارها و بارها پشت سر هم تیکت "چی شد؟" را برای جویا شدن ارسال میکنید. (البته انصاف هم داشته باشید و پنج دقیقه پنج دقیقه این تیکت را نفرستید).
پشتیبانی هر نوع سرور را مطلع شوید. بعضیها واقعا پشتیبانی دارند!؟ ولی وقتی زنگ میزنید میگویند این پشتیبانی مربوط به لینوکس است و بچههای ویندوز فقط ساعات اداری هستند. خلاصه حواستان را جمع کنید که بچههای آن بخش همیشه باشند.
بنابراین مطمئن شوید که یک پشتیبانی 24/7 واقعی داشته باشند.
امکانات اضافه
در کنار خود هاست یک سری ویژگیهایی چون FTP و تعدادی دامنههای پشتیبانی شده و زیر دامنهها و تعداد دیتابیسها و ایمیل و ... هم هستند که در قدیم یادم هست محدودیتهایی در این رابطه ایجاد کرده بودند که امروزه از این نظر در اکثر هاستها آن طور که دیدم این محدودیتها رفع شده است که البته خیلی هم خوب هست و این محدودیتها بیشتر شبیه سودجویی بود تا چیز دیگر.
هزینه
با همهی حرفهای بالا به نظر میرسد که هزینه، همه این معادلات را به هم میریزد. هاستینگهای مختلف و قیمتهای مختلف، تصمیم گیرنده شما هستید که چقدر به عوامل بالا اهمیت میدهید و چگونه آنها را در راستای قیمت اولویت بندی میکنید. اگر محدودیتی ندارید سعی کنید همهی عوامل را بررسی کنید. نکته اینکه اکثر هاستها طرحی به نام ضمانت برگشت پول را در هفت روز یا گاها یک ماه، دارند و در صورتیکه از سرویس خریداری شده راضی نبودید میتوانید پول خود را پس گرفته و سرویس معلق شود.
کل مطالب گفته شده در چک لیست به طور خلاصه : اول از همه بررسی فضای ذخیره سازی و پهنای باند، بعد از آن بررسی توانایی سرویس دهی و سرعت سرورها، داشتن محیط امن و پشتیبانی مناسب بود.
هاستینگ در قرارداد
هنگام عقد قرارداد با مشتری، در قرارداد در مورد هاستینگ که خودش تهیه میکند، این نکته ذکر شود که شما در مورد مسائلی که مربوط به هاست و دامنه میشود هیچ مسئولیتی ندارید و باید مشکلات را با مسئولان آن در میان بگذارد.
همچنین این نکته هم ذکر شود در مورد مسائلی چون عدم پشتیبانی مناسب هاست، از محصول یا سرویس شما، شما هیچگونه مسئولیتی در قبال آن ندارید و یا باید این مورد توسط شما دنبال شود یا خرید ایشان با مشاوره و تایید شما از هاست مربوطه باشد.
در صورتی که مشتری نمیتواند شخصا خرید هاستینگ را انجام دهد یا در شرکت مسئولین IT ندارند که این کار را انجام دهند و این مورد به شما محول میشود، در قرارداد ذکر شود که شما هیچ گونه مسئولیتی در قبال مشکلاتی که برای هاست و دامنه رخ میدهد ندارید و تنها میتوانید به عنوان یک واسط یا وکیل در ازای مبلغ توافق شدهای مشکل را با مسئولین هاست و دامنه در میان گذاشته و ماجرا را برای حل مشکل ایجاد شده دنبال کنید یا این کار را به عنوان هدیه ای از طرف خدمات طلایی شما به مشتریان به صورت رایگان انجام دهید.
البته با توجه به جدید بودن این الگو اسم واحدی براش مشخص نشده ولی تو این مطلب «الگوی Delegate Dictionary» معرفی شده که بنظرم از بقیه بهتره.
به طور خلاصه این الگو میگه اگه قراره براساس شرایط (ورودی) خاصی کار خاصی انجام بشه بجای استفاده از IF و Switch از DictionaryوFunc یا Action استفاده کنیم.
برای مثال فرض کنید مدلی به شکل زیر داریم
public class Person { public int Id { get; set; } public Gender Gender { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
خب روش معمول به این شکل میتونه باشه
switch (person.Gender) { case Gender.Male: if (IsMale(person.FirstName)) { //Isvalid } break; case Gender.Female: if (IsFemale(person.FirstName)) { //Isvalid } break; }
ما میایم توابع مورد نظرمون رو داخل یک Dictionary ذخیره میکنیم.
var genderFuncs = new Dictionary<Gender, Func<string, bool>> { {Gender.Male , (x) => IsMale(x)}, {Gender.Female , (x) => IsFemale(x)} };
public static bool IsMale(string name) { //check... return true; } public static bool IsFemale(string name) { //check... if (name == "Farzad") { return false; } return true; }
var dummyPerson = new List<Person> { new Person {Id = 1, Gender = Gender.Male, FirstName = "Mohammad", LastName = "Saheb"}, new Person {Id = 2, Gender = Gender.Female, FirstName = "Farzad", LastName = "Mojidi"} }; foreach (var person in dummyPerson) { bool isValid = genderFuncs[person.Gender].Invoke(person.FirstName); }
var query = context.Students.AsQueryable(); if (searchByName) { query= query.FindStudentsByName(name); } if (orderByAge) { query = query.OrderByAge(); } if (paging) { query = query.SkipAndTake(skip, take); } return query.ToList();
var searchTypeFuncs = new Dictionary<SearchType, Func<IQueryable<Student>, string, IQueryable<Student>>> { {SearchType.FirstName, (x, y) => x.FindStudentsByName(y)}, {SearchType.LastName, (x, y) => x.FindStudentsByLastName(y)} };
public static IList<Student> SearchStudents(IQueryable<Student> students, SearchType type, string keyword) { var result = searchTypeFuncs[type].Invoke(students, keyword); return result.ToList(); }
مکانیزم Eventing
PM> Install-Package DNTFrameworkCore
public class TaskEditingBusinessEventHandler : BusinessEventHandler<EditingBusinessEvent<TaskModel, int>> { private readonly ILogger<TaskEditingBusinessEventHandler> _logger; public TaskEditingBusinessEventHandler(ILogger<TaskEditingBusinessEventHandler> logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public override Task<Result> Handle(EditingBusinessEvent<TaskModel, int> @event) { foreach (var model in @event.Models) { _logger.LogInformation($"Title changed from: {model.OriginalValue.Title} to: {model.NewValue.Title}"); } return Task.FromResult(Ok()); } }
کار با پیادهسازی واسط جنریک IBusinessEventHandler یا ارثبری از کلاس جنریک BusinessEventHandler آغاز میشود؛ سپس نیاز است Type Parameter متناظر را نیز مشخص کنیم. برای این منظور در تکه کد بالا از رخداد جنریک EditingBusinessEvent استفاده شده است. همچنین همانطور که ملاحظه میکنید، نیاز است نوع Model مورد نظر نیز مشخص شده باشد؛ در اینجا از TaskModel به عنوان Model/DTO عملیات CUD موجودیت Task استفاده شده است.
رخدادهای Creating/Created/Deleting/Deleted دارای خصوصیتی بنام Models هستند که نوع آن IEnumerable<TModel> میباشد. ولی این خصوصیت در رخدادهای Editing/Edited از نوع IEnumerable<ModifiedModel<TModel>> میباشد؛ در این صورت به مقادیر موجود در بانک اطلاعاتی و همچنین مقادیری که توسط استفاده کننده از سرویس جاری به عنوان آرگومان به متد ویرایش ارسال شده است، دسترسی خواهیم داشت.
public class ModifiedModel<TValue> { public TValue NewValue { get; set; } public TValue OriginalValue { get; set; } }
استفاده از سرویسهای موجودیتها
OOP : Everything is an object CRUD-based thinking : Everything is CRUD
public class ItemCategoryCreatedBusinessEventHandler : IBusinessEventHandler<CreatedBusinessEvent<ItemCategoryModel, int>> { private readonly ISaleMethodService _saleMethodService; public TaskEditingBusinessEventHandler(ISaleMethodService saleMethodService) { _saleMethodService = saleMethodService ?? throw new ArgumentNullException(nameof(saleMethodService)); } public override Task<Result> Handle(CreatedBusinessEvent<ItemCategoryModel, int> @event) { var methods = _saleMethodService.FindAsnc(); foreach (var method in methods) { foreach (var model in @event.Models) { method.ItemCategories.Add(new SaleMethodItemCategoryModel { ItemCategoryId = model.Id, TrackingState = TrackingState.Added; }); } } return _saleMethodService.EditAsync(methods); }
در نسخههای قبل از Angular CLI 6.0، صرفا امکان Bundle کردن جداگانهی ماژولهایی که در قسمت loadChildren مرتبط با تنظیمات مسیریابی ذکر شده بودند، وجود داشت. بنابراین در برخی از شرایط اگر نیاز به امکان بارگذاری ماژولی به صورت Lazy load بود، باید از سیستم مسیریابی استفاده میشد یا اینکه با یکسری ترفند، CLI و Webpack را مجبور به ساخت فایل chunk جداگانه برای ماژول مورد نظر میکردید. از زمان انتشار Angular CLI 6.0 امکان Lazy loading پویا نیز مهیا میباشد؛ به این ترتیب بدون وابستگی به سیستم مسیریابی، باز هم میتوان از مزایای Lazy loading بهره برد. در این مطلب روش استفاده از این قابلیت و همچنین نحوهی بارگذاری پویای یک کامپوننت مرتبط با یک ماژول Lazy load شده را بررسی خواهیم کرد. برای این منظور در ادامه با ایجاد یک TabLayout با استفاده از Angular Material Tabs با یکی از موارد پر استفادهی قابلیت مذکور آشنا خواهیم شد.
پیش نیازها
- مسیر آموزشی «+AngularJS 2.0»
- مسیر آموزشی «سری آموزشی Angular CLI»
- مسیر آموزشی «مسیریابی در Angular»
- مسیر آموزشی «کتابخانه Angular Material 6x»
کار را با طراحی و پیاده سازی TabService شروع میکنیم. برای این منظور یک سرویس را در فولدر services موجود در کنار CoreModule ایجاد خواهیم کرد؛ به این جهت ابتدا مدلهای زیر را خواهیم داشت:
import { Type, ValueProvider } from '@angular/core'; export interface OpenNewTabModel { label: string; componentType: Type<any>; iconName: string; modulePath?: string; data?: ValueProvider[]; }
import { TabItemComponent } from './tab-item-component'; export interface TabItem { label: string; iconName: string; component: TabItemComponent; }
OpenNewTabModel برای ارسال داده توسط مصرف کننده از این سرویس در نظر گرفته شده است. ولی واسط TabItem دارای خصوصیاتی میباشد که ما برای نمایش یک برگهی جدید نیازمندیم. TabItemComponent نیز دارای خصوصیاتی است که مورد نیاز دایرکتیو« NgComponentOutlet» است.
import { Injector, NgModuleFactory, Type } from '@angular/core'; export interface TabItemComponent { componentType: Type<any>; moduleFactory?: NgModuleFactory<any>; injector: Injector; }
import { BehaviorSubject, Observable } from 'rxjs'; import { Injectable, Injector, NgModuleFactory, NgModuleFactoryLoader } from '@angular/core'; import { OpenNewTabModel } from '../models/open-new-tab-model'; import { TabItem } from '../models/tab-item'; @Injectable({ providedIn: 'root' }) export class TabService { private tabItemSubject: BehaviorSubject<TabItem[]> = new BehaviorSubject< TabItem[] >([]); constructor( private loader: NgModuleFactoryLoader, private injector: Injector ) {} get tabItems$(): Observable<TabItem[]> { return this.tabItemSubject.asObservable(); } open(newTab: OpenNewTabModel) { if (newTab.modulePath) { this.loader .load(newTab.modulePath) .then((moduleFactory: NgModuleFactory<any>) => { this.openInternal(newTab, moduleFactory); }); } else { this.openInternal(newTab); } } private openInternal(newTab: OpenNewTabModel, moduleFactory?: NgModuleFactory<any>) { const newTabItem: TabItem = { label: newTab.label, iconName: newTab.iconName, component: { componentType: newTab.componentType, moduleFactory: moduleFactory, injector: newTab.data ? Injector.create(newTab.data, this.injector) : this.injector } }; this.tabItemSubject.getValue().push(newTabItem); this.tabItemSubject.next(this.tabItemSubject.getValue()); } close(index: number) { this.tabItemSubject.getValue().splice(index, 1); this.tabItemSubject.next(this.tabItemSubject.getValue()); } }
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }
import { Component, OnInit } from '@angular/core'; import { TabService } from './../../../services/tab.service'; @Component({ selector: 'app-tabs', templateUrl: './tabs.component.html', styleUrls: ['./tabs.component.scss'] }) export class TabsComponent implements OnInit { constructor(public service: TabService) {} ngOnInit() {} }
<mat-tab-group> <mat-tab *ngFor="let tabItem of (service.tabItems$ | async); let index = index" > <ng-template mat-tab-label> <mat-icon class="icon" aria-label="icon for tab" >{{tabItem.iconName}}</mat-icon> <span class="full">{{ tabItem.label }}</span> <mat-icon class="close" (click)="service.close(index)" aria-label="close tab button" >close</mat-icon > <!-- </button> --> </ng-template> <ng-container *ngIf="tabItem.component.moduleFactory"> <ng-container *ngComponentOutlet=" tabItem.component.componentType; ngModuleFactory: tabItem.component.moduleFactory; injector: tabItem.component.injector " > </ng-container> </ng-container> <ng-container *ngIf="!tabItem.component.moduleFactory"> <ng-container *ngComponentOutlet=" tabItem.component.componentType; injector: tabItem.component.injector " > </ng-container> </ng-container> </mat-tab> </mat-tab-group>
در تکه کد بالا، ابتدا با استفاده از وهله تزریق شده TabService در کامپوننت مذکور، به شکل زیر، مشترک تغییرات لیست برگهها شدهایم و با استفاده از دایرکتیو *ngFor به ازای تک تک tabItemهای درخواست شده برای گشوده شدن، به شکل زیر کار وهله سازی پویا از کامپوننت مشخص شده انجام میشود:
<ng-container *ngComponentOutlet="tabItem.component.componentType; ngModuleFactory: tabItem.component.moduleFactory; injector: tabItem.component.injector"> </ng-container>
خوب، با استفاده از آنچه تا اینجای مطلب بررسی شد، میتوان یک سیستم راهبری مبتنی بر Tab را نیز برپا کرد که مطلب جدایی را میطلبد. برای تکمیل مکانیزم بارگذاری پویای ماژولها، نیاز است تا مسیر ماژول مورد نظر را در فایل angular.json و بخش lazyModules به شکل زیر معرفی کنید:
"build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/MaterialAngularTabLayout", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss" ], "lazyModules": [ "src/app/lazy/lazy.module" ], "scripts": [] },
به عنوان مثال قصد داریم ماژول LazyModule را به صورت پویا بارگذاری کرده و LazyComponent موجود در این ماژول را به صورت پویا در برگهی جدیدی نمایش دهیم. برای این منظور کدهای فایل AppComponent.ts را به شکل زیر تغییر خواهیم داد:
import { Component } from '@angular/core'; import { IdModel } from './core/models/id-model'; import { LazyComponent } from './lazy/lazy.component'; import { OpenNewTabModel } from './core/models/open-new-tab-model'; import { TabService } from './core/services/tab.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'MaterialAngularTabLayout'; constructor(private tabService: TabService) {} loadLazyComponent() { this.tabService.open(<OpenNewTabModel>{ label: 'Loaded Lazy Component', iconName: 'thumb_up', componentType: LazyComponent, modulePath: 'src/app/lazy/lazy.module#LazyModule', data: [{ provide: IdModel, useValue: <IdModel>{ id: 1 } }] }); } }
در تکه کد بالا با تزریق TabService به سازندهی آن، قصد گشودن برگهی جدیدی را توسط متد open آن، داریم. در بدنهی متد loadLazyComponent یک شیء با قرارداد OpenNewTabModel ایجاد شده و به عنوان آرگومان به متد open ارسال شده است. توجه داشته باشید که modulePath اینجا نیز به مانند خصوصیت loadChildren مرتبط با اشیاء مسیریابی، باید مقدار دهی شود. همچنین قصد داشتیم اطلاعاتی را نیز به کامپوننت مورد نظر ارسال کنیم؛ همانند مکانیزم مسیریابی که با پارامترها این چنین کارهایی صورت میپذیرد. در اینجا از یک کلاس به شکل زیر استفاده شدهاست:
export class IdModel { constructor(public id: number) {} }
در این صورت پیاده سازی LazyComponent نیز به شکل زیر خواهد بود:
import { Component, OnInit } from '@angular/core'; import { IdModel } from './../core/models/id-model'; @Component({ selector: 'app-lazy', templateUrl: './lazy.component.html', styleUrls: ['./lazy.component.scss'] }) export class LazyComponent implements OnInit { constructor(private model: IdModel) {} ngOnInit() { console.log(this.model); } }
البته فراموش نکنید کامپوننتی را که نیاز است به صورت پویا بارگذاری شود، در قسمت entryComponents مرتبط با NgModule متناظر به شکل نیز معرفی کنید:
import { CommonModule } from '@angular/common'; import { LazyComponent } from './lazy.component'; import { NgModule } from '@angular/core'; @NgModule({ imports: [CommonModule], declarations: [LazyComponent], entryComponents: [LazyComponent] }) export class LazyModule {}
با خروجی زیر:
و chunk تولید شده برای ماژول مورد نظر:
در صورتیکه در حالت production پروژه را بیلد کنید، هش مرتبط برای chunk تولید شده نیز ایجاد خواهد شد.
کدهای کامل این قسمت را میتوانید از اینجا دریافت کنید.