۱۳۸۹/۰۵/۰۸

راهبري در Silverlight به كمك الگوي MVVM


مقدمات راهبري (Navigation) در سيلورلايت را در اينجا مي‌توانيد مطالعه نمائيد : +
مطلبي را كه در فصل فوق نخواهيد يافت در مورد نحوه‌ي بكارگيري الگوي MVVM جهت پياده سازي Navigation در يك برنامه‌ي سيلورلايت است؛ علت آن هم به اين بر مي‌گردد كه اين فصل پيش از مباحث Binding مطرح شد.

صورت مساله:
يكي از اصول MVVM اين است كه در ViewModel‌ نبايد ارجاعي از View وجود داشته باشد (ViewModel بايد در بي‌خبري كامل از وجود اشياء UI و ارجاع مستقيم به آن‌ها طراحي شود)، اما براي پياده سازي مباحث Navigation نياز است به نحوي به شيء Frame قرار داده شده در صفحه‌ي اصلي يا قالب اصلي برنامه دسترسي يافت تا بتوان درخواست رهنمون شدن به صفحات مختلف را صادر كرد. اكنون چكار بايد كرد؟

راه حل:
يكي از راه حل‌هاي جالبي كه براي اين منظور وجود دارد استفاده از امكانات كلاس Messenger مجموعه‌ي MVVM Light toolkit است. از طريق ViewModel برنامه، آدرس صفحه‌ي مورد نظر را به صورت يك پيغام به View مورد نظر ارسال مي‌كنيم و سپس View برنامه كه به اين پيغام‌ها گوش فرا مي‌دهد، پس از دريافت آدرس مورد نظر، نسبت به فراخواني تابع Navigate شيء Frame رابط كاربري برنامه اقدام خواهد كرد. به اين صورت ViewModel برنامه به View خود جهت اعمال راهبري برنامه، گره نخواهد خورد.

روش پياده سازي:
ابتدا ساختار پروژه را در نظر بگيريد (اين شكل دگرگون شده‌ي Solution explorer مرتبط است با productivity tools نصب شده):



در پوشه‌ي Views ، دو صفحه اضافه شده‌اند كه توسط user control ايي به نام menu ليست شده و راهبري خواهند شد. مونتاژ نهايي هم در MainPage.xaml صورت مي‌گيرد.
كدهاي XAML‌ مرتبط با منوي ساده برنامه به شرح زير هستند (Menu.xaml) :

<UserControl x:Class="MvvmLight6.Views.Menu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MvvmLight6.ViewModels" mc:Ignorable="d"
FlowDirection="RightToLeft" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:MenuViewModel x:Key="vmMenuViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMenuViewModel}}">
<HyperlinkButton Content="صفحه يك" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page1.xaml"
/>
<HyperlinkButton Content="صفحه دو" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page2.xaml"
/>
</StackPanel>
</UserControl>

كدهاي ViewModel مرتبط با اين View كه كار Command گرداني را انجام خواهد داد به شرح زير است:
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLight6.ViewModels
{
public class MenuViewModel
{
public RelayCommand<string> DoNavigate { set; get; }

public MenuViewModel()
{
DoNavigate = new RelayCommand<string>(doNavigate);
}

private static void doNavigate(string url)
{
Messenger.Default.Send(url, "MyNavigationService");
}
}
}

تمام آيتم‌هاي منوي فوق يك روال را صدا خواهند زد : DoNavigate . تنها تفاوت آن‌ها در CommandParameter ارسالي به RelayCommand ما است كه حاوي آدرس قرارگيري فايل‌هاي صفحات تعريف شده است. اين آدرس‌ها با كمك امكانات كلاس Messenger مجموعه‌ي MVVM light toolkit به View اصلي برنامه ارسال مي‌گردند.
كدهاي XAML مرتبط با MainPage.xaml به شرح زير هستند:

<UserControl x:Class="MvvmLight6.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:usr="clr-namespace:MvvmLight6.Views"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="268" />
</Grid.ColumnDefinitions>
<usr:Menu Grid.Column="1" />
<sdk:Frame Margin="5"
Name="frame1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Grid.Column="0" />
</Grid>
</UserControl>

و كار دريافت پيغام‌ها (يا همان آدرس صفحات جهت انجام راهبري) و عكس العمل نشان دادن به آن‌ها توسط كدهاي ذيل صورت خواهد گرفت:
using System;
using GalaSoft.MvvmLight.Messaging;

namespace MvvmLight6
{
public partial class MainPage
{
public MainPage()
{
registerMessenger();
InitializeComponent();
}

private void registerMessenger()
{
Messenger.Default.Register<string>(this, "MyNavigationService", doNavigate);
}

private void doNavigate(string uri)
{
frame1.Navigate(new Uri(uri, UriKind.Relative));
}
}
}

ابتدا يك Messenger در اينجا رجيستر مي‌شود و سپس به ازاي هر بار دريافت پيغامي با token مساوي MyNavigationService ، متد doNavigate فراخواني خواهد گرديد.
كدهاي اين مثال را از اينجا مي‌توانيد دريافت كنيد.

۱۳۸۹/۰۵/۰۴

دو تنظيم ضروري VS.NET جهت كار با WPF و Silverlight


تنظيم اول: تغيير نحوه‌ي نمايش پيش فرض فايل‌هاي XAML

اگر فايل XAML شما اندكي حجيم شود نمايش آن در VS.NET كمي طولاني خواهد شد و حالت پيش فرض نمايش در VS.NET هم split view mode است (نمايش XAML و پيش نمايش آن با هم). اين مورد هم پس از مدتي تبديل به عذاب مي‌شود. براي رفع آن مي‌توان حالت پيش فرض نمايش يك فايل XAML را به XAML View تنها تغيير داد.
براي اين منظور به منوي Tools ، گزينه‌ي Options و سپس قسمت تنظيمات Text editor مراجعه كنيد. در اينجا در قسمت XAML ، گزينه‌ي Miscellaneous را انتخاب كرده و سپس "Always open documents in full XAML view" را تيك بزنيد.



حتي ممكن است اين مورد هم رضايت بخش نباشد. در اين حالت مي‌توان ويرايشگر پيش فرض را كلا تغيير داد. Design tab را در پايين صفحه از دست مي‌دهيم اما هنوز intellisense كار مي‌كند و اگر نياز به designer بود فقط كافي است كليك راست كرده و گزينه‌ي View designer را انتخاب كرد:
روي يك فايل XAML دلخواه كليك راست كرده و گزينه‌ي Open with را انتخاب كنيد. سپس "Source Code (Text) Editor" را انتخاب كرده و روي دكمه‌ي Set as Default كليك كنيد. تمام!
هر چند Blend اين مشكلات را ندارد و با فايل‌هاي حجيم XAML به خوبي كاري مي‌كند.


تنظيم دوم: تغيير نحوه‌ي نمايش مشكلات ناشي از Binding

عموما اگر مشكلاتي در حين عمليات Binding در WPF يا Silverlight وجود داشته باشند، خطاها در Debugger Output Window نمايش داده مي‌شوند. حالت پيش فرض هم فقط روي Error تنظيم شده است به اين معنا كه warning ها را مشاهده نخواهيد كرد. براي تغيير اين مورد بايد به صورت زير عمل كرد:
به منوي Tools ، گزينه‌ي Options و سپس قسمت تنظيمات Debugging مراجعه كنيد. گزينه‌ي Output Window -> WPF Trace Settings را انتخاب نمائيد. سپس در اينجا قسمت WPF trace settings را يافته و مقدار پيش فرض Data binding را كه به Error تنظيم شده است، به Warning تنظيم نمائيد.



۱۳۸۹/۰۵/۰۲

نحوه‌ي خاتمه‌ي سشن‌هاي كاربران، از راه دور


يكي از مواردي رو كه بعضي از ادمين‌ها هيچ وقت ياد نمي‌گيرند اين است كه لطفا پس از اتمام كار ريموت، logoff كنيد و سشن را باز نگه نداريد. اگر چندين سشن به همين ترتيب باز بمانند پس از مدتي با پيغام حداكثر تعداد كانكشن‌هاي همزمان به يك سرور مواجه خواهيم شد و ديگر امكان اتصال نخواهد بود مگر اينكه يكي از سشن‌هاي باز خاتمه پيدا كند (همچنين مسايل امنيتي را هم در نظر بگيريد). باز بودن يك سشن هم همانطور كه عنوان شد به معناي فعال بودن كاربر نيست. عموما به معناي عدم logoff است.
خوب الان نياز است كه يك اتصال ريموت جهت به روز رساني برنامه‌اي برقرار شود اما نمي‌شود! كساني هم كه سشن باز بر روي سرور دارند هم اكنون در دسترس نيستند. چكار بايد كرد؟
عموما اين كار رو مي‌كنند: فلاني برو دكمه‌ي ريست سرور رو بزن! بعد هم كه سرور بالا اومد ديدن ديتابيس suspect شده واقعا لذت بخش خواهد بود!
راه بهتري هم هست:
برنامه‌اي به نام psexec از SysInternals موجود است كه آن‌را مي‌توان از آدرس ذيل دريافت كرد:

توسط اين برنامه مي‌توان به خط فرمان سرور ريموت دسترسي يافت (البته بديهي است كه پيشتر بايد اين دسترسي را داشته باشيد) و سپس امكان ليست كردن سشن‌هاي باز و همچنين خاتمه‌ي آن‌ها ميسر خواهد شد؛ به شرح زير:
الف) دسترسي به خط فرمان سرور از راه دور
psexec \\x.x.x.x -u domain\user -p password cmd
براي مثال:
psexec \\192.168.1.16 -u domain\admin -p password cmd

ب) ليست كردن سشن‌هاي باز
پس از اجراي موفقيت آميز دستور فوق، خط فرمان سرور در اختيار شما خواهد بود. اكنون دستور qwinsta را وارد كنيد. در ليست ارائه شده شماره Id ها مهم است.

ج) بر اساس Id هاي قابل مشاهده با استفاده از دستور زير مي‌توان سشن را خاتمه داد:
logoff [id# of session to quit] /v
براي مثال:
logoff 2 /v
با حذف سشن‌هاي باز امكان استفاده از امكانات ريموت دسكتاپ مجددا برقرار خواهد شد.

۱۳۸۹/۰۴/۱۴

MEF و الگوي Singleton


در مورد معرفي مقدماتي MEF مي‌توانيد به اين مطلب مراجعه كنيد و در مورد الگوي Singleton به اينجا.


كاربردهاي الگوي Singleton عموما به شرح زير هستند:
1) فراهم آوردن دسترسي ساده و عمومي به DAL (لايه دسترسي به داده‌ها)
2) دسترسي عمومي به امكانات ثبت وقايع سيستم در برنامه logging -
3) دسترسي عمومي به تنظيمات برنامه
و موارد مشابهي از اين دست به صورتيكه تنها يك روش دسترسي به اين اطلاعات وجود داشته باشد و تنها يك وهله از اين شيء در حافظه قرار گيرد.

با استفاده از امكانات MEF ديگر نيازي به نوشتن كدهاي ويژه توليد كلاس‌هاي Singleton نمي‌باشد زيرا اين چارچوب كاري دو نوع روش وهله سازي از اشياء (PartCreationPolicy) را پشتيباني مي‌كند: Shared و NonShared . حالت Shared دقيقا همان نام ديگر الگوي Singleton است. البته لازم به ذكر است كه حالت Shared ، حالت پيش فرض توليد وهله‌ها بوده و نيازي به ذكر صريح آن همانند ويژگي زير نيست:
[PartCreationPolicy(CreationPolicy.Shared)]

مثال:
فرض كنيد قرار است از كلاس زير تنها يك وهله بين صفحات يك برنامه‌ي Silverlight توزيع شود. با استفاده از ويژگي‌ Export به MEF اعلام كرده‌ايم كه قرار است سرويسي را ارائه دهيم :

using System;
using System.ComponentModel.Composition;

namespace SlMefTest
{
[Export]
public class WebServiceData
{
public int Result { set; get; }

public WebServiceData()
{
var rnd = new Random();
Result = rnd.Next();
}
}

}
اكنون براي اثبات اينكه تنها يك وهله از اين كلاس در اختيار صفحات مختلف قرار خواهد گرفت، يك User control جديد را به همراه يك دكمه كه مقدار Result را نمايش مي‌دهد به برنامه اضافه خواهيم كرد. دكمه‌ي ديگري را نيز به همين منظور به صفحه‌ي اصلي برنامه اضافه مي‌كنيم.
كدهاي صفحه اصلي برنامه (كه از يك دكمه و يك Stack panel جهت نمايش محتواي يوزر كنترل تشكيل شده) به شرح بعد هستند:
<UserControl x:Class="SlMefTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<Button Content="MainPageButton" Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0" Name="button1"
VerticalAlignment="Top" Width="98" Click="button1_Click" />
<StackPanel Name="panel1" Margin="5"/>
</StackPanel>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class MainPage
{
[Import]
public WebServiceData Data { set; get; }

public MainPage()
{
InitializeComponent();
this.Loaded += mainPageLoaded;
}

void mainPageLoaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
panel1.Children.Add(new SilverlightControl1());
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
با استفاده از ويژگي Import به MEF اعلام مي‌كنيم كه به اطلاعاتي از نوع شيء WebServiceData نياز داريم و توسط متد CompositionInitializer.SatisfyImports كار وهله سازي و پيوند زدن export و import هاي همانند صورت مي‌گيرد. سپس استفاده‌ي مستقيم از Data.Result مجاز بوده و مقدار آن null نخواهد بود.

كدهاي User control ساده اضافه شده به شرح زير هستند:

<UserControl x:Class="SlMefTest.SilverlightControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<Button Content="UserControlButton"
Height="23"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Name="button1"
VerticalAlignment="Top"
Width="125"
Click="button1_Click" />
</Grid>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;

namespace SlMefTest
{
public partial class SilverlightControl1
{
[Import]
public WebServiceData Data { set; get; }

public SilverlightControl1()
{
InitializeComponent();
this.Loaded += silverlightControl1Loaded;
}

void silverlightControl1Loaded(object sender, RoutedEventArgs e)
{
CompositionInitializer.SatisfyImports(this);
}

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Data.Result.ToString());
}
}
}
اكنون قبل از شروع برنامه يك break point را در سازنده‌ي كلاس WebServiceData قرار دهيد. سپس برنامه را آغاز نمائيد. تنها يكبار اين سازنده فراخواني خواهد شد (هر چند در دو كلاس كار Import اطلاعات WebServiceData صورت گرفته است). همچنين با كليك بر روي دو دكمه‌اي كه اكنون در صفحه‌ي اصلي برنامه ظاهر مي‌شوند، فقط يك عدد مشابه نمايش داده مي‌شود (با توجه به اينكه اطلاعات هر دكمه در يك وهله‌ي جداگانه قرار دارد؛ يكي متعلق است به صفحه‌ي اصلي و ديگري متعلق است به user control اضافه شده).

۱۳۸۹/۰۴/۱۲

چگونه يك ايميل مفيد خودكار را طراحي كنيم؟


از آنجائيكه مدتي قسمتي از كارم مرتبط بود به طراحي ايميل‌هاي خودكار براي برنامه‌هاي تهيه شده (مثلا، ايميل‌هاي مرحله به مرحله يك گردش كاري ... اطلاع رساني‌هاي خودكار از وضعيت داده‌ها،‌ گزارشاتي از برنامه‌ها كه به صورت خبرنامه‌هاي ايميلي در بازه‌هاي زماني مشخصي به اشخاص مشخص شده ارسال مي‌شد و غيره)، لازم مي‌دونم خلاصه‌اي از تجربيات برخورد با كاربران را در اين مورد در ادامه ذكر كنم، شايد مفيد باشد.

1) حتما در انتهاي ايميل خودكار ارسالي، ساعت و تاريخ شمسي ارسال پيام را نيز ذكر كنيد.
عموما از آنجائيكه سيستم استاندارد ارسال ايميل بر اساس تاريخ ميلادي است و تقريبا تمام كلاينت‌هاي دريافت ايميل موجود نيز توانايي شمسي سازي تاريخ دريافت و ارسال ايميل را ندارند (مگر با يك سري افزونه و يا دستكاري در سيستم عامل كه آنچنان خوشايند و مرسوم نيست)، ذكر تاريخ شمسي در انتهاي پيام بسيار مفيد خواهد بود و در اكثر اوقات استناد به ايميل‌هاي دريافت شده بر اساس تاريخ دريافت آن‌ها است.

2) سعي كنيد از بكارگيري عناوين (subject) ثابت جهت ارسال ايميل‌هاي خودكار پرهيز كنيد.
دقيقا يادم مياد زمانيكه براي مدير عامل شركتي سه بار پشت سرهم ايميلي با يك عنوان ارسال شده بود بنده را بازخواست كردند كه چرا برنامه‌ي شما ايميل تكراري ارسال مي‌كند!
بله، سعي مي‌كنند محتوا را از روي عنوان ايميل حدس بزنند و زمانيكه يك عنوان ثابت را براي ايميل‌هاي خودكار خود انتخاب كرديد، تكراري به نظر خواهند رسيد يا حتي ممكن است به اشتباه پيش از خوانده شدن حذف شوند.
براي مثال فرض كنيد ايميل ارجاع كاري را قرار است به صورت خودكار ارسال كنيد. انتخاب عنوان ثابت براي مثال "ارجاع كار جديد" اشتباه است! اين عنوان بايد بر اساس نوع كار هر بار به صورت پويا متغير باشد؛ مثلا: "ارجاع كار جديد: از طرف : ... ، موضوع: ... ، درجه اهميت: ..." كه اين سه نقطه‌ها بايد توسط برنامه هر بار پر شوند.

3) هر چه مي‌توانيد اطلاعات بيشتري را توسط يك ايميل خودكار منتقل كنيد.
مورد قبل را در نظر بگيريد. ذكر "ارجاع كار جديد ..." در عنوان و سپس مجددا ذكر همين عنوان به عنوان بدنه‌ي ايميل خودكار به زودي ايميل‌هاي شما را تبديل به نوعي Spam آزار دهنده خواهد كرد. كار جديدي ارجاع شده است؟ آيا مي‌توان خلاصه‌اي از اين كار را به همراه ايميل نيز ارسال كرد تا كاربر حتما براي مشاهده‌ي ريز جزئيات كار به برنامه مراجعه نكند و اين ايميل واقعا ارزش مطالعه را داشته باشد و سبب تسريع در انجام كارها شود؟
براي مثال ذكر كلي اين مورد كه درخواست مرخصي جديدي را بايد تائيد يا رد كنيد، كافي نيست. ريز جزئيات مرخصي را هم به همراه ايميل ارسال كنيد.

4) ايميل شما بايد حاوي لينكي جهت باز كردن برنامه‌ي تحت وب مرتبط نيز باشد.
كاري ارجاع شده است؟ بهتر است لينك پويايي را جهت هدايت كاربر به صفحه‌ي مرتبط رسيدگي به همان كار ارجاعي ارسال كنيد. به اين صورت زحمت او را كمتر كرده و يك مرحله گزارش گيري را حذف خواهيد كرد. يا حداقل يك محل مراجعه‌ي كلي بعدي را به اين صورت مي‌توان ارائه داد.

5) از بكارگيري قسمت from ايي مانند DoNotReply@Site.Com خودداري كنيد.
كاربر دريافت كننده‌ي ايميل بايد بداند كه در صورت وجود مشكل بايد به كجا مراجعه كند؟ چه كسي اين ايميل را ارسال كرده؟
هرچند برنامه به صورت خودكار تمام قسمت‌هاي اين ايميل ارسالي را تهيه مي‌كند اما اگر خبرنامه‌ي تنظيم شده‌اي نيست، حتما شخص ارسال كننده‌اي دارد. يا حداقل يك ايميل عمومي را براي اين مورد تنظيم كنيد (ايميلي كه وجود خارجي داشته و هر از چندگاهي بررسي مي‌شود).

6) رنگ زمينه و اندازه‌ي قلم مناسبي را انتخاب كنيد.
دقيقا براي هر كدام از موارد ذكر شده چندين بار مشكل داشته‌ام! عموما كساني كه ايميل‌ها را دريافت مي‌كنند سن و سال دار هستند. بنابراين انتخاب فونت tahoma با اندازه‌ي 8 يا pt 7 سبب توبيخ زود هنگام شما خواهد شد!
همچنين هر چه ساده‌تر بهتر. دقيقا مشكلات از زماني آغاز مي‌شوند كه طرحي را انتخاب كنيد يا رنگي را براي زمينه بكار ببريد. اينجا است كه هر روز يك سليقه‌ي تحميلي را بايد پذيرا باشيد.

7) دقيقا مشخص كنيد كه ايميل دريافتي آيا رونوشت‌ است يا خير!
همان مبحث ارجاع كار را در نظر بگيريد. پس از اينكه سيستم راه اندازي شد، مدير يكي از قسمت‌ها چند روز بعد اين درخواست را "حتما" ارسال خواهد كرد: رونوشت تمام كارهاي ارجاعي به كليه پرسنل بخش و همچنين ريز اقدامات آن‌ها بايد براي بنده نيز ارسال شود.
در اينجا تنها افزودن قسمت CC به ايميل‌هاي خودكار كفايت نمي‌كند. حتما به صورت درشت در بالاي ايميل، قبل از شروع بدنه ذكر كنيد كه ايميل دريافتي يك رونوشت است. در غير اينصورت بايد پاسخگوي علت دريافت ايميل‌هايي باشيد كه به درخواست خودشان CC شده است!

8) از ايميل‌هاي خودكار برنامه log تهيه كنيد.
بارها به اين مساله برخورد كرده‌ام كه اشخاص براي شانه خالي كردن از انجام كار محوله، سعي در تخريب كار شما خواهند داشت. خيلي ساده عنوان مي‌كنند كه ايميلي را دريافت نكرده‌اند. حالا شما بيايد ثابت كنيد كه اگر سيستم مشكل داشت كلا براي هيچ كسي ايميل ارسال نمي‌شد، نه فقط براي شما. در اينگونه مواقع وجود يك لاگ از ايميل‌ها (ثبت در بانك اطلاعاتي) و ارجاع به آن‌ها بسيار راه گشا است.

9) راهي را براي خلاص شدن از شر دريافت ايميل‌هاي خودكار نيز پيش بيني كنيد!
همان مورد 7 را در نظر بگيريد. دو روز اول خيلي ذوق خواهند كرد! روز سوم وقتي انبوهي از ايميل‌ها را دريافت كردند، مشكل شما هم شروع خواهد شد. بنابراين امكان تنظيم دريافت يا عدم دريافت ايميل را حتما در برنامه قرار دهيد. يا حداقل نحوه‌ي ايجاد يك پوشه جديد و فيلتر كردن ايميل‌هاي رسيده و هدايت خودكار آن‌ها به اين پوشه‌ي جديد را آموزش دهيد.


خوب! حالا به نظر شما اين ايميل خودكار ارسالي سايت IDevCenter كه اخيرا اضافه شده است چه نمره‌اي را كسب مي‌كند؟



- تاريخ شمسي در انتهاي ايميل ندارد.
- عنوان‌ها ثابت هستند.
- هيچ جزئياتي ارائه نشده است.
- لينك مرتبط دارد.
- قسمت from مناسبي دارد.
- ساده است؛ خوب است! فقط اندازه قلم آن بهتر است يك شماره بزرگتر شود.
- بحث رونوشت اينجا مورد ندارد.
- بحث لاگ ... شخصي است.
- امكان تنظيم دريافت ايميل پيش بيني شده است.
نمره از 7 : 3.5

۱۳۸۹/۰۴/۱۱

انجام پي در پي اعمال Async به كمك Iterators - قسمت دوم


در قسمت قبل ايده‌ي اصلي و مفاهيم مرتبط با استفاده از Iterators مطرح شد. در اين قسمت به يك مثال عملي در اين مورد خواهيم پرداخت.

چندين كتابخانه و كلاس جهت مديريت Coroutines در دات نت تهيه شده كه ليست آن‌ها به شرح زير است:
1) Using C# 2.0 iterators to simplify writing asynchronous code
2) Wintellect's Jeffrey Richter's PowerThreading Library
3) Rob Eisenberg's Build your own MVVM Framework codes

و ...

مورد سوم كه توسط خالق اصلي كتابخانه‌ي Caliburn (يكي از فريم ورك‌هاي مشهور MVVM براي WPF و Silverlight) در كنفرانس MIX 2010 ارائه شد، اين روزها در وبلاگ‌هاي مرتبط بيشتر مورد توجه قرار گرفته و تقريبا به يك روش استاندارد تبديل شده است. اين روش از يك اينترفيس و يك كلاس به شرح زير تشكيل مي‌شود:

using System;

namespace SLAsyncTest.Helper
{
public interface IResult
{
void Execute();
event EventHandler Completed;
}
}

using System;
using System.Collections.Generic;

namespace SLAsyncTest.Helper
{
public class ResultEnumerator
{
private readonly IEnumerator<IResult> _enumerator;

public ResultEnumerator(IEnumerable<IResult> children)
{
_enumerator = children.GetEnumerator();
}

public void Enumerate()
{
childCompleted(null, EventArgs.Empty);
}

private void childCompleted(object sender, EventArgs args)
{
var previous = sender as IResult;

if (previous != null)
previous.Completed -= childCompleted;

if (!_enumerator.MoveNext())
return;

var next = _enumerator.Current;
next.Completed += childCompleted;
next.Execute();
}
}
}

توضيحات:
مطابق توضيحات قسمت قبل، براي مديريت اعمال همزمان به شكلي پي در پي، نياز است تا يك IEnumerable را به همراه yield return در پايان هر مرحله از كار ايجاد كنيم. در اينجا اين IEnumerable را از نوع اينترفيس IResult تعريف خواهيم كرد. متد Execute آن شامل كدهاي عمليات Async خواهند شد و پس از پايان كار رخداد Completed صدا زده مي‌شود. به اين صورت كلاس ResultEnumerator به سادگي مي‌تواند يكي پس از ديگري اعمال Async مورد نظر ما را به صورت متوالي فراخواني نمائيد. با هر بار فراخواني رخداد Completed، متد MoveNext صدا زده شده و يك مرحله به جلو خواهيم رفت.
براي مثال كدهاي ساده WCF Service زير را در نظر بگيريد.

using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Threading;

namespace SLAsyncTest.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public class TestService
{
[OperationContract]
public int GetNumber(int number)
{
Thread.Sleep(2000);//Simulating a log running operation
return number * 2;
}
}
}

قصد داريم در طي دو مرحله متوالي اين WCF Service را در يك برنامه‌ي Silverlight فراخواني كنيم. كدهاي قسمت فراخواني اين سرويس بر اساس پياده سازي اينترفيس IResult به صورت زير درخواهند آمد:

using System;
using SLAsyncTest.Helper;

namespace SLAsyncTest.Model
{
public class GetNumber : IResult
{
public int Result { set; get; }
public bool HasError { set; get; }

private int _num;
public GetNumber(int num)
{
_num = num;
}

#region IResult Members
public void Execute()
{
var srv = new TestServiceReference.TestServiceClient();
srv.GetNumberCompleted += (sender, e) =>
{
if (e.Error == null)
Result = e.Result;
else
HasError = true;

Completed(this, EventArgs.Empty); //run the next IResult
};
srv.GetNumberAsync(_num);
}

public event EventHandler Completed;
#endregion
}
}
در متد Execute كار فراخواني غيرهمزمان WCF Service به صورتي متداول انجام شده و در پايان متد Completed صدا زده مي‌شود. همانطور كه توضيح داده شد، اين فراخواني در كلاس ResultEnumerator ياد شده مورد استفاده قرار مي‌گيرد.
اكنون قسمت‌هاي اصلي كدهاي View Model برنامه به شكل زير خواهند بود:

private void doFetch(object obj)
{
new ResultEnumerator(executeAsyncOps()).Enumerate();
}

private IEnumerable<IResult> executeAsyncOps()
{
FinalResult = 0;
IsBusy = true; //Show BusyIndicator

//Sequential Async Operations
var asyncOp1 = new GetNumber(10);
yield return asyncOp1;

//using the result of the previous step
if(asyncOp1.HasError)
{
IsBusy = false; //Hide BusyIndicator
yield break;
}

var asyncOp2 = new GetNumber(asyncOp1.Result);
yield return asyncOp2;

FinalResult = asyncOp2.Result; //Bind it to the UI

IsBusy = false; //Hide BusyIndicator
}
در اينجا يك IEnumerable از نوع IResult تعريف شده است و در طي دو مرحله‌ي متوالي اما غيرهمزمان كار دريافت اطلاعات از WCF Service صورت مي‌گيرد. ابتدا عدد 10 به WCF Service ارسال مي‌شود و خروجي 20 خواهد بود. سپس اين عدد در مرحله‌ي بعد مجددا به WCF Service ارسال گرديده و حاصل نهايي كه عدد 40 مي‌باشد در اختيار سيستم Binding قرار خواهد گرفت.
اگر از اين روش استفاده نمي‌شد ممكن بود به اين جواب برسيم يا خير. ممكن بود مرحله‌ي دوم ابتدا شروع شود و سپس مرحله‌ي اول رخ دهد. اما با كمك Iterators و yield return به همراه كلاس ResultEnumerator موفق شديم تا عمليات دوم همزمان را در حالت تعليق قرار داده و پس از پايان اولين عمليات غير همزمان، مرحله‌ي بعدي فراخواني را بر اساس مقدار حاصل شده از WCF Service آغاز كنيم.
اين روش براي برنامه‌ نويس‌ها آشناتر است و همان سيستم فراخواني A->B->C را تداعي مي‌كند اما كليه اعمال غيرهمزمان هستند و ترد اصلي برنامه قفل نخواهد شد.

كدهاي كامل اين مثال را از اينجا مي‌توانيد دريافت كنيد.

۱۳۸۹/۰۴/۱۰

انجام پي در پي اعمال Async به كمك Iterators - قسمت اول


تقريبا تمام اعمال كار با شبكه در Silverlight از مدل asynchronous programming پيروي مي‌كنند؛ از فراخواني يك متد وب سرويس تا دريافت اطلاعات از وب و غيره. اگر در ساير فناوري‌هاي موجود در دات نت فريم ورك براي مثال جهت كار با يك وب سرويس هر دو متد همزمان و غيرهمزمان در اختيار برنامه نويس هستند اما اينجا خير. اينجا فقط روش‌هاي غيرهمزمان مرسوم هستند و بس. خيلي هم خوب. يك چارچوب كاري خوب بايد روش استفاده‌ي صحيح از كتابخانه‌هاي موجود را نيز ترويج كند و اين مورد حداقل در Silverlight اتفاق افتاده است.
براي مثال فراخواني‌هاي زير را در نظر بگيريد:
private int n1, n2;

private void FirstCall()
{
Service.GetRandomNumber(10, SecondCall);
}

private void SecondCall(int number)
{
n1 = number;
Service.GetRandomNumber(n1, ThirdCall);
}

private void ThirdCall(int number)
{
n2 = number;
// etc
}
عموما در اعمال Async پس از پايان عمليات در تردي ديگر، يك متد فراخواني مي‌گردد كه به آن callback delegate نيز گفته مي‌شود. براي مثال توسط اين سه متد قصد داريم اطلاعاتي را از يك وب سرويس دريافت و استفاده كنيم. ابتدا FirstCall فراخواني مي‌شود. پس از پايان كار آن به صورت خودكار متد SecondCall فراخواني شده و اين متد نيز يك عمليات Async ديگر را شروع كرده و الي آخر. در نهايت قصد داريم توسط مقادير بازگشت داده شده منطق خاصي را پياده سازي كنيم. همانطور كه مشاهده مي‌كنيد اين اعمال زيبا نيستند! چقدر خوب مي‌شد مانند دوران synchronous programming (!) فراخواني‌هاي اين متدها به صورت ذيل انجام مي‌شد:
private void FetchNumbers()
{
int n1 = Service.GetRandomNumber(10);
int n2 = Service.GetRandomNumber(n1);
}
در برنامه نويسي متداول هميشه عادت داريم كه اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممكن است ابتدا C انجام شود، سپس A و بعد B يا هر حالت ديگري صرفنظر از تقدم و تاخر آن‌ها در حين معرفي متدهاي مرتبط در يك قطعه كد. همچنين ميزان خوانايي اين نوع كدنويسي نيز مطلوب نيست. مانند مثال اول ذكر شده، يك عمليات به ظاهر ساده به چندين متد منقطع تقسيم شده است. البته به كمك lambda expressions مثال اول را به شكل زير نيز مي‌توان در طي يك متد ارائه داد اما اگر تعداد فراخواني‌ها بيشتر بود چطور؟ همچنين آيا استفاده از عدد n2 بلافاصله پس از عبارت ذكر شده مجاز است؟ آيا عمليات واقعا به پايان رسيده و مقدار مطلوب به آن انتساب داده شده است؟
private void FetchNumbers()
{
int n1, n2;

Service.GetRandomNumber(10, result =>
{
n1 = result;
Service.GetRandomNumber(n1, secondResult =>
{
n2 = secondResult;
});
});
}

به عبارتي مي‌خواهيم كل اعمال انجام شده در متد FetchNumbers هنوز Async باشند (ترد اصلي برنامه را قفل نكنند) اما پي در پي انجام شوند تا مديريت آن‌ها ساده‌تر شوند (هر لحظه دقيقا بدانيم كه كجا هستيم) و همچنين كدهاي توليدي نيز خواناتر باشند.
روش استانداري كه توسط الگوهاي برنامه نويسي براي حل اين مساله پيشنهاد مي‌شود، استفاده از الگوي coroutines است. توسط اين الگو مي‌توان چندين متد Async را در حالت معلق قرار داده و سپس در هر زماني كه نياز به آن‌ها بود عمليات آن‌ها را از سر گرفت.
دات نت فريم ورك حالت ويژه‌اي از coroutines را توسط Iterators پشتيباني مي‌كند (از C# 2.0 به بعد) كه در ابتدا نياز است از ديدگاه اين مساله مروري بر آن‌ها داشته باشيم. مثال بعد يك enumerator را به همراه yield return ارائه داده است:

using System;
using System.Collections.Generic;
using System.Threading;

namespace CoroutinesSample
{
class Program
{
static void printAll()
{
foreach (int x in integerList())
{
Console.WriteLine(x);
}
}

static IEnumerable<int> integerList()
{
yield return 1;
Thread.Sleep(1000);
yield return 2;
yield return 3;
}

static void Main()
{
printAll();
}
}
}

كامپايلر سي شارپ در عمل يك state machine را براي پياده سازي اين عمليات به صورت خودكار توليد خواهد كرد:

private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>2__current = 1;
this.<>1__state = 1;
return true;

case 1:
this.<>1__state = -1;
Thread.Sleep(0x3e8);
this.<>2__current = 2;
this.<>1__state = 2;
return true;

case 2:
this.<>1__state = -1;
this.<>2__current = 3;
this.<>1__state = 3;
return true;

case 3:
this.<>1__state = -1;
break;
}
return false;
}

در حين استفاده از يك IEnumerator ابتدا در وضعيت شيء Current آن قرار خواهيم داشت و تا زمانيكه متد MoveNext آن فراخواني نشود هيچ اتفاق ديگري رخ نخواهد داد. هر بار كه متد MoveNext اين enumerator فرخواني گردد (براي مثال توسط يك حلقه‌ي foreach) اجراي متد integerList ادامه خواهد يافت تا به yield return بعدي برسيم (ساير اعمال تعريف شده در حالت تعليق قرار دارند) و همينطور الي آخر.
از همين قابليت جهت مديريت اعمال Async پي در پي نيز مي‌توان استفاده كرد. State machine فوق تا پايان اولين عمليات تعريف شده صبر مي‌كند تا به yield return برسد. سپس با فراخواني متد MoveNext به عمليات بعدي رهنمون خواهيم شد. به اين صورت ديدگاهي پي در پي از يك سلسه عمليات غيرهمزمان حاصل مي‌گردد.

خوب ما الان نياز به يك كلاس داريم كه بتواند enumerator ايي از اين دست را به صورت خودكار مرحله به مرحله آن هم پس از پايان واقعي عمليات Async قبلي (يا مرحله‌ي قبلي)، اجرا كند. قبل از اختراع چرخ بايد متذكر شد كه ديگران اينكار را انجام داده‌اند و كتابخانه‌هاي رايگان و يا سورس بازي براي اين منظور موجود است.


ادامه دارد ...