۱۳۸۸/۰۶/۰۹

تعيين اعتبار ورودي Int64 يا بزرگتر در ASP.Net


كنترل range validator در ASP.net امكان كنترل ورودي از نوع Int32 ، double‌ و غيره را فراهم مي‌كند. اما اگر كاربر حتما بايد عددي صحيح را وارد كرده و اين عدد از بازه‌ي اعداد Int32 خارج بود (مثلا اعداد Int64 يا حتي بزرگتر) اين كنترل كاربرد خود را از دست مي‌دهد.
در اين حالت بجاي استفاده از كنترل range validaotr مي‌شود از RegularExpressionValidator معروف به صورت زير استفاده كرد:

<asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server"
ControlToValidate="TextBox1" ErrorMessage="RegularExpressionValidator"
SetFocusOnError="True" ValidationExpression="\d+"></asp:RegularExpressionValidator>

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


۱۳۸۸/۰۶/۰۸

استثناي Sequence contains no elements در حين استفاده از LINQ


در ابتدا مثال‌هاي زير را در نظر بگيريد:

using System;
using System.Collections.Generic;
using System.Linq;

namespace testWinForms87
{
public class Data
{
public int id { get; set; }
public string name { get; set; }
}

class CLinqTests
{
public static int TestGetListMin1()
{
var lst = new List<Data>
{
new Data{ id=1, name="id1"},
new Data{ id=2, name="id2"},
new Data{ id=3, name="name3"}
};

return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}

public static int TestGetListMin2()
{
var lst = new List<Data>();

return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}
}
}
در متد TestGetListMin1 قصد داريم كوچكترين آي دي ركوردهايي را كه نام آن‌ها حاوي id است، از ليست تشكيل شده از كلاس Data بدست آوريم (همانطور كه مشخص است سه ركورد از نوع Data در ليست lst ما قرار گرفته‌اند).
محاسبات آن كار مي‌كند و مشكلي هم ندارد. اما هميشه در دنياي واقعي همه چيز قرار نيست به اين خوبي پيش برود. ممكن است همانند متد TestGetListMin2 ، ليست ما خالي باشد (براي مثال از ديتابيس، ركوردي مطابق شرايط كوئري‌هاي قبلي بازگشت داده نشده باشد). در اين حالت هنگام فراخواني متد Min ، استثناي Sequence contains no elements رخ خواهد داد و همانطور كه در مباحث defensive programming عنوان شد، وظيفه‌ي ما اين نيست كه خودرو را به ديوار كوبيده (يا منتظر شويم تا كوبيده شود) و سپس به فكر چاره بيفتيم كه خوب، عجب! مشكلي رخ داده است!
اكنون چه بايد كرد؟ حداقل يك مرحله بررسي اينكه آيا كوئري ما حاوي ركوردي مي‌باشد يا خير بايد به اين متد اضافه شود (به صورت زير):

public static int TestGetListMin3()
{
var lst = new List<Data>();
var query = from c in lst
where c.name.Contains("id")
select c.id;

if (query.Any())
return query.Min();
else
return -1;
}
البته مي‌شد اگر هيچ ركوردي بازگشت داده نمي‌شد، يك استثناي سفارشي را ايجاد كرد، اما به شخصه ترجيح مي‌دهم عدد منهاي يك را بر گردانم (چون مي‌دانم ركوردهاي من عدد مثبت هستند و اگر حاصل منفي شد نيازي به ادامه‌ي پروسه نيست).

شبيه به اين مورد در هنگام استفاده از تابع Single مربوط به LINQ نيز ممكن است رخ دهد (توليد استثناي ذكر شده) اما در اينجا مايكروسافت تابع SingleOrDefault را نيز پيش بيني كرده است. در اين حالت اگر كوئري ما ركوردي را برنگرداند، SingleOrDefault مقدار نال را برگشت داده و استثنايي رخ نخواهد داد (نمونه‌ي ديگر آن متدهاي First و FirstOrDefault هستند).
در مورد متدهاي Min و Max ، متدهاي MinOrDefault يا MaxOrDefault در دات نت فريم ورك وجود ندارند. مي‌توان اين نقيصه را با استفاده از extension methods برطرف كرد.

using System;
using System.Collections.Generic;
using System.Linq;

public static class LinqExtensions
{
public static T MinOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Min<T>();

return defaultValue;
}

public static T MaxOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Max<T>();

return defaultValue;
}
}
اكنون با استفاده از extension methods فوق، كد ما به صورت زير تغيير خواهد كرد:

public static int TestGetListMin4()
{
var lst = new List<Data>();
return (from c in lst
where c.name.Contains("id")
select c.id).MinOrDefault(-1);
}

۱۳۸۸/۰۶/۰۶

در هم تنيدگي كدهاي خود را كمتر كنيد


مطلب "آشنايي با الگوي MVP" مقدمه‌ي كوتاهي بود بر يكي از روش‌هايي كه توسط آن مي‌توان گره خوردگي كدهاي خود را كمتر، نگهداري طولاني مدت و اعمال تغييرات بعدي به آن‌ها را ساده‌تر كرده و همچنين امكان استفاده مجدد از كدهاي موجود را فراهم آورد. در همين ارتباط ويديويي تحت عنوان Decoupling Your Code, By Example را مي‌توانيد از آدرس زير دريافت كنيد:



ماخذ



۱۳۸۸/۰۶/۰۵

حذف تمامي تگ‌ها منهاي چند تگ خاص از HTML‌ دريافتي


در ادامه مطلب "عبارات باقاعده‌اي در مورد كار با تگ‌ها" ، عبارت باقاعده مربوطه به حذف تمامي تگ‌ها براي فرمت زدايي يك متن بسيار جالب است اما مشكلي را كه به وجود خواهد آورد،‌ از بين بردن سطرهاي موجود است. به عبارت ديگر با استفاده از اين عبارت با قاعده، كل متن در امتداد يك سطر قرار مي‌گيرد. اكنون مي‌خواهيم تمامي تگ‌ها منهاي دو تگ مربوط به p و br حذف شوند. چه بايد كرد؟

private static readonly Regex _pbrRegex = new Regex(@"<(?!br|/br|p|/p).+?>",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// حذف تمامي تگ‌ها منهاي دو تگ ذكر شده
/// </summary>
/// <param name="html"></param>
/// <returns></returns>
public static string CleanTagsExceptPbr(string html)
{
return _pbrRegex.Replace(html, string.Empty);
}
و اگر بخواهيم يك سري تست براي آن بنويسيم به موارد زير مي‌توان اشاره كرد:

using NUnit.Framework;

namespace testWinForms87
{
[TestFixture]
public class CTestRegExHelperPbr
{
[Test]
public void TestCleanTagsExceptPbr1()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<b>data1</b><br/>data2"),
"data1<br/>data2");
}

[Test]
public void TestCleanTagsExceptPbr2()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<b>data1</b><br>data2"),
"data1<br>data2");
}

[Test]
public void TestCleanTagsExceptPbr3()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<p><b>data1</b><br/>data2</p>"),
"<p>data1<br/>data2</p>");
}

[Test]
public void TestCleanTagsExceptPbr4()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<b>data1</b><p>data2<br />"),
"data1<p>data2<br />");
}
}
}



۱۳۸۸/۰۶/۰۳

رفع مشكل initializing toolbox هنگام باز كردن يك پروژه وب


بعد از نصب مجموعه AJAX Control Toolkit و همچنين نمونه‌هاي مشابه موجود براي سيلورلايت و WPF دو مشكل براي VS2008 من رخ داد :

1- از كار افتادن نوار ابزار كنترل‌ها (كار نمي‌كرد! نمي‌شد روي كنترلي كليك كرد)
2- به ازاي باز كردن هر صفحه aspx و امثال آن در IDE ، حدود يك دقيقه در پايين صفحه، سمت چپ نوشته مي‌شد، initializing toolbox و بعد هم IDE تقريبا هنگ مي‌كرد.

نحوه رفع مشكل:
روي tool box كليك راست كرده و گزينه reset toolbox را انتخاب كنيد.
حدود 10 دقيقه با مصرف CPU نزديك به 100 درصد همه چيز مرتبط و مثل روز اول مي‌شود.
بعد از اين، باز كردن فايل‌هاي پروژه فقط با يك كليك امكان پذير شده و مشكل هنگ IDE برطرف مي‌شود.

البته VS 2008 خام با پروژه‌هاي وب خيلي مشكل دارد و عملا بعد از نصب SP1 آن كاملا روان شده و درست كار مي‌كند.


۱۳۸۸/۰۶/۰۲

ELMAH 1.1


نگارش جديد ELMAH ارائه شده است. اين نگارش كاملا با نگارش قبلي سازگار بوده و تنها بازنويسي فايل dll آن با نگارش جديد كفايت مي‌كند.

ليست موارد فيكس شده
- System.Data.SQLite.dll causes image problems on 64-bit IIS/ASP.NET
- Incorrect jscript filter sample

ليست تازه‌هاي آن
- Ability to set mail priority in errorMail config section
- Allow configuration-based filtering rules to be diffrent for e-mail and log
- Adjust behavior of smtpPort attribute in
- Remove the XML declaration from the string returned by ErrorXml.EncodeString


دريافت از گوگل كد و يا دريافت از رپيد شير (سورس +‌ بايناري)

۱۳۸۸/۰۶/۰۱

آشنايي با Defensive programming - قسمت دوم


در ادامه يك سري از خط مشي‌هاي متداول در defensive programming را با هم مرور خواهيم كرد:

1- بررسي نال بودن اشياء
سعي در استفاده از اشياء نال، به يك NullReferenceException منتهي خواهد شد. اگر به هر دليلي امكان نال بودن يك شيء وجود داشت، پيش از استفاده از آن، حتما اين وضعيت ‌را بررسي نمائيد.
بهترين ابزاري هم كه براي اين منظور مي‌توان استفاده كرد، نگارش جديد افزونه‌ي ReSharper است كه زير شيء‌ايي را كه احتمال نال بودن آن مي‌رود يك خط آبي رنگ مي‌كشد.



2- بررسي آرگومان‌هاي دريافتي
براي نمونه اگر متد شما تاريخي را بر اساس DateTime دريافت مي‌كند، حتما حدود آن‌را بررسي نمائيد. براي مثال دريافت تاريخ 31 اسفند از كاربر، به يك ArgumentOutOfRangeException منتهي خواهد شد. بنابراين آرگومان‌هاي دريافت شده بايد انتظارات مربوطه را برآورده كنند و پيش از استفاده حتما بررسي گردند تا بتوان مشكلات را به كاربر به صورت واضحي گوشزد كرد. (خطاي ArgumentOutOfRangeException براي كاربر نهايي بي‌معني است)

3- وضعيت اشياء را بررسي كنيد
براي مثال بستن يك كانكشن به ديتابيس در صورت بسته بودن آن،‌ به يك InvalidOperationException منتهي مي‌شود. بنابراين بهتر است ابتدا وضعيت اين شيء بررسي شده و سپس عمليات خاصي بر روي آن صورت گيرد.

4- هنگام كار با آرايه‌ها دقت كنيد
اگر انديس فراخواني شده كمتر از صفر يا بيشتر از اندازه‌ي آرايه باشد به يك IndexOutOfRangeException بر خواهيد خورد. بنابراين همواره بهتر است كه اين بررسي پيش از بروز واقعه انجام شود.

5- مراقب الگوريتم‌هاي بازگشتي باشيد
هر چند متدهاي بازگشتي در بعضي از موارد كار راه انداز هستند اما اگر بدون دقت از آن‌ها استفاده شود ممكن است سبب ايجاد يك حلقه‌ي بي نهايت شده و نهايتا برنامه با يك StackOverFlowException خاتمه مي‌يابد (اين مورد در دات نت فريم ورك تنها حالتي است كه با try و catch قابل مهار نيست).

6- هنگام تبديل اشيايي از نوع object‌ مراقب باشيد
اگر قصد تبديل يك شيء را به نوعي داشته باشيد كه با مقدار ذخيره شده در آن مطابقت ندارد، يك InvalidCastException حاصل خواهد شد. بنابراين در اينگونه موارد بهتر است از اپراتورهاي as و يا is استفاده كنيد. هنگام استفاده از as اگر عمليات تبديل با موفقيت صورت نگيرد، حاصل عمليات تنها يك شيء نال خواهد بود و استثنايي رخ نخواهد داد.

7- بجاي متد Parse از TryParse استفاده كنيد
براي مثال در دات نت جهت تبديل يك رشته به مقداري عددي مي‌توان از int.Parse و يا int.TryParse استفاده كرد. در حالت اول اگر عمليات تبديل ميسر نباشد حتما يك FormatException رخ خواهد داد اما در حالت دوم در صورت موفقيت آميز بودن عمليات تبديل، خروجي true خواهد بود و حاصل اصلي را با يك پارامتر از نوع out در اختيار شما قرار مي‌دهد.


و به صورت خلاصه
- ورودي‌هاي كاربر را محدود كرده (مثلا اگر قرار است عددي را وارد كند، از يك تكست باكس عددي (masked edit controls) استفاده كنيد) و يا آن‌ها را دقيقا بررسي نمائيد تا احتمال بروز خطاهاي بعدي را كاهش دهيد.
- زمانيكه مي‌توان از بروز يك exception جلوگيري كرد، بهتر است مديريت آن‌را به قسمت catch بلاك try/catch واگذار نكرد.
- و هنگاميكه با برنامه نويسي نمي‌توانيد تمامي خطاهاي ممكن را پيش بيني كرده و آن‌ها را مديريت كنيد، براي مديريت استثناء‌ها برنامه داشته باشيد.

۱۳۸۸/۰۵/۳۱

آشنايي با Defensive programming


تصادف براي يك راننده حتي در صورت داشتن بيمه نامه‌اي معتبر، گران تمام خواهد شد (از لحاظ جاني/مادي/...). بنابراين صرف نظر از اينكه شركت بيمه كننده چه ميزان از خسارت راننده را جبران خواهد كرد، بايد تا حد ممكن از تصادفات بر حذر بود (defensive driving).

در برنامه نويسي، استثناء‌ها (Exceptions) مانند تصادفات هستند و مديريت استثناءها (exception handling)، همانند بيمه خودرو مي‌باشند. هر چند مديريت استثناء‌ها جهت بازگردان برنامه شما به ادامه مسير مهم‌ هستند، اما جايگزين خوبي براي Defensive programming به شمار نمي‌روند. استثناء‌ها و مديريت آن‌ها براي برنامه گران تمام مي‌شوند (خصوصا از لحاظ ميزان مصرف منابع سيستمي و سربارهاي مربوطه). بنابراين در برنامه بايد توجه خاصي را به اين موضوع معطوف داشت كه چه زماني، چگونه و در كجا ممكن است استثنائي رخ دهد و علاج واقعه را پيش از وقوع آن نمود.

اصل اول Defensive programming : هميشه ورودي دريافتي را تعيين اعتبار كنيد
به مثال زير دقت بفرمائيد:

public void LogEntry(string msg)
{
string path = GetPathToLog();
using (StreamWriter writer = File.AppendText(path))
{
writer.WriteLine(DateTime.Now.ToString(CultureInfo.InstalledUICulture));
writer.WriteLine("Entry: {0}", msg);
writer.WriteLine("--------------------");
}
}

قرار هست رخ‌دادهاي برنامه را توسط اين متد، لاگ كنيم. اكنون لحظه‌اي دقت نمائيد كه اين تابع در چه مواقعي ممكن است دچار مشكل شود:
path مي‌تواند يك رشته خالي باشد.
path مي‌تواند نال باشد.
path مي‌تواند حاوي كاراكترهاي غيرمجازي باشد.
path مي‌تواند فرمت نادرستي داشته باشد.
path مي‌تواند به محلي ناصحيح اشاره نمايد.
path مي‌تواند اصلا وجود نداشته باشد.
فايل مورد نظر ممكن است readonly باشد.
برنامه ممكن است دسترسي لازم را براي نوشتن در مسير ذكر شده، نداشته باشد.
فايل مورد نظر ممكن است توسط پروسه‌اي ديگر قفل شده باشد.
ممكن است در لحظه نوشتن يا خواندن بر روي فايل، هارد ديسك دچار مشكل گردد.
و ...

رخ دادن هر كدام از موارد ذكر شد منجر به بروز يك استثناء خواهد شد.

چگونه اين وضعيت را بهبود بخشيم؟
فرض كنيد متد GetPathToLog قرار است مسير ذخيره سازي لاگ‌ها را از كاربر در يك برنامه ASP.Net دريافت كند. براي اين منظور بايد حداقل دو مورد را منظور كرد.

<asp:TextBox ID="txtPath" runat="server" MaxLength="248" />

<asp:RequiredFieldValidator ID="reqval_txtPath" runat="server" ControlToValidate="txtPath" ErrorMessage="Path is required." />

<asp:RegularExpressionValidator ID="regex_txtPath" runat="server" ControlToValidate="txtPath" ErrorMessage="Path is invalid." ValidationExpression='^([a-zA-Z]\:)(\\{1}|((\\{1})[^\\]([^/:*?<>”|]*(?<![ ])))+)$' />

براي تكست باكس ارائه شده، ابتدا يك RequiredFieldValidator در نظر گرفته شده تا مطمئن شويم كه كاربر حتما مقداري را وارد خواهد كرد. اما اين كافي نيست. سپس با استفاده از عبارات باقاعده و RegularExpressionValidator بررسي خواهيم كرد كه آيا فرمت ورودي صحيح است يا خير.

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


۱۳۸۸/۰۵/۳۰

استفاده از LINQ جهت جستجوي فايل‌ها


يكي ديگر از كاربردهاي anonymous types ، امكان استفاده از قابليت‌هاي LINQ براي جستجوي فايل‌ها و پوشه‌ها است.
مثال:

using System;
using System.Linq;
using System.IO;

namespace LINQtoDir
{
class Program
{
static void Main(string[] args)
{
var query = from f in new DirectoryInfo(@"C:\Documents and Settings\vahid\My Documents\My Pictures")
.GetFiles("*.*", SearchOption.AllDirectories)
where f.Extension.ToLower() == ".png" || f.Extension.ToLower() == ".jpg"
orderby f.LastAccessTime
select new
{
DateLastModified = f.LastWriteTime,
Extension = f.Extension,
Size = f.Length,
FileName = f.Name
};

foreach (var file in query)
Console.WriteLine(file.FileName);

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

در اين مثال توسط كوئري نوشته شده، تمامي تصاوير jpg و يا png موجود در پوشه my pictures يافت شده و سپس بر اساس LastAccessTime مرتب مي‌شوند. در آخر با استفاده از anonymous types ، يك شيء IEnumerable از خواص مورد نظر فايل‌هاي يافت شده، بازگشت داده مي‌شود. اكنون هر استفاده‌ي دلخواهي را مي‌توان از اين شيء انجام داد.

۱۳۸۸/۰۵/۲۹

درخواست


لطفا مطالب و سؤالات غير مرتبط با عناوين هر يك از مطالب ارسالي در سايت را مطرح نفرمائيد. (در غير اينصورت مطلب شما بدون تائيد، يك ضرب حذف خواهد شد؛ حتي شما!)
كاربري اين وبلاگ شخصي به فوروم تبديل نخواهد شد.

براي پاسخ به سؤالات خودتون مي‌تونيد به فوروم‌هاي برنامه نويسي مانند دات نت سورس مراجعه نمائيد.


با تشكر

خواندني‌هاي 29 مرداد


اس كيوال سرور

الگوهاي طراحي برنامه نويسي شيءگرا

توسعه وب

دات نت فريم ورك

دبليو پي اف و سيلور لايت

سي و مشتقات

متفرقه

محيط‌هاي مجتمع توسعه

مرورگرها

پي اچ پي

۱۳۸۸/۰۵/۲۸

آشنايي با الگوي MVP


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

يكي از الگوهايي كه شيوه‌ي صحيح اين جدا سازي را ترويج مي‌كند، الگوي MVP يا Model-View-Presenter مي‌باشد. خلاصه‌ي اين الگو به صورت زير است:


Model :
من مي‌دانم كه چگونه اشياء برنامه را جهت حصول منطقي خاص، پردازش كنم.
من نمي‌دانم كه چگونه بايد اطلاعاتي را به شكلي بصري به كاربر ارائه داد يا چگونه بايد به رخ‌دادها يا اعمال صادر شده از طرف كاربر پاسخ داد.

View :
من مي‌دانم كه چگونه بايد اطلاعاتي را به كاربر به شكلي بصري ارائه داد.
من مي‌دانم كه چگونه بايد اعمالي مانند data binding و امثال آن را انجام داد.
من نمي‌دانم كه چگونه بايد منطق پردازشي موارد ذكر شده را فراهم آورم.

Presenter :
من مي‌دانم كه چگونه بايد درخواست‌هاي رسيده كاربر به View را دريافت كرده و آن‌ها را به Model‌ انتقال دهم.
من مي‌دانم كه چگونه بايد اطلاعات را به Model ارسال كرده و سپس نتيجه‌ي پردازش آن‌ها را جهت نمايش در اختيار View قرار دهم.
من نمي‌دانم كه چگونه بايد اطلاعاتي را ترسيم كرد (مشكل View است نه من) و نمي‌دانم كه چگونه بايد پردازشي را بر روي اطلاعات انجام دهم. (مشكل Model است و اصلا ربطي به اينجانب ندارد!)


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

الف) پياده سازي به روش متداول (اسپاگتي كد)

protected void btnGetData_Click(object sender, EventArgs e)
{
lblResult.Text = (Math.PI * double.Parse(txtRadius.Text) * double.Parse(txtRadius.Text)).ToString();
}
بله! كار مي‌كنه!
اما اين مشكلات را هم دارد:
- منطق برنامه (روش محاسبه مساحت دايره) با رابط كاربر گره خورده.
- كدهاي برنامه در پروژه‌ي ديگري قابل استفاده نيست. (شما متد يا كلاسي را اين‌جا با قابليت استفاده مجدد مي‌توانيد پيدا مي‌كنيد؟ آيا يكي از اهداف برنامه نويسي شيءگرا توليد كدهايي با قابليت استفاده مجدد نبود؟)
- چگونه بايد براي آن آزمون واحد نوشت؟

ب) بهبود كد و جدا سازي لايه‌ها از يكديگر

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

پياده سازي منطق برنامه:

1- ايجاد Model :
يك فايل جديد را به نام CModel.cs به پروژه اضافه كرده و كد زير را به آن خواهيم افزود:

using System;

namespace MVPTest
{
public interface ICircleModel
{
double GetArea(double radius);
}

public class CModel : ICircleModel
{
public double GetArea(double radius)
{
return Math.PI * radius * radius;
}
}
}
همانطور كه ملاحظه مي‌كنيد اكنون منطق برنامه از موارد زير اطلاعي ندارد:
- خبري از textbox و برچسب و غيره نيست. اصلا نمي‌داند كه رابط كاربري وجود دارد يا نه.
- خبري از رخ‌دادهاي برنامه و پاسخ دادن به آن‌ها نيست.
- از اين كد مي‌توان مستقيما و بدون هيچ تغييري در برنامه‌هاي ديگر هم استفاده كرد.
- اگر باگي در اين قسمت وجود دارد، تنها اين كلاس است كه بايد تغيير كند و بلافاصله كل برنامه از اين بهبود حاصل شده مي‌تواند بدون هيچگونه تغييري و يا به هم ريختگي استفاده كند.
- نوشتن آزمون واحد براي اين كلاس كه هيچگونه وابستگي به UI ندارد ساده است.


2- ايجاد View :
فايل ديگري را به نام CView.cs را به همراه اينترفيس زير به پروژه اضافه مي‌كنيم:

namespace MVPTest
{
public interface IView
{
string RadiusText { get; set; }
string ResultText { get; set; }
}
}

كار View دريافت ابتدايي مقادير از كاربر توسط RadiusText و نمايش نهايي نتيجه توسط ResultText است البته با يك اما.
View نمي‌داند كه چگونه بايد اين پردازش صورت گيرد. حتي نمي‌داند كه چگونه بايد اين مقادير را به Model جهت پردازش برساند يا چگونه آن‌ها را دريافت كند (به همين جهت از اينترفيس براي تعريف آن استفاده شده).

3- ايجاد Presenter :
در ادامه فايل جديدي را به نام CPresenter.cs‌ با محتويات زير به پروژه خواهيم افزود:

namespace MVPTest
{
public class CPresenter
{
IView _view;

public CPresenter(IView view)
{
_view = view;
}

public void CalculateCircleArea()
{
CModel model = new CModel();
_view.ResultText = model.GetArea(double.Parse(_view.RadiusText)).ToString();
}
}
}

كار اين كلاس برقراري ارتباط با Model است.
مي‌داند كه چگونه اطلاعات را به Model ارسال كند (از طريق _view.RadiusText) و مي‌داند كه چگونه نتيجه‌ي پردازش را در اختيار View قرار دهد. (با انتساب آن به _view.ResultText)
نمي‌داند كه چگونه بايد اين پردازش صورت گيرد (كار مدل است نه او). نمي‌داند كه نتيجه‌ي نهايي را چگونه نمايش دهد (كار View است نه او).
روش معرفي View به اين كلاس به constructor dependency injection معروف است.

اكنون كد وب فرم ما كه در قسمت (الف) معرفي شده به صورت زير تغيير مي‌كند:

using System;

namespace MVPTest
{
public partial class _Default : System.Web.UI.Page, IView
{
protected void Page_Load(object sender, EventArgs e)
{
}

public string RadiusText
{
get { return txtRadius.Text; }
set { txtRadius.Text = value; }
}
public string ResultText
{
get { return lblResult.Text; }
set { lblResult.Text = value; }
}

protected void btnGetData_Click(object sender, EventArgs e)
{
CPresenter presenter = new CPresenter(this);
presenter.CalculateCircleArea();
}
}
}

در اين‌جا يك وهله از Presenter براي برقراري ارتباط با Model ايجاد مي‌شود. همچنين كلاس وب فرم ما اينترفيس View را نيز پياده سازي خواهد كرد.

۱۳۸۸/۰۵/۲۷

روش‌هايي براي حذف ركوردهاي تكراري


هر برنامه نويسي در طول عمر كاري خود حداقل يكبار با اين مساله مواجه خواهد شد: "چگونه يك سري ركورد تكراري موجود را بايد حذف كرد؟"

ابتدا جدول موقتي زير را كه در آن يك سري ركورد تكراري ثبت شده است در نظر بگيريد:

CREATE TABLE #Employee
(
ID INT,
FIRST_NAME NVARCHAR(100),
LAST_NAME NVARCHAR(300)
)

INSERT INTO #Employee VALUES ( 1, 'Vahid', 'Nasiri' );
INSERT INTO #Employee VALUES ( 2, 'name1', 'lname1' );
INSERT INTO #Employee VALUES ( 3, 'name2', 'lname2' );
INSERT INTO #Employee VALUES ( 2, 'name1', 'lname1' );
INSERT INTO #Employee VALUES ( 3, 'name2', 'lname2' );
INSERT INTO #Employee VALUES ( 4, 'name3', 'lname3' );

روش‌هاي حذف ركوردهاي تكراري جدول موقتي Employee

الف) استفاده از يك جدول موقتي ديگر بر اساس تمامي فيلدهاي موجود

SELECT DISTINCT *
FROM #Employee

SELECT * INTO #DuplicateEmployee
FROM #Employee

INSERT #DuplicateEmployee
SELECT DISTINCT *
FROM #Employee

BEGIN TRAN
DELETE #Employee
INSERT #Employee
SELECT *
FROM #DuplicateEmployee

COMMIT TRAN

DROP TABLE #DuplicateEmployee

SELECT DISTINCT *
FROM #Employee

ب) استفاده از يك جدول موقتي ديگر بر اساس يك مجموعه از فيلدهاي موجود

SELECT DISTINCT * FROM #Employee

SELECT * INTO #DuplicateEmployee FROM #Employee

INSERT #DuplicateEmployee
SELECT ID,
FIRST_NAME,
LAST_NAME
FROM #Employee
GROUP BY
ID,
FIRST_NAME,
LAST_NAME
HAVING COUNT(*) > 1

BEGIN TRAN
DELETE #Employee
FROM #DuplicateEmployee
WHERE #Employee.ID = #DuplicateEmployee.ID
AND #Employee.FIRST_NAME = #DuplicateEmployee.FIRST_NAME
AND #Employee.LAST_NAME = #DuplicateEmployee.LAST_NAME

INSERT #Employee
SELECT *
FROM #DuplicateEmployee

COMMIT TRAN
DROP TABLE #DuplicateEmployee

SELECT DISTINCT * FROM #Employee

ج) استفاده از rowcount بر اساس يك مجموعه از فيلدهاي موجود

SELECT DISTINCT *
FROM #Employee

SET ROWCOUNT 1
SELECT 1
WHILE @@rowcount > 0
DELETE #Employee
WHERE 1 < (
SELECT COUNT(*)
FROM #Employee a2
WHERE #Employee.ID = a2.ID
AND #Employee.FIRST_NAME = a2.FIRST_NAME
AND #Employee.LAST_NAME = a2.LAST_NAME
)

SET ROWCOUNT 0

SELECT DISTINCT *
FROM #Employee

د) استفاده از Analytical Functions بر اساس يك مجموعه از فيلدهاي موجود

SELECT DISTINCT *
FROM #Employee;

WITH #DeleteEmployee AS (
SELECT ROW_NUMBER()
OVER(PARTITION BY ID, First_Name, Last_Name ORDER BY ID) AS
RNUM
FROM #Employee
)

DELETE
FROM #DeleteEmployee
WHERE RNUM > 1

SELECT DISTINCT *
FROM #Employee

ه) استفاده از يك فيلد identity جديد بر اساس يك مجموعه از فيلدهاي موجود

SELECT DISTINCT *
FROM #Employee;

ALTER TABLE #Employee ADD UNIQ_ID INT IDENTITY(1, 1)

DELETE
FROM #Employee
WHERE UNIQ_ID < (
SELECT MAX(UNIQ_ID)
FROM #Employee a2
WHERE #Employee.ID = a2.ID
AND #Employee.FIRST_NAME = a2.FIRST_NAME
AND #Employee.LAST_NAME = a2.LAST_NAME
)

ALTER TABLE #Employee DROP COLUMN UNIQ_ID

SELECT DISTINCT *
FROM #Employee

و در آخر

DROP TABLE #Employee

۱۳۸۸/۰۵/۲۶

آشنايي با jQuery Live


در نگارش‌هاي اخير كتابخانه jQuery (از نگارش 1.3 به بعد) متدي به نام live به آن اضافه شده است كه كاربرد آن‌را در ادامه مرور خواهيم كرد.
ابتدا مثال زير را در نظر بگيريد:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestLive.aspx.cs" Inherits="TestJQueryAjax.TestLive" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>

<script type="text/javascript">
$(document).ready(function() {
$('a.mylink').click(function(e) {
var $a = $(this);
alert($a.attr('id'));
});

$('a#lnkLoad').click(function(e) {
$('div#dynContent').load('live.ashx');
});
});
</script>

</head>
<body>
<form id="form1" runat="server">
<a href='#' id='lnk1' class='mylink'>link1</a>
<br />
<a href='#' id='lnkLoad'>load .ashx</a>
<div id='dynContent'>
</div>
</form>
</body>

</html>


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace TestJQueryAjax
{
/// <summary>
/// Summary description for $codebehindclassname$
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class live : IHttpHandler
{

public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("<a href='#' id='lnk2' class='mylink'>link2</a>");
}

public bool IsReusable
{
get
{
return false;
}
}
}
}
در اين مثال ساده با كليك بر روي لينك‌هايي با كلاس mylink ،‌ يك alert حاوي id آن لينك نمايش داده خواهد شد.
همچنين اگر بر روي لينكي با id مساوي lnkLoad كليك شود، محتوايي پويا از يك generic handler به نام live.ashx دريافت شده و به div ايي با id مساوي dynContent اضافه مي‌گردد.
اين محتواي دريافتي از generic handler ما نيز كلاسي مساوي mylink دارد، اما اين‌بار هر چقدر بر روي لينك اضافه شده به صفحه كليك كنيم كار نمي‌كند. چرا؟ چون در هنگام فراخواني document.ready ، اين لينك وجود نداشته و روال رخدادگرداني به آن bind نشده است.
به صورت خلاصه مي‌خواهيم روال كليك بر روي لينك‌هايي با كلاس mylink هميشه كار كند. (چه در مورد عناصري در صفحه كه از قبل وجود داشته‌اند و چه عناصري كه توسط عملياتي Ajax ايي بعدا اضافه خواهند شد)
اين مشكل با معرفي متد live حل شده است. براي اين منظور تنها كافي است كد ما به صورت زير تغيير كند:

<script type="text/javascript">
$(document).ready(function() {
$('a.mylink').live("click", function() {
var $a = $(this);
alert($a.attr('id'));
});
.
.
.

اكنون jQuery كليه لينك‌هايي با كلاس مساوي mylink را كه از اين پس اضافه خواهند شد، به صورت live و زنده تحت نظر قرار مي‌دهد و عكس العمل نشان خواهد داد.

۱۳۸۸/۰۵/۲۳

استفاده از LINQ to XML جهت خواندن فيدهاي RSS


مثال زير را به عنواني نمونه‌اي از كاربرد LINQ to XML براي خواندن فيدهاي RSS كه اساسا به فرمت XML هستند مي‌توان ارائه داد.
ابتدا كد كامل مثال را در نظر بگيريد:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace LinqToRSS
{
public static class LanguageExtender
{
public static string SafeValue(this XElement input)
{
return (input == null) ? string.Empty : input.Value;
}

public static DateTime SafeDateValue(this XElement input)
{
return (input == null) ? DateTime.MinValue : DateTime.Parse(input.Value);
}
}

public class RssEntry
{
public string Title { set; get; }
public string Description { set; get; }
public string Link { set; get; }
public DateTime PublicationDate { set; get; }
public string Author { set; get; }
public string BlogName { set; get; }
public string BlogAddress { set; get; }
}

public class Rss
{
static XElement selectDate(XElement date1, XElement date2)
{
return date1 ?? date2;
}

public static List<RssEntry> GetEntries(string feedUrl)
{
//applying namespace in an XElement
XName xn = XName.Get("{http://purl.org/dc/elements/1.1/}creator");//{namespace}root
XName xn2 = XName.Get("{http://purl.org/dc/elements/1.1/}date");

var feed = XDocument.Load(feedUrl);
if (feed.Root == null) return null;

var items = feed.Root.Element("channel").Elements("item");
var feedQuery =
from item in items
select new RssEntry
{
Title = item.Element("title").SafeValue(),
Description = item.Element("description").SafeValue(),
Link = item.Element("link").SafeValue(),
PublicationDate =
selectDate(item.Element(xn2), item.Element("pubDate")).SafeDateValue(),
Author = item.Element(xn).SafeValue(),
BlogName = item.Parent.Element("title").SafeValue(),
BlogAddress = item.Parent.Element("link").SafeValue()
};

return feedQuery.ToList();
}
}

class Program
{
static void Main(string[] args)
{
List<RssEntry> entries = Rss.GetEntries("http://weblogs.asp.net/aspnet-team/rss.aspx");
if (entries != null)
foreach (var item in entries)
Console.WriteLine(item.Title);

Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}

توضيحات:
1- در اين مثال فقط جهت سهولت بيان آن در يك صفحه، تمامي كلاس‌هاي تعريف شده در يك فايل آورده شدند. اين روش صحيح نيست و بايد به ازاي هر كلاس يك فايل جدا در نظر گرفته شود.
2- كلاس LanguageExtender از قابليت extension methods سي شارپ 3 استفاده مي‌كند. به اين صورت كلاس XElement دات نت بسط يافته و دو متد به آن اضافه مي‌شود كه به سادگي در كدهاي خود مي‌توان از آن‌ها استفاده كرد. هدف آن هم بررسي نال بودن يك آيتم دريافتي و ارائه‌ي حاصلي امن براي اين مورد است.
3- كلاس RssEntry به جهت استفاده در خروجي كوئري LINQ تعريف شد. مي‌خواهيم خروجي نهايي، يك ليست جنريك از نوع RssEntry باشد.
4- متد اصلي برنامه، GetEntries است. اين متد آدرس اينترنتي يك فيد را دريافت كرده و پس از آناليز، آن‌را به صورت يك ليست بر مي‌گرداند.

<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.asp.net/utility/FeedStylesheets/rss.xsl" media="screen"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Latest Microsoft Blogs</title>
<link>http://weblogs.asp.net/aspnet-team/default.aspx</link>
<description />
<dc:language>en</dc:language>
<generator>CommunityServer 2007 SP1 (Build: 20510.895)</generator>
<item>
<title>Comments on my recent benchmarks.</title>
<link>http://misfitgeek.com/blog/aspnet/comments-on-my-recent-benchmarks/</link>
<pubDate>Mon, 10 Aug 2009 23:33:59 GMT</pubDate>
<guid isPermaLink="false">c06e2b9d-981a-45b4-a55f-ab0d8bbfdc1c:7166225</guid>
<dc:creator>Misfit Geek: msft</dc:creator>
<slash:comments>0</slash:comments>
<wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://weblogs.asp.net/aspnet-team/rsscomments.aspx?PostID=7166225</wfw:commentRss>
<comments>http://misfitgeek.com/blog/aspnet/comments-on-my-recent-benchmarks/#comments</comments>
<description>Overall I’ve been pretty impressed ...</description>
<category domain="http://weblogs.asp.net/aspnet-team/archive/tags/ASP.NET/default.aspx">ASP.NET</category>
</item>
</channel>
</rss>
براي نمونه خروجي يك فيد مي‌تواند به صورت فوق باشد. آيتم‌هاي آن به صورت قابل بيان است:
var items = feed.Root.Element("channel").Elements("item");
و نكته مهمي كه اينجا وجود دارد، اعمال فضاهاي نام بكار رفته در اين فايل xml پيشرفته مي‌باشد. براي اعمال فضاهاي نام به يكي از دو روش زير مي‌توان عمل كرد:

XName.Get("{mynamespace}root");
//or
XName.Get("root", "mynamespace");

۱۳۸۸/۰۵/۲۱

خلاصه‌اي از LINQ to XML


در اين مقاله مروري سريع و كاربردي خواهيم داشت بر توانايي‌هاي مقدماتي LINQ to XML .

فايل Employee.XML را با محتويات زير در نظر بگيريد:

<Employees>
<Employee>
<Name>Vahid</Name>
<Phone>11111111</Phone>
<Department>IT</Department>
<Age>52</Age>
</Employee>
<Employee>
<Name>Farid</Name>
<Phone>124578963</Phone>
<Department>Civil</Department>
<Age>35</Age>
</Employee>
<Employee>
<Name>Mehdi</Name>
<Phone>1245788754</Phone>
<Department>HR</Department>
<Age>30</Age>
</Employee>
</Employees>

1- چگونه يك فايل XML را جهت استفاده توسط LINQ بارگذاري كنيم؟

قبل از شروع، اسمبلي System.Xml.Linq بايد به ارجاعات برنامه اضافه شود. سپس:

using System.Xml.Linq;

XDocument xDoc = XDocument.Load("Employee.xml");

2- اگر محتويات XML دريافتي به صورت رشته بود (مثلا از يك ديتابيس دريافت شد)، اكنون چگونه بايد آن‌را بارگذاري كرد؟

اين‌كار را با استفاده از يك StringReader به صورت زير مي‌توان انجام داد:

// loading XML from string
StringReader sr = new StringReader(stringXML);
XDocument xDoc = XDocument.Load(sr);

3- چگونه يك كوئري ساده شامل تمامي ركوردهاي Employee مجموعه Employees را تهيه كنيم؟

using System.Collections;

IEnumerable<XElement> empList = from e in xDoc.Root.Elements("Employee") select e;
توسط كوئري فوق، تمامي ركوردهاي كاركنان در يك Collection در اختيار ما خواهند بود. نكته‌ي مهم عبارت LINQ فوق، xDoc.Root.Elements("Employee") مي‌باشد. به اين صورت از xDoc بارگذاري شده، ابتدا Root و يا همان محتواي فايل XML را جهت بررسي انتخاب كرده و سپس گره‌هاي مرتبط با كاركنان را انتخاب مي‌كنيم.
اكنون كه مجموعه كاركنان توسط متغير empList در اختيار ما است، دسترسي به محتويات آن به سادگي زير خواهد بود:

foreach (XElement employee in empList)
{
foreach (XElement e in employee.Elements())
{
Console.WriteLine(e.Name + " = " + e.Value);
}
}
در اين‌جا حلقه خارجي اطلاعات كلي تمامي كاركنان را باز مي‌گرداند و حلقه داخلي اطلاعات يك گره دريافت شده را نمايش مي‌دهد.

4- كوئري بنويسيد كه اطلاعات تمامي كاركنان بخش HR را باز گرداند.

IEnumerable<XElement> hrList = from e in xDoc.Root.Elements("Employee")
where e.Element("Department").Value == "HR"
select e;

همانطور كه ملاحظه مي‌كنيد همانند عبارات SQL ، در تمامي عناصر متعلق به كاركنان، عناصري كه دپارتمان آن‌ها مساوي HR است بازگشت داده مي‌شود.

5- كوئري بنويسيد كه ليست تمامي كاركنان بالاي 30 سال را ارائه دهد.

IEnumerable<XElement> tList = from e in xDoc.Root.Elements("Employee")
where int.Parse(e.Element("Age").Value) > 30
select e;

چون حاصل e.Element("Age").Value يك رشته است، براي اعمال فيلترهاي عددي بايد اين رشته‌ها تبديل به عدد شوند. به همين جهت از int.Parse استفاده شده است.

6- كوئري بنويسيد كه ليست تمامي كاركنان بالاي 30 سال را مرتب شده بر اساس نام باز گرداند.

IEnumerable<XElement> tList = from e in xDoc.Root.Elements("Employee")
where int.Parse(e.Element("Age").Value) > 30
orderby e.Element("Name").Value
select e;
در اينجا همانند عبارات SQL از orderby جهت مرتب سازي بر اساس عناصر نام استفاده شده است.

7- تبديل نتيجه‌ي يك كوئري LINQ به ليستي از اشياء

مفهومي به سي شارپ 3 اضافه شده است به نام anonymous types . براي مثال:



توسط اين قابليت مي‌توان يك شيء را بدون نياز به تعريف ابتدايي آن ايجاد كرد و حتي از intelliSense موجود در IDE نيز بهره مند شد. اين نوع‌هاي ناشناس توسط واژه‌هاي كليدي new و var توليد مي‌شوند. كامپايلر به صورت خودكار براي هر anonymous type يك كلاس ايجاد مي‌كند.
دقيقا از همين توانايي در LINQ نيز مي‌توان استفاده نمود:

var empList = from e in xDoc.Root.Elements("Employee")
orderby e.Element("Name").Value
select new
{
Name = e.Element("Name").Value,
Phone = e.Element("Phone").Value,
Department = e.Element("Department").Value,
Age = int.Parse(e.Element("Age").Value)
};
در اين‌جا حاصل كوئري، تبديل به ليستي از اشياءanonymous مي‌شود. اكنون براي نمايش آن‌ها نيز مي‌توان از واژه كليدي var استفاده نمود كه از هر لحاظ نسبت به روش اعمال foreach بر روي Xelement ها كه در مثال 3 مشاهده كرديم خواناتر است:

foreach (var employee in empList)
{
Console.WriteLine("Name = " + employee.Name);
Console.WriteLine("Dep = " + employee.Department);
Console.WriteLine("Phone = " + employee.Phone);
Console.WriteLine("Age = " + employee.Age);
}
و البته بديهي است كه مي‌توان از anonymous types استفاده نكرد و دقيقا تعريف شيء را پيش از انتخاب آن نيز مشخص نمود. براي مثال:

public class Employee
{
public string Name { get; set; }
public string Phone { get; set; }
public string Department { get; set; }
public int Age { get; set; }
}
در اين حالت، قسمت select new عبارت LINQ ما به select new Employee تغيير خواهد كرد.
براي مثال اگر بخواهيم ليست دريافتي را به صورت يك ليست جنريك بازگشت دهيم خواهيم داشت:

public class Employee
{
public string Name { get; set; }
public string Phone { get; set; }
public string Department { get; set; }
public int Age { get; set; }
}

List<Employee> Get()
{
XDocument xDoc = XDocument.Load("Employee.xml");
var items =
from e in xDoc.Root.Elements("Employee")
orderby e.Element("Name").Value
select new Employee
{
Name = e.Element("Name").Value,
Phone = e.Element("Phone").Value,
Department = e.Element("Department").Value,
Age = int.Parse(e.Element("Age").Value)
};
return items.ToList();
}

۱۳۸۸/۰۵/۱۹

تعيين اعتبار يك checkBoxList با كمك jQuery


checkBoxList جزو كنترل‌هايي در ASP.Net است كه نمي‌توان RequiredFieldValidator استاندارد را بر آن اعمال كرد. به عبارتي اگر نياز بود حداقل يك آيتم چك باكس ليست حتما توسط كاربر انتخاب شود، راه حل آماده‌اي براي آن وجود ندارد. پياده سازي اين‌كار با استفاده از jQuery به سادگي ميسر است كه در ادامه آن‌را مرور خواهيم كرد.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="CheckBoxListValidator._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
//<![CDATA[
function CheckItems(sender, args) {
//Get the total nuumber of selected CheckBoxes
var num = jQuery("table#<%=CheckBoxList1.ClientID%> input:checked").length;
args.IsValid = num > 0;
}
//]]>
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:CheckBoxList ID="CheckBoxList1" runat="server">
<asp:ListItem>item1</asp:ListItem>
<asp:ListItem>item2</asp:ListItem>
</asp:CheckBoxList>
<asp:CustomValidator ClientValidationFunction="CheckItems" ID="ValidateIt"
runat="server" ErrorMessage="(*)"> </asp:CustomValidator>
<asp:Button ID="Button1" runat="server" />
</div>
</form>
</body>
</html>

توضيحات:
يك CustomValidator استاندارد را به فرم اضافه كرده‌ايم تا توسط تابعي كه به ClientValidationFunction آن معرفي مي‌شود، كار اعتبار سنجي سمت كاربر را انجام دهد. اين تابع يا همان CheckItems مثال فوق، امضاي استاندارد و آشنايي دارد. اگر تعيين اعتبار صورت گرفته باشد، بايد args.IsValid در آن به true تنظيم شود يا بر عكس.
اصل قضيه هم، همين يك سطر كد زير است:

var num = jQuery("table#<%=CheckBoxList1.ClientID%> input:checked").length;
كار اين سطر كه از جي‌كوئري استفاده مي‌كند، پيدا كردن جدولي است كه ID آن مساوي آي دي سمت كلاينت چك باكس ليست ما است (ASP.Net يك چك باكس ليست را به صورت يك جدول حاوي چك باكس‌ها رندر مي‌كند). سپس در همان ناحيه مشغول به جستجوي چك باكس‌هايي مي‌شود كه تيك خورده‌اند. نهايتا تعداد آن‌ها را بر مي‌گرداند.

۱۳۸۸/۰۵/۱۸

نحوه به تاخير انداختن ارسال ايميل‌ها در آوت لوك


مطلب امروز به كنترل شخصي مرتبط است. به درد همه مي‌خوره! :)
چگونه ارسال ايميلي را كه ممكن است 5 دقيقه بعد از ارسال آن به شدت پشيمان شويم، كنترل كنيم؟!

براي به تاخير انداختن تمامي ايميل‌هاي ارسالي از طريق آوت لوك مي‌توان به صورت زير عمل كرد:

به منوي tools‌ گزينه rules and alerts مراجعه كنيد.


در صفحه باز شده بر روي دكمه new rule كليك كنيد.
در پنجره بعدي گزينه Check messages after sending را انتخاب كرده و بر روي دكمه next كليك كنيد.


در صفحه بعد تنها بر روي گزينه next كليك كنيد (تا تنظيمات ما بر روي تمامي ايميل‌هاي ارسالي اعمال شود).
سپس بر روي دكمه yes پيغام باز شده كليك نمائيد.
تنظيمات اصلي مطلب جاري مربوط به اين صفحه است. در اينجا گزينه defer delivery by a number of minutes را تيك بزنيد.




سپس بر روي لينك a number of كليك كنيد تا صفحه وارد كردن ميزان زمان به تاخير انداختن ارسال را بتوان وارد كرد. پس از وارد كردن يك عدد دلخواه و كليك بر روي دكمه ok ، بر روي دكمه next كليك نمائيد.
در صفحه بعد نيز بر روي دكمه next كليك كنيد. (البته در اينجا مي‌توان مشخص كرد كه براي مثال اگر عنوان ويژه‌اي بكار برده شد يا به گروه خاصي ايميل ارسال گرديد، اين محدوديت برداشته شود)
و در آخرين صفحه، نامي دلخواه را وارد كرده و بر روي دكمه‌ي خاتمه كليك نمائيد.
در صفحه‌ي اصلي rules & alerts نيز بر روي دكمه apply كليك كنيد، تا تنظيمات اعمال گردد.

از اين پس هر ايميل ارسالي شما مدتي در outbox معطل شده و سپس ارسال مي‌گردد.
هنوز تا 5 دقيقه ديگر فرصت هست! با مراجعه به outbox مي‌توان ايميل مورد نظر را در صورت منصرف شدن حذف يا ويرايش كرد. (بنابر تجربه 3 دقيقه كافي است!)

مطلب ارسالي فوق براي آوت لوك 2007 تنظيم شد. اگر آوت لوك شما 2003 است لطفا به آدرس زير مراجعه كرده و قسمت Delay delivery of all messages را مطالعه نمائيد.



۱۳۸۸/۰۵/۱۶

استفاده از قابليت Script Data اس كيوال سرور 2008 از طريق برنامه نويسي


همانطور كه مطلع هستيد قابليت تهيه عبارات Insert از جداول يك ديتابيس، به صورت استاندارد به management studio 2008 اضافه شده است. براي استفاده از اين قابليت از طريق برنامه نويسي به صورت زير مي‌توان عمل نمود:

الف) سه ارجاع را به اسمبلي‌هاي زير اضافه نمائيد:
Microsoft.SqlServer.ConnectionInfo
Microsoft.SqlServer.Management.Sdk.Sfc
Microsoft.SqlServer.Smo

ب) اكنون كدي كه عمليات Script Data را با استفاده از قابليت‌هاي SMO ارائه مي‌دهد به صورت زير خواهد بود:

using System;
using System.Text;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;

/// <summary>
/// تهيه اسكريپت ركوردها
/// </summary>
/// <param name="dbName">نام ديتابيس مورد نظر</param>
/// <param name="table">نام جدولي كه بايد اسكريپت شود</param>
/// <param name="instance">وهله سرور</param>
/// <param name="userName">نام كاربري جهت اتصال</param>
/// <param name="pass">كلمه عبور جهت اتصال به سرور</param>
/// <returns>اسكريپت تهيه شده</returns>
public static string ScriptData(string dbName, string table, string instance, string userName, string pass)
{
try
{
ServerConnection serverConnection = new ServerConnection(instance, userName, pass);
Server server = new Server(serverConnection);
Database database = server.Databases[dbName];
if (database == null) return string.Empty;
Table tb = database.Tables[table];
Scripter scripter = new Scripter(server) {Options = {ScriptData = true}};
if (tb == null) return string.Empty;
StringBuilder sb = new StringBuilder();
foreach (string s in scripter.EnumScript(new Urn[] {tb.Urn}))
sb.AppendLine(s);
return sb.ToString();
}
catch(Exception ex)
{
//todo: log ...
return string.Empty;
}
}

نكته:
اسمبلي‌هاي SMO به همراه مجموعه SQL Server 2008 نصب مي‌شوند. بنابراين متد و برنامه‌ي فوق بر روي سروري با اين مشخصات بدون مشكل اجرا خواهد شد. اما اگر قصد توزيع برنامه خود را داريد بايد Microsoft SQL Server 2008 Management Objects را نيز به همراه برنامه خود نصب نمائيد.

۱۳۸۸/۰۵/۱۵

استفاده از كلاس‌هاي *My وي بي در #C


يك سري قابليت در فضاي نام Microsoft.VisualBasic وجود دارد كه به ظاهر ساير برنامه نويسان دات نت از آن محروم هستند. براي مثال My.Computer.Network.IsAvailable براي بررسي اينكه آيا اتصال به شبكه برقرار است يا My.Computer.Audio.Play جهت نواختن يك فايل صوتي، كلاس‌هاي My.Application، My.Computer، My.User My.Webservices، My.DataSources و امثال آن.
از اين فضاي نام در C# يا تمامي زبان‌هاي ديگر دات نت نيز مي‌توان استفاده كرد. تنها كافي است ارجاعي را به Microsoft.VisualBasic.dll اضافه كنيد، در ادامه using Microsoft.VisualBasic.MyServices و سپس معادل‌هاي آن‌ها به صورت زير خواهند بود:

'VB code
Me.cbNetworked.Checked = My.Computer.Network.IsAvailable

// C# code
MyComputer mc = new MyComputer();
cbNetworked.Checked = mc.Network.IsAvailable;

'VB code
Me.cbAltKey.Checked = My.Computer.Keyboard.AltKeyDown
Me.cbCapsLock.Checked = My.Computer.Keyboard.CapsLock
Me.cbCtrlKey.Checked = My.Computer.Keyboard.CtrlKeyDown
' etc...

// C# code
MyComputer mc = new MyComputer();
this.cbAltKey.Checked = mc.Computer.Keyboard.AltKeyDown;
this.cbCapsLock.Checked = mc.Computer.Keyboard.CapsLock;
this.cbCtrlKey.Checked = mc.Computer.Keyboard.CtrlKeyDown;
' etc...

'VB code
My.Computer.Audio.Play(lbClips.SelectedItem)

// C# code
MyAudio ma = new MyAudio();
ma.Play(lbClips.SelectedItem);

'VB code
My.Computer.Info.TotalPhysicalMemory
'etc...

// C# code
MyComputer mc = new MyComputer();
mc.Info.TotalPhysicalMemory;
// etc...

هر چند در واقعيت اين فضاي نام تنها محصور كننده‌ي يك سري از كلاس‌هاي ديگر دات نت است. براي مثال اگر به سورس دات نت فريم ورك مراجعه كنيد، My.Computer.Network.IsAvailable آن دقيقا محصور كننده‌ي متد NetworkInterface.GetIsNetworkAvailable واقع شده در فضاي نام استاندارد System.Net.NetworkInformation است.

۱۳۸۸/۰۵/۱۳

مزيت‌هاي استفاده از رويه‌هاي ذخيره شده؛ واقعيت يا توهم؟!


متن زير يك سري نكات و يا شايد توهماتي را مطرح مي‌كند كه در مورد رويه‌هاي ذخيره شده در اس كيوال سرور رايج هستند.

1- رويه‌هاي ذخيره شده در مقابل SQL Injection مقاوم هستند. كوئري‌هاي Ad hoc هميشه اين آسيب پذيري را به همراه دارند.
نادرست است! رويه‌هاي ذخيره شده‌اي كه رشته‌ها را به صورت پارامتر دريافت كرده و آن‌ها را به صورت يك عبارت sql اجرا مي‌كنند، آسيب پذير هستند. اگر هنگام استفاده از كوئري‌هاي Ad hoc از پارامترها استفاده شود، در برابر حملات SQL Injection مصون خواهيد بود.

2- execution plan رويه‌هاي ذخيره شده كش مي‌شوند اما اين Plan براي كوئري‌هاي Ad hoc هر بار محاسبه و توليد مي‌گردد.
نادرست است! اس كيوال سرور تا اين اندازه بي هوش نيست! اگر execution plan ايي موجود باشد حتما استفاده خواهد شد و براي موتور اس كيوال سرور اصلا اهميتي ندارد كه كوئري در حال اجرا از يك رويه ذخيره شده صادر شده است يا از يك كوئري Ad hoc . رويه‌هاي ذخيره شده پيش كامپايل شده نيستند و مانند تمامي كوئري‌هاي ديگر در زمان اجرا كامپايل مي‌شوند.

3- زمانيكه از رويه ذخيره شده استفاده مي‌كنيد همه چيز را در يك مكان به صورت متمركز و مجتمع خواهيد داشت (مديريت بهتر)
نادرست است! در يك مكان متمركز در اختيار شما نيستند. برنامه جاي خود را دارد و رويه‌هاي ذخيره شده در ديتابيس در جاي ديگري قرار دارند و براي مثال اگر قرار باشد يك پارامتر را به رويه ذخيره شده خود اضافه كنيد، كدهاي شما نيز بايد تغيير كنند.

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

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

نظر شما چيست؟


۱۳۸۸/۰۵/۱۱

معرفي Babel Obfuscator


Babel Obfuscator يك ابزار خط فرمان سورس باز code obfuscation اسمبلي‌هاي دات نت فريم ورك است.


اين ابزار موارد زير را پشتيباني مي‌كند:
- Support NET Framework 1.1, 2.0, 3.5
- Obfuscate Namespace, Type (aslo generic types), Method, Events, Properties and Fields
- Unicode Normalization
- Support Generic Types and Virtual Function Obfuscation
- MSIL Control Flow Obfuscation
- String Encryption
- Dead Code Removal
- Selective Obfuscation with XML Rule Files
- Declarative Obfuscation using Custom Attributes
- MSBuild Integration
- Strong Name Signature
- Break tools like Reflector-Reflexil plug-in v0.8 and Ildasm


وبلاگ نويسنده آن
دريافت Babel Obfuscator از گوگل كد و يا رپيد شير

پس از نصب، جهت مشاهده پارامترهاي خط فرمان آن به فايل ReadMe.htm مراجعه نمائيد و يا اگر علاقمند باشيد كه از آن به صورت يكپارچه با Reflector استفاده كنيد، مي‌توان از افزونه زير كمك گرفت:



۱۳۸۸/۰۵/۱۰

استفاده از jQuery Ajax جهت تعيين اعتبار يك فرم


فرض كنيد تعيين اعتبار يكي از فيلدهاي فرم نياز به انجام محاسباتي در سمت سرور دارد و اين‌كار را مي‌خواهيم با استفاده از jQuery Ajax‌ انجام دهيم. مشكلي كه در اينجا وجود دارد، اين است كه A در Ajax به معناي asynchronous است. يعني زمانيكه كاربر دكمه submit را فشرد، ديگر برنامه منتظر اين نخواهد شد كه پاسخ كامل دريافت شود ، ساير پردازش‌ها صورت گيرد و سپس فرم را به سرور ارسال نمايد (شبيه به ايجاد يك ترد جديد در برنامه‌هاي ويندوزي). مثال زير را در نظر بگيريد:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestCustomValidation.aspx.cs"
Inherits="TestJQueryAjax.TestCustomValodation" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>

<script type="text/javascript">
function validate() {
var number1 = $("#<%=txtNumber1.ClientID %>").val();
var number2 = $("#<%=txtNumber2.ClientID %>").val();
var result = false;
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/ValidateIt',
data: '{"number1":' + number1 + ',"number2":' + number2 + '}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
if (msg.d) {
result = true;
alert('بسيار خوب');
}
else {
result = false;
alert('دوباره سعي كنيد');
}
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
result = false;
alert("خطايي رخ داده است");
}
});
//debugger;
return result;
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
number 1 :
<asp:TextBox runat="server" ID="txtNumber1" />
<br />
number 2 :
<asp:TextBox runat="server" ID="txtNumber2" />
<br />
<asp:Button ID="btnSubmit" Text="Submit" UseSubmitBehavior="false" runat="server"
OnClientClick="if(!validate()){ return false;}" OnClick="btnSubmitClick" />
</div>
</form>
</body>
</html>

اين مثال يك نوع اعتبار سنجي سفارشي را در حين submit با استفاده از وب سرويس زير انجام مي‌دهد (حاصلضرب دو عدد دريافتي را بررسي مي‌كند كه بايد مساوي 10 باشند. البته هدف از اين مثال ساده، آشنايي با نحوه‌ي انجام اين نوع عمليات است كه مي‌تواند شامل كار با ديتابيس و غيره هم باشد. و گرنه بديهي است اين بررسي را با دو سطر كد جاوا اسكريپتي نيز مي‌شد انجام داد):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Web.Script.Services;

namespace TestJQueryAjax
{
/// <summary>
/// Summary description for AjaxSrv
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : System.Web.Services.WebService
{
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public bool ValidateIt(int number1, int number2)
{
return number1 * number2 == 10;
}
}
}
در اين مثال هنگام submit ، عمليات اعتبار سنجي با توجه به وضعيت asynchronous عمليات Ajax ، تمام و كمال رخ نداده ، يا اعتبار سنجي انجام نمي‌شود و يا پيغام خطايي را دريافت خواهيم كرد.

راه حل چيست؟
راه حل‌هاي فضايي بسياري را در وب در اين مورد مي‌توان پيدا كرد؛ اما راه حل استاندارد آن در اين حالت ويژه، استفاده از Ajax در حالت غيرهمزمان است. يعني اين فريم ورك Ajax را وادار كنيم كه تا پايان عمليات مورد نظر، منتظر بماند و سپس فرم را ارسال كند. براي اين منظور تنها كافي است يك سطر زير را پيش از فراخواني تابع Ajax ، اضافه و فراخواني نمائيم:

$.ajaxSetup({async: false}) ;
نكته:
UseSubmitBehavior دكمه ما را به شكل زير رندر مي‌كند (دكمه به يك button معمولي (بجاي حالت submit) تبديل شده و سپس يك doPostBack را اضافه خواهد كرد):

<input id="btnSubmit" type="button" onclick="if(!validate()){ return false;};__doPostBack('btnSubmit','')" value="Submit" name="btnSubmit"/>