‏نمایش پست‌ها با برچسب Silverlight. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب Silverlight. نمایش همه پست‌ها

۱۳۹۰/۰۴/۲۰

نمايش يك فايل PDF در WinForms ، WPF و سيلورلايت


شايد PDF را بشود تنها فرمت گزارشگيري دانست كه همه‌جا و در تمام سيستم عامل‌ها پشتيباني مي‌شود. از ويندوز تا لينوكس از وب تا WPF تا سيلورلايت تا همه جا و از همه مهم‌تر اينكه خروجي آن دقيقا همان چيزي است كه كاربر نهايي مي‌خواهد: من مي‌خوام اون چيزي رو كه مي‌بينم، دقيقا همان را، بدون كم و كاست و با همان صفحه بندي، بتوانم چاپ كنم.
براي توليد PDF مي‌شود از كتابخانه‌ي iTextSharp استفاده كرد اما براي نمايش آن حداقل در ويندوز بهترين راه حل استفاده از COM Components‌ شركت Adobe است كه به همراه برنامه رايگان Adobe PDF reader ارائه مي‌شود. در ادامه نحوه‌ي استفاده از اين Active-X را بررسي خواهيم كرد.

نمايش PDF در WPF
در تمام حالت‌ها هدف اين است كه به نحوي به اكتيوايكس شركت Adobe دسترسي پيدا كنيم؛ يا با اضافه كردن آن به پروژه يا استفاده از امكانات يكپارچه مرورگرها. در WPF از زمان ارائه سرويس پك يك دات نت سه و نيم (به بعد)، كنترل مرورگر وب هم به جمع كنترل‌هاي قابل استفاده در آن اضافه شده است. در اينجا به سادگي چند سطر زير مي‌شود يك فايل PDF را در WPF نمايش داد:
<Window x:Class="WpfAppTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pdf Report" Height="495" WindowState="Maximized"
WindowStartupLocation="CenterScreen" Width="703">
<Grid>
<WebBrowser x:Name="WebBrowser1"/>
</Grid>
</Window>
و بعد هم در كدهاي برنامه تنها كافي است كه مقدار Source كنترل WebBrowser را مقدار دهي كرد:
WebBrowser1.Source = new Uri(PdfFilePath);


نمايش PDF در WinForms
اكتيوايكس نمايش دهنده PDF شركت Adobe اساسا در فايل ذيل قرار گرفته است:
C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll
بنابراين براي استفاده از آن در يك برنامه‌ي WinForms بايد مراحل ذيل طي شود:
الف) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ب) سپس گزينه‌ي Adobe PDF reader كه به همان مسير dll فوق اشاره مي‌كند را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.
ج) اكنون اين كنترل جديد را بر روي فرم برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر به پروژه اضافه مي‌شوند.

اكنون نحوه‌ي استفاده از اين شيء COM به همراه آزاد سازي منابع مرتبط به شرح زير خواهند بود:
using System.Windows.Forms;

namespace WindowsFormsAppTests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
axAcroPDF1.Dispose();
}

void Form1_Load(object sender, System.EventArgs e)
{
axAcroPDF1.LoadFile(PdfFilePath);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}

نمايش PDF در Silverlight

در Silverlight هم از نسخه‌ي 4 به بعد كنترل WebBrowser همانند آنچه كه در WPF موجود است، اضافه شده است؛ اما اين كنترل فقط در حالت اجراي در خارج از مرورگر برنامه Silverlight در دسترس مي‌باشد. بنابراين روش ديگري را بايد انتخاب كرد. اين روش بر اساس تعامل سيلورلايت با كدهاي HTML صفحه كار مي‌كند. يك IFrame مخفي را در صفحه بالاي شيء مرتبط با سيلورلايت قرار خواهيم داد. سپس در سيلورلايت Src اين IFrame را به مسير فايل PDF تنظيم مي‌كنيم و همين. به اين ترتيب فايل PDF نمايش داده مي‌شود.
اين IFrame به صورت زير در همان صفحه‌ي aspx ايي كه object مرتبط با Silverlight نمايش داده مي‌شود قرار مي‌گيرد:
<iframe id="pdfFrame" style="visibility:hidden; position:absolute"><b>No Content</b></iframe>
<div id="silverlightControlHost">
سپس در كدهاي سيلورلايت، ابتدا اين IFrame يافت شده:
var iFrame = HtmlPage.Document.GetElementById("pdfFrame");
در ادامه بر اساس اطلاعات مكاني يك Grid ساده به نام pdfHost كه در صفحه قرار گرفته، اين iFrame بالاتر از سطح Grid (بر اساس z-index تنظيم شده) نمايش داده مي‌شود:
var gt = pdfHost.TransformToVisual(Application.Current.RootVisual);
var offset = gt.Transform(new Point(0, 0));
var controlLeft = (int)offset.X;
var controlTop = (int)offset.Y;
iFrame.SetStyleAttribute("left", string.Format("{0}px", controlLeft));
iFrame.SetStyleAttribute("top", string.Format("{0}px", controlTop));
iFrame.SetStyleAttribute("visibility", "visible");
iFrame.SetStyleAttribute("height", string.Format("{0}px", pdfHost.ActualHeight));
iFrame.SetStyleAttribute("width", string.Format("{0}px", pdfHost.ActualWidth));
iFrame.SetStyleAttribute("z-index", "1000");
و در آخر نام فايلي را كه مي‌خواهيم مشاهده كنيم به يك صفحه‌ي aspx در همان سايت ارسال مي‌كنيم:
iFrame.SetProperty("src", "ShowPdf.aspx?file=" + fileName);
كدهاي اين صفحه در حد يك Response.Redirect ساده براي نمايش دادن فايل pdf در مرورگر كافي هستند. در كل در اينجا سيلورلايت تنها نقش انتخاب فايل را به عهده دارد و كار اصلي را خود مرورگر انجام مي‌دهد.

۱۳۹۰/۰۱/۰۹

استفاده وسيع مايكروسافت از Silverlight در پروژه‌هاي جديد خود


برخلاف محصولات دهه قبل مايكروسافت، در تعداد زيادي از محصولات جديد سرور آن (خصوصا در كنترل پنل‌هاي تحت وب اين محصولات)، استفاده وسيعي از Silverlight مشاهده مي‌شود كه ليستي از آن‌ها را در ادامه مشاهده خواهيد كرد:

Lync Server 2010 (يا همان communication server قديم) محصولي است جهت مديريت ارتباطات پيشرفته: (+)



ويندوز Azure كه يكي از محصولات استراتژيك مايكروسافت محسوب مي‌شود: (+)



Windows Intune جهت بررسي و به روز رساني وضعيت شبكه، سرورها و كامپيوترهاي آن بكار مي‌رود: (+)



System Center براي مديريت سرورهاي مايكروسافت كاربرد دارد: (+)



پروژه جديد Crescent از تيم SQL Server جهت data visualization پيشرفته اطلاعات: (+)



در تكميل مثال‌هاي فوق مي‌توان به Visual Studio LightSwitch نيز اشاره كرد. هدف اصلي اين محصول فراهم آوردن امكان توليد برنامه‌هاي بانك اطلاعاتي مبتني بر سيلورلايت جهت افرادي با تجربه‌ي كمتر برنامه نويسي مي‌باشد: (+)

اين نوع محصولات ويژه سرور عموما جهت Windows platform تهيه مي‌شوند و زمانيكه بازه‌ي سيع‌تري از كاربران مدنظر باشند همانند Office web apps كمتر از Silverlight استفاده شده است و اينجا شايد اين سؤال مطرح شود كه چرا Silverlight ؟ در اين مورد مطلب مفصلي را اينجا مي‌توانيد مطالعه كنيد: (+)

۱۳۸۹/۰۸/۱۸

آدرس‌ها و ابزارهايي جهت سهولت دريافت ويديوهاي PDC 2010


اخيرا دو برنامه جهت دريافت ساده‌تر فايل‌هاي PDC 2010 با سيلورلايت تهيه شده‌اند كه بر اساس قابليت اجراي خارج از مرورگر آن (OOB=Out Of Browser) طراحي و پياده سازي شده‌اند:
برنامه‌ي آقاي Mike Taulty سورس باز بوده و بر اساس الگوي MVVM پياده سازي شده است.


علاوه بر آن يك ليست ديگر نيز در اين زمينه وجود دارد:

۱۳۸۹/۰۶/۳۰

چه زماني بهتر است از Silverlight استفاده شود؟


1- نياز به توانايي‌هاي موجود در برنامه‌هاي Desktop را داريد اما همچنين نياز است تا آن‌ها را تحت وب نيز ارائه دهيد.
يكي از دلايل اقبال به برنامه‌هاي تحت وب در سازمان‌ها عدم نياز به نصب آن‌ها و توزيع هر چه ساده‌تر اينگونه برنامه‌ها در شبكه است. تنها كافي است چند فايل را بر روي سرور به روز رساني كنيد و پس از آن تمام كلاينت‌ها از آخرين نگارش برنامه شما بهره‌مند خواهند شد (+). توزيع برنامه‌هاي سيلورلايت نيز به همين منوال است. علاوه بر آن استفاده از فناورهايي مانند MEF امكان ماژولار ساختن برنامه و دريافت آخرين ماژول‌هاي تهيه شده (فايل‌هاي XAP مجزاي از برنامه به صورت افزونه) را بر اساس انتخاب و سطح دسترسي كاربر نيز ميسر مي‌سازد.

2- نياز است تا يك برنامه‌ي گرافيكي تمام عيار را تحت وب ارائه دهيد.
توانايي‌هاي XAML به همراه يكي از زبان‌هاي دات نت جهت خلق جلوه‌هاي بصري، پويانمايي و گرافيكي بسيار بسيار فراتر از كتابخانه‌هاي جاوا اسكريپتي موجود هستند و نكته‌ي مهم آن‌ها هم اين است كه لازم نيست حتما يك متخصص مثلا جاوا اسكريپت باشيد تا بتوانيد براي مثال پويانمايي را ارائه دهيد. امكان استفاده از انواع و اقسام قلم‌ها و قرار دادن آن‌ها در برنامه، امكان استفاده از گرافيك برداري و غيره را نيز لحاظ كنيد.

3- برنامه‌ي شما نياز است تا از طريق وب توزيع شود اما نياز به سطح دسترسي بيشتري نسبت به يك برنامه‌ي وب معمولي دارد.
تمام برنامه‌هاي توزيع شده از طريق مرورگرها محدود به سطوح دسترسي آن‌ها نيز هستند. اما امكان نصب خارج از مرورگر برنامه‌هاي سيلورلايت نيز وجود دارد. در اين حالت مي‌توان در صورت نياز و همچنين تائيد صريح كاربر، به سطوح دسترسي بيشتري دست يافت. براي مثال دسترسي به اسكنر در يك برنامه‌ي وب متداول بي‌معنا است. اما سيلورلايت 4 در حالت اجراي در خارج از مرورگر امكان تعامل با اشياء COM را نيز دارد.

4- برنامه‌ي وب شما نياز است تا مدت زمان زيادي فعال باقي بماند.
يك برنامه دريافت ايميل يا يك برنامه مونيتورينگ را در نظر بگيريد. اينگونه برنامه‌ها بايد مرتبا بدون نياز به دخالت كاربر، فعال باقي بمانند و با سرور ارتباط داشته باشند. نوشتن اينگونه برنامه‌ها با HTML و جاوا اسكريپت و فناوري‌هاي مشابه واقعا مشكل بوده و نياز به دانش فني بالايي دارند. اما اين مساله و حيات يك برنامه سيلورلايت تا زمانيكه مرورگر بسته نشده است جزو خواص اوليه اينگونه برنامه‌ها است.

5- از مشكلات مديريت حالت در برنامه‌هاي متداول وب به تنگ آمده‌ايد.
اگر براي مثال برنامه نويس ASP.NET باشيد حتما با مباحث State management آشنايي داريد (از سشن و كوكي گرفته تا ViewState (ايي كه همه به نحوي قصد كوچك كردن آن‌را دارند!) و غيره). تمام اين‌ها هم براي اين است كه بتوان تجربه‌ي كاري برنامه‌هاي دسكتاپ را در محيط مرورگرها شبيه سازي كرد. اين مشكلات در سيلورلايت حل شده است. يك برنامه‌ي سيلورلايت State full است نه Stateless . همچنين اگر از حافظه‌اي هم استفاده مي‌كند اين مورد در سمت كاربر است و نه سمت سرور و نه منقضي شدن زود هنگام سشن‌ها و صدها ترفند براي مقياس پذيري همين مساله‌ي بسيار كوچك با تعداد كاربران بالا در برنامه‌هاي متداول وب.
به عبارتي تصور كنيد كه برنامه‌ي دسكتاپ سال‌هاي قبل شما هم اكنون داخل مرورگر دارد اجرا مي‌شود و چيزي به نام وب سرور وجود ندارد كه پس از نمايش صفحه‌ي وب شما، كليه‌ي اشياء مرتبط با آن‌را در سمت سرور تخريب كند چون بايد پاسخگوي كاربران همزمان بي‌شماري باشد و منابع سرور هم محدود است. (سيلورلايت يك فناوري سمت كاربر است. بنابراين وب سرور صرفا نقش توزيع آن‌را به عهده دارد يا حداكثر ارائه‌ي يك وب سرويس جهت تعاملات بعدي مانند كار با بانك اطلاعاتي)

6- نياز داريد تا برنامه‌ي وب شما تحت تمام مرورگرها به يك شكل به نظر برسد و همچنين رفتار يكساني هم داشته باشد.
هيچ وقت روزي را فراموش نمي‌كنم كه حين پرداخت الكترونيكي بانك XYZ به كمك مرورگر فايرفاكس، دكمه‌ي پرداخت در مرحله‌ي آخر، كار نمي‌كرد! هر چقدر روي آن كليك مي‌كردم اتفاقي نمي‌افتاد! تراكنش برگشت خورد و همين خريد ساده با مرورگر IE به سادگي انجام شد.
با سيلورلايت اين مشكلات را نخواهيد داشت زيرا كار نمايش برنامه شما توسط افزونه‌ي مربوطه صورت مي‌گيرد و اين افزونه مستقل است از نوع مرورگر شما.

7- نياز است برنامه‌ي وب شما در حالت آفلاين هم كار كند.
برنامه‌هاي سيلورلايت تنها زمانيكه نياز به دريافت يا ثبت اطلاعاتي از سرور داشته باشند، بايد آنلاين باشند. همچنين اين برنامه‌ها دسترسي به مفهوم جديدي به نام Isolated Storage دارند كه در آن مي‌توان اطلاعات را به ازاي هر كاربر آن هم با ضريب امنيتي بالا بر روي هارد شخص ذخيره كرد و زمان آنلاين شدن برنامه آن‌ها را به سرور انتقال داد.

8- برنامه وب شما نياز است تا با فايل‌هاي مالتي مديا تعامل داشته و آن‌ها را پخش كند.
حتي تگ Video در HTML5 نيز به پاي توانايي‌هاي مالتي مديا در Silverlight مانند smooth streaming, multicasting, editing, video brushes نمي‌رسد. براي مثال با استفاده از video brushes مي‌توان يك فايل ويديويي در حال پخش را بر روي يك وجه يك شيء در حال پويانمايي نقاشي و نمايش داد.

9- نياز به پشتيباني از multi-touch در برنامه‌ي وب شما وجود دارد.
برخلاف HTML ، تعاملات multi-touch در Silverlight ميسر است.

10- نياز به ايجاد برنامه‌هاي بازي تحت وب داريد.
به طور قطع مي‌توان بازيي‌هايي در حد Pong را با جاوا اسكريپت هم ايجاد كرد، اما اگر نياز به توليد بازي‌هايي جدي‌تر وجود داشت براي مثال انتقال بازي Quake به محيط وب، Silverlight در اين زمينه هم حرف‌هاي زيادي براي گفتن دارد (+).

11- نياز به توليد برنامه‌ي دسكتاپ چند سكويي داريد.
سيلورلايت هم اكنون تحت ويندوز، MAC OS-X ، لينوكس و ... پشتيباني مي‌شود (+). همچنين برنامه‌هاي سيلورلايت قابليت اجراي در خارج از مرورگر را هم دارند.
با سيلورلايت ديگر نيازي نخواهد بود تا كاربران لينوكسي ابتدا Wine را نصب كنند تا بتوانند از يك برنامه‌ي ويندوزي كه انتقال پذير نيست در لينوكس هم بتوانند استفاده كنند؛ چون پروژه‌ي مون لايت لينوكسي براي اين منظور مهيا است.

12- نياز به توليد برنامه‌هاي تحت وب سريع و با كارآيي بالا داريد.
فايل‌هاي نهايي Silverlight با توجه به ماهيت كامپايل شده‌ي آن‌ها به طور قطع از كدهاي جاوا اسكريپتي سمت كلاينت كه بايد توسط مرورگر تفسير و پردازش شوند (و هر كدام هم از موتور خاص خودشان استفاده مي‌كنند)، سريعتر اجرا مي‌شوند (+).

13- از پيچيدگي‌هاي پياده سازي برنامه‌هاي متداول وب خسته شده‌ايد.
هنوز هم با تمام پيشرفت‌هاي حاصل، توليد برنامه‌هاي وب پيشرفته مشكل است. از يك طرف ناسازگاري يك سري از مرورگرها با يك سري از قابليت‌ها را بايد در نظر داشت، تا فراگيري فريم ورك‌هاي Ajax و غيره تا مشكل بودن طراحي كنترل‌هاي جديد فراتر از آن چيزي كه HTML استاندارد ارائه مي‌دهد. بله، به طور قطع دانش فني بالايي در اين زمينه در طي ساليان توليد شده است، اما باز هم فراگيري و تسلط به آن‌ها زمان قابل توجهي را طلب مي‌كند.
در سيلورلايت كليه تعاملات با شبكه به صورت پيش فرض غيرهمزمان است (همان ايده‌ي اصلي Ajax) همچنين با توجه به state full بودن اينگونه برنامه‌ها، عملا برنامه نويس‌ها بدون درگير شدن با مفاهيم اجكسي و مديريت حالت، برنامه‌ي پيشرفته‌ي وبي را در مدت زمان كوتاهي توليد كرده‌اند و اين برنامه در تمام مرورگرهايي كه قابليت بارگذاري افزونه‌ي سيلورلايت را دارند به يك شكل و كيفيت اجرا مي‌شود.

14- در زمينه ميزان مصرف پهناي باند ملاحظاتي ويژه‌اي وجود دارد.
يك برنامه‌ي سيلورلايت تنها يكبار بايد دريافت شود. پس از آن در سمت كاربر كش خواهد شد (تا زمان به روز رساني بعدي برنامه در سرور). همين مساله در دفعات بعدي مراجعه كاربر به سايت نقش قابل توجهي را در كاهش ميزان مصرف پهناي باند (يا به قولي ميزان كمتر data transfer) كلي دارد.

15- فرصت كافي براي فراگيري انبوهي از فناوري‌هاي مختلف را نداريد!
بله! براي ايجاد يك برنامه‌ي تحت وب كه كاربر آن پس از مشاهده بگويد WOW نياز است به HTML ، JS ، CSS ، AJAX ، يكي از فناوري‌هاي سمت سرور و ... مسلط بود (علاوه بر اينكه بايد بدانيد فلان كد JS در IE كار مي‌كند اما در فايرفاكس خير. فايرفاكس فلان قسمت CSS را پشتيباني مي‌كند اما IE خير! و ...).
اما براي استفاده از سيلورلايت فقط كافي است به XAML و يكي از زبان‌هاي دات نت مانند سي شارپ يا VB.NET مسلط باشيد (البته هيچ وقت از دست ASP.NET خلاص نخواهيد شد! حداقل در حد راه اندازي يك وب سرويس يا مفاهيم امنيتي آن).
اين مورد خصوصا براي افرادي كه برنامه نويس دسكتاپ هستند اما علاقمندند تا برنامه‌ي وب نيز توليد كنند بسيار مهم است. با حداقل آموزش مي‌توانند توانايي‌هاي خود را به وب نيز گسترش دهند. علاوه بر آن عمده‌ي دانش Silverlight شما جهت توليد برنامه‌هاي WPF (با توجه به اينكه Silverlight فرزند WPF محسوب مي‌شود) يا Windows phone 7‌ و غيره نيز مي‌تواند بكار گرفته شود.

16- نياز به اجراي كدهاي چند ريسماني در سمت كاربر داريد.
تا اين لحظه پشتيباني رسمي از مباحث چند ريسماني در JavaScript و استانداردهاي مرتبط با آن وجود ندارد. Silverlight به اكثر امكانات Threading موجود در دات نت فريم ورك دسترسي داشته و دانش فعلي شما قابل انتقال است.


و دست آخر بايد به نكته اشاره كرد كه هدف از Silverlight ساخت وب سايت معمولي نيست. اين نوع كارها را با همان ابزارهاي متداول انجام دهيد. هدف اصلي آن ساخت برنامه است (Application در مقابل Web site). مشتري‌هاي اصلي اين نوع برنامه‌ها هم بيشتر سازمان‌ها و اينترانت‌هاي پر سرعت و بسته‌ي آن‌ها هستند كه نه نگران حجم افزونه‌ي سيلورلايت هستند و نه مشكلي با حجم برنامه‌ي سيلورلايت شما در يك شبكه‌ي داخلي پر سرعت دارند.

۱۳۸۹/۰۶/۲۹

نكته‌اي مهم در طراحي قالب‌ برنامه‌هاي Silverlight


قالب سيلورلايتي را ايجاد كرده بودم و IE در حالت نمايش عادي اين قالب 30 درصد CPU Usage ثابت داشت. علت را هم متوجه نمي‌شدم؛ چون در اين حالت اصلا كدي وجود نداشت كه بخواهد CPU Usage ايي را ايجاد كند. يك سري كد XAML جهت نمايش قالب در كنار هم قرار گرفته بودند و همين.
تا اينكه ديروز در وبلاگ رسمي مرتبط با كارآيي برنامه‌هاي Silverlight مطلبي منتشر شد كه دقيقا مشكل طراحي قالب من هم همان بود:

خلاصه آن:
اگر در حالت نمايش برنامه Silverlight شما (بدون اينكه كدي در حال اجرا باشد) به صورت ثابت CPU Usage بالايي را مشاهده مي‌كنيد، پارامتر enableRedrawRegions تگ بارگذاري افزونه‌ي Silverlight را به true مقدار دهي كنيد.
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2">
...
<param name="enableRedrawRegions" value="true"/>
...
</object>

پس از فعال سازي اين گزينه، برنامه را اجرا كنيد. نواحي را كه مرتبا در حال ترسيم مجدد هستند، با رنگ‌هاي آبي، زرد و صورتي مشاهده خواهيد كرد.
در اين حالت اگر ناحيه‌اي مرتبا در حال به روز رساني مشاهده گرديد، دقيقا همين ناحيه است كه سبب CPU Usage بالا و ثابت برنامه شما شده است و بايد فكري به حال آن كرد.

چه زماني ممكن است اين حالت (ترسيم‌هاي مجدد بدون پايان) رخ دهد؟
عموما اين مشكل در حين استفاده ناصحيح از افكت‌هاي پيش فرض Silverlight مانند DropShadowEffect رخ مي‌دهد. براي مثال مي‌خواهيد قسمتي از قالب شما سايه دار باشد اما نحوه‌ي اعمال اين سايه بسيار مهم است.
        <Border CornerRadius="3">
<!--High cpu usage-->
<Border.Effect>
<DropShadowEffect BlurRadius="7" Color="#FF1E2224" Opacity="3" ShadowDepth="6" Direction="200" />
</Border.Effect>
<StackPanel>
...
...

در اين مثال ابتدا يك border تعريف شده و سپس سايه‌اي به آن اعمال گرديده است. سپس داخل اين Border يك StackPanel قرار گرفته است به همراه يك سري از اشياء زير مجموعه آن. اين كار غلط است! همين مورد به ظاهر ساده 30 درصد CPU Usage ثابت را در برنامه ايجاد كرده بود.

علت چيست؟
با اين نحوه‌‌ي تعريف اشتباه DropShadowEffect ، هر نوع تغيير بصري در مجموعه‌ي Border و StackPanel داخل آن، سبب ترسيم مجدد كل ناحيه مي‌گردد. اين تغيير بصري حتي مي‌تواند شامل چشمك زدن يك cursor درون يك TextBox در قسمتي از اين ناحيه نيز باشد كه با استفاده از ويژگي enableRedrawRegions ، اين مورد را به خوبي مي‌توان مشاهده نمود.

راه حل اين مساله كدام است؟
از دو Border استفاده كنيد. يك Border با ضخامت كم تنها براي نمايش سايه (كه داراي هيچ نوع شيء فرزندي نيست) و Border و StackPanel قبلي هم به همان صورت ابتدايي (البته با حذف DropShadowEffect از آن) باقي بماند.

۱۳۸۹/۰۶/۲۵

كنترل DatePicker شمسي مخصوص Silverlight 4


Silverlight 4 تاريخ شمسي را از دات نت فريم ورك به ارث نبرده است (+). اما اضافه كردن آن كار خاصي نيست. مجموعه‌ي سورس باز Silverlight toolkit هم داراي DatePicker تاريخ ميلادي است اما به دلايلي كه عرض شد، تاريخ شمسي را پشتيباني نمي‌كند.

كارهايي كه توسط ساير برنامه نويس‌هاي ايراني تابحال در اين مورد انجام شده است:
- اضافه كردن DatePicker فارسي به مجموعه‌ي Silverlight toolkit : (+)
به دو دليل من از اين راه حل استفاده نخواهم كرد:
الف) patch ارائه شده هنوز با Silverlight toolkit يكپارچه نشده است و هربار بايد اين تغييرات را اعمال كرد و غيره ...
ب) شايد من اصلا نخواهم كه از Silverlight toolkit استفاده كنم. آن وقت چه بايد كرد؟
اين تنها كاري است كه جهت Silverlight انجام شده است.

دو نمونه‌ي خوب ديگر هم براي WPF موجود است كه تبديل آن‌ها به Silverlight كار ساده‌اي نيست (چون Silverlight تمام كلاس‌هاي WPF را نيز به ارث نبرده است):
- Farsi Library - Working with Dates, Calendars, and DatePickers
- PersianDate and some WPF controls for it

به همين جهت يك كنترل DatePicker و تقويم شمسي مستقل را براي Silverlight 4 آماده كرده‌ام كه از آدرس ذيل قابل دريافت است:





نحوه استفاده:
الف) ارجاعي را به اسمبلي SilverlightPersianDatePicker.dll به پروژه خود اضافه كنيد. اگر مباحث library caching هم براي شما مهم است، فايل SilverlightPersianDatePicker.extmap.xml پيوست شده را نيز فراموش نكنيد.
ب) xmlns آن بايد به XAML جاري اضافه شود؛ براي مثال:
xmlns:dp="clr-namespace:SilverlightPersianDatePicker.Views;assembly=SilverlightPersianDatePicker"
ج) سپس استفاده از آن به سادگي يك سطر زير خواهد بود:
<dp:PDatePicker x:Name="txtDate" TextBoxWidth="100"  Margin="5"  />

خاصيت SelectedDate آن تاريخ ميلادي و خاصيت SelectedPersianDate آن تاريخ شمسي را بر مي‌گرداند.


كتابخانه‌هاي كمكي كه در حين توسعه‌ي آن استفاده شدند:
كلاس تقويم شمسي اميد خندان راد (كه براي روزهاي دات نت 1 تهيه شده بود).
پنل UniformGrid كه در Silverlight موجود نيست.(+)
رفتار StaysOpen مرتبط با Popup كه در Silverlight از WPF به ارث نرسيده است.(+)
استفاده از كلاس DelegateCommand جان پاپا (براي سهولت Commanding در الگوي MVVM). (+)


۱۳۸۹/۰۶/۲۲

مديريت رخدادهاي MouseLeftButtonDown و MouseLeftButtonUp در Silverlight


نياز بود تا بتوان رخدادهاي MouseLeftButtonDown و MouseLeftButtonUp يك TextBox را در Silverlight مديريت كرد. شايد عنوان كنيد كه خيلي ساده است! دو روال رخداد گردان مربوطه را اضافه كنيد و سپس تعاريف آن‌ها را در كدهاي XAML خود قيد نمائيد. اما واقعيت اين است كه كار نمي‌كند! نه؛ كار نمي‌كند! :)

مشكل از كجاست؟ پاسخي كه در MSDN در اين مورد آمده است به صورت زير مي‌باشد:

"Certain control classes (for example Button) provide control-specific handling for mouse events such as MouseLeftButtonDown. The control-specific handling typically involves handling the event at a class level rather than at the instance level, and marking the MouseLeftButtonDown event data's Handled value as true such that the event cannot be handled by instances of the control class, nor by other objects anywhere further along the event route. In the case of Button, the class design does this so that the Click event can be raised instead."


به عبارتي رخداد Click زحمت كشيده و رخدادهاي MouseLeftButtonDown و MouseLeftButtonUp را نيز handled معرفي مي‌كند و ديگر روال رخدادگردان شما فراخواني نخواهد شد (در WPF هم به همين صورت است) و موارد ذكر شده در visual tree يك TextBox منتشر نمي‌گردند.

راه حل چيست؟

حداقل دو راه حل وجود دارد:
الف) يك كنترل TextBox سفارشي را از كنترل TextBox اصلي بايد به ارث برد و رخدادهايي را كه توسط رخداد Click به صورت handled معرفي شده‌اند، unhandled كرد:

public class MyTextBox : TextBox
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}

protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
e.Handled = false;
}
}

ب) يا امكان گوش فرا دادن به رخدادهاي handled نيز ميسر است. فقط كافي است اين گوش فرادهنده را توسط متد AddHandler كه پارامتر آخر آن به true تنظيم شده است (رخدادهاي handled نيز لحاظ شوند)، معرفي كرد:

public MainPage()
{
InitializeComponent();
txt1.AddHandler(FrameworkElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(txt1_MouseLeftButtonDown), true);
}
private void txt1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{

//do something
}

۱۳۸۹/۰۶/۱۲

به روز رساني‌هاي مهم هفته دوم شهريور 89


  • نسخه‌ي جديد برنامه Resharper ارائه شده به همراه بهبودهايي در كارآيي آن.
ليست موارد برطرف شده : +
دريافت : +

  • به روز رساني‌هايي هم در مورد سيلورلايت 4 ارائه شده و اگر آپديت ويندوز شما روشن بوده باشد، حتما حداقل runtime آن‌را به صورت خودكار دريافت كرده‌ايد و از آنجائيكه visual studio LightSwitch هم مبتني بر سيلورلايت 4 و WCF RIA Services است؛ اين به روز رساني‌ها شامل حال اين برنامه نيز مي‌گردد.

دريافت SDK جديد: +
دريافت Runtime جديد: +
توضيحات بيشتر در مورد موارد فيكس شده: +


۱۳۸۹/۰۵/۲۹

نحوه‌ي مشاهده‌ي خروجي SQL توليد شده توسط WCF RIA Services


اين روزها با وجود ORMs ، كوئري SQL‌ نوشتن شبيه به دوراني شده كه با وجود زبان‌هاي سطح بالا، عده‌اي علاقمند هستند با استفاده از زبان اسمبلي برنامه نويسي كنند! WCF RIA Services به صورت پيش فرض از entity framework استفاده مي‌كند (هر چند مي‌توان از ساير ORMs هم استفاده كرد)، بنابراين عنوان صحيح‌تر بحث اين خواهد بود: چگونه خروجي SQL توليد شده توسط Entity framework را بررسي كنيم؟

الف) استفاده از SQL Server profiler
اولين برنامه‌اي كه از سال‌ها قبل، حتي پيش از ظهور ORMs وجود داشته، برنامه‌ي SQL server profiler است، كه عموما در مسير ذيل قابل دستيابي است:
Start Menu->Programs->Microsoft SQL Server 2008->Performance Tools->SQL Server profiler



نكته مهم:
حين كار با SQL Server profiler ، ممكن است انبوهي از كوئري‌هاي ديگر مثلا مرتبط با SQL Server agent يا reporting services و غيره نيز لاگ شوند. اما الان ما تنها به كوئري‌هاي برنامه‌ي خود نياز داريم. براي اين منظور به كانكشن استرينگ خود، گزينه‌ي Application Name=My Application Name را نيز اضافه كنيد:

<connectionStrings>
<add name="dmEntities" connectionString="metadata=res://*/Models.dmDataModel.csdl|res://*/Models.dmDataModel.ssdl|res://*/Models.dmDataModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=(local);Initial Catalog=dm;Integrated Security=True;Application Name=My Application Name;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
</connectionStrings>

اكنون اگر برنامه را با پروفايلر مورد بررسي قرار دهيد خروجي به صورت زير خواهد بود:



براي فيلتر كردن Application Name مورد نظر، در ابتداي كار كه يك سشن جديد را آغاز مي‌كنيد به برگه‌ي events selection مراجعه كرده و بر روي دكمه‌ي column filter كليك كنيد. گزينه‌ي application name را در صفحه‌ي باز شده انتخاب نموده و در قسمت Like آن مطابق تصوير زير ، نام برنامه‌ي خود را وارد نمائيد:




ب) استفاده از IntelliTrace در VS.NET 2010
برنامه را در حالت ديباگ در VS.NET 2010 اجرا كنيد. در هر لحظه‌اي مي‌توان روي گزينه‌ي Break all كليك كرد و خروجي SQL توليد شده را نيز علاوه بر اطلاعات ديگر مشاهده نمود:




ج) استفاده از برنامه‌ي حرفه‌اي entity framework profiler
اين برنامه از هر دو مورد قبل كاملتر بوده و اساسا براي لاگ كردن كوئري‌ها، مدت زمان اجرا، گزارشگيري از وضعيت برنامه، كداميك از كوئري‌ها سنگين‌تر هستند، حتي از طريق كدام متد فراخواني شده‌اند، ارائه‌ي گزارشات و راهنمايي‌هايي در مورد چگونگي بهبود كارآيي برنامه‌ي تهيه شده و امثال آن كاربرد دارد.



استفاده از آن هم بسيار ساده است. ابتدا ارجاعي را به اسمبلي HibernatingRhinos.Profiler.Appender.v4.0 به پروژه‌ي ASP.NET خود اضافه كنيد (همان پروژه‌ي هوست مربوط به WCF RIA Service ما). سپس به فايل Global.asax.cs برنامه مراجعه كرده و يك سطر ذيل را اضافه كنيد:

protected void Application_Start(object sender, EventArgs e)
{
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
}

از اين پس تنها كافي است برنامه‌ي پروفايلر در حال اجرا بوده و برنامه شما نيز اجرا شود. كليه‌ي تبادلات با ديتابيس لاگ خواهند شد.

۱۳۸۹/۰۵/۲۴

نحوه‌ي فعال سازي library caching زمانيكه يك Silverlight library را توليد كرده‌ايم


در مورد كاهش حجم فايل‌هاي XAP سيلورلايت زمانيكه از اسمبلي‌هاي كتابخانه‌هاي ديگر مانند Silverlight toolkit استفاده مي‌شود، در اين فصل بحث شده است و راه حل، استفاده از گزينه‌ي reduce XAP size by using application library caching است. به اين صورت كاربران ديگر به ازاي هر بار مشاهده‌ي سايت نيازي نخواهند داشت تا يك سري كتابخانه‌ي كمكي را كه هيچ تغييري در آن‌ها حاصل نخواهد شد، دريافت كنند و اطلاعات آن‌ها از cache مرورگر خوانده مي‌شود. اين مورد با كتابخانه‌ها و ابزارهاي كمكي توليد شده توسط مايكروسافت كار مي‌كند. اما اگر خودتان يك Silverlight library را توليد كنيد، چنين اتفاقي رخ نخواهد داد و باز هم فايل اسمبلي كتابخانه‌ي شما درون فايل XAP اصلي برنامه قرار گرفته و خبري از caching مجزاي آن نيست. چرا اينطور است؟ چكار بايد كرد؟!
علت آن بر مي‌گردد به نحوه‌ي پياده سازي library caching در VS.NET و Silverlight . براي اين منظور چند مرحله بايد طي شود تا اين قابليت براي كتابخانه‌هاي ساخت خودمان نيز فعال گردد:
الف) به كتابخانه‌ي خود بايد امضاي ديجيتال اضافه كنيد:
اينكار با استفاده از امكانات خود VS.NET بسيار ساده است. به خواص پروژه مراجعه كنيد. سپس برگه‌ي Signing را باز كرده و گزينه‌ي Sign the assembly را انتخاب كنيد (شكل زير). در قسمت choose a strong name key file ، گزينه‌ي new را انتخاب كرده و پس از وارد كردن يك نام دلخواه و گذر واژه‌اي، فايل pfx امضاي ديجيتال اسمبلي شما توليد خواهد شد. اكنون تنها كافي است يكبار ديگر برنامه را كامپايل كنيد.


ب) به يك فايل extMap.xml هم نياز است:
هنگام پياده سازي قابليت library caching ، VS.NET به دنبال فايلي به نام AssemblyFileName.extmap.xml دقيقا در كنار فايل اسمبلي مورد نظر مي‌گردد. ساختار عمومي اين فايل XML به صورت زير است:

<?xml version="1.0"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<assembly>
<name>SLHelper</name>
<version>1.0.0.0</version>
<publickeytoken>f265933def965412</publickeytoken>
<relpath>SLHelper.dll</relpath>
<extension downloadUri="SLHelper.zip" />
</assembly>
</manifest>

نام، شماره نگارش، مسير قرارگيري فايل اسمبلي مورد نظر و همچنين نام نهايي آن حين جدا سازي آن از XAP برنامه بايد مشخص گردد. گزينه‌ي publickeytoken مهم‌ترين تنظيم اين فايل است و قسمت الف را به همين منظور نياز داشتيم. اين عدد را به سادگي با استفاده از برنامه‌ي reflector مي‌توان بدست آورد (شكل زير).



جهت ساده سازي قسمت (ب)، برنامه‌ي كمكي را از آدرس ذيل مي‌توانيد دريافت كنيد:
Utility: Extmap Maker

براي مطالعه بيشتر
Silverlight 3: Cached Assemblies and you can to

۱۳۸۹/۰۵/۲۰

يكپارچه كردن ELMAH با WCF RIA Services


پيشتر در مورد ELMAH مطلبي را منتشر كرده بودم و اگر برنامه نويس ASP.NET هستيد و با ELMAH آشنايي نداريد،‌ جدا نيمي از عمر كاري شما بر فنا است!
هاست پيش فرض يك WCF RIA Service هم يك برنامه‌ي ASP.NET است. بنابراين كليه‌ي خطاهاي رخ داده در سمت سرور را بايد بتوان به نحوي لاگ كرد تا بعدا با مطالعه‌ي آن‌ها اطلاعات ارزشمندي را از نقايص برنامه در عمل و پيش از گوشزد شدن آن‌ها توسط كاربران، دريافت، بررسي و رفع كرد.
كليه خطاها را لاگ مي‌كنم تا:
- بدانم معناي جمله‌ي "برنامه كار نمي‌كنه" چي هست.
- بدون روبرو شدن با كاربران يا حتي سؤال و جوابي از آن‌ها بدانم دقيقا مشكل از كجا ناشي شده.
- بدانم رفتارهاي عمومي كاربران كه منجر به بروز خطا مي‌شوند كدام‌ها هستند.
- بدانم در كداميك از قسمت‌هاي برنامه تعيين اعتبار ورودي كاربران يا انجام نشده يا ضعيف و ناكافي است.
- بدانم زمانيكه دوستي (!) قصد پايين آوردن برنامه را با تزريق SQL داشته، دقيقا چه چيزي را وارد كرده، در كجا و چه زماني؟
- بتوانم Remote worker خوبي باشم.

ELMAH هم براي لاگ كردن خطاهاي مديريت نشده‌ي يك برنامه‌ي ASP.NET ايجاد شده است. بنابراين بايد بتوان اين دو (WCF RIA Services و ELMAH) را به نحوي با هم سازگار كرد. براي اينكار نياز است تا يك مديريت كننده‌ي خطاي سفارشي را با پياده سازي اينترفيس IErrorHandler تهيه كنيم (تا خطاهاي مديريت نشده‌ي حاصل را به سمت ELMAH هدايت كند) و سپس آن‌را به كمك يك ويژگي يا Attribute به DomainService خود جهت لاگ كردن خطاها اعمال نمائيم. روش تعريف اين Attribute را در كدهاي بعد ملاحظه خواهيد نمود (در اينجا نياز است تا دو ارجاع را به اسمبلي‌هاي Elmah.dll كه دريافت كرده‌ايد و اسمبلي استاندارد System.ServiceModel نيز به پروژه اضافه نمائيد):

//add a reference to "Elmah.dll"
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;

namespace ElmahWcf
{
public class HttpErrorHandler : IErrorHandler
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
return false;
}

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error == null)
return;

if (HttpContext.Current == null) //In case we run outside of IIS
return;

Elmah.ErrorSignal.FromCurrentContext().Raise(error);
}
#endregion
}
}

//add a ref to "System.ServiceModel" assembly
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace ElmahWcf
{
public class ServiceErrorBehaviorAttribute : Attribute, IServiceBehavior
{
Type errorHandlerType;
public ServiceErrorBehaviorAttribute(Type errorHandlerType)
{
this.errorHandlerType = errorHandlerType;
}

#region IServiceBehavior Members

public void AddBindingParameters(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters)
{ }

public void ApplyDispatchBehavior(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
cd.ErrorHandlers.Add(errorHandler);
}
}

public void Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{ }
#endregion
}
}
اكنون پس از تعريف ويژگي ServiceErrorBehavior، نوبت به اعمال آن مي‌رسد. به فايل DomainService خود مراجعه كرده و يك سطر زير را به آن اضافه نمائيد:
    [ServiceErrorBehavior(typeof(HttpErrorHandler))] //Integrating with ELMAH
[EnableClientAccess()]
public partial class MyDomainService : LinqToEntitiesDomainService<myEntities>

در ادامه نحوه‌ي افزودن تعاريف متناظر با ELMAH به Web.Config برنامه ذكر شده است. اين تعاريف براي IIS6 و 7 به بعد هم تكميل گرديده است. خطاها هم به صورت فايل‌هاي XML در پوشه‌اي به نام Errors كه به ريشه‌ي سايت اضافه خواهيد نمود (يا هر پوشه‌ي دلخواه ديگري)، لاگ مي‌شوند.
به نظر من اين روش، از ذخيره سازي اطلاعات لاگ‌ها در ديتابيس بهتر است. چون اساسا زمانيكه خطايي رخ مي‌دهد شايد مشكل اصلي همان ارتباط با ديتابيس باشد.
قسمت ارسال خطاها به صورت ايميل نيز comment شده است كه در صورت نياز مي‌توان آن‌را فعال نمود:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>

<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/Errors" />
<!-- <errorMail
from="errors@site.net"
to="nasiri@site.net"
subject="prj-error"
async="true"
smtpPort="25"
smtpServer="mail.site.net"
noYsod="true" /> -->
</elmah>

<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="DomainServiceModule"
preCondition="managedHandler"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add name="Elmah" verb="POST,GET,HEAD" path="myelmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</handlers>
</system.webServer>
<system.web>
<globalization
requestEncoding="utf-8"
responseEncoding="utf-8"
/>
<authentication mode="Forms">
<!--one month ticket-->
<forms name=".403AuthV"
cookieless="UseCookies"
slidingExpiration="true"
protection="All"
path="/"
timeout="43200" />
</authentication>
<httpHandlers>
<add verb="POST,GET,HEAD" path="myelmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="DomainServiceModule"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpModules>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>
</compilation>
</system.web>
<connectionStrings>
</connectionStrings>
<system.serviceModel>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
اكنون براي مثال به يكي از متدهاي DomainService خود سطر زير را اضافه كرده و برنامه را آزمايش كنيد:
throw new Exception("This is an ELMAH test");

سپس به آدرس http://localhost/myelmah.axd مراجعه نموده و اطلاعات لاگ شده حاصل را بررسي كنيد:


اين روش با WCF Services هاي متداول هم كار مي‌كند. فقط در اين سرويس‌ها بايد aspNetCompatibilityEnabled مطابق تگ‌هاي ذكر شده‌ي system.serviceModel فوق در web.config لحاظ شوند (اين مورد به صورت پيش فرض در WCF RIA Services وجود دارد). همچنين ويژگي زير نيز بايد به سرويس شما اضافه گردد:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

منابع مورد استفاده:
Integrating ELMAH for a WCF Service
Making WCF and ELMAH play nice together
Getting ELMAH to work with WCF services



پ.ن.
اگر به خطاهاي ASP.NET دقت كرده باشيد كه به yellow screen of death هم مشهور هستند (در مقابل صفحات آبي ويندوز!)، ابتداي آن خيلي بزرگ نوشته شده Server Error و سپس ادامه‌ي خطا. همين مورد دقيقا يادم هست كه هر بار سبب بازخواست مديران شبكه بجاي برنامه نويس‌ها مي‌شد! (احتمالا اين هم يك نوع بدجنسي تيم ASP.NET براي گرفتن حال ادمين‌هاي شبكه است! و گرنه مثلا مي‌توانستند همان ابتدا بنويسند program/application error بجاي server error)

۱۳۸۹/۰۵/۱۸

مديريت بهتر كدهاي توليد شده توسط WCF RIA Services


در حين كار با WCF RIA Services اگر تمام موجوديت‌هاي تعريف شده انتخاب شوند يك فايل طويل DomainService حاصل خواهد شد. كار كردن با اين فايل نه زيبا است و نه ساده. بعد از يك مدت شايد بگوئيم، خوب! من به ازاي هر جدول يك DomainService جدا توليد مي‌كنم با نامي مختص به آن و اين اطلاعات را در فايلي جداگانه نيز ذخيره خواهم كرد. پس از انجام اينكار با خطاي زير مواجه خواهيم شد:

The entity type ... is exposed by multiple DomainService types. Entity types cannot be shared across DomainServices

به صورت خلاصه: مهم نيست سيستم شما از چند جدول تشكيل شده است؛ مهم اين است كه تنها يك فايل DomainService را بايد توليد كنيد و البته اين يك محدوديت نيست؛ يك هدف محسوب مي‌شود؛ از اين ديدگاه كه موجوديت‌هاي مرتبط بايد در يك Domain قرار گيرند و تنها در يك دومين هستند كه روابط بين آن‌ها معنا پيدا مي‌كند.
- روش توصيه شده براي مديريت اين كلاس DomainService طويل، استفاده از واژه‌ي كليدي partial است (براي مثال public partial class MyDomainService). به اين صورت هر موجوديت را مي‌توان در يك فايل جداگانه قرار داد و به اين ترتيب مديريت ساده‌تري را بر روي اطلاعات توليد شده داشت و همچنين تمامي اين فايل‌ها در نهايت يك كلاس واحد را تشكيل مي‌دهند و اصل وجود يك DomainService واحد در برنامه زير سؤال نخواهد رفت.
- كدهايي را كه خودتان نيز به اين مجموعه اضافه خواهيد كرد، در لابلاي كدهاي توليد شده قرار ندهيد. در صورت تغييري در جداول نياز است تا اين فايل‌ها مجددا توليد شوند و اينجا است كه تمام تغييرات خود را از دست خواهيد داد. براي اين منظور باز هم يك كلاس partial ديگر را تعريف كنيد تا كدهاي سفارشي خود را بتوان به صورت مجزايي از كدهاي توليد شده به صورت خودكار، در آن قرار داد (براي مثال به نام MyServiceNameDomainService.extensions.cs).
- جايي كه قرار است يك سري از فايل‌ها مجددا توليد شوند استفاده از برنامه‌هاي سورس كنترل را فراموش نكنيد؛ تا هر زماني بتوان كدهاي جديد را با كدهاي نگارش‌هاي قبل به سادگي مقايسه كرد؛ يا حتي به نگارش‌هاي قبلي بازگشت نمود.
- اين نكته را به خاطر داشته باشيد كه اگر اطلاعاتي به صورت خودكار از پيش تعريف شده و موجود است (كلاس دومين يا متاديتاي آن)،‌ اين اطلاعات حين استفاده از امكانات توليد كد خودكار، مجددا توليد نخواهند شد. به همين جهت بايد ابتدا آن‌ها را به صورت comment در آورد (يا از پروژه خارج نمود؛ استفاده از امكانات include و exclude پوشه‌ها يا فايل‌ها در ويژوال استوديو) تا همواره كدهاي آخرين اطلاعات موجود توليد گردند.

۱۳۸۹/۰۵/۱۶

يكسان سازي "ي" و "ك" دريافتي در حين استفاده از WCF RIA Services


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

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

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

در مورد ‌مقدمات WCF RIA Services كه درSilverlight و ASP.NET كاربرد دارد مي‌توانيد به اين مطلب مراجعه كنيد: +

جهت تكميل اين بحث متدي تهيه شده كه كار يكسان سازي ي و ك دريافتي از كاربر را حين ثبت توسط امكانات WCF RIA Services انجام مي‌دهد (دقيقا پيش از فراخواني متد SubmitChanges بايد بكارگرفته شود):


namespace SilverlightTests.RiaYeKe
{
public static class PersianHelper
{
public static string ApplyUnifiedYeKe(this string data)
{
if (string.IsNullOrEmpty(data)) return data;
return data.Replace("ی", "ي").Replace("ک", "ك");
}
}
}

using System.Linq;
using System.Windows.Controls;
using System.Reflection;
using System.ServiceModel.DomainServices.Client;

namespace SilverlightTests.RiaYeKe
{
public class RIAHelper
{
/// <summary>
/// يك دست سازي ي و ك در عبارات ثبت شده در بانك اطلاعاتي پيش از ورود به آن
/// اين متد بايد پيش از فراخواني متد
/// SubmitChanges
/// استفاده شود
/// </summary>
/// <param name="dds"></param>
public static void ApplyCorrectYeKe(DomainDataSource dds)
{
if (dds == null)
return;

if (dds.DataView.TotalItemCount <= 0)
return;

//پيدا كردن موجوديت‌هاي تغيير كرده
var changedEntities = dds.DomainContext.EntityContainer.GetChanges().Where(
c => c.EntityState == EntityState.Modified ||
c.EntityState == EntityState.New);

foreach (var entity in changedEntities)
{
//يافتن خواص اين موجوديت‌ها
var propertyInfos = entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
);

foreach (var propertyInfo in propertyInfos)
{
//اگر اين خاصيت رشته‌اي است ي و ك آن را استاندارد كن
if (propertyInfo.PropertyType != typeof (string)) continue;
var propName = propertyInfo.Name;
var val = new PropertyReflector().GetValue(entity, propName);
if (val == null) continue;
new PropertyReflector().SetValue(
entity,
propName,
val.ToString().ApplyUnifiedYeKe());
}
}
}
}
}

توضيحات:
از آنجائيكه حين فراخواني متد SubmitChanges فقط موجوديت‌هاي تغيير كرده جهت ثبت ارسال مي‌شوند، ابتدا اين موارد يافت شده و سپس خواص عمومي تك تك اين اشياء توسط عمليات Reflection بررسي مي‌گردند. اگر خاصيت مورد بررسي از نوع رشته‌اي بود، يكبار اين يك دست سازي اطلاعات ي و ك دريافتي صورت خواهد گرفت (و از آنجائيكه اين تعداد هميشه محدود است عمليات Reflection سربار خاصي نخواهد داشت).
اگر در كدهاي خود از DomainDataSource استفاده نمي‌كنيد باز هم تفاوتي نمي‌كند. متد ApplyCorrectYeKe را از قسمت DomainContext.EntityContainer به بعد دنبال كنيد.
اكنون تنها مورد باقيمانده بحث جستجو است كه با اعمال متد ApplyUnifiedYeKe به مقدار ورودي متد جستجوي خود، مشكل حل خواهد شد.

كلاس PropertyReflector بكارگرفته شده هم از اينجا به عاريت گرفته شد.
دريافت كدهاي اين بحث

۱۳۸۹/۰۵/۰۸

راهبري در 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 تنظيم نمائيد.



۱۳۸۹/۰۴/۱۴

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 اضافه شده).

۱۳۸۹/۰۴/۱۱

انجام پي در پي اعمال 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 قبلي (يا مرحله‌ي قبلي)، اجرا كند. قبل از اختراع چرخ بايد متذكر شد كه ديگران اينكار را انجام داده‌اند و كتابخانه‌هاي رايگان و يا سورس بازي براي اين منظور موجود است.


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

۱۳۸۹/۰۳/۲۰

آموزش سيلورلايت 4 - قسمت‌هاي 21 تا 27


فصل عنوان فايل مرتبط
21 بررسي كنترل DataForm +
22 برنامه نويسي گرافيكي در Silverlight +
23 آشنايي با پويا نمايي در Silverlight +
24 برنامه نويسي چند ريسماني در Silverlight +
25 تعامل با كدهاي HTML صفحه در Silverlight +
26 استفاده از WCF RIA Services در Silverlight +
27 استفاده از MEF در Silverlight 4 +
منابع و مآخذ +
سورس تمام مثال‌هاي كتاب +

و يا دريافت تمام قسمت‌ها به صورت يكجا : +



توضيحي در مورد مثال‌ها:
تمام پوشه‌هاي مثال‌ها داراي شماره هستند و اين شماره‌ها متناظرند با شماره‌ي فضاهاي نام مثال‌ها

آموزش سيلورلايت 4 - قسمت‌هاي 11 تا 20


فصل عنوان فايل مرتبط
11 آشنايي با MVVM Light Toolkit +
12 اعمال قالب‌هاي متفاوت به برنامه‌هاي Silverlight +
13 استفاده از Web Services در Silverlight +
14 آشنايي با مفاهيم مرتبط با شيء Application و مديريت آن +
15 امنيت در Silverlight +
16 روش‌هاي تعيين اعتبار ورودي كاربر در Silverlight +
17 استفاده از تصاوير و فايل‌هاي چند رسانه‌اي در Silverlight +
18 بررسي جامع توانايي‌هاي كنترل DataGrid +
19 معرفي ساير امكانات و ويژگي‌هاي اختصاصي Silverlight 4 +
20 بررسي اجراي خارج از مرورگر برنامه‌هاي Silverlight +