public static string CreateConnectionStringForSQLCe(string dbPath = @"|DataDirectory|\NerdDinners.sdf") { SqlCeConnectionStringBuilder sqlConnection = new SqlCeConnectionStringBuilder(); sqlConnection.Password = "9023fase93"; sqlConnection.DataSource = dbPath; EntityConnectionStringBuilder connection = new EntityConnectionStringBuilder(); connection.Metadata = @"res://*/NerdDinnersModel.csdl|res://*/NerdDinnersModel.ssdl|res://*/NerdDinnersModel.msl"; connection.Provider = "System.Data.SqlServerCe.3.5"; connection.ProviderConnectionString = sqlConnection.ToString(); return connection.ToString(); } public static string CreateConnectionStringForSQLServer() { //Build an SQL connection string SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder() { DataSource = "MyPC", // Server name InitialCatalog = "db1", //Database UserID = "user1", //Username Password = "mypassword", //Password }; //Build an Entity Framework connection string EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder { Provider = "System.Data.SqlClient", Metadata = "res://*/testModel.csdl|res://*/testModel.ssdl|res://*/testModel.msl", ProviderConnectionString = sqlString.ToString() }; return entityString.ConnectionString; }
استفاده از این صفت هم بسیار ساده است:
private void A ( [CallerMemberName] string callerName = "") { Console.WriteLine("Caller is " + callerName); } private static void B() { // let's call A A(); }
ولی یک استفادهی بسیار کاربردی از این صفت، در پیاده سازی رابط INotifyPropertyChanged میباشد.
معمولا هنگام پیاده سازی INotifyPropertyChanged کدی شبیه به این را مینویسیم:
public class PersonViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private string name; public string Name { get { return name; } set { this.name = value; OnPropertyChanged("Name"); } } }
یعنی در Setter معمولا نام ویژگی ای را که تغییر کرده است، به متد OnPropertyChanged میفرستیم تا اطلاع رسانیهای لازم انجام پذیرد. تا اینجای کار همه چیز خوب و آرام است. اما به محضی که کد شما کمی طولانی شود و شما به دلایلی نیاز به Refactor کردن کد و احیانا تغییر نام ویژگیها را پیدا کنید، آن موقع مسائل جدیدی بروز پیدا میکند.
برای مثال فرض کنید پس از نوشتن کلاس PersonViewModel تصمیم میگیرد نام ویژگی Name را به FirstName تغییر دهید؛ چرا که میخواهید اجزای نام یک شخص را به صورت مجزا نگهداری و پردازش کنید. پس احتمالا با زدن کلید F2 روی فیلد name آن را به firstName و ویژگی Name را به FirstName تغییر نام میدهید. همانند کد زیر:
private string firstName; public string FirstName { get { return firstName; } set { this.firstName = value; OnPropertyChanged("Name"); } }
برنامه را کامپایل کرده و در کمال تعجب میبینید که بخشی از برنامه درست رفتار نمیکند و تغییراتی که در نام کوچک شخص توسط کاربر ایجاد میشود به درستی بروزرسانی نمیشوند. علت ساده است: ما کد را به صورت اتوماتیک Refactor کرده ایم و گزینهی Include String را در حین Refactor، در حالت پیشفرض غیرفعال رها کردهایم. پس جای تعجبی ندارد که در هر جای کد که رشتهای به نام "Name" با ماهیت نام شخص داشته ایم، دست نخورده باقی مانده است. در واقع در کد تغییر یافته، هنگام تغییر FirstName، ما به سیستم گزارش میکنیم که ویژگی Name (که اصلا وجود ندارد) تغییر یافته است و این یعنی خطا.
حال احتمال بروز این خطا را در ViewModel هایی با دهها ویژگی و ترکیبهای مختلف در نظر بگیرید. پس کاملا محتمل است و برای خیلی از دوستان این اتفاق رخ داده است.
و اما راه حل چیست؟ به کارگیری صفت CallerMemberName
بهتر است که یک کلاس انتزاعی برای تمام ViewModelهای خود داشته باشیم و پیاده سازی جدید INPC را در درون آن قرار دهیم تا براحتی VMهای ما از آن مشتق شوند:
public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { OnPropertyChangedExplicit(propertyName); } protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) { var memberExpression = (MemberExpression)projection.Body; OnPropertyChangedExplicit(memberExpression.Member.Name); } void OnPropertyChangedExplicit(string propertyName) { this.CheckPropertyName(propertyName); PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } #region Check property name [Conditional("DEBUG")] [DebuggerStepThrough] public void CheckPropertyName(string propertyName) { if (TypeDescriptor.GetProperties(this)[propertyName] == null) throw new Exception(String.Format("Could not find property \"{0}\"", propertyName)); } #endregion // Check property name }
در این کلاس، ما پارامتر propertyName را از متد OnPropertyChanged، توسط صفت CallerMemberName حاشیه نویسی کردهایم. این کار باعث میشود در Setterهای ویژگیها، به راحتی بدون نوشتن نام ویژگی، عملیات اطلاع رسانی تغییرات را انجام دهیم. بدین صورت که کافیست متد OnPropertyChanged بدون هیچ آرگومانی در Setter فراخوانی شود و صفت CallerMemberName به صورت اتوماتیک نام ویژگی ای که فراخوانی از درون آن انجام شده است را درون پارامتر propertyName قرار میدهد.
پس کلاس PersonViewModel را به صورت زیر میتوانیم اصلاح و تکمیل کنیم:
public class PersonViewModel : ViewModelBase { private string firstName; public string FirstName { get { return firstName; } set { this.firstName = value; OnPropertyChanged(); OnPropertyChanged(() => this.FullName); } } private string lastName; public string LastName { get { return lastName; } set { this.lastName = value; OnPropertyChanged(); OnPropertyChanged(() => this.FullName); } } public string FullName { get { return string.Format("{0} {1}", FirstName, LastName); } } }
کلاس ViewModelBase یک پیاده سازی دیگر از OnPropetyChanged هم دارد که به شما اجازه میدهد با استفاده دستورات لامبدا، OnPropertyChanged را برای هر یک از اعضای دلخواه کلاس نیز فراخوانی کنید. همانطور که در مثال فوق میبینید، تغییرات نام خانوادگی در نام کامل شخص نیز اثرگذار است. در نتیجه به وسیلهی یک Func به راحتی بیان میکنیم که FullName هم تغییر کرده است و اطلاع رسانی برای آن نیز باید صورت پذیرد.
برای استفاده از صفت CallerMemberName باید دات نت هدف خود را 4.5 یا 4.6 قرار دهید.
ارجاع:
Raise INPC witout string name
- لیستی از حقوق کارکنان را داریم. در گزارش نهایی آن نیاز است عدد حقوق کارکنانی با مبلغ کمتر از 1000، با رنگی دیگر نمایش داده شوند.
همچنین در این گزارش هر ردیفی که در ماه 7 واقع شده نیز ظاهر عدد سلول مربوط به آن ماه، به رنگ قهوهای و زمینه زرد تغییر یابد.
- در ستون مشخصات افراد این گزارش، نیاز است تصویر کارمند به همراه نام او در ذیل این تصویر (داخل یک سلول) نمایش داده شوند.
چیزی شبیه به این گزارش!
مورد اول در گزارشات، اصطلاحا به conditional formatting معروف است و مورد دوم مرتبط است به تهیه قالبهای سفارشی، بجای استفاده از قالبهای سلولهای پیش فرض PdfReport؛ که در ادامه نحوه انجام این موارد را بررسی خواهیم کرد.
ابتدا سورس کامل این مثال را ملاحظه نمائید:
using System; using iTextSharp.text; using PdfRpt.Core.Contracts; using PdfRpt.Core.Helper; using PdfRpt.FluentInterface; namespace PdfReportSamples.CustomCellTemplate { public class CustomCellTemplatePdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); doc.Orientation(PageOrientation.Portrait); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" }); doc.Compression(new CompressionSettings { CompressionLevel = CompressionLevel.BestCompression, EnableCompression = true }); }) .DefaultFonts(fonts => { fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"); }) .PagesFooter(footer => { footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("گزارش جدید ما"); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.SnowyPineTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); table.MultipleColumnsPerPage(new MultipleColumnsPerPage { ColumnsGap = 20, ColumnsPerPage = 2, ColumnsWidth = 250, IsRightToLeft = true, TopMargin = 7 }); }) .MainTableDataSource(dataSource => { var table = new System.Data.DataTable("لیست حقوق"); table.Columns.Add("شخص", typeof(string)); table.Columns.Add("ماه", typeof(int)); table.Columns.Add("مبلغ", typeof(decimal)); var rnd = new Random(); for (int i = 0; i < 200; i++) table.Rows.Add("شخص " + i, rnd.Next(1, 12), rnd.Next(400, 2000)); dataSource.DataTable(table); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); events.CellCreated(args => { //change the background color of the cell based on the value if (args.RowType == RowType.DataTableRow && args.Cell.RowData.Value != null && args.Cell.RowData.Value is decimal) { if ((decimal)args.Cell.RowData.Value <= 1000) args.Cell.BasicProperties.BackgroundColor = BaseColor.CYAN; } }); }) .MainTableSummarySettings(summary => { summary.OverallSummarySettings("جمع کل"); summary.PageSummarySettings("جمع صفحه"); summary.PreviousPageSummarySettings("نقل از ستون قبل"); }) .MainTableColumns(columns => { columns.AddColumn(column => { column.PropertyName("rowNo"); column.IsRowNumber(true); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(0); column.Width(1); column.HeaderCell("ردیف"); }); columns.AddColumn(column => { column.PropertyName("شخص"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(1); column.Width(3); column.HeaderCell("شخص"); column.ColumnItemsTemplate(t => t.CustomTemplate(new MyCustomCellTemplate())); }); columns.AddColumn(column => { column.PropertyName("ماه"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(2); column.Width(2); column.HeaderCell("ماه"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.ConditionalFormatFormula(list => { var cellValue = int.Parse(list.GetSafeStringValueOf("ماه", nullValue: "0")); if (cellValue == 7) { return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Bold | DocumentFontStyle.Underline, FontColor = new BaseColor(System.Drawing.Color.Brown), BackgroundColor = new BaseColor(System.Drawing.Color.Yellow) }; } return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Normal }; }); }); }); columns.AddColumn(column => { column.PropertyName("مبلغ"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(3); column.Width(2); column.HeaderCell("مبلغ"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); column.AggregateFunction(aggregateFunction => { aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum); aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); }); }) .Export(export => { export.ToXml(); export.ToExcel(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptDataTableSample.pdf")); } } }
using System; using System.Collections.Generic; using iTextSharp.text; using iTextSharp.text.pdf; using PdfRpt.Core.Contracts; using PdfRpt.Core.Helper; namespace PdfReportSamples.CustomCellTemplate { public class MyCustomCellTemplate : IColumnItemsTemplate { Random _rnd = new Random(); public void CellRendered(PdfPCell cell, Rectangle position, PdfContentByte[] canvases, CellAttributes attributes) { } public CellBasicProperties BasicProperties { set; get; } public Func<IList<CellData>, CellBasicProperties> ConditionalFormatFormula { set; get; } public PdfPCell RenderingCell(CellAttributes attributes) { var pdfCell = new PdfPCell(); var table = new PdfPTable(1) { RunDirection = PdfWriter.RUN_DIRECTION_RTL }; var filePath = AppPath.ApplicationPath + "\\Images\\" + _rnd.Next(1, 5).ToString("00") + ".png"; var photo = PdfImageHelper.GetITextSharpImageFromImageFile(filePath); table.AddCell(new PdfPCell(photo, fit: false) { Border = 0, VerticalAlignment = Element.ALIGN_BOTTOM, HorizontalAlignment = Element.ALIGN_CENTER }); var name = attributes.RowData.TableRowData.GetSafeStringValueOf("شخص"); table.AddCell(new PdfPCell(attributes.BasicProperties.PdfFont.FontSelector.Process(name)) { Border = 0, HorizontalAlignment = Element.ALIGN_CENTER }); pdfCell.AddElement(table); return pdfCell; } } }
توضیحات:
- در این مثال از منبع دادهای از نوع DataTable استفاده شده است؛ که نحوه بکارگیری آنرا در متد MainTableDataSource ملاحظه میکنید. ستونهای تعریف شده در MainTableColumns نیز بر اساس ستونهای DataTable مشخص شدهاند.
- در متد DocumentPreferences، نحوه مشخص سازی فشرده سازی نهایی فایل PDF را ملاحظه میکنید. این مورد از مزایای استفاده از فایلهای PDF است.
- برای اعمال فرمت شرطی اطلاعات در PdfReport دو روش وجود دارد.
الف) استفاده از متد MainTableEvents و کار کردن با رخدادهای تعریف شده در آن مانند CellCreated. در اینجا میتوان در نحوه رندر شدن یک سلول دخالت کرد:
events.CellCreated(args => { //change the background color of the cell based on the value if (args.RowType == RowType.DataTableRow && args.Cell.RowData.Value != null && args.Cell.RowData.Value is decimal) { if ((decimal)args.Cell.RowData.Value <= 1000) args.Cell.BasicProperties.BackgroundColor = BaseColor.CYAN; } });
ب) همانطور که در قسمت تعریف ستون «ماه» ملاحظه میکنید، توسط متد template.ConditionalFormatFormula نیز، امکان فرمت شرطی اطلاعات فراهم شده است. در اینجا میتوان به لیست اطلاعات سلولهای ردیف جاری دسترسی یافت و سپس بر اساس آن تصمیم گیری کرد.
- جهت تعریف قالبهای سفارشی سلولها کافی است اینترفیس IColumnItemsTemplate را پیاده سازی کنیم؛ که نمونهای از آن را در کدهای MyCustomCellTemplate فوق ملاحظه میکنید. در اینجا فرصت خواهید داشت هر شکل و طرح متنوعی را تهیه کرده و به صورت یک PdfPCell بازگشت دهید. برای نمونه در مثال فوق، یک جدول را در سلول تعریف شده قرار دادهایم. این جدول یک ستون دارد و هر سلولی که به آن اضافه خواهد شد، یک ردیف را تشکیل خواهد داد. در ردیف اول آن تصویر قرار گرفته و در ردیف دوم آن مقدار سلول جاری.
مثال - نمایش بلادرنگ میزان مصرف CPU و حافظه سرور بر روی کلیه کلاینتهای متصل توسط SignalR
در اینجا از Smoothie Charts برای ترسیم نمودارهای بلادرنگ سازگار با Canvas مخصوص HTML5 استفاده شده است.
پیشنیازها
پیشنیازهای این مطلب با مطلب «مثال - نمایش درصد پیشرفت عملیات توسط SignalR» یکی است. برای مثال، نحوه دریافت وابستگیها، تنظیمات فایل global.asax و افزودن اسکریپتها، تفاوتی با مثال قبلی ندارند.
تهیه منبع داده اطلاعات نمایشی
using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace SignalR04.Common { public class Counter { public string Name { set; get; } public float Value { set; get; } } public class PerformanceCounterProvider { private readonly List<PerformanceCounter> _counters = new List<PerformanceCounter>(); public PerformanceCounterProvider() { _counters.Add(new PerformanceCounter("Processor", "% Processor Time", "_Total", readOnly: true)); _counters.Add(new PerformanceCounter("Memory", "Pages/sec", readOnly: true)); _counters.Add(new PerformanceCounter("PhysicalDisk", "% Disk Time", "_Total", readOnly: true)); } public IList<Counter> GetResults() { return _counters.Select(c => new Counter { Name = c.CategoryName, Value = c.NextValue() }).ToList(); } } }
تهیه هاب ارسال دادهها به کلاینتها
using System.Threading; using Microsoft.AspNet.SignalR; using ThreadTimer = System.Threading.Timer; namespace SignalR04.Common { public class PerformanceCounterHub : Hub { private ThreadTimer _threadTimer; //keep it alive private readonly PerformanceCounterProvider _perfService = new PerformanceCounterProvider(); public PerformanceCounterHub() { _threadTimer = new ThreadTimer(timerCallback, null, Timeout.Infinite, 1000); _threadTimer.Change(dueTime: 1000, period: 2000); } private void timerCallback(object state) { var results = _perfService.GetResults(); this.Clients.All.newCounters(results); } } }
این هاب به صورت خودکار با اولین بار وهله سازی، پس از فراخوانی متد connection.hub.start در سمت کلاینت، شروع به کار میکند.
کدهای سمت کلاینت نمایش نمودارها
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="Scripts/jquery.signalR-1.1.3.min.js" type="text/javascript"></script> <script type="text/javascript" src='<%= ResolveClientUrl("~/signalr/hubs") %>'></script> <script src="Scripts/smoothie.js" type="text/javascript"></script> </head> <body> <form id="form1" runat="server"> <div> <div> <h2>Processor</h2> <canvas id="Processor" width="800" height="100"></canvas> </div> <div> <h2>Memory</h2> <canvas id="Memory" width="800" height="100"></canvas> </div> <div> <h2>PhysicalDisk</h2> <canvas id="PhysicalDisk" width="800" height="100"></canvas> </div> </div> </form> <script type="text/javascript"> var ChartEntry = function (name) { var self = this; self.name = name; self.chart = new SmoothieChart({ millisPerPixel: 50, labels: { fontSize: 15} }); self.timeSeries = new TimeSeries(); self.chart.addTimeSeries(self.timeSeries, { lineWidth: 3, strokeStyle: "#00ff00" }); }; ChartEntry.prototype = { addValue: function (value) { var self = this; self.timeSeries.append(new Date().getTime(), value); }, start: function () { var self = this; self.canvas = document.getElementById(self.name); self.chart.streamTo(self.canvas); } }; $(function () { $.connection.hub.logging = true; var performanceCounterHub = $.connection.performanceCounterHub; var charts = []; performanceCounterHub.client.newCounters = function (updatedCounters) { $.each(updatedCounters, function (index, updateCounter) { var entry; $.each(charts, function (idx, chart) { if (chart.name == updateCounter.Name) { entry = chart; return; } }); if (!entry) { entry = new ChartEntry(updateCounter.Name); charts.push(entry); entry.start(); } entry.addValue(updateCounter.Value); }); }; $.connection.hub.start(); }); </script> </body> </html>
- id هر canavs به Name اطلاعات دریافتی از سرور تنظیم شده است تا نمودارها در جای صحیحی ترسیم شوند.
- سپس نحوه کپسوله سازی SmoothieChart را مشاهده میکنید؛ چطور میتوان از آن یک شیء جاوا اسکریپتی ایجاد کرد و چطور اطلاعات را به آن اضافه نمود.
- نهایتا کار هاب را آغاز میکنیم. Callback ایی به نام performanceCounterHub.client.newCounters دقیقا متصل است به فراخوانی this.Clients.All.newCounters سمت سرور. در اینجا updatedCounters دریافتی، یک آرایه جاوا اسکریپتی است که هر عضو آن دارای Name و Value است. بر این اساس، تنها کافی است این مقادیر را که هر دو ثانیه یکبار به روز میشوند، به SmoothieChart برای ترسیم ارسال کنیم.
کدهای کامل این مثال را از اینجا نیز میتوانید دریافت کنید:
SignalR04.zip
CheckBoxList در ASP.NET MVC
اما ببخشید متوجه نشدم روشی که شما میگین چطوری هستش. فکر میکنم روش شما سادهتر باشه. کدهای من اینه:
//this method is in "UserController" class that select all available roles form database [NonAction] public List<SelectListItem> GetRoleList() { var usrMgmt = new UserManagement(); var RoleList = new List<SelectListItem>(); foreach (KeyValuePair<string, string> pair in usrMgmt.GetAllRoles()) { RoleList.Add(new SelectListItem { Text = pair.Value, Value = pair.Key, Selected = false }); } return RoleList; } //--------------------------------------------------------------------------- //this method is in "UserController" class that use for editing a user [HttpGet] public ActionResult Edit(string Username) { var roles = GetRoleList(); UserManagement usrMgmt = new UserManagement(); var query = usrMgmt.Select(Username); foreach (string role in query.Roles) { int index = roles.FindIndex(x=>x.Value == role); roles[index].Selected = true; } ViewBag.Roles = roles; return View(query); } //-------------------------------------------------------------- //this method is in "UserManagement" class in model layer, return a //dictionary<string,string> that first string is english role name and second one is persian role //name public Dictionary<string,string> GetAllRoles() { Dictionary<string, string> roles = new Dictionary<string, string>(); var db = new SamaEntities(); string query = ""; string[] roleNames = Roles.GetAllRoles(); foreach (string role in roleNames) { query = db.aspnet_Roles.Single(x => x.RoleName == role).Description; roles.Add(role, query); } return roles; }
تمرین 1 - ایجاد فرم ثبت نام
میخواهیم به برنامه، فرم ثبت نام را که حاوی سه فیلد نام کاربری، کلمهی عبور و نام است، اضافه کنیم. نام کاربری باید از نوع ایمیل باشد. بنابراین اعتبارسنجی مرتبطی نیز باید برای این فیلد تعریف شود. کلمهی عبور وارد شده باید حداقل 5 حرف باشد. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشدهاست، باید دکمهی submit فرم، غیرفعال باقی بماند. لینک ورود به این فرم نیز باید به منوی راهبری سایت اضافه شود.
برای حل این تمرین، فایل جدید registerForm.jsx را در پوشهی components ایجاد میکنیم و سپس توسط میانبرهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت RegisterForm را ایجاد کرده و سپس آنرا به صورت زیر تکمیل میکنیم:
- ابتدا در فایل app.js، پس از import ماژول آن:
import RegisterForm from "./components/registerForm";
<Route path="/register" component={RegisterForm} />
<NavLink className="nav-item nav-link" to="/register"> Register </NavLink>
import Joi from "@hapi/joi"; import React from "react"; import Form from "./common/form"; class RegisterForm extends Form { state = { data: { username: "", password: "", name: "" }, errors: {} }; schema = { username: Joi.string() .required() .email({ minDomainSegments: 2, tlds: { allow: ["com", "net"] } }) .label("Username"), password: Joi.string() .required() .min(5) .label("Password"), name: Joi.string() .required() .label("Name") }; doSubmit = () => { // Call the server console.log("Submitted"); }; render() { return ( <div> <h1>Register</h1> <form onSubmit={this.handleSubmit}> {this.renderInput("username", "Username")} {this.renderInput("password", "Password", "password")} {this.renderInput("name", "Name")} {this.renderButton("Register")} </form> </div> ); } } export default RegisterForm;
- سپس state این کامپوننت را با شیءای حاوی دو خاصیت data و error، مقدار دهی اولیه میکنیم. خواص متناظر با المانهای فرم را نیز به صورت یک شیء، به خاصیت data انتساب دادهایم.
- پس از آن، خاصیت schema تعریف شدهاست؛ تا قواعد اعتبارسنجی تک تک فیلدهای فرم را به کمک کتابخانهی Joi، مطابق نیازمندیهایی که در ابتدای تعریف این تمرین مشخص کردیم، ایجاد کند.
- در ادامه، متد doSubmit را ملاحظه میکنید. این متد پس از کلیک بر روی دکمهی Register و پس از اعتبارسنجی موفقیت آمیز فرم، به صورت خودکار فراخوانی میشود.
- در آخر، تعریف فرم ثبتنام را مشاهده میکنید که نکات آنرا در قسمت قبل، با معرفی کامپوننت Form و افزودن متدهای کمکی رندر input و button به آن، بررسی کردیم و در کل با نکات بررسی شدهی در فرم لاگینی که تا به اینجا ایجاد کردیم، تفاوتی ندارد.
تمرین 2- ایجاد فرم ثبت و یا ویرایش یک فیلم
فرم جدید ثبت و ویرایش یک فیلم، نکات بیشتری را به همراه دارد. در اینجا میخواهیم در بالای لیست نمایش فیلمها، یک دکمهی new movie را اضافه کنیم تا با کلیک بر روی آن، به فرم ثبت و ویرایش فیلمها هدایت شویم. این فرم، از فیلدهای یک عنوان متنی، انتخاب ژانر از یک drop down list، تعداد موجود (بین 1 و 100) و امتیاز (بین صفر تا 10) تشکیل شدهاست. همچنین تا زمانیکه اعتبارسنجی فرم تکمیل نشدهاست، دکمهی submit فرم باید غیرفعال باقی بماند. پس از ذخیره شدن این فیلم (در لیست درون حافظهای برنامه)، با مراجعهی به لیست فیلمها و انتخاب آن از لیست (با کلیک بر روی لینک آن)، باید مجددا به همین فرم، در حالت ویرایش این رکورد هدایت شویم. به علاوه اگر در بالای صفحه یک id اشتباه وارد شد، باید صفحهی «پیدا نشد» نمایش داده شود.
کامپوننت MovieForm و مسیریابی آنرا در قسمت 17، تعریف و اضافه کردیم. برای تعریف لینکی به آن، به کامپوننت movies مراجعه کرده و بالای متنی که تعداد کل آیتمهای موجود در بانک اطلاعاتی را نمایش میدهد، المان زیر را اضافه میکنیم:
import { Link } from "react-router-dom"; // ... <div className="col"> <Link to="/movies/new" className="btn btn-primary" style={{ marginBottom: 20 }} > New Movie </Link> <p>Showing {totalCount} movies in the database.</p>
سپس به کامپوننت src\components\movieForm.jsx که پیشتر آنرا اضافه کرده بودیم، مراجعه کرده و به صورت زیر آنرا تکمیل میکنیم:
import Joi from "@hapi/joi"; import React from "react"; import { getGenres } from "../services/fakeGenreService"; import { getMovie, saveMovie } from "../services/fakeMovieService"; import Form from "./common/form"; class MovieForm extends Form { state = { data: { title: "", genreId: "", numberInStock: "", dailyRentalRate: "" }, genres: [], errors: {} };
- سپس این کامپوننت نیز از کامپوننت Form ارث بری میکند تا به امکانات ویژهی آن دسترسی پیدا کند.
- در ادامه در خاصیت state، طبق روالی که در کامپوننت فرم درنظر گرفتهایم، دو خاصیت data و errors باید حضور داشته باشند. در خاصیت data، شیءای که نام خاصیتهای آن با فیلدهای فرم تطابق دارد، ذکر شدهاند. در اینجا برای ذخیره سازی اطلاعات انتخاب شدهی از drop down list مرتبط با ژانرهای سینمایی، از خاصیت genreId استفاده میشود؛ این تنها اطلاعاتی است که از کل آیتمهای یک drop down list نیاز داریم. آرایهی genres که آیتمهای این drop down list را مقدار دهی میکند، در روال componentDidMount، از سرویس مرتبطی دریافت و مقدار دهی خواهد شد.
در ادامهی کدهای کامپوننت MovieForm، کدهای schema اعتبارسنجی شیء data را ملاحظه میکنید:
schema = { _id: Joi.string(), title: Joi.string() .required() .label("Title"), genreId: Joi.string() .required() .label("Genre"), numberInStock: Joi.number() .required() .min(0) .max(100) .label("Number in Stock"), dailyRentalRate: Joi.number() .required() .min(0) .max(10) .label("Daily Rental Rate") };
اکنون به مرحلهی componentDidMount میرسیم:
componentDidMount() { const genres = getGenres(); this.setState({ genres }); const movieId = this.props.match.params.id; if (movieId === "new") return; const movie = getMovie(movieId); if (!movie) return this.props.history.replace("/not-found"); this.setState({ data: this.mapToViewModel(movie) }); }
- پس از آن، نحوهی دریافت پارامتر id مسیریابی رسیده را ملاحظه میکنید. این id اگر به "new" تنظیم شده بود، یعنی قرار است، اطلاعات جدیدی ثبت شوند. بنابراین متد جاری را خاتمه میدهیم (چون کار ادامهی این متد، مقدار دهی اولیهی تمام فیلدهای فرم، بر اساس اطلاعات شیء دریافت شدهی از سرویس فیلمها است). در غیراینصورت (و با مشخص بودن id)، با استفاده از این id و متد getMovie سرویس src\services\fakeMovieService.js، سعی خواهیم کرد تا اطلاعات شیء movie متناظری را دریافت کنیم. اگر خروجی این متد null بود، یعنی id وارد شده معتبر نیست. به همین جهت کاربر را به صفحهی not-found هدایت میکنیم. اگر دقت کنید در اینجا بجای متد push، از متد replace استفاده کردهایم. چون اگر از متد push استفاده میکردیم و کاربر بر روی دکمهی back مرورگر کلیک میکرد، دوباره به همین صفحه، با id غیرمعتبر قبلی وارد میشد و یک حلقهی بیپایان رخ میداد. همچنین به return ای هم که به همراه متد replace استفاده شده، دقت کنید. کار redirect به یک صفحهی دیگر، به معنای عدم اجرای کدهای پس از آن نیست. بنابراین اگر میخواهیم کار این متد با redirect، به پایان برسد، ذکر return الزامی است.
- در پایان این متد، خاصیت data موجود در state را به روز رسانی میکنیم؛ تا سبب رندر فرم، با اطلاعات شیء movie یافت شده گردد و چون ساختار شیء movie دریافت شدهی از سرویس، با ساختار data تعریف شدهی در state یکی نیست، نیاز به نگاشت این دو به هم، توسط متد سفارشی mapToViewModel زیر است:
mapToViewModel(movie) { return { _id: movie._id, title: movie.title, genreId: movie.genre._id, numberInStock: movie.numberInStock, dailyRentalRate: movie.dailyRentalRate }; }
در ادامهی کدهای کامپوننت فرم فیلمها، به متد doSubmit میرسیم:
doSubmit = () => { saveMovie(this.state.data); this.props.history.push("/movies"); };
در انتهای این کامپوننت نیز به متد رندر آن میرسیم:
render() { return ( <div> <h1>Movie Form</h1> <form onSubmit={this.handleSubmit}> {this.renderInput("title", "Title")} {this.renderSelect("genreId", "Genre", this.state.genres)} {this.renderInput("numberInStock", "Number in Stock", "number")} {this.renderInput("dailyRentalRate", "Rate")} {this.renderButton("Save")} </form> </div> ); }
برای تعریف متد جدید renderSelect به این صورت عمل میکنیم:
- ابتدا فایل جدید src\components\common\select.jsx را ایجاد کرده و سپس آنرا جهت نمایش یک drop down list، ویرایش میکنیم:
import React from "react"; const Select = ({ name, label, options, error, ...rest }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <select name={name} id={name} {...rest} className="form-control"> <option value="" /> {options.map(option => ( <option key={option._id} value={option._id}> {option.name} </option> ))} </select> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Select;
- پس از آن به کامپوننت src\components\common\form.jsx مراجعه کرده و متد رندر آنرا اضافه میکنیم:
import Select from "./select"; // ... class Form extends Component { // ... renderSelect(name, label, options) { const { data, errors } = this.state; return ( <Select name={name} value={data[name]} label={label} options={options} onChange={this.handleChange} error={errors[name]} /> ); } }
تمرین 3- جستجوی در لیست فیلمها
میخواهیم در بالای لیست نمایش فیلمها، یک search box را قرار دهیم تا توسط آن بتوان بر اساس عنوان وارد شده، در فیلمهای موجود جستجو کرد. همچنین این جستجو قرار است کلی بوده و حتی در صورت انتخاب ژانر خاصی از منوی کنار صفحه، باید در کل اطلاعات موجود جستجو کند. به علاوه اگر کاربر ژانری را انتخاب کرد، این text box باید خالی شود.
برای اینکار ابتدا فایل جدید src\components\searchBox.jsx را ایجاد کرده و به صورت زیر آنرا تکمیل میکنیم:
import React from "react"; const SearchBox = ({ value, onChange }) => { return ( <input type="text" name="query" className="form-control my-3" placeholder="Search..." value={value} onChange={e => onChange(e.currentTarget.value)} /> ); }; export default SearchBox;
سپس به کامپوننت movies مراجعه کرده و آنرا ذیل متن نمایش تعداد رکوردها، درج میکنیم:
<p>Showing {totalCount} movies in the database.</p> <SearchBox value={searchQuery} onChange={this.handleSearch} />
import SearchBox from "./searchBox"; //... class Movies extends Component { state = { //... selectedGenre: {}, searchQuery: "" }; handleSearch = query => { this.setState({ searchQuery: query, selectedGenre: null, currentPage: 1 }); }; handleGenreSelect = genre => { console.log("handleGenreSelect", genre); this.setState({ selectedGenre: genre, searchQuery: "", currentPage: 1 }); };
متد handleGenreSelect را نیز اندکی تغییر دادهایم تا اگر گروهی انتخاب شد، مقدار searchQuery را خالی کند. اگر در اینجا searchQuery را به نال تنظیم میکردیم، controlled component جعبهی جستجو، تبدیل به کامپوننت کنترل نشدهای میشد و در این حالت، React، اخطار تبدیل بین این دو را صادر میکرد.
در آخر، ابتدای متد getPageData هم جهت اعمال searchQuery، به صورت زیر تغییر میکند:
getPagedData() { const { pageSize, currentPage, selectedGenre, movies: allMovies, sortColumn, searchQuery } = this.state; let filteredMovies = allMovies; if (searchQuery) { filteredMovies = allMovies.filter(m => m.title.toLowerCase().startsWith(searchQuery.toLowerCase()) ); } else if (selectedGenre && selectedGenre._id) { filteredMovies = allMovies.filter(m => m.genre._id === selectedGenre._id); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-21.zip
Rss آب و هوای هر شهر در یاهو به صورت یک لینک یکتا میباشد؛ به شکل زیر :
و این لینک http://weather.yahooapis.com/forecastrss?w=28350859&u=c اطلاعات آب و هوای تهران را در قالب یک RSS به شما نمایش خواهد داد.
خوب، حالا پارامتر دوم یعنی پارامتر u چکاری را انجام میدهد؟
* چنانچه مقدار پارامتر u برابر c باشد، یعنی شما دمای آب و هوای شهر مد نظر را بر اساس سانتیگراد میخواهید.
* اگر مقدار پارامتر u برابر f باشد، یعنی شما دمای آب و هوای آن شهر مورد نظر را بر اساس فارنهایت میخواهید.
برای گرفتن WOEID شهرها هم به این سایت بروید http://woeid.rosselliot.co.nz و اسم هر شهری که میخواهید بزنید تا WOEID را به شما نمایش دهد.
در این مثال من از یک DropDown استفاده کردم که کاربر با انتخاب هر شهر از DropDown، آب و هوای آن شهر را مشاهده میکند.
Action مربوط به صفحهی Index به صورت زیر میباشد :
[HttpGet] public ActionResult Index() { ViewBag.ProvinceList = _RPosition.Positions; ShowWeatherProvince(8); return View(); }
حال تابعی را که آب و هوای مربوط به هر شهر را نمایش میدهد، به شرح زیر است:
public ActionResult ShowWeatherProvince(int dpProvince) { XDocument rssXml=null; CountryName CountryName = new CountryName(); if (dpProvince != 0) { switch (dpProvince) { case 1: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345768&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Azarbayejan-e Sharqhi" }; break; } case 2: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345767&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Azarbayejan-e Qarbi" }; break; } case 3: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254335&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Ardebil" }; break; } case 4: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=28350859&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Alborz" }; break; } case 5: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345787&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Esfahan" }; break; } case 6: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345775&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Ilam" }; break; } case 7: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254463&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Bushehr" }; break; } case 8: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=28350859&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Tehran" }; break; } case 9: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345769&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Chahar Mahall va Bakhtiari" }; break; } case 10: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=56189824&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Razavi Khorasan" }; break; } case 11: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345789&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Shomali Khorasan" }; break; } case 12: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345789&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Jonubi Khorasan" }; break; } case 13: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345778&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Khuzestan" }; break; } case 14: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2255311&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Zanjan" }; break; } case 15: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345784&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Semnan" }; break; } case 16: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345770&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Sistan va Baluchestan" }; break; } case 17: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345772&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Fars" }; break; } case 18: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=20070200&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Qazvin" }; break; } case 19: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2255062&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Qom" }; break; } case 20: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345779&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Kordestan" }; break; } case 21: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254796&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Kerman" }; break; } case 22: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254797&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Kermanshah" }; break; } case 23: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345771&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Kohgiluyeh va Buyer Ahmad" }; break; } case 24: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=20070201&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Golestan" }; break; } case 25: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345773&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Gilan" }; break; } case 26: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345782&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Lorestan" }; break; } case 27: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345783&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Markazi" }; break; } case 28: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345780&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Mazandaran" }; break; } case 29: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2254664&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Hamedan" }; break; } case 30: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2345776&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Hormozgan" }; break; } case 31: { rssXml = XDocument.Load("http://weather.yahooapis.com/forecastrss?w=2253355&u=c"); CountryName = new CountryName() { Country = "Iran", City = "Yazd" }; break; } } ViewBag.Location = CountryName; XNamespace yWeatherNS = "http://xml.weather.yahoo.com/ns/rss/1.0"; List<YahooWeatherRssItem> WeatherList = new List<YahooWeatherRssItem>(); for (int i = 0; i < 4; i++) { YahooWeatherRssItem YahooWeatherRssItem = new YahooWeatherRssItem() { Code = Convert.ToInt32(rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("code").Value), Day = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("day").Value, Low = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("low").Value, High = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("high").Value, Text = rssXml.Descendants("item").Elements(yWeatherNS + "forecast").ElementAt(i).Attribute("text").Value, }; WeatherList.Add(YahooWeatherRssItem); } ViewBag.FeedList = WeatherList; } return PartialView("_Weather"); }
حالا کد مربوط به خواندن فایل Rss را برایتان توضیح میدهم : حلقهی for 0 تا 4 (که در کد بالا مشاهده میکنید)یعنی اطلاعات 4 روز آینده را برایم برگردان.
من تگهای Code ، Day ، Low ، High و text فایل RSS را در این حلقه For میخوانم که البته مقادیر این 4 روز را در لیستی اضافه میکنم که نوع این لیست هم از نوع YahooWeatherRssItem میباشد. من این کلاس را در فایل ضمیمه قرار دادم. اکنون هر کدام از این تگها را برایتان توضیح میدهم:
code : هر آب و هوا کدی دارد .مثلا آب و هوای نیمه ابری یک کد ، آب و هوای آفتابی کدی دیگر و ...
Low: حداقل دمای آن روز را به ما میدهد .
High: حداکثر دمای آن روز را به میدهد .
day: نام روز از هفته را بر میگرداند مثلا شنبه ، یکشنبه و ....
text: که توضیحاتی میدهد مثلا اگر هوا آفتابی باشد مقدار sunny را بر میگرداند و ...
خوب، تا اینجا ما Rss مربوط به هر شهر را خواندیم حالا در قسمت Design باید چکار کنیم .
کدهای html صفحهی Index ما شامل کدهای زیر است :
@{ ViewBag.Title = "Weather"; } <link href="~/Content/User/Weather/Weather.css" rel="stylesheet" /> @section scripts{ <script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script> <script type="text/javascript"> $("#dpProvince").change(function () { $(this).parents("form").submit(); }); </script> } <h2>Weather</h2> <div id="Progress"> <img src="~/Images/User/Other/ajax-loader.gif" /> </div> <div id="BoxContent"> @Html.Partial("_Weather")</div> @using (Ajax.BeginForm(actionName: "ShowWeatherProvince", ajaxOptions: new AjaxOptions { UpdateTargetId = "BoxContent", LoadingElementId = "Progress", InsertionMode = InsertionMode.Replace })) { <div style="padding-top:15px;"> <div style="float:left; width:133px; ">Select Your Province</div> <div style="float:left"> @Html.DropDownList("dpProvince", new SelectList(ViewBag.ProvinceList, "Id", "Name"),"Select Your Province", new { @class = "webUserDropDown", @style = "width:172px" })</div> </div> }
@{ List<Weather.YahooWeatherRssItem> Feeds = ViewBag.FeedList; } <div> @{ HtmlString StartTable = new HtmlString("<table class='WeatherTable' cellspacing='0' cellpadding='0'><tbody><tr>"); HtmlString EndTable = new HtmlString("</tr></tbody></table>"); HtmlString StartTD = new HtmlString("<td>"); HtmlString EndTD = new HtmlString("</td>"); } <div style="width: 300px;"> @{ @StartTable foreach (var item in Feeds) { @StartTD <div>@item.Day</div> <div> @{ string FileName = ""; switch (item.Code) { case 0: { FileName = "/Images/User/Weather/Tornado.png"; break; } case 1: { FileName = "/Images/User/Weather/storm2.gif"; break; } case 2: { FileName = "/Images/User/Weather/storm2.gif"; break; } case 3: { FileName = "/Images/User/Weather/storm2.gif"; break; } case 4: { FileName = "/Images/User/Weather/15.gif"; break; } case 5: { FileName = "/Images/User/Weather/29.gif"; break; } case 6: { FileName = "/Images/User/Weather/29.gif"; break; } case 7: { FileName = "/Images/User/Weather/29.gif"; break; } case 8: { FileName = "/Images/User/Weather/26.gif"; break; } case 9: { FileName = "/Images/User/Weather/drizzle.png"; break; } case 10: { FileName = "/Images/User/Weather/26.gif"; break; } case 11: { FileName = "/Images/User/Weather/18.gif"; break; } case 12: { FileName = "/Images/User/Weather/18.gif"; break; } case 13: { FileName = "/Images/User/Weather/19.gif"; break; } case 14: { FileName = "/Images/User/Weather/19.gif"; break; } case 15: { FileName = "/Images/User/Weather/19.gif"; break; } case 16: { FileName = "/Images/User/Weather/22.gif"; break; } case 17: { FileName = "/Images/User/Weather/Hail.png"; break; } case 18: { FileName = "/Images/User/Weather/25.gif"; break; } case 19: { FileName = "/Images/User/Weather/dust.png"; break; } case 20: { FileName = "/Images/User/Weather/fog_icon.png"; break; } case 21: { FileName = "/Images/User/Weather/hazy_icon.png"; break; } case 22: { FileName = "/Images/User/Weather/2017737395.png"; break; } case 23: { FileName = "/Images/User/Weather/32.gif"; break; } case 24: { FileName = "/Images/User/Weather/32.gif"; break; } case 25: { FileName = "/Images/User/Weather/31.gif"; break; } case 26: { FileName = "/Images/User/Weather/7.gif"; break; } case 27: { FileName = "/Images/User/Weather/38.gif"; break; } case 28: { FileName = "/Images/User/Weather/6.gif"; break; } case 29: { FileName = "/Images/User/Weather/35.gif"; break; } case 30: { FileName = "/Images/User/Weather/7.gif"; break; } case 31: { FileName = "/Images/User/Weather/33.gif"; break; } case 32: { FileName = "/Images/User/Weather/1.gif"; break; } case 33: { FileName = "/Images/User/Weather/34.gif"; break; } case 34: { FileName = "/Images/User/Weather/2.gif"; break; } case 35: { FileName = "/Images/User/Weather/freezing_rain.png"; break; } case 36: { FileName = "/Images/User/Weather/30.gif"; break; } case 37: { FileName = "/Images/User/Weather/15.gif"; break; } case 38: { FileName = "/Images/User/Weather/15.gif"; break; } case 39: { FileName = "/Images/User/Weather/15.gif"; break; } case 40: { FileName = "/Images/User/Weather/12.gif"; break; } case 41: { FileName = "/Images/User/Weather/22.gif"; break; } case 42: { FileName = "/Images/User/Weather/22.gif"; break; } case 43: { FileName = "/Images/User/Weather/22.gif"; break; } case 44: { FileName = "/Images/User/Weather/39.gif"; break; } case 45: { FileName = "/Images/User/Weather/thundershowers.png"; break; } case 46: { FileName = "/Images/User/Weather/19.gif"; break; } case 47: { FileName = "/Images/User/Weather/thundershowers.png"; break; } case 3200: { FileName = "/Images/User/Weather/1211810662.png"; break; } } } <img alt='@item.Text' title='@item.Text' src='@FileName'> </div> <div> <span>@item.High°</span> <span>@item.Low°</span> </div> @EndTD } } @EndTable </div> </div>
چنانچه در مورد RSS وضعیت آب و هوای یاهو اطلاعات دقیقتری را میخواهید بدانید به این لینک بروید.
در آموزش بعدی قصد دارم برایتان این بخش را توضیح دهم که بر اساس IP بازدید کننده سایت شما، اطلاعات آب و هوایی شهر بازدید کننده را برایش در سایت نمایش دهد.
Files-06bf65bac63d4dd694b15fc24d4cb074.zip
موفق باشید
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{id:int}")] public IActionResult Get(int id) { return Ok(id); } }
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{id}")] public IActionResult Get(int id) { // id is 0 here if you pass string. return Ok(id); } }
1- Inline Constraints
app.UseMvc(routes => { routes.MapRoute("Values", "api/values/{id:int}", new { controller = "Values", action = "Get" }); });
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{name:minlength(2):maxlength(10):alpha}")] public IActionResult Get(string name) { return Ok(name); } }
2- MapRoute's Constraints Argument
app.UseMvc(routes => { routes.MapRoute( name: "Values", template: "api/values/{name}", defaults: new { controller = "Values", action = "Get" }, constraints: new { name = new CompositeRouteConstraint(new List<IRouteConstraint> { new AlphaRouteConstraint(), new MinLengthRouteConstraint(2), new MaxLengthRouteConstraint(10) }) }); });
ایجاد یک Constraint سفارشی
public class StartsWithConstraint : IRouteConstraint { public StartsWithConstraint(string startsWith) { if (string.IsNullOrWhiteSpace(startsWith)) throw new ArgumentNullException(nameof(StartsWith)); StartsWith = startsWith; } private string StartsWith { get; } public bool Match(HttpContext httpContext, IRouter route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (parameterName == null) throw new ArgumentNullException(nameof(parameterName)); if (values == null) throw new ArgumentNullException(nameof(values)); if (!values.TryGetValue(parameterName, out var value) || value == null) return false; string valueString = Convert.ToString(value, CultureInfo.InvariantCulture); return valueString.StartsWith(StartsWith); } }
services.Configure<RouteOptions>(opt => opt.ConstraintMap.Add("startsWith", typeof(StartsWithConstraint)));
[Route("api/[controller]")] public class ValuesController : ControllerBase { [HttpGet("{name:minlength(2):maxlength(10):alpha:startsWith(Mo)}")] public IActionResult Get(string name) { return Ok(name); } }
ساختار مورد نیاز یک Kendo UI Tree View
فرض کنید قصد دارید نظرات تو در توی مطلبی را توسط Kendo UI Tree View نمایش دهید. مدل خود ارجاع دهندهی آن میتواند چنین شکلی را داشته باشد:
namespace KendoUI11.Models { public class BlogComment { public int Id { set; get; } public string Body { set; get; } public int? ParentId { get; set; } // مخصوص کندو یو آی هستند public bool HasChildren { get; set; } public string imageUrl { get; set; } } }
چند خاصیت بعدی مانند HasChildren و imageUrl مخصوص Kendo UI هستند. از imageUrl اختیاری میتوان جهت نمایش آیکنی در کنار یک آیتم استفاده کرد و HasChildren به این معنا است که آیا گره جاری دارای عناصر فرزندی میباشد یا خیر.
تهیه یک منبع داده نمونه
شکل ابتدای مطلب، از طریق منبع داده ذیل تهیه شدهاست:
using System.Collections.Generic; namespace KendoUI11.Models { /// <summary> /// منبع داده فرضی جهت سهولت دموی برنامه /// </summary> public static class BlogCommentsDataSource { private static readonly IList<BlogComment> _cachedItems; static BlogCommentsDataSource() { _cachedItems = createBlogCommentsDataSource(); } public static IList<BlogComment> LatestComments { get { return _cachedItems; } } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> private static IList<BlogComment> createBlogCommentsDataSource() { var list = new List<BlogComment>(); var comment1 = new BlogComment { Id = 1, Body = "نظر من این است که", HasChildren = true, ParentId = null }; list.Add(comment1); var comment12 = new BlogComment { Id = 2, Body = "پاسخی به نظر اول", HasChildren = true, ParentId = 1 }; list.Add(comment12); var comment12A = new BlogComment { Id = 3, Body = "پاسخی دیگری به نظر اول", HasChildren = false, ParentId = 1 }; list.Add(comment12A); var comment121 = new BlogComment { Id = 4, Body = "پاسخی به پاسخ به نظر اول", HasChildren = false, ParentId = 2 }; list.Add(comment121); var comment2 = new BlogComment { Id = 5, Body = "نظر 2", HasChildren = true, ParentId = null, imageUrl= "images/search.png" }; list.Add(comment2); var comment21 = new BlogComment { Id = 6, Body = "پاسخ به نظر 2", HasChildren = false, ParentId = 5 }; list.Add(comment21); return list; } } }
در این لیست دو رکورد، دارای ParentId مساوی null هستند. از این null بودنها جهت کوئری گرفتن و نمایش ریشههای TreeView در ادامه استفاده خواهیم کرد.
بازگشت نظرات با فرمت JSON به سمت کلاینت
در ادامه یک کنترلر ASP.NET MVC را مشاهده میکنید که توسط اکشن متد GetBlogComments، رکوردهای مورد نظر را با فرمت JSON به سمت کلاینت ارسال میکند:
using System.Linq; using System.Web.Mvc; using KendoUI11.Models; namespace KendoUI11.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); // shows the page. } [HttpGet] public ActionResult GetBlogComments(int? id) { if (id == null) { //دریافت ریشهها return Json( BlogCommentsDataSource.LatestComments .Where(x => x.ParentId == null) // ریشهها .ToList(), JsonRequestBehavior.AllowGet); } else { //دریافت فرزندهای یک ریشه return Json( BlogCommentsDataSource.LatestComments .Where(x => x.ParentId == id) .ToList(), JsonRequestBehavior.AllowGet); } } } }
اگر مقدار id به سمت سرور ارسال شود، یعنی کاربر گره و نودی را گشودهاست. بر این اساس، تمامی فرزندان این گره را یافته و بازگشت میدهیم.
کدهای سمت کاربر نمایش Kendo UI Tree View
برای کار با Kendo UI TreeView نیاز است از منبع داده خاصی به نام HierarchicalDataSource به نحو ذیل استفاده کنیم. در قسمت transport آن مشخص میکنیم که اطلاعات باید از چه آدرسی خوانده شوند که در اینجا به آدرس اکشن متد GetBlogComments اشاره میکند.
همچنین نیاز است مشخص کنیم کدامیک از خواص مدل بازگردانده شده، همان hasChildren است که در مثال فوق دقیقا به همین نام نیز تنظیم شدهاست.
<!--نحوهی راست به چپ سازی --> <div class="k-rtl k-header demo-section"> <div id="my-treeview"></div> </div> @section JavaScript { <script type="text/javascript"> $(function () { var dataSource = new kendo.data.HierarchicalDataSource({ transport: { read: { url: "@Url.Action("GetBlogComments", "Home")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, schema: { model: { id: "Id", hasChildren: "HasChildren" } } }); $("#my-treeview").kendoTreeView({ //استفاده از قالب در صورت نیاز template: kendo.template($("#treeview-template").html()), checkboxes: { checkChildren: false }, dataSource: dataSource, dataTextField: "Body", //رخدادها select: function (e) { console.log("Selecting: " + this.text(e.node)); }, check: function (e) { console.log("Checkbox changed :: " + this.text(e.node)); }, change: function (e) { console.log("Selection changed"); }, collapse: function (e) { console.log("Collapsing " + this.text(e.node)); }, expand: function (e) { console.log("Expanding " + this.text(e.node)); } }); }); </script> <script id="treeview-template" type="text/kendo-ui-template"> <strong> #: item.Body # </strong> </script> <style scoped> .demo-section { width: 100%; height: 300px; } </style> }
- در ابتدا به ازای هر ردیف این TreeView، از یک قالب استفاده شدهاست. تعریف این مورد اختیاری است. اگر نیاز به سفارشی سازی نحوهی نمایش هر آیتم را داشتید، میتوان از قالبها استفاده کرد.
- قسمت checkboxes مشخص میکند که آیا نیاز است در کنار هر آیتم یک checkbox نیز نمایش داده شود یا خیر.
- dataSource را به HierarchicalDataSource تنظیم کردهایم.
- dataTextField مشخص میکند که کدام فیلد دربرگیرندهی متن هر آیتم TreeView است.
- تعدادی رخداد منتسب به TreeView نیز تنظیم شدهاند که خروجی آنها را در console تصویر ابتدای بحث مشاهده میکنید.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
در این پست و با استفاده از سری پستهای آقای مهندس یوسف نژاد در این زمینه، یک Attribute جهت هماهنگ سازی با سیستم اعتبار سنجی ASP.NET MVC در سمت سرور و سمت کلاینت (با استفاده از jQuery Validation) بررسی خواهد شد.
قبل از شروع مطالعه سری پستهای MVC و Entity Framework الزامی است.
برای انجام این کار ابتدا مدل زیر را در برنامه خود ایجاد میکنیم.
using System;
public class SampleModel
{
public DateTime StartDate { get; set; }
public string Data { get; set; }
public int Id { get; set; }
}
using System; using System.ComponentModel.DataAnnotations; public class SampleModel { [Required(ErrorMessage = "Start date is required")] public DateTime StartDate { get; set; } [Required(ErrorMessage = "Data is required")] public string Data { get; set; } public int Id { get; set; } }
@model SampleModel @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <section> <header> <h3>SampleModel</h3> </header> @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) @using (Html.BeginForm("SaveData", "Sample", FormMethod.Post)) { <p> @Html.LabelFor(x => x.StartDate) @Html.TextBoxFor(x => x.StartDate) @Html.ValidationMessageFor(x => x.StartDate) </p> <p> @Html.LabelFor(x => x.Data) @Html.TextBoxFor(x => x.Data) @Html.ValidationMessageFor(x => x.Data) </p> <input type="submit" value="Save"/> } </section>
public class SampleController : Controller { // // GET: /Sample/ public ActionResult Index() { return View(); } public ActionResult SaveData(SampleModel item) { if (ModelState.IsValid) { //save data } else { ModelState.AddModelError("","لطفا خطاهای زیر را برطرف نمایید"); RedirectToAction("Index", item); } return View("Index"); } }
تا اینجای کار روال عادی همیشگی است. اما برای طراحی Attribute دلخواه جهت اعتبار سنجی (مثلا برای مجبور کردن کاربر به وارد کردن یک فیلد با پیام دلخواه و زبان دلخواه) باید یک کلاس جدید تعریف کرده و از کلاس RequiredAttribute ارث ببرد. در پارامتر ورودی این کلاس جهت کار با Resourceهای ثابت در نظر گرفته شده است اما برای اینکه فیلد دلخواه را از دیتابیس بخواند این روش جوابگو نیست. برای انجام آن باید کلاس RequiredAttribute بازنویسی شود.
کلاس طراحی شده باید به صورت زیر باشد:
public class VegaRequiredAttribute : RequiredAttribute, IClientValidatable { #region Fields (2) private readonly string _resourceId; private String _resourceString = String.Empty; #endregion Fields #region Constructors (1) public VegaRequiredAttribute(string resourceId) { _resourceId = resourceId; ErrorMessage = _resourceId; AllowEmptyStrings = true; } #endregion Constructors #region Properties (1) public new String ErrorMessage { get { return _resourceString; } set { _resourceString = GetMessageFromResource(value); } } #endregion Properties #region Methods (2) // Public Methods (1) public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { yield return new ModelClientValidationRule { ErrorMessage = GetMessageFromResource(_resourceId), ValidationType = "required" }; } // Private Methods (1) private string GetMessageFromResource(string resourceId) { var errorMessage = HttpContext.GetGlobalResourceObject(_resourceId, "Yes") as string; return errorMessage ?? ErrorMessage; } #endregion Methods }
1- ابتدا دستور
HttpContext.GetGlobalResourceObject(_resourceId, "Yes") as string;
که عنوان کلید Resource را از سازنده کلاس گرفته (کد اقای یوسف نژاد) رشته معادل آن را از دیتابیس بازیابی میکند.
2- ارث بری از اینترفیس IClientValidatable، در صورتی که از این اینترفیس ارث بری نداشته باشیم. Validator طراحی شده در طرف کلاینت کار نمیکند. بلکه کاربر با کلیک بروی دکمه مورد نظر دادهها را به سمت سرور ارسال میکند. در صورت وجود خطا در پست بک خطا نمایش داده خواهد شد. اما با ارث بری از این اینترفیس و پیاده سازی متد GetClientValidationRules میتوان تعریف کرد که در طرف کلاینت با استفاده از Unobtrusive jQuery پیام خطای مورد نظر به کنترل ورودی مورد نظر (مانند تکست باکس) اعمال میشود. مثلا در این مثال خصوصیت data-val-required به input هایی که قبلا در مدل ما Reqired تعریف شده اند اعمال میشود.
حال در مدل تعریف شده میتوان به جای Required میتوان از VegaRequiredAttribute مانند زیر استفاده کرد. (همراه با نام کلید مورد نظر در دیتابیس)
public class SampleModel { [VegaRequired("RequiredMessage")] public DateTime StartDate { get; set; } [VegaRequired("RequiredMessage")] public string Data { get; set; } public int Id { get; set; } }
با اجرای برنامه اینبار خروجی به شکل زیر خواهد بود.
جهت فعال ساری اعتبار سنجی سمت کلاینت ابتدا باید اسکریپتهای زیر به صفحه اضافه شود.
<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.validate.unobtrusive.min.js")" type="text/javascript"></script>
<appSettings> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true"/> </appSettings>
<script type="text/javascript"> jQuery.validator.addMethod('required', function (value, element, params) { if (value == null | value == "") { return false; } else { return true; } }, ''); jQuery.validator.unobtrusive.adapters.add('required', {}, function (options) { options.rules['required'] = true; options.messages['required'] = options.message; }); </script>
موفق و موید باشید.
منابع ^ و ^ و ^ و ^