@Html.ActionLink(linkText: item.Name,result: MVC.Word.Details(id : item.ID, name : item.Name.ToSeoUrl() ))
همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و pluginهای مربوط به Cordova آشنا خواهیم شد.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1">
روال کار jQuery Mobile
از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.
لیست کامل اتریبیوتهای -data به همراه مقادیری که میپذیرند
شما میتوانید از امکانات Theme Roller برای شخصی سازی تمهای مورد نیاز استفاده کنید.
Cordova Plugins
از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت http://plugreg.com/plugins میتوانید سراغ پلاگینهای مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins و تب Core یکسری از پلاگینهایی را که در Cordova گنجانده شده است، مشاهده میکنید. با کلیک بر روی دکمهی Add میتوانید آن را دانلود کرده و از APIهای آن استفاده کنید.
برای مثال پلاگین Notification را به پروژه اضافه میکنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و محتوای فایلهای index.html , custome.js را به شکل زیر در نظر میگیرم:
$(function() { $("#alert").on('tap', function(event) { navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید"); }); $("#prompt").on('tap', function(event) { navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]); }); function onPrompt(results) { navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null); } $("#confirm").on('tap', function(event) { navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]); }); function onConfirm(buttonIndex) { navigator.notification.alert(buttonIndex , null); } $("#beep").on('tap', function(event) { navigator.notification.beep(1); }); });
رخداد tap زمانی صادر میشود که کاربر، دکمهی مورد نظر را لمس کند و یکی از رخدادهای jQuery Mobile میباشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification میتوانید به متدهای مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.
برای آشنایی با این پلاگین میتوانید داکیومنت آن را مطالعه کنید.
در کد بالا با استفاده از متدهای callback توانستهایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>CordovaApp01</title> <meta name="viewport" content="width=device-width, initial-scale=1"/> <!-- CordovaApp01 references --> <link href="css/index.css" rel="stylesheet" /> <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" /> </head> <body> <div data-role="page" id="page1"> <div data-role="header"> <h2> تست پلاگین Notification </h2> </div> <div data-role="content"> <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a> <button data-role="button" id="alert" data-inline="true" >alert</button> <button data-role="button" id="confirm" data-inline="true">confirm</button> <button data-role="button" id="beep" data-inline="true" >beep</button> <button data-role="button" id="prompt" data-inline="true" >prompt</button> </div> <div data-role="footer"> <h2>من فوتر هستم</h2> </div> </div> <div data-role="page" id="page2"> <div data-role="header"> <h1>Header</h1> </div> <div data-role="content"> Content </div> <div data-role="footer"> <h1>Footer</h1> </div> </div> <!-- Cordova reference, this is added to your app when it's built. --> <script src="scripts/jquery-2.1.3.min.js"></script> <script src="cordova.js"></script> <script src="scripts/platformOverrides.js"></script> <script src="scripts/index.js"></script> <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script> <script src="scripts/custom.js"></script> </body> </html>
در کد بالا 4 تا button دیده میشود که ویژگی data-role آنها مقدار button در نظر گرفته شدهاست تا توسط jQuery Mobile به عنوان button شناخته شوند و استایلهای لازم بر روی آنها اعمال گردد. قرار است طبق کد js ایی که نوشتهایم، با لمس کردن هر کدام از دکمهها، notification هایی نمایش داده شوند.
برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینهی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:
Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01 Error448throw e;CordovaApp-01 Error449^CordovaApp-01 Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
adb connect localhost:5558
<a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
در مقالهی بعد، به مباحث Database در Cordova خواهیم پرداخت.
ادامه دارد...
اولین و اساسیترین قدم در نگهداری یک سیستم مبتنی بر داده، تهیه پشتیبانهای منظم و همچنین قابل اطمینان میباشد.
دستور T-SQL زیر بدون ریاستور کردن یک فایل بک آپ اس کیوال سرور، سعی در تعیین اعتبار آن میکند:
RESTORE VERIFYONLY
FROM DISK = 'C:\SQL_Backup\Test1'
WITH FILE = 1,
LOADHISTORY
The backup set on file 1 is valid.
SELECT DISTINCT physical_device_name
FROM msdb.dbo.backupmediafamily
ORDER BY
physical_device_name
DECLARE @path NVARCHAR(1000),
@msg NVARCHAR(MAX),
@NewLine CHAR(2),
@sql NVARCHAR(2000)
SET @NewLine = CHAR(13) + CHAR(10)
SET @msg = ''
DECLARE DATABASES_CURSOR CURSOR
FOR
SELECT DISTINCT physical_device_name
FROM msdb.dbo.backupmediafamily
ORDER BY
physical_device_name
OPEN DATABASES_CURSOR
FETCH NEXT FROM DATABASES_CURSOR INTO @path
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT 'Verifying: ' + @path
SET @sql = 'RESTORE VERIFYONLY FROM DISK = ''' + @path
+ ''' WITH FILE = 1, LOADHISTORY'
EXEC sp_executesql @sql
IF @@ERROR <> 0
BEGIN
SET @msg = @msg + 'Failed to verify: ' + @path + @NewLine
END
FETCH NEXT FROM DATABASES_CURSOR INTO @path
END
CLOSE DATABASES_CURSOR
DEALLOCATE DATABASES_CURSOR
IF @msg <> ''
BEGIN
PRINT @msg
-- send email
EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@copy_recipients = 'Administrator@site.net', -- Change This
@Subject = 'backup verification info.',
@Body = @msg
,@importance = 'High'
END
اسکریپت فوق بر روی تمامی مسیرهای ثبت شده موجود که در آنها پیشتر پشتیبان تهیه شده است، دستور RESTORE VERIFYONLY را اجرا میکند و در آخر اگر پیغامی حاصل شد، یعنی مشکلی پدید آمده و ایمیلی را به اشخاص مورد نظر ارسال میکند.
میتوان بر روی این اسکریپت یک job تهیه کرد که هر روز پس از تهیه بک آپ خودکار، کار بررسی صحت عملیات را نیز انجام دهد.
نوشتن TagHelperهای سفارشی برای ASP.NET Core
using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Html; namespace Sample { /// <summary> /// Html Helper Extensions /// </summary> public static class HtmlHelperExtensions { /// <summary> /// Convert IHtmlContent/TagBuilder to string /// </summary> public static string GetString(this IHtmlContent content) { using (var writer = new StringWriter()) { content.WriteTo(writer, HtmlEncoder.Default); return writer.ToString(); } } } }
ASP.NET MVC #17
var token = $('[name=__RequestVerificationToken]').val(); data.__RequestVerificationToken = token; ... ... ... $.ajax({ url: url, type: "POST", data: data });
public abstract class myabstractclass { public abstract string dosomething( string input ); public double round( double number , int decimals ) { return math.round( number , decimals ); } }
روش اول:
در این روش ابتدا باید کلاسی نوشت تا کلاس abstract بالا رو پیاده سازی کنه:
public class mynewclass : myabstractclass { public override string dosomething( string input ) { return input; } }
[testclass] public class mytest { [testmethod] public void testround() { mynewclass mynewclass = new mynewclass(); var result = mynewclass.round( 5.55 , 1 ); assert.areequal( 5.6 , result ); } }
البته روش بالا خیلی مورد پسند من نیست.
در روش دوم که من خیلی بیشتر بهش علاقه دارم دیگه نیازی به استفاده از یک کلاس دوم برای پیاده سازی کلاس abstract نیست. بلکه در این روش از ابزار rhinomocks برای این کار استفاده میکنیم . استفاده از rhino mocks به چندین روش امکان پذیره که امروز 2 روش اونو براتون توضیح میدم:
در روش اول از mockrepository استفاده میکنیم و در روش دوم از روش aaa یا arrange-act-assert
استفاده از mockrepository :
ابتدا کدهای مربوطه رو مینویسم:
[testmethod] public void testwithmockrepository() { var mockrepository = new rhino.mocks.mockrepository(); var mock = mockrepository.partialmock<myabstractclass>(); using ( mockrepository.record() ) { expect.call( mock.dosomething( arg<string>.is.anything ) ).return( "hi..." ).repeat.once(); } using ( mockrepository.playback() ) { assert.areequal( "hi..." , mock.dosomething( "salam" ) ); } }
در مورد expect.call باید بگم که از این کلاس برای شبیه سازی رفتار یک متد استفاده میشه (مثلا در مواقعی که یک متد برای انجام عملیات باید به دیتا بیس وصل شده و یک query را اجرا کنه) برای اینکه در تست، از این عملیات صرف نظر بشه از mockها استفاده کرده و رفتار متد رو به روش بالا شبیه سازی میکنیم. البته کار کردن با rhino mocks به صورت بالا دیگه از مد افتاده و جدیدا از روش aaa استفاده میشه که اونو در پایین توضیح میدم:
[testmethod] public void testwithaaa() { var mock = mockrepository.generatepartialmock<myabstractclass>(); mock.expect( x => x.dosomething( arg<string>.is.anything ) ).return( "hi..." ).repeat.once();//arange var result = mock.dosomething( "salam" );//act assert.areequal( "hi..." , result );//assert }
[testmethod] public void testwithaaa() { var mock = mockrepository.generatepartialmock<myabstractclass>(); mock.expect( x => x.dosomething( arg<string>.is.anything ) ).return( "hi..." ).repeat.once();//arange var result = mock.dosomething( "salam" );//act var result2 = mock.dosomething( "bye" );//act assert.areequal( "hi..." , result );//assert }
خطای آن هم واضح داره میگه که expected#1 هستش در حالی که actual#2 (تعداد دفعات حقیقی از دفعات مورد انتظار بیشتره)
توی پستهای بعدی (اگه وقت بشه) حتما در مورد rhino mocks بیشتر توضیح میدم
Twitter Bootstrap
- آغاز به کار با Twitter Bootstrap در ASP.NET MVC
- استفاده از Twitter Bootstrap در کارهای روزمره طراحی وب
- نگاهی به اجزای تعاملی Twitter Bootstrap
- استفاده از modal dialogs مجموعه Twitter Bootstrap برای گرفتن تائید از کاربر
- نمایش فرمهای مودال Ajax ایی در ASP.NET MVC به کمک Twitter Bootstrap
- ساخت قالبهای نمایشی و ادیتور دکمه سه وضعیتی سازگار با Twitter bootstrap در ASP.NET MVC
- استفاده از دکمههای CSS توئیتر در ASP.NET MVC
- استفاده از دکمههای CSS توئیتر در ASP.NET MVC - قسمت دوم
- اعمال کلاسهای ویژه اعتبارسنجی Twitter bootstrap به فرمهای ASP.NET MVC
- نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Popover به کمک Twitter bootstrap
- نمایش خطاهای اعتبارسنجی سمت کاربر ASP.NET MVC به شکل Tooltip به کمک Twitter bootstrap
- مشکل اعتبار سنجی jQuery validator در Bootstrap tabs
- ویرایش قالب پیش فرض Add View در ASP.NET MVC برای سازگار سازی آن با Twitter bootstrap
- ساخت منوهای چند سطحی در ASP.NET MVC
- بوت استرپ (نگارش 3) چیست؟
- بررسی سیستم جدید گرید بوت استرپ 3
- تغییرات صورت گرفته در المانهای تایپوگرافی و شیوه نامههای بوت استرپ 3
- نگاهی به اجزای سیستم راهبری بوت استرپ 3
- اجزای جاوا اسکریپتی بوت استرپ 3
- کار با فرمها در بوت استرپ 3
- صفحات مودال در بوت استرپ 3
- نحوهی صحیح کار کردن با بوت استرپ
- نمایش اخطارها و پیامهای بوت استرپ به کمک TempData در ASP.NET MVC
- سازگارسازی کلاسهای اعتبارسنجی Twitter Bootstrap 3 با فرمهای ASP.NET MVC
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(15), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)), }, SlidingExpiration = false, ExpireTimeSpan = TimeSpan.FromMinutes(30) });
UserManager.UpdateSecurityStampAsync(userId);
- کاربر در مکان A لاگین میشود.
- همان کاربر مکان خود را تغییر داده و ده دقیقه بعد در مکان B لاگین میشود.
- کاربر رمزعبورش را در مکان B در دقیقه 12ام تغییر میدهد.
- کاربر به مکان A برمی گردد و یک درخواست را در دقیقه 20ام ارسال میکند.
- چون کاربر یک درخواست را بعد از مدت زمان مشخص شده در ValidateInterval (یعنی 15 دقیقه) در مکان A ارسال میکند، پس عملیات چک کردن فیلد SecurityStamp انجام میشود و از آنجایی که این فیلد به علت تغییر رمز عبور، به روز شده است، بنابراین کاربر لاگ آوت خواهد شد.
در پروژهی iOS، در فایل AppDelegate.cs، بعد از Forms.Init، کد زیر را کپی کنید:
SfListViewRenderer.Init();
همین کد را در MainPage.xaml.cs در پروژه UWP، قبل از LoadApplication قرار دهید. نیازی به انجام کاری در Android نیست.
سپس Product Key این محصول را به دست آورده و در پروژه XamApp، فولدر Views در فایل SyncfusionLicense قرار دهید.
حال برای نمایش لیستی از محصولات، ابتدا کلاس Product را ایجاد میکنیم. چه در زمانیکه یک Rest api را در سمت سرور فراخوانی میکنیم و چه زمانیکه با دیتابیس بر روی گوشی یعنی Sqlite کار میکنیم، در نهایت لیستی از یک کلاس را داریم (در اینجا Product).
public class Product : Bindable { public int Id { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public decimal Price { get; set; } }
در یک View Model جدید با نام ProductsViewModel، در OnNavigatedToAsync، دیتا را از سرور یا دیتابیس، بر روی گوشی دریافت میکنیم؛ اما در این مثال، برای راحتی بیشتر یک List را New میکنیم:
public class ProductsViewModel : BitViewModelBase { public List<Product> Products { get; set; } public async override Task OnNavigatedToAsync(INavigationParameters parameters) { Products = new List<Product> // getting products from server or sqlite database { new Product { Id = 1, IsActive = true, Name = "Product1" , Price = 12.2m /* m => decimal */ }, new Product { Id = 2, IsActive = false, Name = "Product2" , Price = 14 }, new Product { Id = 3, IsActive = true, Name = "Product3" , Price = 11 }, }; await base.OnNavigatedToAsync(parameters); } }
حال نوبت به دادن یک Template میرسد. مثلا فرض کنید میخواهیم نام را درون یک Label نمایش دهیم و بر اساس فعال یا غیر فعال بودن Product، یک Checkbox را تغییر داده، تیک بزنیم یا نزنیم و در نهایت نمایش قیمت را در یک Label دیگر خواهیم داشت.
<sfListView:SfListView ItemsSource="{Binding Products}"> <sfListView:SfListView.ItemTemplate> <DataTemplate> <FlexLayout x:DataType="model:Product" Direction="Row"> <Label FlexLayout.Basis="50%" Text="{Binding Name}" VerticalTextAlignment="Center" /> <bitControls:BitCheckbox InputTransparent="True" FlexLayout.Basis="25%" IsChecked="{Binding IsActive}" /> <Label FlexLayout.Basis="25%" Text="{Binding Price}" VerticalTextAlignment="Center" /> </FlexLayout> </DataTemplate> </sfListView:SfListView.ItemTemplate> </sfListView:SfListView>
همانطور که میبینید، در DataTemplate از Flex Layout استفاده شده است. Flex Layout در کنار Grid, Stack, Relative, Absolute و سایر Layoutهای Xamarin Forms در پروژه قابلیت استفاده دارد و مزیتهای خاص خود را دارد.
این Data Template توسط List View، حداکثر سه بار ساخته میشود؛ چون View Model در لیست مثال خود، سه Product دارد. خود List View تکنیکهای Virtualization و Cell Reuse را بدون نیاز به هیچ کد اضافهای هندل میکند و Performance خوبی دارد. در View مربوطه یعنی ProductsView.xaml، هر Binding ای (مثل Binding Products) به View Model اشاره میکند، اما درون Data Template، هر Binding به Product ای اشاره میکند که آن ردیف List View، دارد نمایشاش میدهد. برای همین x:DataType را روی Flex Layout درون Data Template به Product وصل کردهایم. در این صورت اگر بنویسیم Binding N_ame، به ما خطا داده میشود که کلاس Product هیچ Property با نام N_ame ندارد که خطای درستی است.
روی BitCheckbox مقدار InputTransparent را برابر با True دادهایم که باعث میشود کلیک روی Checkbox عملا در نظر گرفته نشود. این منطقی است، زیرا عوض کردن مقدار Checkbox در این مثال ما ذخیره نمیشود و کاربرد نمایشی دارد و فقط باعث گیج شدن کاربر میشود.
کنترل BitCheckbox از مجموعه کنترلهای Bit است که اخیرا با BitDatePicker آن آشنا شدهاید. برای آشنایی با نحوه افزودن این کنترلها به یک پروژه، به مستندات Bit Framework مراجعه کنید. خود Syncfusion نیز Checkbox دارد.
حال فرض کنید که قرار است دکمهای برای هر ردیف List View داشته باشیم که با زدن روی آن، اطلاعات Product به سرور ارسال شود و جزئیات بیشتری دریافت و در قالب یک Alert نمایش داده شود. برای این کار، ابتدا به Data Template که Flex Layout است، یک دکمه اضافه میکنیم. سپس Command آن دکمه را به View Model بایند میکنیم. در آن Command البته احتیاج داریم بدانیم درخواست نمایش جزئیات بیشتر، برای کدام Product داده شده. این مهم با Command Parameter شدنی است.
برای پیاده سازی این مثال، در سمت View Model داریم:
public BitDelegateCommand<Product> ShowProductDetailsCommand { get; set; }public IUserDialogs UserDialogs { get; set; } async Task ShowProductDetails(Product product) { string productDetail = $"Product: {product.Name}'s more info: ..."; // get more info from server. await UserDialogs.AlertAsync(productDetail, "Product Detail"); }
کامند ShowProductDetailCommand یک پارامتر را از جنس Product میگیرد و آن Product ای است که روی دکمه آن کلیک شدهاست. با Clone کردن آخرین نسخه XamApp و درخواست نمایش صفحهی Products در App.xaml.cs به صورت زیر و اجرای برنامه، میتوانید درک بهتری از عملکرد آن داشته باشید:
await NavigationService.NavigateAsync("/Nav/Products", animated: false);
سپس در View مربوطه داریم:
...<Button Command="{Binding ShowProductDetailsCommand}" CommandParameter="{Binding .}" Text="Detail..." /> </FlexLayout> </DataTemplate>
CommandParameter اگر برابر با Binding Id میبود، به Command در سمت View Model، بجای کل Product، فقط Id آن ارسال میشد. ولی Show Product Detail Command منتظر یک Product کامل است، نه فقط Id آن. با نوشتن
CommandParameter="{Binding .}"
کل Product با کلیک روی دکمه به Command ارسال میشود.
اکنون اگر پروژه را Build کنید، خطایی را از x:DataType خواهید گرفت که منطقی است. اگر Binding Name و Binding Price دو Property با نامهای Name و Price را از کلاس Product جستجو میکنند، پس قاعدتا ShowProductDetailCommand نیز در همان کلاس مدل، یعنی Product جستجو میشود! ولی میدانیم که این Command در View Model ما یعنی ProductsViewModel است. برای حل این مشکل، به جای Binding از bit:ViewModelBinding استفاده میکنیم:
Command="{bit:ViewModelBinding ShowProductDetailsCommand}"
در این صورت، بجای جستجو کردن ShowProductDetailCommand در کلاس Product، این را در ProductsViewModel جستجو میکند که منجر به خروجی درست میشود.
این List View دارای امکاناتی چون Infinite loading، Pull to refresh و Grouping-Sorting-Filtering و ... است که میتوانید از روی مستندات خوب Syncfusion، آنها را راه اندازی کنید و اگر به مشکلی برخوردید نیز اینجا بپرسید. همچنین نگاهی به لیست 129 کنترل دیگر بیاندازید و ببینید که در برنامههای خود از کدام یک از آنها میتوانید استفاده کنید.
private static string GetScript() { string path = AppDomain.CurrentDomain.BaseDirectory + @"Scripts\script.sql"; var file = new FileInfo(path); string script = file.OpenText().ReadToEnd(); return script; } private static void ExecuteScript() { string script = GetScript(); //split the script on "GO" commands var splitter = new[] {"\r\nGO\r\n"}; string[] commandTexts = script.Split(splitter, StringSplitOptions.RemoveEmptyEntries); foreach (string commandText in commandTexts) { using (var ctx = new DbContext()) { if (!string.IsNullOrEmpty(commandText)) { ctx.Database.ExecuteSqlCommand(commandText); } } } }