تاریخ شمسی با Extension Method برای DateTime
در قسمتی که آرایه مربوط به مقدار دهی اولیه ماهها را تعریف کردید تعدا آنها 13 میباشد که آیتم اخری برابر با رشته متنی خالی میباشد . دلیل خاصی دارد ؟
ممنون
یکی از نکات امنیتی که استاندارد Owasp بررسی مینماید هدر X-Content-Type-Options است که جهت جلوگیری از حملات از طریق فایلهای نامرتبط میباشد. در این رخنه ممکن است فایلی که مرورگر دریافت میکند با آنچه که وب سایت ما آن را میشناسد متفاوت باشد. به عنوان مثال یک فایل اسکریپت که به عنوان یک فایل استایل معرفی میگردد ولی قابلیت اجرای کدهای آن در مرورگر امکان پذیر است؛ به این نوع حملات MIME Sniffing میگویند. در یکی از سایتهایی که در حال حاضر در حال توسعه آن هستیم بخشی از گزارشها با استفاده از ابزار FastReport ایجاد شده بود و با توجه به اینکه در این ابزار از فایلهای axd استفاده میگردید و مرورگر نوع دیتای برگشتی و MimeType معرفی شده را همخوان نمیدانست، از نمایش و بارگذاری آن ممانعت به عمل آورده و در نتیجه فایل گزارش دیده نمیشد. در این مقاله قصد داریم به معرفی این نوع حمله، روش جلوگیری از آن و همچنین رفع محدودیت پیش آمده را بررسی کرده تا در موارد مشابه از آن استفاده نماییم.
Mime Sniffing
زمانیکه مرورگر در هدر(پاسخ) Response، نوع محتوای ارسال شده، Content-Type را دریافت نکند، یا مرورگر متوجه مغایرتی در آن شود، این نوع رفتار را Mime Sniffing شناسایی میکند. نحوه این شناساییها در هر مرورگری میتواند متفاوت باشد؛ ولی عموما بر اساس type ارسالی و پسوند مورد نظر میباشد. در بعضی از موارد نیز خواندن بایتهای ابتدایی یک فایل نیز میتواند نشان دهد که محتوای ارسالی واقعا چیست. به عنوان مثال برای فایلهایی با پسوند Gif، الگوی بایتهای ابتدایی شامل 47 49 46 38 39 میباشد؛ ولی از آنجا که در همه فایلها، بایتهای ابتدایی الگوی یکسانی ندارند، پس نمیتوان به این روش نیز بسنده کرد.
روش اینکه به مروگر بگوییم جلوی این نوع حملات را بگیرد و در صورت شناسایی Sniffing از اجرای آن سر باز بزند، استفاده از هدر X-Content-Type-Options میباشد که نحوه افزودن آن در فایل web.config به شکل زیر است:
<httpProtocol> <customHeaders> ... <remove name="X-Content-Type-Options"/> <add name="X-Content-Type-Options" value="nosniff" /> .. </customHeaders> </httpProtocol>
در پروژه ما به دلیل اینکه بخشی از گزارشها با استفاده از FastReport طراحی شده بود این مورد برای ما ایجاد مشکل میکرد و در گزارش نمایش داده نمیشد و در کنسول پیامهایی به شکل زیر دریافت میکردیم:
Refused to execute script from 'https://localhost:44377/WebResource.axd?d=xxx' because its MIME type ('text/js') is not executable, and strict MIME type checking is enabled.
با نگاهی به Response دریافتی نیز میتوان بازگشت هدر امنیتی X-Content-Type-Options را نیز مشاهده نمود:
پس باید این هدر را برای بعضی از آدرسها که میتوانند دچار مشکلات اجرایی گردند حذف کرده و برای مابقی بخشها همچنان این هدر فعال باشد؛ پس با افزودن کد زیر به web.config، هدر مورد نظر را برای این نوع فایل حذف میکنیم:
<location path="WebResource.axd" > <system.webServer> <httpProtocol> <customHeaders> <remove name="X-Content-Type-Options" /> </customHeaders> </httpProtocol> </system.webServer> </location>
نتیجه آن را میتوانید در صفحه ذیل برای همان درخواست و پاسخ قبلی نیز مشاهده نمایید:
تعریف Enum برنامه به صورت زیر است:
namespace WpfBindRadioButtonToEnum.Models { public enum Gender { Female, Male } }
using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace WpfBindRadioButtonToEnum.Converters { public class EnumBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (Enum.IsDefined(value.GetType(), value) == false) return DependencyProperty.UnsetValue; return Enum.Parse(value.GetType(), parameter.ToString()).Equals(value); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return Enum.Parse(targetType, parameter.ToString()); } } }
اکنون اگر ViewModel برنامه به شکل زیر باشد که GenderValue را در اختیار View قرار میدهد:
using System.ComponentModel; using WpfBindRadioButtonToEnum.Models; namespace WpfBindRadioButtonToEnum.ViewModels { public class MainWindowViewModel : INotifyPropertyChanged { Gender _genderValue; public Gender GenderValue { get { return _genderValue; } set { _genderValue = value; notifyPropertyChanged("GenderValue"); } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; private void notifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } }
<Window x:Class="WpfBindRadioButtonToEnum.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:VM="clr-namespace:WpfBindRadioButtonToEnum.ViewModels" xmlns:C="clr-namespace:WpfBindRadioButtonToEnum.Converters" xmlns:Models="clr-namespace:WpfBindRadioButtonToEnum.Models" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <VM:MainWindowViewModel x:Key="VMainWindowViewModel" /> <C:EnumBooleanConverter x:Key="CEnumBooleanConverter" /> </Window.Resources> <StackPanel DataContext="{Binding Source={StaticResource VMainWindowViewModel}}"> <TextBlock Text="Gender" Margin="3" /> <RadioButton Content="{x:Static Models:Gender.Male}" IsChecked="{Binding GenderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource CEnumBooleanConverter}, ConverterParameter={x:Static Models:Gender.Male}}" Margin="3" GroupName="G1" /> <RadioButton Content="{x:Static Models:Gender.Female}" IsChecked="{Binding GenderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource CEnumBooleanConverter}, ConverterParameter={x:Static Models:Gender.Female}}" Margin="3" GroupName="G1" /> </StackPanel> </Window>
در اینجا EnumBooleanConverter تهیه شده، کار تبدیل مقدار true و false دریافتی از IsChecked را به معادل Enum آن و برعکس، انجام میدهد.
به صورت خلاصه: ابتدا تبدیلگر EnumBooleanConverter باید اضافه شود. سپس به ازای هر گزینهی Enum، یک RadioButton در صفحه قرار میگیرد که ConverterParameter خاصیت IsChecked آن مساوی است با یکی از گزینههای Enum متناظر.
Plan چیست؟
در اینجا Plan کوئری سادهای را مشاهده میکنید. کار آن انتخاب نام، نام خانوادگی و آدرس ایمیل افرادی است که نام خانوادگی آنها با Whit شروع میشود و بر روی دو جدول که با هم جوین شدهاند عمل میکند.
اولین موردی را که باید در یک Plan به آن دقت کرد، عملگرهای آن است که شامل select، nested loop، index seek و clustered index seek میباشند. index seek بر روی جدول اشخاص و clustered index seek بر روی جدول ایمیلها صورت میگیرد. nested loop بیانگر جوین بین جداول است. این عملگرها بیانگر اعمال فیزیکی هستند که رخ دادهاند.
همچنین تعدادی پیکان (arrow) را هم مشاهده میکنید که بیانگر جهت سیلان دادهها است. اطلاعات از طریق index seek و clustered index seek به nested loop میرسند و در نهایت به عملگر select ارائه خواهند شد.
در این تصویر، هزینههای تخمینی مرتبط با هر عملگر نیز قابل مشاهدهاست که نسبت به کل کوئری محاسبه شدهاند. این هزینه، بدون واحد است و به معنای میزان زمان و یا CPU صرف شدهی برای انجام عمل خاصی نیست و صرفا برای مقایسهی هزینهی نسبی عملگرها در کل یک Plan کاربرد دارد. باید دقت داشت که هزینههای نمایش داده شدهی در یک Plan، همیشه تخمینی هستند. در قسمتهای قبل در مورد نحوهی دریافت estimated plan و actual plan بحث کردیم. هیچگاه چیزی به نام Actual cost در یک Actual plan وجود ندارد و همیشه تخمینی است. روش محاسبهی آنها توسط الگوریتمهای بهینه ساز است و مستقل از سخت افزار مورد استفاده.
در یک پلن، مدت زمان انجام یک کوئری، میزان I/O ، locks و wait statistics قابل مشاهده نیستند. البته اگر از SQL Server 2016 به بعد استفاده میکنید و یک Actual plan را محاسبه کردهاید، مدت زمان انجام یک کوئری و میزان I/O نیز در Plan قابل مشاهدهاند.
از چه جهتی باید یک Plan را خواند؟
اگر هدف، بررسی «سیلان کنترل» است (Control flow)، باید یک Plan را از «چپ به راست» خواند. یعنی از عملگر select شروع میکنیم که کوئری ما را کنترل میکند. سپس به nested loop میرسیم که نام و نام خانوادگی را از جدول اشخاص دریافت میکند. این nested loop نیز با کمک ایندکسهای تعریف شده، شرط کوئری را بر آورده میکند.
اما جهت «سیلان اطلاعات» در یک Plan از «راست به چپ» است (Data flow). اطلاعات از طریق index seekها به حلقه و سپس select میرسند.
چگونه یک Query Plan را شروع به بررسی کنیم؟
ابتدا در management studio از منوی Query، گزینهی Include actual execution plan را انتخاب میکنیم. سپس کوئری زیر را اجرا میکنیم:
USE [WideWorldImporters]; GO SELECT [s].[StateProvinceName], [s].[SalesTerritory], [s].[LatestRecordedPopulation], [s].[StateProvinceCode] FROM [Application].[Countries] [c] JOIN [Application].[StateProvinces] [s] ON [s].[CountryID] = [c].[CountryID] WHERE [c].[CountryName] = 'United States'; GO
در اینجا چهار عملگر select، nested loop، clustered index seek و clustered index scan مشاهده میشوند. شاید اینطور به نظر برسد که در این Plan، ابتدا clustered index scan و clustered index seek انجام میشوند و سپس به nested loop میرسیم (اگر Plan را بر اساس سیلان داده، از راست به چپ بخوانیم)؛ اما اینطور نیست. عملگرها در اینجا در حقیقت یک سری iterator هستند که با دریافت ردیفهای مرتبط، بلافاصله آنها را به nested loop ارسال میکنند. این nested loop نیز ردیفهایی را که با جوین انجام شده تطابق دارند، به سمت select ارسال میکند.
اگر به تصویر دقت کنید هر کدام از ایندکسها به یک جدول اشاره میکنند که نام آن بالای عدد هزینه درج شدهاست. برای مشاهده نام کامل شیء متناظر با آن، میتوان اشارهگر ماوس را بر روی ایندکس حرکت داد و به اطلاعات قسمت Object دقت کرد:
و یا اگر اطلاعات کاملتری از این popup را نیاز داشتید، عملگر مدنظر را انتخاب کرده و سپس دکمهی F4 را فشار دهید:
در برگهی خواص ظاهر شده میتوان ریز جزئیات تمام اطلاعات مرتبط با عملگر انتخاب شده را مشاهده کرد. برای مثال در اینجا حتی اطلاعات Logical reads را بدون روشن کردن SET STATISTICS IO ON میتوان مشاهده کرد:
همچنین با توجه به انتخاب گزینهی Include actual execution plan، تعداد ردیفهای بازگشت داده شدهی واقعی و تخمینی، با هدایت اشارهگر ماوس بر روی یکی از اشیاء مرتبط با بررسی ایندکسها، قابل مشاهده هستند:
گزارش این تعداد ردیفها، با حرکت اشارهگر ماوس، بر روی پیکانهای منتهی به nested loop و یا select نیز قابل مشاهده هستند:
به این ترتیب میتوان دریافت که چه مقدار اطلاعات در طول این Plan و قسمتهای مختلف آن، از سمت راست به چپ، در حال جابجایی است.
اکنون در ادامه سعی میکنیم توسط DMO's، این Plan را از Plan cache دریافت کنیم:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT [cp].[size_in_bytes], [cp].[cacheobjtype], [cp].[objtype], [cp].[plan_handle], [dest].[text], [plan].[query_plan] FROM [sys].[dm_exec_cached_plans] [cp] CROSS APPLY [sys].[dm_exec_sql_text]([cp].[plan_handle]) [dest] CROSS APPLY [sys].[dm_exec_query_plan]([cp].[plan_handle]) [plan] WHERE [dest].[text] LIKE '%StateProvinces%' OPTION(MAXDOP 1, RECOMPILE);
همانطور که مشاهده میکنید، اینبار تنها اطلاعات تخمینی در این Plan ظاهر شدهاند؛ چون اطلاعات آن از کش خوانده شدهاست. همچنین در اینجا اطلاعات I/O مانند حالت Actual Plan، در برگهی خواص عملگرهای این Plan، قابل مشاهده نیستند.
نگاهی به اطلاعات XML ای یک Plan
اگر کوئری زیر را با فرض انتخاب Include actual execution plan در منوی Query اجرا کنیم:
SELECT [o].[OrderID], [ol].[OrderLineID], [o].[OrderDate], [o].[CustomerID], [ol].[Quantity], [ol].[UnitPrice] FROM [Sales].[Orders] [o] JOIN [Sales].[OrderLines] [ol] ON [o].[OrderID] = [ol].[OrderID]; GO
در اینجا با کلیک راست بر روی Plan، میتوان گزینهی Show Execution Plan XML را نیز انتخاب کرد. گاهی از اوقات کار کردن با این اطلاعات، به صورت XML ای سادهتر است و فرمت آن از هر نگارش به نگارش دیگر SQL Server میتواند متفاوت باشد.
برای مثال اگر در برگهی نمایش این اطلاعات، دکمههای ctrl+f را فشرده و به دنبال runtime بگردیم، خیلی سریعتر میتوان به اطلاعات I/O ،CPU و تعداد ردیفهای بازگشت داده شده، رسید.
و یا حتی اطلاعات wait statistics را نیز میتوان به سادگی در اینجا مشاهده کرد تا مشخص شود چرا یک کوئری خوب عمل نمیکند:
اجرای چند کوئری با هم و بررسی Query Plan آنها
اگر دو کوئری زیر را با فرض انتخاب Include actual execution plan در منوی Query با هم اجرا کنیم:
USE [WideWorldImporters]; GO SELECT [CustomerID], [TransactionAmount] FROM [Sales].[CustomerTransactions] WHERE [CustomerID] = 1056; GO SELECT [o].[OrderID], [ol].[OrderLineID], [o].[OrderDate], [o].[CustomerID], [ol].[Quantity], [ol].[UnitPrice] FROM [Sales].[Orders] [o] JOIN [Sales].[OrderLines] [ol] ON [o].[OrderID] = [ol].[OrderID]; GO
هزینهی اولین کوئری نسبت به کل batch جاری، 10 درصد است و هزینهی دومین کوئری، 90 درصد. بنابراین اگر چندین کوئری را با هم اجرا کنیم، به این صورت میتوان هزینهی هر کدام را نسبت به کل عملیات، تخمین بزنیم. در هر کوئری نیز هزینههایی درج شدهاند که صرفا متعلق به همان کوئری هستند. برای مثال در اولین کوئری، key lookup سنگینترین عملگر کل کوئری است.
CREATE TABLE Users ( id INT NOT NULL AUTO_INCREMENT, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, gender ENUM('Male', 'Female') NOT NULL, PRIMARY KEY (id) ); CREATE TABLE BlogPosts ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, user_id INT NOT NULL, KEY user_id (user_id), CONSTRAINT `blogposts_ibfk_1` FOREIGN KEY (user_id) REFERENCES Users (id), PRIMARY KEY (id) )
SELECT U.id, CONCAT_WS(' ', U.first_name, U.last_name) AS FullName, BP.title FROM Users AS U JOIN BlogPosts AS BP ON U.id = BP.user_id;
تبدیل یک ساختار Relational به JSON
میتوانیم خروجی موردنظر را در قالب JSON نیز کوئری بگیریم:
SELECT JSON_OBJECT('id', U.id, 'user', CONCAT_WS(' ', U.first_name, U.last_name), 'title', BP.title) FROM Users AS U JOIN BlogPosts AS BP ON U.id = BP.user_id;
به عنوان مثال میتوانیم لیست کاربران را به همراه بلاگ پستهایشان، اینگونه کوئری بگیریم:
SELECT JSON_OBJECT('user', CONCAT_WS(' ', U.first_name, U.last_name), 'blog_posts', JSON_ARRAYAGG(JSON_OBJECT('id', BP.id, 'title', BP.title))) AS JSON FROM Users AS U JOIN BlogPosts AS BP ON U.id = BP.user_id GROUP BY U.id;
خروجی کوئری فوق اینچنین خواهد بود:
تبدیل یک ساختار JSON به Relational
همچنین میتوانیم یک ساختار JSON را به صورت Relational تبدیل کنیم. اینکار توسط تابع JSON_TABLE قابل انجام است. کاری که این تابع انجام میدهد، ایجاد یک جدول موقت و کپی کردن دیتای موردنظر درون آن است. فرض کنید ساختار JSON زیر را به اینصورت درون دیتابیس ذخیره کردهایم:
{ "id": "1", "new": false, "sku": "asdf123", "tag": ["fashion", "men", "jacket", "full sleeve"], "name": "Lorem ipsum jacket", "image": [ "/assets/img/product/fashion/1.jpg", "/assets/img/product/fashion/3.jpg", "/assets/img/product/fashion/6.jpg", "/assets/img/product/fashion/8.jpg", "/assets/img/product/fashion/9.jpg" ], "price": 12.45, "rating": 4, "category": ["fashion", "men"], "discount": 10, "offerEnd": "October 5, 2020 12:11:00", "saleCount": 54, "description": { "fullDescription": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?", "shortDescription": "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur." } }
اکنون میخواهیم چنین خروجیای داشته باشیم:
کوئری موردنیاز برای تهیه خروجی فوق اینچنین خواهد بود:
SELECT newTable.* FROM experiments.productMetadata, JSON_TABLE( data, "$" COLUMNS ( id INT PATH "$.id", new CHAR(5) PATH "$.new", sku CHAR(20) PATH "$.sku", price FLOAT PATH "$.price", rating FLOAT PATH "$.rating", name CHAR(255) PATH "$.name", discount FLOAT PATH "$.discount", offerEnd TEXT PATH "$.offerEnd", saleCount INT PATH "$.saleCount", description TEXT PATH "$.description.shortDescription" ) ) AS newTable;
تابع JSON_TABLE دو ورودی نیاز خواهد داشت؛ ورودی اول ستون JSONی است که میخوایم از آن کوئری بگیریم. ورودی دوم با تعیین path شروع خواهد شد. از آنجائیکه محتوای داخل ستون data به صورت آبجکت ذخیره شدهاست، از $ استفاده کردهایم که به معنای داکیومنت جاری است. سپس توسط کلمه کلیدی COLUMNS ساختار جدول موقتمان را تعریف خواهیم کرد. این ساختار به صورت یک آرگومان به COLUMNS ارسال خواهد شد و شبیه به ساختار CREATE TABLE است؛ با این تفاوت که بعد از تعریف نوع دادهای هر ستون باید مسیر رسیدن به مقدار موردنظر را نیز تعیین کنیم که در واقع همان سینتکس pathی است که در مثالهای قبل نیز بررسی کردیم. به عنوان مثال برای رسیدن به مقدار پراپرتی name، مسیر را به صورت name.$ نوشتهایم. این path از آرایه نیز پشتیبانی میکند؛ مثلاً برای دسترسی به عنصر اول آرایه tag کافی است اینگونه عمل کنیم:
tag CHAR(20) PATH "$.tag[0]"
همچنین تابع JSON_TABLE، از ساختارهای تودرتو نیز پشتیبانی میکند. به عنوان مثال برای داشتن مقادیر tag, category, image در خروجی میتوانیم از کلمه کلیدی NESTED استفاده کنیم:
SELECT newTable.* FROM experiments.productMetadata, JSON_TABLE( data, "$" COLUMNS ( id INT PATH "$.id", new CHAR(5) PATH "$.new", sku CHAR(20) PATH "$.sku", price FLOAT PATH "$.price", rating FLOAT PATH "$.rating", NESTED PATH '$.tag[*]' COLUMNS (tag TEXT PATH '$'), name CHAR(255) PATH "$.name", discount FLOAT PATH "$.discount", offerEnd TEXT PATH "$.offerEnd", saleCount INT PATH "$.saleCount", description TEXT PATH "$.description.shortDescription", NESTED PATH '$.image[*]' COLUMNS (image TEXT PATH '$'), NESTED PATH '$.category[*]' COLUMNS (category TEXT PATH '$') ) ) AS newTable;
درون COLUMNS میتوانیم یک name FOR ORDINALITY نیز تعیین کنیم. این فیلد دقیقاً مشابه AUTO INCREMENT در ساختار CREATE TABLE میباشد. به این معنا که به ازای هر آیتم فیلد nested، یک واحد اضافه خواهد شد. از آن میتوانیم به عنوان rowId برای آیتم آرایه استفاده کنیم:
NESTED PATH '$.category[*]' COLUMNS (categoryRowId FOR ORDINALITY, category TEXT PATH '$')
همچنین از ON EMPTY برای پراپرتیهایی که در ساختار JSON وجود ندارند نیز میتوانیم استفاده کنیم. به عنوان مثال در کوئری زیر گفتهایم در صورت عدم وجود price، یک مقدار پیشفرض باید نمایش داده شود و همچنین در صورت عدم وجود name، یک خطا در خروجی نمایش داده شود:
name CHAR(255) PATH "$.name" ERROR ON EMPTY, price FLOAT PATH "$.price" DEFAULT "0" ON EMPTY,
همچنین میتوانیم مقدار NULL را در صورت عدم وجود name ست کنیم:
name CHAR(255) PATH "$.name" NULL ON EMPTY,
گام سوم: افزودن یک button
... <div> <div> <input type="text" id="inputName" maxlength="15"> </div> <div> <button id="generateButton">Aye! Gimme a name!</button> </div> </div> ...
import 'dart:html'; ButtonElement genButton;
void main() { querySelector('#inputName').onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); }
... void setBadgeName(String newName) { querySelector('#badgeName').text = newName; }
... void generateBadge(Event e) { setBadgeName('Meysam Khoshbakht'); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { // To do: add some code here. } else { // To do: add some code here. } }
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(inputName); if (inputName.trim().isEmpty) { genButton..disabled = false ..text = 'Aye! Gimme a name!'; } else { genButton..disabled = true ..text = 'Arrr! Write yer name!'; } }
گام چهارم: ایجاد کلاس PirateName
در این مرحله فقط کد مربوط به فایل dart را تغییر میدهیم. ابتدا کلاس PirateName را ایجاد میکنیم. با ایجاد نمونه ای از این کلاس، یک نام بصورت تصادفی انتخاب میشود و یا نامی بصورت اختیاری از طریق سازنده انتخاب میگردد.
نخست کتابخانه dart:math را به ابتدای فایل dart اضافه کنید
import 'dart:html'; import 'dart:math' show Random;
توضیحات
- با استفاده از کلمه کلیدی show، شما میتوانید فقط کلاسها، توابع و یا ویژگیهای مورد نیازتان را import کنید.
- کلاس Random یک عدد تصادفی را تولید میکند
در انتهای فایل کلاس زیر را تعریف کنید
... class PirateName { }
در داخل کلاس یک شی از کلاس Random ایجاد کنید
class PirateName { static final Random indexGen = new Random(); }
توضیحات
- با استفاده از static یک فیلد را در سطح کلاس تعریف میکنیم که بین تمامی نمونههای ایجاد شده از کلاس مشترک میباشد
- متغیرهای final فقط خواندنی میباشند و غیر قابل تغییر هستند.
- با استفاده از new میتوانیم سازنده ای را فراخوانی نموده و نمونه ای را از کلاس ایجاد کنیم
دو فیلد دیگر از نوع String و با نامهای _firstName و _appelation به کلاس اضافه میکنیم
class PirateName { static final Random indexGen = new Random(); String _firstName; String _appellation; }
متغیرهای خصوصی با (_) تعریف میشوند. Dart کلمه کلیدی private را ندارد.
دو لیست static به کلاس فوق اضافه میکنیم که شامل لیستی از name و appelation میباشد که میخواهیم آیتمی را بصورت تصادفی از آنها انتخاب کنیم.
class PirateName { ... static final List names = [ 'Anne', 'Mary', 'Jack', 'Morgan', 'Roger', 'Bill', 'Ragnar', 'Ed', 'John', 'Jane' ]; static final List appellations = [ 'Jackal', 'King', 'Red', 'Stalwart', 'Axe', 'Young', 'Brave', 'Eager', 'Wily', 'Zesty']; }
کلاس List میتواند شامل مجموعه ای از آیتمها میباشد که در Dart تعریف شده است.
سازنده ای را بصورت زیر به کلاس اضافه میکنیم
class PirateName { ... PirateName({String firstName, String appellation}) { if (firstName == null) { _firstName = names[indexGen.nextInt(names.length)]; } else { _firstName = firstName; } if (appellation == null) { _appellation = appellations[indexGen.nextInt(appellations.length)]; } else { _appellation = appellation; } } }
توضیحات
- سازنده تابعی همنام کلاس میباشد
- پارامترهایی که در {} تعریف میشوند اختیاری و Named Parameter میباشند. Named Parameterها پارمترهایی هستند که جهت مقداردهی به آنها در زمان فراخوانی، از نام آنها استفاده میشود.
- تابع nextInt() یک عدد صحیح تصادفی جدید را تولید میکند.
- جهت دسترسی به عناصر لیست از [] و شمارهی خانهی لیست استفاده میکنیم.
- ویژگی length تعداد آیتمهای موجود در لیست را بر میگرداند.
در این مرحله یک getter برای دسترسی به pirate name ایجاد میکنیم
class PirateName { ... String get pirateName => _firstName.isEmpty ? '' : '$_firstName the $_appellation'; }
توضیحات
- Getterها متدهای خاصی جهت دسترسی به یک ویژگی به منظور خواندن مقدار آنها میباشند.
- عملگر سه گانه :? دستور میانبر عبارت شرطی if-else میباشد
- $ یک کاراکتر ویژه برای رشتههای موجود در Dart میباشد و میتواند محتوای یک متغیر یا ویژگی را در رشته قرار دهد. در رشته '$_firstName the $_appellation' محتوای دو ویژگی _firstName و _appellation در رشته قرار گرفته و نمایش مییابند.
- عبارت (=> expr;) یک دستور میانبر برای { return expr; } میباشد.
تابع setBadgeName را بصورت زیر تغییر دهید تا یک پارامتر از نوع کلاس PirateName را به عنوان پارامتر ورودی دریافت نموده و با استفاده از Getter مربوط به ویژگی pirateName، مقدار آن را در badge name نمایش دهد.
void setBadgeName(PirateName newName) { querySelector('#badgeName').text = newName.pirateName; }
تابع updateBadge را بصورت زیر تغییر دهید تا یک نمونه از کلاس PirateName را با توجه به مقدار ورودی کاربر در فیلد input تولید نموده و تابع setBadgeName رافراخوانی نماید. همانطور که در کد مشاهده میکنید پارامتر ورودی اختیاری firstName در زمان فراخوانی با ذکر نام پارامتر قبل از مقدار ارسالی نوشته شده است. این همان قابلیت Named Parameter میباشد.
void updateBadge(Event e) { String inputName = (e.target as InputElement).value; setBadgeName(new PirateName(firstName: inputName)); ... }
تابع generateBadge را بصورت زیر تغییر دهید تا به جای نام ثابت Meysam Khoshbakht، از کلاس PirateName به منظور ایجاد نام استفاده کند. همانطور که در کد میبینید، سازندهی بدون پارامتر کلاس PirateName فراخوانی شده است.
void generateBadge(Event e) { setBadgeName(new PirateName()); }
همانند گام سوم برنامه را اجرا کنید و نتیجه را مشاهده نمایید.
inject$ در AngularJs
ابتدا مثال زیر را به روشهای قبلی پیاده سازی میکنیم:
var app = angular.module('myApp', []); app.factory('bookService', function () { var books = [ { name: 'A' }, { name: 'B' }, { name: 'C' } ]; return books; }); app.controller('bookCtrl', function ($scope, bookService) { $scope.books = bookService; })
<script type="text/javascript" src="~/scripts/Modules/module5.js"></script> <div ng-app="myApp"> <div ng-controller="bookCtrl"> <table> <tr ng-repeat="book in books"> <td> {{book.name}} </td> </tr> </table> </div> </div>
var bookCtrl = function (sc,bs) { sc.books = bs; };
app.controller('bookCtrl',bookCtrl);
bookCtrl.$inject = ['$scope','bookService'];
var app = angular.module('myApp', []); app.factory('bookService', function () { var books = [ { name: 'A' }, { name: 'B' }, { name: 'C' } ]; return books; }); var bookCtrl = function (sc,bs) { sc.books = bs; }; bookCtrl.$inject = ['$scope','bookService']; app.controller('bookCtrl',bookCtrl);
از این پس در هنگام فراخوانی تابع کنترلر bookCtrl سرویسهای scope$ و bookService به ترتیب به عنوان آرگومانهای اول و دوم در اختیار کنترلر قرار میگیرند. میتوان به جای فراخوانی مستقیم inject$، تزریق وابستگیها را در هنگام تعریف توابع سازنده به صورت زیر نیز فراهم ساخت:
app.controller('bookCtrl', ['$scope', 'bookService', function (sc, bs) { sc.books = bs; }])
ابتدا یک پروژه Asp.Net MVC ایجاد کنید. در فولدر scripts تمام فایلهای جاوااسکریپ پروژه قرار خواهند داشت. اگر قصد داشته باشیم که فایلهای جاوااسکریپی سایر فریم ورکها را استفاده نماییم (مثل backbone.js و ExtJs و...) برای طبقه بندی بهتر فایل ها، بهتر است که یک فولدر با نامی مشخص بسازیم و فایلهای مورد نیاز را در آن قرار دهیم. البته اگر از nuget برای نصب این فریم ورکها استفاده نمایید عموما این کار انجام خواهد شد.
حال با استفاده از Package Manager Console و اجرای دستور زیر، اقدام به نصب requireJs کنید
PM> Install-package requireJs
یک فولدر به نام MyFiles در فولدر Scripts بسازید و فایلهای purchase.js و product.js و credits.js در پروژه قبل را در آن کپی نمایید. کد فایلهای پروژه قبل به صورت زیر بوده است:
purchase.js
define(["credits","products"], function(credits,products) { console.log("Function : purchaseProduct"); return { purchaseProduct: function() { var credit = credits.getCredits(); if(credit > 0){ products.reserveProduct(); alert('purchase done');' return true;
} alert('purchase cancel'); return false; } } });
products.js
define(function(products) { return { reserveProduct: function() { console.log("Function : reserveProduct"); return true; } } });
define(function() { console.log("Function : getCredits"); return { getCredits: function() { var credits = "100"; return credits; } } });
برای قدم بعدی، در متد RegisterBundles فایل bundleConfig پروژه دستور زیر را وارد نمایید:
bundles.Add( new ScriptBundle( "~/bundles/require" ).Include( "~/Scripts/require.js" ) );
حال برای استفاده و لود ماژول purchase در انتهای فایل Index فولدر Home تغییرات زیر را اعمال نمایید:
@section scripts { <script type="text/javascript"> require(['purchase'], function (purchase) { purchase.purchaseProduct(); }); </script> }