کتابخانه سورس باز Stateless، برای طراحی و پیاده سازی «ماشینهای حالت گردش کاری مانند» تهیه شده و مزایای زیر را نسبت به Windows workflow foundation دارا است:
- جمعا 30 کیلوبایت است!
- تمام اجزای آن سورس باز است.
- دارای API روان و سادهای است.
- امکان تبدیل UML state diagrams، به نمونه معادل Stateless بسیار ساده و سریع است.
- به دلیل code first بودن، کار کردن با آن برای برنامه نویسها سادهتر بوده و افزودن یا تغییر اجزای آن با کدنویسی به سادگی میسر است.
دریافت کتابخانه Stateless از Google code و یا از NuGet
پیاده سازی مثال کلید برق با Stateless
در ادامه همان مثال ساده کلید برق قسمت قبل را با Stateless پیاده سازی خواهیم کرد:
using System; using Stateless; namespace StatelessTests { class Program { static void Main(string[] args) { try { string on = "On", off = "Off"; var space = ' '; var onOffSwitch = new StateMachine<string, char>(initialState: off); onOffSwitch.Configure(state: off).Permit(trigger: space, destinationState: on); onOffSwitch.Configure(state: on).Permit(trigger: space, destinationState: off); Console.WriteLine("Press <space> to toggle the switch. Any other key will raise an error."); while (true) { Console.WriteLine("Switch is in state: " + onOffSwitch.State); var pressed = Console.ReadKey(true).KeyChar; onOffSwitch.Fire(trigger: pressed); } } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); Console.WriteLine("Press any key to continue..."); Console.ReadKey(true); } } } }
امضای کلاس StateMachine را در ذیل مشاهده میکنید؛ جهت توضیح آرگومانهای جنریک string و char معرفی شده در مثال:
public class StateMachine<TState, TTrigger>
برای مثال در اینجا حالات روشن و خاموش، با رشتههای on و off مشخص شدهاند و رویداد قابل قبول دریافتی، کاراکتر فاصله است.
سپس نیاز است این ماشین حالت را برای معرفی رویدادهایی (trigger در اینجا) که سبب تغییر حالت آن میشوند، تنظیم کنیم. اینکار توسط متدهای Configure و Permit انجام خواهد شد. متد Configure، یکی از حالات از پیش تعیین شده را جهت تنظیم، مشخص میکند و سپس در متد Permit تعیین خواهیم کرد که بر اساس رخدادی مشخص (برای مثال در اینجا فشرده شدن کلید space) وضعیت حالت جاری، به وضعیت جدیدی (destinationState) منتقل شود.
نهایتا این ماشین حالت در یک حلقه بینهایت مشغول به کار خواهد شد. برای نمونه یک Thread پس زمینه (BackgroundWorker) نیز میتواند همین کار را در برنامههای ویندوزی انجام دهد.
یک نکته
علاوه بر روشهای یاد شدهی تشخیص الگوی ماشین حالت که در قسمت قبل بررسی شدند، مورد refactoring انبوهی از if و elseها و یا switchهای بسیار طولانی را نیز میتوان به این لیست افزود.
استفاده از Stateless Designer برای تولید کدهای ماشین حالت
کتابخانه Stateless دارای یک طراح و Code generator بصری سورس باز است که آنرا به شکل افزونهای برای VS.NET میتوانید در سایت Codeplex دریافت کنید. این طراح از کتابخانه GLEE برای رسم گراف استفاده میکند.
کار مقدماتی با آن به نحو زیر است:
الف) فایل StatelessDesignerPackage.vsix را از سایت کدپلکس دریافت و نصب کنید. البته نگارش فعلی آن فقط با VS 2012 سازگار است.
ب) ارجاعی را به اسمبلی stateless به پروژه خود اضافه نمائید (به یک پروژه جدید یا از پیش موجود).
ج) از منوی پروژه، گزینه Add new item را انتخاب کرده و سپس در صفحه ظاهر شده، گزینه جدید Stateless state machine را انتخاب و به پروژه اضافه نمائید.
کار با این طراح، با ادیت XML آن شروع میشود. برای مثال گردش کاری ارسال و تائید یک مطلب جدید را در بلاگی فرضی، به نحو زیر وارد نمائید:
<statemachine xmlns="http://statelessdesigner.codeplex.com/Schema"> <settings> <itemname>BlogPostStateMachine</itemname> <namespace>StatelessTests</namespace> <class>public</class> </settings> <triggers> <trigger>Save</trigger> <trigger>RequireEdit</trigger> <trigger>Accept</trigger> <trigger>Reject</trigger> </triggers> <states> <state start="yes">Begin</state> <state>InProgress</state> <state>Published</state> <state>Rejected</state> </states> <transitions> <transition trigger="Save" from="Begin" to="InProgress" /> <transition trigger="Accept" from="InProgress" to="Published" /> <transition trigger="Reject" from="InProgress" to="Rejected" /> <transition trigger="Save" from="InProgress" to="InProgress" /> <transition trigger="RequireEdit" from="Published" to="InProgress" /> <transition trigger="RequireEdit" from="Rejected" to="InProgress" /> </transitions> </statemachine>
به علاوه کدهای زیر که به صورت خودکار تولید شدهاند:
using Stateless; namespace StatelessTests { public class BlogPostStateMachine { public delegate void UnhandledTriggerDelegate(State state, Trigger trigger); public delegate void EntryExitDelegate(); public delegate bool GuardClauseDelegate(); public enum Trigger { Save, RequireEdit, Accept, Reject, } public enum State { Begin, InProgress, Published, Rejected, } private readonly StateMachine<State, Trigger> stateMachine = null; public EntryExitDelegate OnBeginEntry = null; public EntryExitDelegate OnBeginExit = null; public EntryExitDelegate OnInProgressEntry = null; public EntryExitDelegate OnInProgressExit = null; public EntryExitDelegate OnPublishedEntry = null; public EntryExitDelegate OnPublishedExit = null; public EntryExitDelegate OnRejectedEntry = null; public EntryExitDelegate OnRejectedExit = null; public GuardClauseDelegate GuardClauseFromBeginToInProgressUsingTriggerSave = null; public GuardClauseDelegate GuardClauseFromInProgressToPublishedUsingTriggerAccept = null; public GuardClauseDelegate GuardClauseFromInProgressToRejectedUsingTriggerReject = null; public GuardClauseDelegate GuardClauseFromInProgressToInProgressUsingTriggerSave = null; public GuardClauseDelegate GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit = null; public GuardClauseDelegate GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit = null; public UnhandledTriggerDelegate OnUnhandledTrigger = null; public BlogPost() { stateMachine = new StateMachine<State, Trigger>(State.Begin); stateMachine.Configure(State.Begin) .OnEntry(() => { if (OnBeginEntry != null) OnBeginEntry(); }) .OnExit(() => { if (OnBeginExit != null) OnBeginExit(); }) .PermitIf(Trigger.Save, State.InProgress , () => { if (GuardClauseFromBeginToInProgressUsingTriggerSave != null) return GuardClauseFromBeginToInProgressUsingTriggerSave(); return true; } ) ; stateMachine.Configure(State.InProgress) .OnEntry(() => { if (OnInProgressEntry != null) OnInProgressEntry(); }) .OnExit(() => { if (OnInProgressExit != null) OnInProgressExit(); }) .PermitIf(Trigger.Accept, State.Published , () => { if (GuardClauseFromInProgressToPublishedUsingTriggerAccept != null) return GuardClauseFromInProgressToPublishedUsingTriggerAccept(); return true; } ) .PermitIf(Trigger.Reject, State.Rejected , () => { if (GuardClauseFromInProgressToRejectedUsingTriggerReject != null) return GuardClauseFromInProgressToRejectedUsingTriggerReject(); return true; } ) .PermitReentryIf(Trigger.Save , () => { if (GuardClauseFromInProgressToInProgressUsingTriggerSave != null) return GuardClauseFromInProgressToInProgressUsingTriggerSave(); return true; } ) ; stateMachine.Configure(State.Published) .OnEntry(() => { if (OnPublishedEntry != null) OnPublishedEntry(); }) .OnExit(() => { if (OnPublishedExit != null) OnPublishedExit(); }) .PermitIf(Trigger.RequireEdit, State.InProgress , () => { if (GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit != null) return GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit(); return true; } ) ; stateMachine.Configure(State.Rejected) .OnEntry(() => { if (OnRejectedEntry != null) OnRejectedEntry(); }) .OnExit(() => { if (OnRejectedExit != null) OnRejectedExit(); }) .PermitIf(Trigger.RequireEdit, State.InProgress , () => { if (GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit != null) return GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit(); return true; } ) ; stateMachine.OnUnhandledTrigger((state, trigger) => { if (OnUnhandledTrigger != null) OnUnhandledTrigger(state, trigger); }); } public bool TryFireTrigger(Trigger trigger) { if (!stateMachine.CanFire(trigger)) { return false; } stateMachine.Fire(trigger); return true; } public State GetState { get { return stateMachine.State; } } } }
ماشین حالت فوق دارای چهار حالت شروع، در حال بررسی، منتشر شده و رد شده است. معمول است که این چهار حالت را به شکل یک enum معرفی کنند که در کدهای تولیدی فوق نیز به همین نحو عمل گردیده و public enum State معرف چهار حالت ذکر شده است. همچنین رویدادهای ذخیره، نیاز به ویرایش، ویرایش، تائید و رد نیز توسط public enum Trigger معرفی شدهاند.
در قسمت Transitions، بر اساس یک رویداد (Trigger در اینجا)، انتقال از یک حالت به حالتی دیگر را سبب خواهیم شد.
تعاریف اصلی تنظیمات ماشین حالت، در سازنده کلاس BlogPostStateMachine انجام شده است. این تعاریف نیز بسیار ساده هستند. به ازای هر حالت، یک Configure داریم. در متدهای OnEntry و OnExit هر حالت، یک سری callback function فراخوانی خواهند شد. برای مثال در حالت Rejected یا Approved میتوان ایمیلی را به ارسال کننده مطلب جهت یادآوری وضعیت رخ داده، ارسال نمود.
متدهای PermitIf سبب انتقال شرطی، به حالتی دیگر خواهند شد. برای مثال رد یا تائید یک مطلب نیاز به دسترسی مدیریتی خواهد داشت. این نوع موارد را توسط delgateهای Guard ایی که برای مدیریت شرطها ایجاد کرده است، میتوان تنظیم کرد. PermitReentryIf سبب بازگشت مجدد به همان حالت میگردد. برای مثال ویرایش و ذخیره یک مطلب در حال انتشار، سبب تائید یا رد آن نخواهد شد؛ صرفا عملیات ذخیره صورت گرفته و ماشین حالت مجددا در همان مرحله باقی خواهد ماند.
نحوه استفاده از ماشین حالت تولیدی:
همانطور که عنوان شد، حداقل استفاده از ماشینهای حالت، refactoing انبوهی از if و elseها است که در حالت مدیریت یک چنین گردشهای کاری باید تدارک دید.
namespace StatelessTests { public class BlogPostManager { private BlogPostStateMachine _stateMachine; public BlogPostManager() { configureWorkflow(); } private void configureWorkflow() { _stateMachine = new BlogPostStateMachine(); _stateMachine.GuardClauseFromBeginToInProgressUsingTriggerSave = () => { return UserCanPost; }; _stateMachine.OnBeginExit = () => { /* save data + save state + send an email to admin */ }; _stateMachine.GuardClauseFromInProgressToPublishedUsingTriggerAccept = () => { return UserIsAdmin; }; _stateMachine.GuardClauseFromInProgressToRejectedUsingTriggerReject = () => { return UserIsAdmin; }; _stateMachine.GuardClauseFromInProgressToInProgressUsingTriggerSave = () => { return UserHasEditRights; }; _stateMachine.OnInProgressExit = () => { /* save data + save state + send an email to user */ }; _stateMachine.OnPublishedExit = () => { /* save data + save state + send an email to admin */ }; _stateMachine.GuardClauseFromPublishedToInProgressUsingTriggerRequireEdit = () => { return UserHasEditRights; }; _stateMachine.OnRejectedExit = () => { /* save data + save state + send an email to admin */ }; _stateMachine.GuardClauseFromRejectedToInProgressUsingTriggerRequireEdit = () => { return UserHasEditRights; }; } public bool UserIsAdmin { get { return true; // TODO: Evaluate if user is an admin. } } public bool UserCanPost { get { return true; // TODO: Evaluate if user is authenticated } } public bool UserHasEditRights { get { return true; // TODO: Evaluate if user is owner or admin } } // User actions public void Save() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Save); } public void RequireEdit() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.RequireEdit); } // Admin actions public void Accept() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Accept); } public void Reject() { _stateMachine.TryFireTrigger(BlogPostStateMachine.Trigger.Reject); } } }
برای به حرکت درآوردن این ماشین، نیاز به یک سری اکشن متد نیز میباشد. تعدادی از این موارد را در انتهای کلاس فوق ملاحظه میکنید. کد نویسی آنها در حد فراخوانی متد TryFireTrigger ماشین حالت است.
یک نکته:
ماشین حالت تولیدی به صورت پیش فرض در حالت State.Begin قرار دارد. میتوان این مورد را از بانک اطلاعاتی خواند و سپس مقدار دهی نمود تا با هر بار وهله سازی ماشین حالت دقیقا مشخص باشد که در چه مرحلهای قرار داریم و TryFireTrigger بتواند بر این اساس تصمیمگیری کند که آیا مجاز است عملیاتی را انجام دهد یا خیر.
همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار 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 خواهیم پرداخت.
ادامه دارد...
آموزش سیلورلایت 4 - قسمتهای 21 تا 27
بنابراین مدل قابل استفاده در سمت کلاینت هم از این سرویس سمت سرور دریافت میشود و جالب اینجا است که تمام مباحث مطلع سازی تغییرات خواص، اعتبار سنجی، نکات ریز binding و غیره در این مدلهای WCF RIA Services به صورت خودکار گنجانده شده و بار کدنویسی شما بسیار کمتر میشود.
برای MVVM ، کنترل domain data source را با کد نویسی تولید میکنم تا بتونم در View Model استفاده کنم و از حالت متداول کشیدن و رها کردن این کنترل روی فرم که با اصول MVVM سازگار نیست به این صورت رها خواهم شد.
چرا برنامه نویسی موبایل؟
با افزایش روزافزون SmartPhone ها و تبلتها، بازار تکنولوژی به این سمت سوق پیدا کردهاست. از این رو شرکتهای ارائه دهنده نرم افزاری، از این موقعیت استفاده کرده و هر کدام پلتفرم متفاوتی را برای برنامه نویسی بر روی این اسمارت فونها ارائه دادهاند. یکی از بزرگترین دغدغههای امروزه شرکتهای برنامه نویسی و توسعه نرم افزار موبایل، انتخاب درست پلتفرم برای توسعه نرم افزار میباشد. در این مقاله قصد دارم یکی از این پلت فرمها را بررسی کرده و معرفی کنم.
شرکت xamarin کار خود را در سال 2011 با ارایه نسخه Cross Platform پلتفرم .Net به نام Mono آغاز کرد. بعد از ارایه این نسخه از .Net، زامارین به کمک Mono توانست پیاده سازی بر روی Android و IOS را به نامهای MonoForAndroid و MonoTouch ارایه دهد. بعد از این نسخهها برنامه نویسان توانستند بر روی سیستم عاملهای اندروید و آی او اس به صورت Native کد خود را به زبان C# نوشته و آنها را اجرا کنند.
چرا باید از Xamarin استفاده کنم؟
در ادامه مقاله قصد دارم شما را با برخی از ویژگیهای زامارین آشنا کرده و مزایای استفاده از آن را بیان کنم.
Xamarin امکانی را فراهم کردهاست که برنامه نویسان به دو روش متفاوت قادر خواهند بود برنامههای خود را بنویسند:
Xamarin Native :1
زامارین به شما این امکان را میدهد که بتوانید به صورت مستقیم برای هر پلتفرم به صورت جداگانه برنامه نویسی کنید. در اندروید اینکار با Xamarin.Droid و در IOS اینکار با Xamarin.Touch امکانپذیر است. مزیتهای استفاده از این روش عبارتند از:
· بهره گیری از یک زبان برنامه نویسی
همانطور که میدانید یادگیری یک زبان برنامه نویسی هزینهی زیادی را برای یک سازمان و یا یک شخص به همراه دارد. در زامارین این امکان فراهم شدهاست که با استفاده از تنها یک زبان برنامه نویسی مانند C# ، برنامه نویسان بتوانند برای پلتفرمهای مختلف برنامه بنویسند. در نظر داشته باشید که UI در هر پلتفرم به صورت جداگانه پیاده سازی میشود. به طور مثال در اندروید به وسیلهی Android Xml میتوانید ظاهر برنامه خود را پیاده سازی کنید و منطقهای خود را با زبان C# برای تمامی پلت فرمها به صورت یکسان بنویسید.
· تجربه کاربری Native
زامارین به شما این امکان را خواهد داد که با استفاده از کنترلهای Native هر پلتفرم به تجربه کاربری همان پلت فرم دسترسی پیدا کنید و اپلیکیشنی با ظاهر و UX همان پلتفرم بسازید.
· استفاده 100% از امکانات هر پلتفرم
زامارین به دلیل Native بودن این امکان را به برنامه نویسان ارائه میدهد که با استفاده از یک زبان و با بکارگیری Cycle Life مخصوص هر پلتفرم، به 100% امکانات و API های هر پلتفرم دسترسی پیدا کنند.
· Performance
به دلیل اینکه برنامههای زامارین به صورت Native اجرا میشوند Performance بالایی دارند.
· دسترسی به API های موجود در .Net
شما قادر خواهید بود با دانش موجود مانند Entity Framework Code و.. به فریم ورک .Net دسترسی پیدا کرده و از API های درون آن استفاده کنید. زامارین از یکی از پیاده سازیهای .Net Standard استفاده میکند.
· استفاده مجدد از کد
در زامارین قادر خواهید بود که از کدهای خود، استفاده مجدد کرده و این امر سبب مدیریت بهتر بر روی کد، کد نویسی کمتر، هزینه نگهداری کد کمتر، توسعه راحتتر اپلیکیشن و ... میشود.
· تست خودکار
در زامارین شما میتوانید برای کدهای خود تست خودکار نوشته و آنها را به صورت خودکار تست کنید.
· Bind کردن Library های Objective-C و Java
زامارین طوری طراحی شدهاست که دست شما را در هیچگونه شرایطی نخواهد بست. شما میتوانید به صورت مستقیم کدهایی را که به زبان های Java و Objective-C نوشته شدهاند، به پروژه اضافه کرده و هیچگونه نگرانی از بابت کدهای از قبل نوشته شده که به زبانهای Objective-C و Java هستند، نداشته باشید.
· Designer
در زمارین این امکان وجود دارد که در هر پلتفرم از طریق Designer مخصوص به آن پلتفرم، UI خود را طراحی و پیاده سازی کنید.
· Async
در برنامه نویسی غیر همزمان ( Asynchronous Programming ) این امکان وجود دارد که برنامه شما بدون توقف، یک قسمت از کد را اجرا کرده و منتظر اجرای قسمتهای دیگر کد نشود؛ یا به اصطلاح برنامه از حالت Response خارج نشود. در زبانهای Java ، Objective-C و Swift اینکار باید با CallBack و به صورت Manual مدیریت شود؛ اما #C این امکان را فراهم آورده است که به راحتی اینکار را انجام داده و برنامه خود را همیشه در حالت پاسخ دهی نگه دارید.
public async Task<List<FeedItem>> GetFeedItems(DateTime date) { var feed = "http://planet.xamarin.com/feed/"; var response = await httpClient.GetStringAsync(feed); var items = await ParseFeedAsync(response); return items.Where(item => item.Published.Date == date).ToList(); }
· Parallel Programming
در برنامه نویسی موازی( Parallel Programming ) برخلاف برنامه نویسی MultiThread که بر روی یک هسته CPU اجرا میشود، بر روی چندین هسته CPU به صورت موازی اجرا میشود. زامارین از این نوع برنامه نویسی پشتیبانی میکند.
Xamarin.Forms: 2
پس از معرفی Xamarin Forms API شما میتوانید علاوه بر مزیتهایی که در بالا اشاره شد، کدهای Logic خود را با زبان C# و کدهای UI خود را با زبان XAML پیاده سازی کرده و با یک بار نوشتن کد، در پلتفرمهای مختلف خروجی خود را مشاهده کنید. مزیت استفاده از Xamarin Forms عبارتند از:
· استفاده از کد واحد برای پیاده سازی UI و Logic
یکی از بهترین مزیتهایی را که میتوان به آن اشاره نمود این است که شما کافیست یک بار کد خود را بنویسید و Xamarin Forms کد شما را در پلت فرمهای متفاوت پیاده سازی خواهد کرد.
<?xml version="1.0" encoding="UTF-8"?> <TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyApp.MainPage"> <TabbedPage.Children> <ContentPage Title="Profile" Icon="Profile.png"> <StackLayout Spacing="20" Padding="20" VerticalOptions="Center"> <Entry Placeholder="Username" Text="{Binding Username}"/> <Entry Placeholder="Password" Text="{Binding Password}" IsPassword="true"/> <Button Text="Login" TextColor="White" BackgroundColor="#77D065" Command="{Binding LoginCommand}"/> </StackLayout> </ContentPage> <ContentPage Title="Settings" Icon="Settings.png"> <!-- Settings --> </ContentPage> </TabbedPage.Children> </TabbedPage>
برای پیاده سازی UI در Xamarin Forms باید از XAML استفاده کنید. همچنین مانند روش قبلی میتوانید از زبان C# برای پیاده سازی منطق، استفاده نمایید.
با استفاده از Xamarin Forms شما تجربه نوشتن کد Cross Platform را در کنار Native بودن آن خواهید داشت.
· استفاده از یک کنترل مخصوص یک پلتفرم در بین کد ( Embedding )
در Xamarin Forms این امکان وجود دارد که اگر شما خواستید یک کنترل مخصوص IOS را در بین کدهای خود استفاده کنید، بتوانید به راحتی اینکار را انجام دهید.( (Embedding
اگر به تصویر بالا دقت کنید متوجه خواهید شد که در یکسری از کنترلها، تصاویر متفاوت هستند. در نسخه اندروید یک Action Button در قسمت پایین صفحه مشاهده میکنید که در نسخهی IOS آن موجود نیست. یعنی به صورت مستقیم کنترل Action Button که مختص به پلت فرم اندروید میباشد، درون Xamarin Forms استفاده شده است.
· دسترسی به هر پلتفرم به طور مستقیم
شما قادر خواهید بود به طور مستقیم به هر پلت فرم دسترسی پیدا کرده و به طور مثال در هر پلتفرم، UI مخصوص به خود را با Property های مخصوص به خود طراحی کنید.
· UITest و Test Cloud
در Xamarin Forms میتوانید برای UI خود تست نوشته و آنها را به وسیله Xamarin Test Cloud بر روی صدها Device متفاوت تست کنید. (این امکان فقط برای Android و IOS وجود دارد.)
· Life Cycle مشابه در تمامی پلتفرم ها
همانطور که میدانید پلتفرمهای مختلف، Life Cycle های متفاوتی برای مدیریت اپلیکیشن دارند. یکی از مزیتهای استفاده از Xamarin Forms این است که شما میتوانید برای تمامی پلتفرمها بهوسیلهی یک Life Cycle یکسان کد بنویسید.
· Previewer
یکی از بهترین قابلیتهایی که در Xamarin Forms اضافه شدهاست این است که شما قادر خواهید بود به صورت Real Time خروجی فایل XAML خود را به وسیله Previewer مشاهده نمایید.
· Performance Profiler
به وسیله Xamarin Profiler شما میتوانید میزان مصرف حافظه، Performance و ... را در اندروید و IOS اندازه گیری نمایید.
نکات قابل توجه:
Ø استفاده همزمان از Xamarin Forms و Xamarin Native
شما میتوانید کدهای خود را با حداکثر Reusability نوشته و در صورت لزوم با کدهای Xamarin Native ترکیب کنید.
Ø Documentation خیلی خوب
زمارین مستندات جامع و کاملی را در سایت خود گردآوری کرده که میتوانید به راحتی از آن برای فهم تمامی قسمتهای Xamarin استفاده کنید.
*در دنیای NoSql پیاده سازیهای متفاوتی از دیتابیسها انجام شده است و هر دیتابیس، ویژگیها و مزایا و معایب خاص خودش را دارد. باید قبول کرد که همیشه و همه جا نمیتوان از یک پایگاه داده NoSql مشخص استفاده نماییم (دلایلی نظیر محدودیتهای License، هزینه پیاده سازی و...). در نتیجه بررسی یک دیتابیس دیگر با شرایط و توانمندیهای خاص آن خالی از سود نیست.
» این دیتاییس در گروه Graph databasesها قرار دارد و از زبان SPARQL (بخوانید Sparkle) برای کوئری گرفتن و کار با دادهها بهره میبرد؛
» متن باز و رایگان است
» پشتیبانی از دات نت چهار به بعد؛
» قابل استفاده در Mobile Application - Windows phone 7 , 8؛
» بدون شما (Schema Less) و با قابیلت ذخیره سازی triple و به فرمت RDF
» پشتیبانی از Linq و OData؛
» پشتیبانی از تراکنشها ؛
» پیاده سازی مدل برنامه به صورت Code First؛
» سرعت بالا جهت ذخیره سازی و لود اطلاعات؛
» نیاز به پیکربندیهای خاص جهت پیاده سازی ندارد؛
» ارائه افزونه رایگان Polaris جهت کوئری گفتن و نمایش Visual داده ها.
و غیره که در ادامه با آنها آشنا خواهید شد.
در B*Db دو روش برای ذخیره سازی اطلاعات وجود دارد:
» Append Only : در این روش تمامی تغییرات (عملیات نوشتن) در انتهای فایل index اضافه خواهد شد. این روش مزایای زیر را به دنبال خواهد داشت:
- عملیات نوشتن هیچگاه عملیات خواندن اطلاعات را block نمیکند. در
نتیجه هر تعداد عملیات خواندن فایل (منظور اجرای کوئریهای SPQRL است) میتواند به صورت موازی بر روی Storeها اجرا شود.
- به دلیل اینکه عمل ویرایش واقعی هیچ گاه انجام نمیشود (دادهها فقط اضافه خواهند شد) همیشه میتوانید وضعیت Store را به حالتهای قبلی بازگردانید.
- عملیات نوشتن اطلاعات بسیار سریع خواهد بود.
نکته ای که باید به آن دقت داشت این است که فقط در هنگام ساخت Storeها میتوانید نوع ذخیره سازی آن را تعیین نمایید، بعد از ساخت Store امکان سوئیچ بین حالات امکان پذیر نیست.
همان طور که پیشتر گفته شد B*Db برای ذخیره سازی اطلاعات از سند RDF بهره میبرد. البته با RDF Syntaxهای متفاوت :
هم چنین در B*Db چهار روش برای دست یابی با دادهها (پیاده سازی عملیات CRUD) وجود دارد از قبیل:
» B*Db EntityFramewok
» Data Object Layer
» RDF Client Api
» Dynamic API
که هر کدام در طی پستهای متفاوت بررسی خواهد شد.
بررسی یک مثال با روش B*Db EntityFramework
برای شروع ابتدا یک پروژه جدید از نوع Console Application ایجاد کنید. سپس از طریق Nuget اقدام به نصب Package زیر نمایید:
pm> install-Package BirghtStarDb
PM> Install-Package BirghtStarDbLibs
بعد از نصب پکیجهای بالا یک فایل Text Template با نام MyEntityContext.tt نیز به پروژه افزوده خواهد شد. این فایل جهت تولید خودکار مدلهای برنامه استفاده میشود. اما برای این کار لازم است به ازای هر مدل ابتدا یک اینترفیس ایجاد نمایید. برای مثال:
[Entity] public interface IBook { public int Code { get; set; } public string Title { get; set; } }
» نیاز به ایجاد یک خاصیت به عنوان Id وجود ندارد. به صورت پیش فرض خاصیت Id با نوع string برای هر مدل پیاده سازی میشود. اما اگر قصد دارید این نام خاصیت را تغییر دهید میتوانید به صورت زیر عمل کنید:
[Entity] public interface IBook { [Identifier] public string MyId { get; } public int Code { get; set; } public string Title { get; set; } }
استفاده از اینترفیس برای ساخت مدل انعطاف پذیری بالایی را در اختیار ما قرار میدهد که بعدا مفصل بحث خواهد شد. برای عملیات درج داده میتوان به صورت زیر عمل کنید:
MyEntityContext context = new MyEntityContext("type=embedded;storesdirectory=c:\brightstar;storename=test"); var book = context.Books.Create(); book.Code = 1; book.Title = "Test"; context.Books.Add(book); context.SaveChanges();
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر میتوان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS میتوان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهندههای DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
<ItemGroup> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> </ItemGroup>
این ایده نیز از npm و ابزارهای محلی و مختص به یک پروژهی آن گرفته شدهاست. اما npm امکان نصب این ابزارها را به صورت سراسری نیز دارد که امکان وجود linters ، test runners و یا development web servers را میسر کردهاست و در این حالت نیازی نیست یک چنین ابزارهایی را به ازای هر پروژه نیز یکبار نصب کرد.
معرفی ابزارهای سراسری در NET Core 2.1.
اگر SDK جدید NET Core 2.1 را نصب کرده باشید، پس از Build یک پروژهی مبتنی بر NET Core 2.0. (که توسط فایل global.json، شماره SDK آن محدود و مقید نشدهاست) یک چنین پیامهای اخطاری را مشاهده خواهید کرد:
warning : Using DotNetCliToolReference to reference 'Microsoft.EntityFrameworkCore.Tools.DotNet' is obsolete and can be removed from this project. This tool is bundled by default in the .NET Core SDK. warning : Using DotNetCliToolReference to reference 'Microsoft.DotNet.Watcher.Tools' is obsolete and can be removed from this project. This tool is bundled by default in the .NET Core SDK.
پیامهای اخطار فوق نیز به این معنا هستند که دیگر نیازی نیست تا برای اجرای دستور dotnet watch run، حتما ابزار پروژهی Microsoft.DotNet.Watcher.Tools را به صورت دستی به تمام فایلهای csproj خود اضافه کنید. ابزار Watcher و یا EntityFrameworkCore.Tools اکنون جزو ابزارهای سراسری NET Core 2.1. هستند و بدون نیازی به افزودن ارجاع خاصی به آنها، هم اکنون در تمام پروژههای NET Core. شما قابل دسترسی و استفاده هستند. بنابراین ارجاعات مستقیم به آنها را حذف کنید؛ چون غیرضروری میباشند.
روش نصب ابزارهای سراسری در NET Core.
روش نصب ابزارهای سراسری NET Core. به صورت زیر است:
dotnet tool install -g example
%USERPROFILE%\.dotnet\toolspkgs (Windows) $HOME/.dotnet/toolspkgs (macOS/Linux)
%USERPROFILE%\.dotnet\tools
~/.dotnet/tools
در حال حاضر برای عزل این برنامهها باید به یکی از این مسیرها مراجعه و آنها را دستی حذف کرد (در هر دو مسیر toolspkgs و tools باید حذف شوند).
یک نمونه از این ابزارها را که dotnet-dev-certs نام دارد، پس از نصب SDK جدید، در مکانهای یاد شده، خواهید یافت. کار این ابزار سراسری، تولید یک self signed certificate مخصوص برنامههای ASP.NET Core 2.1 است که پیشتر در مطلب «اجبار به استفادهی از HTTPS در حین توسعهی برنامههای ASP.NET Core 2.1» آنرا بررسی کردیم.
نکته 1: بر اساس تصویر فوق، در خط فرمان، دستور dotnet-dev-certs را صادر کنید. اگر پیام یافت نشدن این دستور یا ابزار را مشاهده کردید، به معنای این است که مسیر نصب آنها به PATH سیستم اضافه نشدهاست. با استفاده از دستورات ذیل میتوانید این مسیر را به PATH سیستم اضافه کنید:
Windows PowerShell: setx PATH "$env:PATH;$env:USERPROFILE/.dotnet/tools" Linux/macOS: echo "export PATH=\"\$PATH:\$HOME/.dotnet/tools\"" >> ~/.bash_profile
نکته 2: اگر به این مسیرها دقت کنید، این ابزارها صرفا برای کاربر جاری سیستم نصب میشوند و مختص به او هستند؛ به عبارتی user-specific هستند و نه machine global.
روش ایجاد ابزارهای سراسری NET Core.
همانطور که عنوان شد، ابزارهای سراسری NET Core. در اصل برنامههای کنسول آن هستند. به همین جهت پس از نصب SDK جدید، در یک پوشهی جدید، دستور dotnet new console را اجرا کنید تا یک برنامهی کنسول جدید مطابق آن ایجاد شود. سپس فایل csproj آنرا به صورت زیر ویرایش کنید:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <IsPackable>true</IsPackable> <PackAsTool>true</PackAsTool> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> </Project>
پس از آن برای تهیهی یک بستهی نیوگت از آن، دستور زیر را اجرا کنید:
dotnet pack -c Release
dotnet tool install -g package-name
EF Code First #12
- اون اینترفیس IUnitOfWork مطرح شده در مثال جاری، وابستگی خاصی به DataLayer نداره. میتونه در لایه سرویس هم تعریف بشه (منظور این است میتونه در یک اسمبلی و پروژه جداگانه هم قرار بگیره و مشکلی نیست). اما DataLayer باید بتونه در حین تزریق وابستگیها وهلهای از IUnitOfWork رو فراهم کنه تا به اون معنا ببخشه؛ به همین جهت Context برنامه باید آنرا پیاده سازی کند تا توسط StructureMap قابل شناسایی و استفاده شود.
اما نهایتا وهله سازی اینترفیس یاد شده توسط DAL صورت میگیره. uow به خودی خود موجودیتی نداره. در اینجا مثلا EF هست که به اون معنا میبخشه و سبب وهله سازی آن خواهد شد. هرچند به ظاهر برنامه با اینترفیسها کار میکند اما تزریق وابستگیها است که به این اینترفیسها موجودیت میبخشد و سبب دسترسی به وهلهای که قرار داد ارائه شده توسط آنها را پیاده سازی کرده میشود.
- در یک سیستم nTier هم مباحث ذکر شده در این قسمت، جاری است. مثلا یک WCF Service قرار گرفته روی یک سرور مجزا هم میتونه از DataLayer و ServiceLayer مثال جاری استفاده کند. استفاده کننده نهایی برای نمایش آن در UI با هیچکدام از دو مورد ذکر شده کاری ندارد و فقط با قراردادهای WCF Service کار میکنه.
Tools -> Code Snippets Manager (Ctrl+K,Ctrl+B)
در ویژوال استودیو 2010 دو نوع snippet وجود دارد :
1- Expansion snippets : که در محل کرسر (Cursor) اضافه میشوند . مثل cw و enum که به ترتیب دستور writeLine و ساختار یک enum را ایجاد میکنند .
2- SurroundsWith snippets : که میتوانند یک تکه کد انتخاب شده را در بر بگیرند مثل for و یا do که کد انتخاب شده را در یک حلقه for و do-while محصور میکنند .
نکته ای که باید توجه داشت این است که یک snippet میتواند از هر دو نوع باشد . برای مثال for و do و یا if ، در صورتی که کدی انتخاب شده باشد آن را محصور میکنند و گرنه ساختار خالی مرتبط را در محل cursor اضافه میکنند .
همانطور که در ابتدا هم ذکر شد ، علاوه بر snippetهای آمادهی موجود ، توسعه دهنده میتواند قطعه کدهایی را خود ایجاد کرده و مورد استفاده قرار دهد .
در اینجا یک expansion snippet خواهیم ساخت تا کار اضافه کردن بلاک try-catch-finally را برای ما انجام دهد .
- ابتدا یک فایل xml به پروژه اضافه میکنیم و آنرا TryCatchFinally.snippet مینامیم . توجه کنید که نام فایل باید به .snippet ختم شود .
- فایل را باز و درون آن راست کلیک کرده و گزینه Insert snippet > Snippet را انتخاب میکنیم . با اینکار یک قالب پایه snippet ( که یک ساختار xml ) است به فایل اضافه میشود . هر فایل snippet از دو بخش اصلی header و snippet تشکیل شده که بخش header اطلاعاتی کلی درباره قطعه کد را نگهداری میکند و بخش snippet مربوط به تعریف محتوای قطعه کد است .
<codesnippet format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <header> <title>title</title> <author>author</author> <shortcut>shortcut</shortcut> <description>description</description> <snippettypes> <snippettype>SurroundsWith</snippettype> <snippettype>Expansion</snippettype> </snippettypes> </header> <snippet> <declarations> <literal> <id>name</id> <default>value</default> </literal> </declarations> <code language="XML"> <!--[CDATA[<test--> <name>$name$</name> $selected$ $end$]]> </code> </snippet> </codesnippet>
- قالب پیش فرض شامل هر دو نوع snippet است .
<codesnippet format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <header> ... <snippettypes> <snippettype>SurroundsWith</snippettype> <snippettype>Expansion</snippettype> </snippettypes> </header> ... </codesnippet>
از آنجا که قصد داریم یک Expansion snippet بسازیم پس تگ SurroundsWith را حذف میکنیم .
<snippettypes> <snippettype>Expansion</snippettype> </snippettypes>
- در بخش header مقدار تگ Title را به “Try Catch Finally”و مقدار تگ Shortcut را به “trycf” و Description را به “Adds a try-catch-finally block ” تغییر میدهیم . Title عنوان snippet است و وجود آن ضروری است . اضافه کردن shortcut اختیاری است و به عنوان یک متن میانبر برای اضافه کردن snippet استفاده میشود .
<Header> <Title>Try Catch Finally</Title> <Author>mohsen.d</Author> <Shortcut>trycf</Shortcut> <Description>Adds a try-catch-finally block</Description>
- تگ Literal برای تعریف جایگزین برای بخشی از کد درون snippet که احتمال دارد پس از اضافه شدن ، توسط برنامه نویس و یا در صورت استفاده از function توسط خود ویژوال استودیو تغییر کند استفاده میشود . در قطعه کد try-catch-finally ، ما میخواهیم به کاربر اجازه بدهیم که نوع استثنائی را که catch میشود تغییر دهد .
تگ id نامی برای بخش قابل ویرایش تعریف میکند ( که از آن در ادامه در تعریف خود قطعه کد استفاده میکنیم ) . آنرا به “ExceptionName” تغییر میدهیم . تگ default هم مقدار پیش فرضی را برای آن بخش مشخص میکند . ما میخواهیم تمام استثناها را Catch کنیم پس مقدار پیش فرض را برابر "Exception" قرار میدهیم .
..... <declarations> <literal> <id>ExceptionName</id> <default>Exception</default> </literal> </declarations> ...
- و در تگ Code ، خود قطعه کدی که ویژوال استودیو باید آن را اضافه کند ، تعریف میشود . مقدار مشخصه Language آن را به CSharp تغییر میدهیم و محتویات داخل آنرا به شکل زیر اضافه میکنیم .
<code language="CSharp"> <!--[CDATA[ try { $end$ } catch($ExceptionName$) { } finally { } ]]--> </code>
به نحوه استفاده از ExceptionName که در قسمت Literal تعریف کردیم توجه کنید . عبارت $end$ هم یک کلمه رزرو شده است که محل قرار گرفتن cursor را بعد از اضافه شدن قطعه کد مشخص میکند .
- در نهایت snippet ما به شکل زیر خواهد بود :
<codesnippet format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <header> <title>Try Catch Finally</title> <author>mohsen.d</author> <shortcut>trycf</shortcut> <description>Adds a try-catch-finally block</description> <snippettypes> <snippettype>Expansion</snippettype> </snippettypes> </header> <snippet> <declarations> <literal> <id>ExceptionName</id> <default>Exception</default> </literal> </declarations> <code language="CSharp"> <!--[CDATA[ try { $end$ } catch($ExceptionName$) { } finally { } ]]--> </code> </snippet> </codesnippet>
اضافه کردن snippet ساخته شده به visual studio
دو راه برای اضافه کردن snippet تعریف شده به ویژوال استودیو وجود دارد :
روش اول قرار دادن فایل .snippet در پوشه code snippets ویژوال استودیو است که مسیر پیش فرض آن
C:\Users\<UserName>\Documents\Visual Studio 2010\Code Snippets\
گزینه دوم import کردن فایل .snippet به داخل ویژوال استودیو است . در ویژوال استودیو به مسیر
Tools -> Code Snippets Manager (Ctrl+K,Ctrl+B)
استفاده از snippet ساخته شده
برای استفاده از snippet میتوانیم متن میانبر تعریف شده را تایپ کنیم و با دو بار فشردن کلید tab قطعه کد تعریف شده به محل کرسر اضافه میشودهمینطور با فشردن کلیدهای Ctrl+K و Ctrl+X به صورت پشت سر هم منوی “Insert Snippet” ظاهر میشود که از طریق آن میتوانیم Snippet موردنظر را یافته ( بدنبال Title تعریف شده برای snippet در پوشه ای که آنرا ذخیره کرده اید بگردید ) و با انتخاب آن کد تعریف شده اضافه خواهد شد .
برای آشنایی با روشهای مختلف دسترسی به snippetها اینجا را بررسی کنید .
ابزارها
در این پروژه هم مجموعه snippetهای موجود ویژوال استودیو 2010 برای زبان سی شارپ ، جهت سازگاری با stylecop ویرایش و refactor شده اند ( در کنار تعریف snippetهای دیگر ).