تشخیص اطلاعات فنی از یک وب سایت
در طراحی ارائه شده، یک رشتهی اتصالی بیشتر در کل برنامه وجود ندارد. اطلاعات بیشتر
- خیر. این طراحی از سیستم مسیریابی و همچنین ViewEngine سفارشی خاصی استفاده میکند که مختص ASP.NET MVC است.
- برای نمونه آدرس نصب moment-jalaali (^)
npm install moment-jalaali
ویجتهای وب Kendo UI مجموعهای از کنترلهای سفارشی HTML 5 هستند که برفراز jQuery تهیه شدهاند. این کنترلها برای برنامههای وب و همچنین برنامههای دسکتاپ لمسی طراحی شدهاند.
بهترین روش برای مشاهدهی این مجموعه، مراجعه به فایل examples\index.html پوشهی اصلی Kendo UI است که لیست کاملی از این ویجتها را به همراه مثالهای مرتبط ارائه میدهد. تعدادی از اعضای این مجموعه شامل کنترلهای ذیل هستند:
Window, TreeView, Tooltip, ToolBar, TimePicker, TabStrip, Splitter, Sortable, Slider, Gantt, Scheduler, ProgressBar, PanelBar, NumericTextBox, Notification, MultiSelect, Menu, MaskedTextBox, ListView, PivotGrid, Grid, Editor, DropDownList, DateTimePicker, DatePicker, ComboBox, ColorPicker, Calendar, Button, AutoComplete
نحوهی استفاده کلی از ویجتهای وب Kendo UI
با توجه به اینکه کنترلهای Kendo UI مبتنی بر jQuery هستند، نحوهی استفاده از آنها، مشابه سایر افزونههای جیکوئری است. ابتدا المانی به صفحه اضافه میشود:
<input id="pickDate" type="text"/>
<script type="text/javascript"> $(function() { $("#pickDate").kendoDatePicker(); }); </script>
<input id="dateOfBirth" type="text" data-role="datepicker" />
برای فعال سازی حالت declarative initialization باید به دو نکتهی مهم دقت داشت:
الف) در مطلب معرفی Kendo UI اسکریپتهای ذیل برای آماده سازی Kendo Ui معرفی شدند:
<!--KendoUI: Web--> <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.web.min.js" type="text/javascript"></script> <!--KendoUI: DataViz--> <link href="styles/kendo.dataviz.min.css" rel="stylesheet" type="text/css" /> <script src="js/kendo.dataviz.min.js" type="text/javascript"></script> <!--KendoUI: Mobile--> <link href="styles/kendo.mobile.all.min.css" rel="stylesheet" type="text/css" /> <script src="js/kendo.mobile.min.js" type="text/javascript"></script>
<link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.all.min.js" type="text/javascript"></script>
یک مثال کامل:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" /> <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" /> <script src="js/jquery.min.js" type="text/javascript"></script> <script src="js/kendo.all.min.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { $("#pickDate").kendoDatePicker(); }); $(function () { // initialize any widgets in the #container div kendo.init($("#container")); }); </script> </head> <body> <span> Pick a date: <input id="pickDate" type="text" /> </span> <div id="container"> <input id="dateOfBirth" type="text" data-role="datepicker" /> <div id="colors" data-role="colorpalette" data-columns="4" data-tile-size="{ width: 34, height: 19 }"></div> </div> </body> </html>
- در اینجا pickDate به صورت معمولی فعال شدهاست.
- اما در قسمت kendo.init نام یک ناحیه یا نام یک کنترل را میتوان ذکر کرد. برای مثال در اینجا کل ناحیهی مشخص شده توسط یک div با id مساوی container به صورت یکجا با تمام کنترلهای داخل آن فعال گردیدهاست.
بنابراین برای اعمال declarative initialization، یک ناحیه را توسط kendo.init مشخص کرده و سپس توسط data-roleها، نام ویجت وب مورد نظر را به صورت lower case مشخص میکنیم. همچنین فایلهای اسکریپت مورد استفاده نیز نباید تداخلی داشته باشند.
تنظیمات ویجتهای وب Kendo UI
تاکنون نمونهی سادهای از بکارگیری ویجتهای وب Kendo UI را بررسی کردیم؛ اما این ویجتها توسط تنظیمات پیش بینی شده برای آنها بسیار قابل تنظیم و تغییر هستند. تنظیمات آنها نیز بستگی به روش استفاده و آغاز آنها دارد. برای مثال اگر این ویجتها را توسط کدهای جاوا اسکریپتی آغاز کردهاید، در همانجا توسط پارامترهای افزونهی جیکوئری میتوان تنظیمات مرتبط را اعمال کرد:
<script type="text/javascript"> $(function () { $("#pickDate").kendoDatePicker({ format: "yyyy/MM/dd" }); }); </script>
در حالت declarative initialization، پارامتر format تبدیل به ویژگی data-format خواهد شد:
<input id="dateOfBirth" type="text" data-role="datepicker" data-format="yyyy/MM/dd" />
تنظیمات DataSource ویجتهای وب
بسیاری از ویجتهای وب Kendo UI با دادهها سر و کار دارند مانند Grid، Auto Complete، Combo box و غیره. این کنترلها دادههای خود را از طریق خاصیت DataSource دریافت میکنند. برای نمونه در اینجا یک combo box را در نظر بگیرید. در مثال اول، خاصیت dataSource کنترل ComboBox در همان افزونهی جیکوئری تنظیم شدهاست:
<input id="colorPicker1" /> <script type="text/javascript"> $(document).ready(function () { $("#colorPicker1").kendoComboBox({ dataSource: ["Blue", "Green", "Red", "Yellow"] }); }); </script>
<input id="colorPicker2" data-role="combobox" data-source='["Blue", "Green", "Red", "Yellow"]' /> <script type="text/javascript"> $(document).ready(function () { kendo.init($("#colorPicker2")); }); </script>
کار با رویدادهای ویجتهای وب
نحوهی کار با رویدادهای ویجتهای وب نیز بر اساس نحوهی آغاز آنها متفاوت است. در مثالهای ذیل، دو حالت متفاوت تنظیم رویداد change را توسط خواص افزونهی جیکوئری:
<input id="colorPicker3" /> <script type="text/javascript"> function onColorChange(e) { alert('Color Change!'); } $(document).ready(function () { $("#colorPicker3").kendoComboBox({ dataSource: ["Blue", "Green", "Red", "Yellow"], change: onColorChange }); }); </script>
<input id="colorPicker4" data-role="combobox" data-source='["Blue", "Green", "Red", "Yellow"]' data-change="onColorChange" /> <script type="text/javascript"> function onColorChange(e) { alert('Color Change!'); } $(document).ready(function () { kendo.init($("#colorPicker4")); }); </script>
تغییر قالب ویجتهای وب
Kendo UI همیشه یک جفت CSS را جهت تعیین قالبهای ویجتهای خود، مورد استفاده قرار میدهد. برای نمونه در مثالهای فوق، kendo.common.min.css حاوی اطلاعات محل قرارگیری و اندازهی ویجتها است. شیوه نامهی دوم همیشه به شکل kendo.[skin].min.css تعریف میشود که دارای اطلاعات رنگ و پس زمینهی ویجتها خواهد بود؛ مانند kendo.black.min.css، kendo.blueopal.min.css و امثال آن که در پوشهی styles قابل مشاهده هستند.
همچنین باید دقت داشت که همیشه common باید پیش از skin ذکر شود؛ زیرا در تعدادی از حالات، شیوه نامهی skin، اطلاعات common را بازنویسی میکند.
علاوه بر skinهای پیش فرض موجود در پوشهی styles، امکان استفاده از یک theme builder آنلاین نیز وجود دارد: kendo-ui-themebuilder
مفاهیم مقدماتی Data Warehouse :
OLTP ( Online Transaction Processing ) : سیستمهایی میباشند که برای اهداف اصلی سازمان استفاده میشوند و این سیستمها کار پردازش و ذخیره کردن دادهها را در OLTP Database انجام میدهند. مانند تمامی سیستمهای ERP,MIS,…
OLTP Database : پایگاه دادهی سیستمهای OLTP میباشد. به طور معمول هر تراکنش کاربر در کمترین زمان ممکن برروی این سیستمها ذخیره میگردد و در طول روز بارها دستورات ( Insert/Update/Delete ) برروی آنها انجام میشود. این پایگاههای داده، همان Main Data ها یا Source System ها میباشند.
ETL ( extract, transform, and load ) : مراحل انتقال داده از OLTP Database به پایگاه دادهی Stage میباشد. ETL سیستمی میباشد که توانایی اتصال به OLTP را دارد و اطلاعات را از OLTP واکشی میکند و به پایگاه دادهی Stage انتقال میدهد. سپس ETL دادهها را مجتمع ( integrates ) کرده و از Stage به DDS ( Dimensional Data Source ) انتقال میدهد .
Retrieves Data : عملیات واکشی دادهها طبق یک سری قوانین و قواعد میباشد .
برای انجام عملیات ETL دو روش وجود دارد
1. Data مجتمع ( Integrate ) و تمیز ( Data cleansing ) شود و در نهایت وارد Data Warehouse گردد.
2. Data وارد Data Warehouse گردد سپس مراحل مجتمع سازی و پاک سازی دادهها بر روی دادهها در خود Data Warehouse انجام گردد.
Consolidates Data : برخی شرکتها دادههای اصلی خودشان را در چندین پایگاه داده دارند. در این حالت برای انجام عملیات ETL باید دادهها تحکیم و مجتمع شوند و سپس در Data Warehouse ذخیره شوند.
به طور کلی موارد زیر در فرایند ETL در نظر گرفته میشود:
1. Data availability : برخی دادهها در یک سیستم وجود دارند ولی در سیستم دیگری وجود ندارند و یا تفاوت در نگهداری دادهها در سیستمهای مختلف داریم. مثلا در یک سیستم آدرس در سه فیلد نگه داری میشود (کشور-شهر-آدرس) اما در سیستمی دیگر در دو فیلد(کشور-آدرس) نگه داری میشود. در این حالت باید ما در ETL راه کار هایی برای مجتمع کردن این موارد در نظر بگیریم.
2. Time ranges : در سیستمهای مختلف امکان دارد بعدهای زمانی مختلف باشد . مثلا در یک سیستم بررسیها در بازهی ساعتی و در سیستم دیگر بررسیها در بازهی روزانه یا ماهانه باشد . بنابر این در تجمیع دادهها باید این مورد مد نظر گرفته شود.
3. Definitions : تعاریف در سیستمهای مختلف میتواند متفاوت باشد. مثلا در یک سیستم، مبلغ کل فاکتور شامل مالیات میباشد ولی در سیستمی دیگر این مبلغ فاقد مالیات میباشد.
4. Conversion : در فرآیند ETL باید باز از قواعد موجود در سیستمهای مختلف آگاهی داشته باشیم. مثلا در یک سیستم ممکن است دما را به صورت سانتیگراد و در دیگری فارنهایت نگه داری کنند.
5. Matching : باید بررسی لازم را انجام دهیم که کدام داده مرتبط با کدام سیستم میباشد. به عبارت دیگر کدام سیستم مالک داده میباشد و دقیقا دادهها در کدام سیستم معتبرتر میباشند. مثلا پرسنل، هم در سیستم حسابداری میباشند هم در سیستم پرسنلی؛ ولی معمولا دادههای اصلی از سیستم پرسنلی میآیند.
Periodically : عملیات واکشی دادهها ( Retrieves Data ) و مجتمع سازی دادهها ( Consolidates Data ) در فرآیند ETL فقط یکبار اتفاق نمیافتد و این مراحل در بازههای زمانی خاص تکرار میگردند. این واکشی و انتقال دادهها میتواند در روز چند بار تکرار شود یا میتواند چند روز یک بار اجرا گردد و این بستگی دارد به سیاست موجود در Data Warehouse .
DDS (Dimensional Data Source) (Data Warehouse) : یک پایگاه داده از نوع نرمال شده ( Normalized ) یا بعدی ( Dimensional ) میباشد. که دادههای مجتمع شده و تمیز شده سیستمهای OLTP را در خود جای داده است. این پایگاه داده برای واکشیهای سیستمهای آنالیز داده مورد استفاده قرار میگیرد. ورود اطلاعات در Data Warehouse به صورت Batch میباشد و به هیچ عنوان مانند پایگاه دادههای OLTP ویرایش دادهها به صورت Online و هر زمان که دادهها تغییر میکنند، صورت نمیگیرد. اطلاعات در Data Warehouse معمولا به صورت تجمیع شده روزانه، ماهانه، فصلی یا سالانه میباشد. DDS ها مجموعه ای از Dimensional Data Mart ها هستند. و عمدتا به صورت denormalized میباشند.
Dimensional Data Mart : مجموعه ای از جداول Fact , Dimension میباشند که در یک بیزینس خاص باهم در ارتباط و مشترک میباشند.
dimensional data store schemas : طراحیهای مختلفی از جداول Fact , Dimension در DDS وجود دارد که عبارتند از
1. Star schema : سادهترین روش پیاده سازی Data Warehouse
2. Snowflake : در این روش جداول Dimension کمی نرمال سازی بیشتری دارند. سیستمهای آنالیز داده با این روش بهتر کار میکنند.
3. Galaxy schemas : طراحی در این روش بسیار سخت و پیچیده میباشد. با این وجود فرایند ETL در این طراحی سادهتر انجام میشود.
نمونهی طراحی Star به صورت زیر میباشد :
تفاوتهای DDS و NDS :
1. در DDS ها هیچ گونه نرمال سازی خاصی انجام نمیدهیم و عملا تمامی جداول را دینرمال کرده ایم، در حالی که در NDS تمامی جداول تا سطح سوم و گاهی تا سطح پنجم نرمال شده اند.
2. سرعت واکشی و پردازش کوئریها روی DDS خیلی بیشتر از NDS ها میباشد.
3. در صورتی که نیاز باشد Data Warehouse های خیلی بزرگ طراحی کنیم با حجم بسیار زیاد توصیه میشود از NDS ها استفاده شود در حالی که برای Data Warehouse های کوچک و متوسط بهتر است از DDS ها استفاده شود.
تصویر طراحی یک (Enterprise Data Source = NDS) EDS در زیر آمده است :
History : جداول Data Warehouse میتوانند در طول زمان بسیار بزرگ شوند و دارای تعداد رکورد زیادی گردند. اینکه حداکثر دادههای چند سال را در Data Warehouse نگه داری کنیم بستگی به سیاستهای سازمانی دارد که سیستم OLAP برای آن تهیه میگردد. استفاده کردن از table partitioning میتواند در جبران افزایش تعداد رکورد کمک زیادی به ما بکند.
slowly changing dimension (SCD) : سه روش برای نگه داری سابقهی تغییرات در جداول Dimension وجود دارد.
1. SCD type 1 : هیچ گونه سابقهی تغییراتی را نگه داری نمیکنیم
2. SCD type 2 : سابقهی تغییرات در ردیفها نگه داری میشود. در این روش هر ردیف، شماره ردیف قبلی را دارد و تعداد نا محدودی از تغییرات را نگه داری میکنیم.
3. SCD type 3 : سابقهی تغییرات در ستونها نگه داری میشوند و فقط ردیف جاری و آخرین تغییرات را نگه داری میکنیم.
Query : فقط ETL حق تغییرات در Data Warehouse را دارد و کاربر نمیتواند Data Warehouse را تغییر دهد. البته کاربران حق Query کردن از Data Warehouse را دارند.
دقت داشته باشید که کوئریهای پیچیده در NDS ها بسیار کندتر از همان کوئری در DDS میباشد.
Business Intelligence : مجموعه ای از فعالیتها که در یک سازمان برای شناخت بهتر وضعیت Business آن سازمان انجام میشود. نتایج BI کمک بسیاری برای تصمیم گیریهای تکنیکی و استراتژیکی درون سازمان میکند. همچنین کمک به بهبود فرایندهای Business جاری میکند.
فعالیتهای Business Intelligence در سه دسته بندی قرار میگیرند :
1. Reporting : گزارشاتی که از Data Warehouse گرفته میشود و به کاربر نمایش داده میشود و عمدتا این گزارشات به صورت tabular form میباشند.
2. OLAP : فعالیتهای انجام شده روی MDB برای گرفتن گزارشات Drill-Down و ... میباشد.
3. Data mining : فرآیند واکشی و داده کاوی دادههای درون سیستم میباشد، که منجر به کشف الگوها و رفتارها و ارتباطات دادهها در سیستم میشود. توسط داده کاوی ما متوجه میشویم چرا برخی دادهها در سیستم تولید شده اند.
a. descriptive analytics : زمانی که از داده کاوی برای شرح وقایع گذشته و حال استفاده میشود.
b. predictive analytics : زمانی که از داده کاوی برای پیش بینی وقایع گذشته استفاده میشود.
Real time data warehouse : به DW هایی گفته میشود که در کمترین زمان، تغییرات OLTP را در خود خواهند داشت. امروزه این نوع DW ها تغییرات 5 دقیقه تا حداکثر 1 ساعت قبل را در خود دارند. برای دسترسی به چنین DW هایی دو راه زیر وجود دارد :
1. بر روی هر جدول، Trigger هایی باشد تا تغییرات را به DW انتقال دهد. (البته برای این منظور باید Business مربوط به ETL را در این تریگرها نوشت)
2. سورس برنامههای اصلی کاربر ( OLTP ) تغییر کند تا علاوه بر OLTP Database ها Data Warehouse را هم تغییر دهند.
روشهای فوق بسیار روی سرعت و کارایی برنامههای اصلی تاثیر خواهند گذاشت.
NDS ( Normalize Data Source ) : در صورتی که طراحی Data Warehouse به صورت Dimensional نباشد و به صورت Normalize باشد، نوع Data Warehouse از نوع NDS میباشد.
روش ساخت MDB :
OLTP Database -> ETL -> Stage Database -> DDS (Dimensional Data Source = Data Warehouse) -> SSAS -> MDB
روش سادهتر ساخت Data Warehouse :
منظور از Source System همان OLTP Database ها میباشد.
به خاطر داشته باشید که Source System ها جزئی از Data Warehouse نمیباشند.
از کاربردهای Data Warehouse میتوان به موارد زیر اشاره کرد
1. Data Mining
2. استفاده در گزارشات
3. تجمیع داده ها
Data Mining کمک به درک بهتر Business جاری در سازمان میکند. همچنین منجر به کشف دانش از درون دادهها میشود.
برای Data Mining میتوانید از انواع پایگاه دادههای موجود مانند رابطه ای ، سلسله مراتبی و چند بعدی استفاده کرد . حتا میتوان از فایلهای XML , Excel نیز استفاده کرد.
Customer Relationship Management (CRM) :
منظور از مشتری، مصرف کنندهی سرویسی است که سازمان شما ارایه میکند. یک سیستم CRM شامل تمامی برنامه ایی میباشد که تمام فعالیتهای مشتری را پشتیبانی میکند.
Operational Data Store (ODS) :
این پایگاه داده به صورت رابطه ای و نرمال شده میباشد و شامل تمامی اطلاعات پایگاه داده ای OLTP میباشد که در این پایگاه داده مجتمع شده اند. تفاوت ODS با Data Warehouse در این میباشد که دادهها در ODS با هر Transaction به روز میشوند (سرعت بروز رسانی اطلاعات در ODS بالاتر از DW میباشد).
Master Data Management (MDM) :
در یک نگاه میتوان دادهها را به دو دسته تقسیم کرد
1. transaction data
2. master data
transaction data : شامل داده ای transactional در سیستمهای OLTP میباشد.
master data : توضیح دهندهی Business جاری در سازمان میباشد.
برای تشخیص این دو نیاز است Business سازمان را به خوبی شناسایی نمایید. به عبارت دیگر رویدادهای Business ی همان transaction data میباشند و master data شامل پاسخهای این سوالها میباشد. چه کسی، چه چیزی و کجا در مورد Business transaction .
Customer data integration (CDI) : عبارت است از MDM در رابطه با مشتری داده ها. کار این قسمت عبارت است از واکشی، پاک سازی ، ذخیره سازی ، نگه داری و به اشتراک گذاشتن داده ای مشتری میباشد.
Unstructured Data : داده ای ذخیره شده در پایگاه داده ، structured Data میباشند و داده هایی مانند عکس و فیلم و صوت و ...
Service-Oriented Architecture (SOA) : یک متد ساخت برنامه میباشد که در این روش تمامی اجزا برنامه به صورت ماژول هایی دیده میشود که در آنها ارتباطات با دیگر سیستمها به صورت سرویس میباشد و این زیر سیستمها را میتوان در پروژههای مختلف به کار برد.
Real-Time Data Warehouse : DW هایی که توسط ETL به روز میشوند در هنگامی که یک Transaction روی OLTP اتفاق میافتد.
مراحل انتقال داده از OLTP Database به MDB به صورت زیر میباشد.
Data quality : مکانیسم اطمینان بخشی از این که در DW دادهای مناسب و درست وارد میشوند. به عبارت دیگر DQ همان firewall برای DW در مقابل دادههای نامناسب میباشد.
برای بهتر مشخص شدن مکان DQ شکل زیر را در نظر بگیرید
نحوهی حرکت داده ای از OLTP به MDB اولین چیزی میباشد که شما باید به آن فکر کنید و برای آن روشی را انتخاب نمایید قبل از ساخت Data Warehouse .
چهار روش برای معماری انتقال اطلاعات از OLTP به DW وجود دارد (البته به عنوان نمونه و شما میتوانید از روشهای دیگر و طراحیهای مختلف و ترکیبی نیز بهره ببرید)
1. single DDS : در این روش فقط Stage , DDS وجود دارد.
2. NDS + DDS : در این روش علاوه بر Stage,DDS از NDS نیز استفاده میشود.
3. ODS + DDS : در این روش از Stage,ODS,DDS استفاده میگردد.
4. federated data warehouse (FDW ) : استفاده از چندین DW که با هم تجمیع شده اند.
تصویر Single DDS :
تصویر NDS + DDS :
تصویر ODS + DDS :
تصویر federated data warehouse (FDW ) :
منبع : Building a Data Warehouse With Examples in SQL Server انتشارات Apress
ASP.NET MVC #7
آشنایی با Razor Views
قبل از اینکه بحث جاری ASP.NET MVC را بتوانیم ادامه دهیم و مثلا مباحث دریافت اطلاعات از کاربر، کار با فرمها و امثال آنرا بررسی کنیم، نیاز است حداقل به دستور زبان یکی از View Engineهای ASP.NET MVC آشنا باشیم.
MVC3 موتور View جدیدی را به نام Razor معرفی کرده است که به عنوان روش برگزیده ایجاد Viewها در این سیستم به شمار میرود و فوق العاده نسبت به ASPX view engine سابق، زیباتر، سادهتر و فشردهتر طراحی شده است و یکی از اهداف آن تلفیق code و markup میباشد. در این حالت دیگر پسوند فایلهای Viewها همانند سابق ASPX نخواهد بود و به cshtml و یا vbhtml تغییر یافته است. همچنین برخلاف web forms view engine از System.Web.Page مشتق نشده است. و باید دقت داشت که Razor یک زبان برنامه نویسی جدید نیست. در اینجا از مخلوط زبانهای سی شارپ و یا ویژوال بیسیک به همراه تگهای html استفاده میشود.
البته این را هم باید عنوان کرد که این مسایل سلیقهای است. اگر با web forms view engine راحت هستید، با همان کار کنید. اگر با هیچکدام از اینها راحت نیستید (!) نمونههای دیگر هم وجود دارند، مثلا:
Razor Views یک سری قابلیت جالب را هم به همراه دارند:
1) امکان کامپایل آنها به درون یک DLL وجود دارد. مزیت: استفاده مجدد از کد، عدم نیاز به وجود صریح فایل cshtml یا vbhtml بر روی دیسک سخت.
2) آزمون پذیری: از آنجائیکه Razor viewها به صورت یک کلاس کامپایل میشوند و همچنین از System.Web.Page مشتق نخواهند شد، امکان بررسی HTML نهایی تولیدی آنهابدون نیاز به راه اندازی یک وب سرور وجود دارد.
3) IntelliSense ویژوال استودیو به خوبی آنرا پوشش میدهد.
4) با توجه به مواردی که ذکر شد، یک اتفاق جالب هم رخ داده است: امکان استفاده از Razor engine خارج از ASP.NET MVC هم وجود دارد. برای مثال یک سرویس ویندوز NT طراحی کردهاید که قرار است ایمیل فرمت شدهای به همراه اطلاعات مدلهای شما را در فواصل زمانی مشخص ارسال کند؟ میتوانید برای طراحی آن از Razor engine استفاده کنید و تهیه خروجی نهایی HTML آن نیازی به راه اندازی وب سرور و وهله سازی HttpContext ندارد.
ساختار پروژه مثال جاری
در ادامه مرور سریعی خواهیم داشت بر دستور زبان Razor engine و جهت نمایش این قابلیتها، یک مثال ساده را در ابتدا با مشخصات زیر ایجاد خواهیم کرد:
الف) یک empty ASP.NET MVC 3 project را ایجاد کنید و نوع View engine را هم در ابتدای کار Razor انتخاب نمائید.
ب) دو کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
namespace MvcApplication3.Models
{
public class Product
{
public Product(string productNumber, string name, decimal price)
{
Name = name;
Price = price;
ProductNumber = productNumber;
}
public string ProductNumber { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
using System.Collections.Generic;
namespace MvcApplication3.Models
{
public class Products : List<Product>
{
public Products()
{
this.Add(new Product("D123", "Super Fast Bike", 1000M));
this.Add(new Product("A356", "Durable Helmet", 123.45M));
this.Add(new Product("M924", "Soft Bike Seat", 34.99M));
}
}
}
کلاس Products صرفا یک منبع داده تشکیل شده در حافظه است. بدیهی است هر نوع ORM ایی که یک ToList را بتواند در اختیار شما قرار دهد، توانایی تشکیل لیست جنریکی از محصولات را نیز خواهد داشت و تفاوتی نمیکند که کدامیک مورد استفاده قرار گیرد.
ج) سپس یک کنترلر جدید به نام ProductsController را به پوشه Controllers برنامه اضافه میکنیم:
using System.Web.Mvc;
using MvcApplication3.Models;
namespace MvcApplication3.Controllers
{
public class ProductsController : Controller
{
public ActionResult Index()
{
var products = new Products();
return View(products);
}
}
}
د) بر روی نام متد Index کلیک راست کرده، گزینه Add view را جهت افزودن View متناظر آن، انتخاب کنید. البته میشود همانند قسمت پنجم گزینه Create a strongly typed view را انتخاب کرد و سپس Product را به عنوان کلاس مدل انتخاب نمود و در آخر خیلی سریع یک لیست از محصولات را نمایش داد، اما فعلا از این قسمت صرفنظر نمائید، چون میخواهیم آن را دستی ایجاد کرده و توضیحات و نکات بیشتری را بررسی کنیم.
ه) برای اینکه حین اجرای برنامه در VS.NET هربار نخواهیم که آدرس کنترلر Products را دستی در مرورگر وارد کنیم، فایل Global.asax.cs را گشوده و سپس در متد RegisterRoutes، در سطر Parameter defaults، مقدار پیش فرض کنترلر را مساوی Products قرار دهید.
مرجع سریع Razor
ابتدا کدهای View متد Index را به شکل زیر وارد نمائید:
@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>
@:line-2: @data <br />
<text>line-3:</text> @data
}
<br />
site@(data)
<br />
@@name
<br />
@(number/10)
<br />
First product: @Model.First().Name
<br />
@if (@number>10)
{
<span>@data</span>
}
else
{
<text>Plain Text</text>
}
<br />
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}
@*
A Razor Comment
*@
<br />
@("First product: " + Model.First().Name)
<br />
<img src="@(number).jpg" />
در ادامه توضیحات مرتبط با این کدها ارائه خواهد شد:
1) نحوه معرفی یک قطعه کد
@model List<MvcApplication3.Models.Product>
@{
ViewBag.Title = "Index";
var number = 12;
var data = "some text...";
<h2>line1: @data</h2>
@:line-2: @data <br />
<text>line-3:</text> @data
}
این کدها متعلق به Viewایی است که در قسمت (د) بررسی ساختار پروژه مثال جاری، ایجاد کردیم. در ابتدای آن هم نوع model مشخص شده تا بتوان سادهتر به اطلاعات شیء Model به کمک IntelliSense دسترسی داشت.
برای ایجاد یک قطعه کد در Viewایی از نوع Razor به این نحو عمل میشود:
@{ ...Code Block.... }
در اینجا مجاز هستیم کدهای سی شارپ را وارد کنیم. یک نکته جالب را هم باید درنظر داشت: امکان نوشتن تگهای html هم در این بین وجود دارد (بدون اینکه مجبور باشیم قطعه کد شروع شده را خاتمه دهیم، به حالت html معمولی سوئیچ کرده و دوباره یک قطعه کد دیگر را شروع نمائیم). مانند line1 مثال فوق. اگر کمی پایینتر از این سطر مثلا بنویسیم line2 (به عنوان یک برچسب) کامپایلر ایراد خواهد گرفت، زیرا این مورد نه متغیر است و نه از پیش تعریف شده است. به عبارتی نباید فراموش کنیم که اینجا قرار است کد نوشته شود. برای رفع این مشکل دو راه حل وجود دارد که در سطرهای دو و سه ملاحظه میکنید. یا باید از تگی به نام text برای معرفی یک برچسب در این میان استفاده کرد (سطر سه) یا اگر قرار است اطلاعاتی به شکل یک متن معمولی پردازش شود ابتدای آن مانند سطر دوم باید یک @: قرار گیرد.
کمی پایینتر از قطعه کد معرفی شده در بالا بنویسید:
<br />
site@data
اکنون اگر خروجی این View را در مرورگر بررسی کنید، دقیقا همین site@data خواهد بود. چون در این حالت Razor تصور خواهد کرد که قصد داشتهاید یک آدرس ایمیل را وارد کنید. برای این حالت خاص باید نوشت:
<br />
site@(data)
به این ترتیب data از متغیر data تعریف شده در code block قبلی برنامه دریافت و نمایش داده خواهد شد.
شبیه به همین حالت در مثال زیر هم وجود دارد:
<img src="@(number).jpg" />
در اینجا اگر پرانتزها را حذف کنیم، Razor فرض را بر این خواهد گذاشت که شیء number دارای خاصیت jpg است. بنابراین باید به نحو صریحی، بازه کاری را مشخص نمائیم.
بکار گیری این علامت @ یک نکته جنبی دیگر را هم به همراه دارد. فرض کنید در صفحه قصد دارید آدرس توئیتری شخصی را وارد کنید. مثلا:
<br />
@name
در این حالت View کامپایل نخواهد شد و Razor تصور خواهد کرد که قرار است اطلاعات متغیری به نام name را نمایش دهید. برای نمایش این اطلاعات به همین شکل، یک @ دیگر به ابتدای سطر اضافه کنید:
<br />
@@name
2) نحوه معرفی عبارات
عبارات پس از علامت @ معرفی میشوند و به صورت پیش فرض Html Encoded هستند (در قسمت 5 در اینباره بیشتر توضیح داده شد):
First product: @Model.First().Name
در این مثال با توجه به اینکه نوع مدل در ابتدای View مشخص شده است، شیء Model به لیستی از Products اشاره میکند.
یک نکته:
مشخص سازی حد و مرز صریح یک متغیر در مثال زیر نیز کاربرد دارد:
<br />
@number/10
اگر خروجی این مثال را بررسی کنید مساوی 12/10 خواهد بود و محاسبهای انجام نخواهد شد. برای حل این مشکل باز هم از پرانتز میتوان کمک گرفت:
<br />
@(number/10)
@if (@number>10)
{
<span>@data</span>
}
else
{
<text>Plain Text</text>
}
یک عبارت شرطی در اینجا با @if شروع میشود و سپس نکاتی که در «نحوه معرفی یک قطعه کد» بیان شد، در مورد عبارات داخل {} صادق خواهد بود. یعنی در اینجا نیز میتوان عبارات سی شارپ مخلوط با تگهای html را نوشت.
یک نکته: عبارت شرطی زیر نادرست است. حتما باید سطرهای کدهای سی شارپ بین {} محصور شوند؛ حتی اگر یک سطر باشند:
@if( i < 1 ) int myVar=0;
4) نحوه استفاده از حلقه foreach
@foreach (var item in Model)
{
<li>@item.Name, $@item.Price </li>
}
حلقه foreach نیز مانند عبارات شرطی با یک @ شروع شده و داخل {} بدنه آن نکات «نحوه معرفی یک قطعه کد» برقرار هستند (امکان تلفیق code و markup با هم).
کسانی که پیشتر با web forms کار کرده باشند، احتمالا الان خواهند گفت که این یک پس رفت است و بازگشت به دوران ASP کلاسیک دهه نود! ما به ندرت داخل صفحات aspx وب فرمها کد مینوشتیم. مثلا پیشتر یک GridView وجود داشت و یک دیتاسورس که به آن متصل میشد؛ مابقی خودکار بود و ما هیچ وقت حلقهای ننوشتیم. در اینجا هم این مساله با نوشتن برای مثال «html helpers» قابل کنترل است که در قسمتهای بعدی به آن پرداخته خواهد شد. به عبارتی قرار نیست به این نحو با Viewهای Razor رفتار کنیم. این قسمت فقط یک آشنایی کلی با Syntax است.
5) امکان تعریف فضای نام در ابتدای View
@using namespace;
6) نحوه نوشتن توضیحات سمت سرور:
@*
A Razor Comment / Server side Comment
*@
7) نحوه معرفی عبارات چند جزئی:
@("First product: " + Model.First().Name)
همانطور که ملاحظه میکنید، ذکر یک پرانتز برای معرفی عبارات چندجزئی کفایت میکند.
استفاده از موتور Razor خارج از ASP.NET MVC
پیشتر مطلبی را در مورد «تهیه قالب برای ایمیلهای ارسالی یک برنامه ASP.Net» در این سایت مطالعه کردهاید. اولین سؤالی هم که در ذیل آن مطلب مطرح شده این است: «در برنامههای ویندوز چطور؟» پاسخ این است که کل آن مثال بر مبنای HttpContext.Current.Server.Execute کار میکند. یعنی باید مراحل وهله سازی HttpContext و شیء Server توسط یک وب سرور و درخواست رسیده طی شود و ... شبیه سازی آن آنچنان مرسوم و کار سادهای نیست.
اما این مشکل با Razor وجود ندارد. به عبارتی در اینجا برای رندر کردن یک Razor View به html نهایی، نیازی به HttpContext نیست. بنابراین از این امکانات مثلا در یک سرویس ویندوز ان تی یا یک برنامه کنسول، WinForms، WPF و غیره هم میتوان استفاده کرد.
برای اینکه بتوان از Razor خارج از ASP.NET MVC استفاده کرد، نیاز به اندکی کدنویسی هست مثلا استفاده از کامپایلر سی شارپ یا وی بی و کامپایل پویای کد و یک سری ست آپ دیگر. پروژهای به نام RazorEngine این کپسوله سازی رو انجام داده و از اینجا http://razorengine.codeplex.com/ قابل دریافت است.
در مطلب جاری قصد داریم با نحوه ارائه یک UI کاربر پسند برای این منظور آشنا شویم و سؤال مهم هم این است: «چگونه میتوان کار کاربر را در حین وارد کردن تعدادی از برچسبهای مرتبط با یک مطلب سادهتر کرد؟». برای این منظور یکی از راه حلهایی که در بسیاری از سایتها مرسوم شده است، استفاده از افزونههایی مانند jQuery TagIt میباشد که در ادامه با نحوه استفاده از آن در ASP.NET MVC آشنا خواهیم شد.
پیشنیازها:
دریافت افزونه TagIt
همچنین دریافت jQuery UI (افزونه TagIt برای نمایش لیست Auto Complete آیتمها از jQuery UI در پشت صحنه استفاده میکند)
<head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/TagIt/jquery-ui-1.8.23.custom.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("~/Content/TagIt/tagit-simple-blue.css")" rel="stylesheet" type="text/css" /> <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Content/TagIt/jquery-ui-1.8.23.custom.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Content/TagIt/tagit.js")" type="text/javascript"></script> @RenderSection("JavaScript", required: false) </head>
آشنایی با مدل برنامه
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace jQueryMvcSample04.Models { public class BlogPostViewModel { [DisplayName("عنوان"), Required(ErrorMessage = "*")] public string Title { set; get; } [DisplayName("متن"), Required(ErrorMessage = "*")] public string Body { set; get; } /// <summary> /// آرایهای محدود از برچسبهای این مطلب خاص به صورت جیسون که پیشتر ثبت شده است /// هدف استفاده در حین ویرایش مطلب /// </summary> public string InitialTags { set; get; } /// <summary> /// آرایهای جیسونی از تمام برچسبهای موجود در سیستم /// هدف نمایش منوی انتخاب برچسبها از لیست /// </summary> public string TagsSource { set; get; } /// <summary> /// آرایهای از برچسبهای وارد شده توسط کاربر در حین ثبت مطلب /// </summary> [DisplayName("برچسبها"), Required(ErrorMessage = "*")] public string[] Tags { set; get; } public int? Id { set; get; } } }
افزونه TagIt برای نمایش اطلاعات خود به دو منبع داده نیاز دارد:
الف) TagsSource : لیستی است به فرمت JSON، از هر آنچه که در سیستم پیشتر به عنوان یک برچسب ثبت شده است. از این لیست برای نمایش منوی خودکار انتخاب آیتمها استفاده میشود.
ب) InitialTags : لیستی است به فرمت JSON، از تمام برچسبهای مرتبط با یک مطلب. از این اطلاعات در حین ویرایش یک مطلب استفاده خواهد شد.
در این ViewModel یک خاصیت دیگر به شکل آرایه، به نام Tags تعریف شده است که لیست برچسبهای وارد شده توسط کاربر را دریافت خواهد کرد.
معرفی کنترلر برنامه
using System.Web.Mvc; using jQueryMvcSample04.Extensions; using jQueryMvcSample04.Models; namespace jQueryMvcSample04.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult Index(int? id) { //در ابتدای کار تمام تگهای موجود در سیستم از بانک اطلاعاتی دریافت خواهند شد //از این تگها برای تشکیل منوی انتخاب برچسبها استفاده میشود var tagsSource = new[] { "C#", "C++", "C", "ASP.NET", "MVC" }.ToJson(); //همچنین صرفا برچسبهای مطلب جاری که پیشتر ثبت شدهاند نیز باید از بانک اطلاعاتی دریافت گردند //از این برچسبها برای ویرایش یک مطلب موجود استفاده خواهد شد var init = new[] { "ASP.NET" }.ToJson(); var model = new BlogPostViewModel { TagsSource = tagsSource, InitialTags = init, Id = id }; return View(model); } [HttpPost] public ActionResult Index(BlogPostViewModel data) { if (this.ModelState.IsValid) { //todo: save data // ... return RedirectToAction(actionName: "index", controllerName: "home"); } //در صورت بروز خطا مجددا اطلاعات موجود نمایش داده خواهند شد data.TagsSource = new[] { "C#", "C++", "C", "ASP.NET", "MVC" }.ToJson(); data.InitialTags = data.Tags.ToJson(); return View(data); } } }
با توجه به توضیحاتی که ارائه شد، کنترلر برنامه ساختار واضحتری را یافته است. در اولین بار نمایش صفحه، لیست منبع داده تگها و همچنین تگهای مرتبط با یک مطلب (در صورت وجود) به View ارائه خواهند شد.
از همین ViewModel، در عملیات Post نیز استفاده گردیده و اطلاعات دریافت میگردد.
تعریف متد الحاقی ToJson مورد استفاده را نیز در ادامه ملاحظه مینمائید:
using System.Linq; using System.Web.Script.Serialization; namespace jQueryMvcSample04.Extensions { public static class JsonExt { public static string ToJson(this string[] initialTags) { if (initialTags == null || !initialTags.Any()) return "[]"; else return new JavaScriptSerializer().Serialize(initialTags); } } }
و مرحله آخر تعریف View متناظر است
@model jQueryMvcSample04.Models.BlogPostViewModel @{ ViewBag.Title = "Index"; } @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>ثبت مطلب جدید</legend> @Html.HiddenFor(model => model.Id) <div class="editor-label"> @Html.LabelFor(model => model.Title) </div> <div class="editor-field"> @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title) </div> <div class="editor-label"> @Html.LabelFor(model => model.Body) </div> <div class="editor-field"> @Html.EditorFor(model => model.Body) @Html.ValidationMessageFor(model => model.Body) </div> <div class="editor-label"> @Html.LabelFor(model => model.Tags) </div> <div class="editor-field"> <ul id="tagsList" dir="ltr" name="Tags"> </ul> @Html.ValidationMessageFor(model => model.Tags) </div> <p> <input type="submit" value="Create" /> </p> </fieldset> } @section JavaScript { <script type="text/javascript"> $(document).ready(function () { var tagsSource = @Html.Raw(Model.TagsSource); $('#tagsList').tagit({ tagSource: tagsSource, select: true, triggerKeys: ['enter', 'comma', 'tab'], initialTags: @Html.Raw(Model.InitialTags) }); }); </script> }
الف) برای نمایش افزونه TagIt از یک ul با id مساوی tagsList استفاده شده است.
ب) خواص اضافی موجود در ViewModel که اطلاعات JSON ایی مورد نیاز را بازگشت میدهند در قسمت اسکریپت صفحه مورد استفاده قرار گرفتهاند. در اینجا نیاز است از Html.Raw استفاده شود تا اطلاعات مرتبط با JSON اشتباها encode نشده و قابل استفاده باشند.
دریافت مثال و پروژه کامل این قسمت
jQueryMvcSample04.zip
- بلاکهای جزئی با عنوان اسپرینت
- ویژگیهای جزئی
- تیمهای کوچک
- product backlog : مجموعهای اولویت بندی شده از نیازمندیهای سطح بالای سیستمی که در نهایت بایستی تحویل داده شود.
- sprint backlog : مواردی از product backlog که قرار است در یک sprint انجام شوند.
- نمودار burn-down :هدف نمودار burn-down، نمایش روند پیشرفت پروژه به صورت نموداری به اعضای تیم توسعه است که حاوی اطلاعاتی دربارۀ کل زمان انجام کار، زمان تخمینزده شده، مقدار کارانجام شده و عقبماندگیهای پروژه است.
- مستقل (Independent): باید خودبسنده باشد و به سایر داستانها وابسته نباشد.
- قابل مذاکره (Negotiable): داستانهای کاربری که بخشی از یک اسپرینت هستند همیشه قابل تغییر و بازنویسی هستند.
- با ارزش (Valuable): یک داستان کاربر باید به کاربر نهایی، ارزشی را ارائه دهد.
- قابل برآورد (Estimable): همیشه باید بتوان اندازۀ داستان کاربر را تخمین زد.
- اندازۀ مناسب (Sized appropriately): داستانهای کاربر نباید آن قدر بزرگ باشند که تبدیلشان به یک طرح یا وظیفه یا امر اولویتبندی شده با درجۀ مشخصی ممکن نباشد.
- قابل آزمون (Testable): داستان کاربر یا توصیفات مربوط به آن باید اطلاعات ضروری برای آزمودن آن را فراهم کنند.
معیار پذیرش
اگرچه product backlog ، sprint backlog و نمودار burn-down بخشهای اصلی اسکرام هستند، معیار پذیرش خروجی جانبی بسیار مهمی از فرآیند اسکرام است. بدون معیار پذیرش خوب، یک پروژه محکوم به شکست است.
معیار پذیرش ضرورتاً شفاف کنندۀ داستان است. چنین معیاری مجموعهای از گامهای مختلف را در اختیار توسعه دهنده میگذارد که پیش از آنکه کار تمام شده تلقی شود، باید انجام دهد. معیار پذیرش، توسط صاحب محصول ( product owner ) به کمک مشتری ایجاد میشود. این معیار انتظار از داستان کاربر را تنظیم میکند. استفاده از این معیار درجای خود نقطۀ شروع خوبی برای نوشتن تستهای خودکار یا حتی توسعۀ آزمون محور توسط توسعهدهنده است. بدین طریق، توسعهدهنده چیزی را تولید میکند که مشتری بدان نیاز داشته و آن را میخواهد.
دیگر مزیت معیار پذیرش وقتی آشکار میشود که یک ویژگی در طول یک اسپرینت کامل نشده و نیاز است تا از اسپرینتها خارج شود. در چنین موردی تیم میتواند معیار پذیرش را به عنوان ابزاری به کار گیرد تا بفهمد که داستان کاربر چگونه به قطعات کوچکتری تقسیم شود تا کماکان ارزشی را برای مشتری فراهم کرده تا بتواند در یک اسپرینت کامل شود.
- Scrum Master
- Product Owner
- Delivery Team
- Scrum Master
- شما چه چیزی را از دیروز تا حالا انجام دادهاید؟
- شما چه برنامهای برای امروز دارید؟
- آیا شما مشکل دیگری که مانع رسیدن به هدفتان باشد، ندارید؟ چه جریانی باعث ایجاد این موانع شدهاند؟ آیا میتوان مانع را حذف کرد یا باید تشدید شود؟
افزودن سرویس httpService.js به برنامه
تا این قسمت، تمام اطلاعات نمایش داده شدهی در لیست فیلمها، از سرویس درون حافظهای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین میشوند. اکنون در ادامه میخواهیم این سرویسها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامهی نمایش لیست فیلمها نیز اضافه میکنیم (فایل آنرا از پروژهی قبلی کپی کرده و در اینجا paste میکنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگیهای خارجی react-toastify و axios است. به همین جهت برای افزودن آنها مراحل زیر را طی میکنیم:
- نصب کتابخانههای react-toastify و axios از طریق خط فرمان (با فشردن دکمههای ctrl+back-tick در VSCode):
> npm i axios --save > npm i react-toastify --save
import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css";
render() { return ( <React.Fragment> <ToastContainer />
دریافت اطلاعات لیست نمایش ژانرها از سرویس backend
با فراخوانی آدرس https://localhost:5001/api/Genres، میتوان لیست ژانرهای سینمایی تعریف شدهی در سرویسهای backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال میکنیم:
چون نمیخواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت میکند، جایگزین میکنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد میکنیم. سپس آنرا طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json"; import http from "./httpService"; export function getGenres() { return http.get(apiUrl + "/genres"); }
{ "apiUrl": "https://localhost:5001/api" }
پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
import { getGenres } from "../services/genreService";
Uncaught TypeError: Object is not a function or its return value is not iterable
async componentDidMount() { const { data } = await getGenres(); const genres = [{ _id: "", name: "All Genres" }, ...data]; this.setState({ movies: getMovies(), genres }); }
دریافت اطلاعات لیست فیلمها از سرویس backend
پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونهی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آنرا به صورت زیر تکمیل میکنیم:
import { apiUrl } from "../config.json"; import http from "./httpService"; const apiEndpoint = apiUrl + "/movies"; function movieUrl(id) { return `${apiEndpoint}/${id}`; } export function getMovies() { return http.get(apiEndpoint); } export function getMovie(movieId) { return http.get(movieUrl(movieId)); } export function saveMovie(movie) { if (movie.id) { return http.put(movieUrl(movie.id), movie); } return http.post(apiEndpoint, movie); } export function deleteMovie(movieId) { return http.delete(movieUrl(movieId)); }
ابتدا دو متد دریافت لیست فیلمها و حذف یک فیلم را که در این کامپوننت استفاده شدهاند، import میکنیم:
import { getMovies, deleteMovie } from "../services/movieService";
async componentDidMount() { const { data } = await getGenres(); const genres = [{ id: "", name: "All Genres" }, ...data]; const { data: movies } = await getMovies(); this.setState({ movies, genres }); }
handleDelete = async movie => { const originalMovies = this.state.movies; const movies = originalMovies.filter(m => m.id !== movie.id); this.setState({ movies }); try { await deleteMovie(movie.id); } catch (ex) { if (ex.response && ex.response.status === 404) { console.log(ex); toast.error("This movie has already been deleted."); } this.setState({ movies: originalMovies }); //undo changes } };
import { toast } from "react-toastify";
اتصال فرم ثبت و ویرایش یک فیلم به backend server
تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شدهی در صفحه، به صفحهی not-found هدایت میشویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService"; import { getMovie, saveMovie } from "../services/fakeMovieService";
import { getGenres } from "../services/genreService"; import { getMovie, saveMovie } from "../services/movieService";
async componentDidMount() { const { data: genres } = await getGenres(); this.setState({ genres }); const movieId = this.props.match.params.id; if (movieId === "new") return; const { data: movie } = await getMovie(movieId); if (!movie) return this.props.history.replace("/not-found"); this.setState({ data: this.mapToViewModel(movie) }); }
async populateGenres() { const { data: genres } = await getGenres(); this.setState({ genres }); } async populateMovie() { try { const movieId = this.props.match.params.id; if (movieId === "new") return; const { data: movie } = await getMovie(movieId); this.setState({ data: this.mapToViewModel(movie) }); } catch (ex) { if (ex.response && ex.response.status === 404) this.props.history.replace("/not-found"); } } async componentDidMount() { await this.populateGenres(); await this.populateMovie(); }
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت میداد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت میگرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.
مرحلهی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام میشود:
doSubmit = async () => { await saveMovie(this.state.data); this.props.history.push("/movies"); };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip