۱۳۸۸/۰۳/۰۸

معرفي ELMAH


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

ELMAH يك ماژول رايگان و سورس باز لاگ كردن خطاهاي مديريت نشده برنامه‌هاي ASP.Net‌ است. براي استفاده از اين ماژول نيازي نيست تا تغييري در برنامه خود ايجاد كنيد يا حتي آن‌را كامپايل مجدد نمائيد. يك فايل dll‌ دارد به همراه كمي تغيير در web.config برنامه جهت معرفي آن و اين تمام كاري است كه براي برپايي آن لازم است صورت گيرد. اين ماژول تمامي خطاهاي مديريت نشده‌ي برنامه شما را لاگ كرده (در حافظه سرور، در يك فايل xml ، در يك ديتابيس اس كيوال سرور يا اوراكل ، در يك ديتابيس اكسس و يا در يك ديتابيس اس كيوال لايت) و براي مرور آن‌ها يك صفحه‌ي وب سفارشي يا فيدي مخصوص را نيز در اختيار شما قرار مي‌دهد. همچنين اين قابليت را هم دارد كه به محض بروز خطايي يك ايميل را نيز به شما ارسال نمايد.

با توجه به اين‌كه اين ماژول در Google code قرار گرفته احتمالا دسترسي به آن مشكل خواهد بود. سورس و فايل‌هاي كامپايل شده آن‌را از آدرس‌هاي زير نيز مي‌توان دريافت نمود:
( + و + و +)

نحوه استفاده از ELMAH :

براي استفاده از ELMAH دو كار را بايد انجام دهيد:
الف) كپي كردن فايل Elmah.dll در پوشه bin برنامه
ب) تنظيم وب كانفيگ برنامه

بهترين مرجع براي آشنايي با نحوه بكار گيري اين ماژول، مراجعه به فايل web.config موجود در پوشه samples آن است. بر اساس اين فايل نمونه:

- ابتدا بايد configSections آن را به وب كانفيگ خود اضافه كنيد.

- سپس تگ elmah بايد اضافه شود. در اين تگ موارد زير مشخص مي‌شوند:
الف) آيا خطاها توسط آدرس elmah.axd توسط كاربران راه دور قابل مشاهده شود يا خير.
ب) خطاها كجا ذخيره شوند؟ موارد زير پشتيباني مي‌شوند:
ديتابيس‌هاي اس كيوال سرور ، اوراكل ، حافظه سرور، فايل‌هاي xml ، ديتابيس SQLite ، ديتابيس اكسس و يا ديتابيسي از نوع VistaDB
ج) آيا خطاها ايميل هم بشوند؟ اگر بلي، تگ مربوطه را تنظيم كنيد.
د) آيا خطاها به اكانت twitter شما نيز ارسال شوند؟

- در ادامه تگ مربوط به معرفي اين httpModules بايد تنظيم شود.

- سپس httpHandlers ايي به نام elmah.axd كه جهت مرور خطاها مي‌توان از آن استفاده نمود معرفي مي‌گردد.

- از IIS7 استفاده مي‌كنيد؟ قسمت system.webServer را نيز بايد اضافه نمائيد.

- و در آخر نحوه‌ي دسترسي به elmah.axd مشخص مي‌شود. اگر اجازه دسترسي از راه دور را داده باشيد، به اين طريق مي‌شود دسترسي را فقط به كاربران مجاز و تعيين اعتبار شده، اعطاء كرد و يا به نقشي مشخص مانند ادمين و غيره.

براي نمونه، اگر بخواهيد از ديتابيس SQLite جهت ذخيره سازي خطاهاي حاصل شده استفاده نمائيد و نيز از ارسال ايميل صرفنظر كنيد، وب كانفيگ برنامه شما بايد به شكل زير تغيير يابد:
<?xml version="1.0"?>

<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.SQLiteErrorLog, Elmah" connectionStringName="cn1" />
</elmah>


<appSettings/>

<connectionStrings>
<add name="cn1" connectionString="data source=~/ErrorsLog/Errors.db" />
</connectionStrings>

<system.web>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>

<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>

<compilation debug="true"/>

<authentication mode="Windows" />
</system.web>

</configuration>
در اينجا يك پوشه جديد به نام ErrorsLog را بايد به ريشه سايت خود اضافه كنيد (يا هر نام دلخواه ديگري كه در قسمت connectionStrings بايد تنظيم شود). فايل Errors.db به صورت خودكار ايجاد خواهد شد. بديهي است كاربر ASP.net بايد دسترسي write بر روي اين پوشه داشته باشد تا عمليات ثبت خطاها با موفقيت صورت گيرد. همچنين فايل System.Data.SQLite.DLL نيز بايد در پوشه bin برنامه شما كپي شود.
ساده‌ترين تنظيم اين ماژول استفاده از حالت xml است كه به ازاي هر خطا يك فايل xml را توليد كرده و نياز به اسمبلي ديگري بجز ماژول مربوطه نخواهد داشت.

تذكر:
از لحاظ امنيتي مثال فوق توصيه نمي‌شود زيرا allowRemoteAccess آن 1 است و قسمت authorization ذكر نشده است. اين مثال فقط جهت راه اندازي و آزمايش اوليه ارائه گرديده است. (همچنين بهتر است اين نام پيش فرض را به نامي ديگر مثلا myloggermdl.axd تغيير داده و در قسمت httpHandlers تنظيم نمائيد. سپس اين نام را به تگ location نيز اضافه كنيد)

اكنون براي مشاهده خروجي اين ماژول به انتهاي آدرس سايت خود، elmah.axd را اضافه كرده و سپس enter كنيد:



همانطور كه در تصوير مشخص است، تمامي خطاهاي لاگ شده گزارش داده شده‌اند. همچنين دو نوع فيد به همراه امكان دريافت خطاها به صورت CSV نيز موجود است. با كليك بر روي لينك details ، صفحه‌ي بسيار ارزنده‌اي ارائه مي‌شود كه تقريبا نحوه‌ي وقوع ماجرا را بازسازي مي‌كند.
نكته‌ي مهمي كه در صفحه‌ي جزئيات ارائه مي‌شود (علاوه بر stack trace‌ و مشخصات كاربر)، مقادير تمامي فيلدهاي يك صفحه هنگام بروز خطا است (قسمت Raw/Source data in XML or in JSON در اين صفحه) :



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


نكته:
ماژول SQLite ايي كه به همراه مجموعه ELMAH ارائه مي‌شود 32 بيتي كامپايل شده (64 بيتي آن نيز موجود است كه بايد از آن در صورت لزوم استفاده شود). بنابراين براي اينكه در يك سرور 64 بيتي به مشكل برنخوريد و خطاي BadImageFormat را دريافت نكنيد نياز است تا به اين نكته دقت داشت.

براي مطالعه بيشتر:

Error Logging Modules And Handlers
Sending ELMAH Errors Via GMail
Exception-Driven Development
Using HTTP Modules and Handlers to Create Pluggable ASP.NET Components

۱۳۸۸/۰۳/۰۶

رسم گراف


كتابخانه‌هاي زيادي براي رسم گراف وجود دارند منجمله mxGraph كه براي استفاده غيرتجاري رايگان و سورس باز است. mxGraph نگارش‌هاي PHP ، Java‌ و JavaScript ايي نيز دارد كه به همراه بسته مربوطه ارائه مي‌شوند.
پس از دريافت آن، در فولدري به نام dotnet مي‌توانيد سورس كتابخانه مربوط به دات نت فريم ورك آن‌را دريافت كنيد.
فايل پروژه‌ي VS.Net را در آن فولدر نخواهيد يافت. حتي آن‌را كامپايل هم نكرده‌اند. (احتمالا به اين دليل كه كسي نپرسد اين پروژه با چه محصولي توليد شده و آيا لايسنس استفاده از آن را داريد يا خير. اين هم يك روش است ...)
براي كامپايل آن، يك پروژه library جديد را در VS.Net آغاز كرده و پوشه‌هاي موجود در پوشه‌ي dotnet را به آن افزوده و سپس آن‌را كامپايل كنيد تا فايل mxGraph.dll توليد شود.

يك مثال ساده از نحوه‌ي استفاده‌ي آن به صورت زير است كه فايل test.png را توليد خواهد كرد.
using System;
using System.Drawing;
using System.Windows.Forms;
using com.mxgraph;
using System.Drawing.Imaging;

void Test1()
{
// Creates graph with model
mxGraph graph = new mxGraph();
Object parent = graph.GetDefaultParent();

// Adds cells into the graph
graph.Model.BeginUpdate();
try
{
Object v1 = graph.InsertVertex(parent, null, "سلام", 20, 20, 80, 30, "strokeColor=#FFCF8A;fillColor=#FFCF8A;gradientColor=white;fontBold=true;fontFamily=tahoma;rounded=true;shadow=true;shape=ellipse");
Object v2 = graph.InsertVertex(parent, null, "!دنياي ظالم", 200, 150, 80, 30, "rounded=true;shadow=true;fontFamily=tahoma");
Object e1 = graph.InsertEdge(parent, null, "e1", v1, v2, "fontFamily=tahoma");
}
finally
{
graph.Model.EndUpdate();
}

mxCellRenderer.CreateImage(graph, null, 1,
Color.White, true, null).Save("test.png", ImageFormat.Png);
}




و يا اگر قصد داشته باشيد كه از آن در ASP.Net استفاده كنيد، يك generic handler را به پروژه خود افزوده (مثلا ImageHandler.ashx) و كد آن‌را براي مثال به صورت زير تغيير دهيد:

using System;
using System.Web;
using com.mxgraph;
using System.Drawing;
using System.Web.Services;
using System.IO;
using System.Drawing.Imaging;

namespace test
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ImageHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
// Creates graph with model
mxGraph graph = new mxGraph();
Object parent = graph.GetDefaultParent();

// Adds cells into the graph
graph.Model.BeginUpdate();
try
{
Object v1 = graph.InsertVertex(parent, null, "سلام", 20, 20, 80, 30, "strokeColor=#FFCF8A;fillColor=#FFCF8A;gradientColor=white;fontBold=true;fontFamily=tahoma;rounded=true;shadow=true;shape=ellipse");
Object v2 = graph.InsertVertex(parent, null, "!دنياي ظالم", 200, 150, 80, 30, "rounded=true;shadow=true;fontFamily=tahoma");
Object e1 = graph.InsertEdge(parent, null, "e1", v1, v2, "fontFamily=tahoma");
}
finally
{
graph.Model.EndUpdate();
}

Image image = mxCellRenderer.CreateImage(graph, null, 1, Color.White, true, null);

// Render BitMap Stream Back To Client
MemoryStream memStream = new MemoryStream();
image.Save(memStream, ImageFormat.Png);

memStream.WriteTo(context.Response.OutputStream);
}

public bool IsReusable
{
get
{
return false;
}
}
}

}
اكنون نحوه استفاده از اين handler در يك صفحه وب به صورت زير است:
<img src="ImageHandler.ashx" />

۱۳۸۸/۰۳/۰۵

finally در سي شارپ چه زماني اجرا نمي‌شود؟


عموما از كدهاي قرار گرفته در قطعه finally جهت آزاد سازي منابع استفاده مي‌شود و تضمين شده است كه اين قطعه همواره اجرا مي‌گردد، صرفنظر از اينكه آيا در قطعه try استثنايي رخ داده است يا خير.

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

using System;

namespace testWinForms87
{
class CTestFinally
{
public static void Run()
{
try
{
TryAndTry();
}
catch (Exception exError)
{
Console.WriteLine(exError.Message);
}
finally
{
Console.WriteLine("Finally...!");
}
Console.ReadKey();
}

static void TryAndTry()
{
try
{
TryAndTry();
}
catch (Exception exError)
{
Console.WriteLine(exError.Message);
}
finally
{
Console.WriteLine("Try: Finally...!");
}
}
}

}

در اين كد به علت بروز stack overflow هيچگاه به finally نخواهيم رسيد.

۱۳۸۸/۰۳/۰۴

مقايسه ركوردهاي دو جدول


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

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

CREATE TABLE #tableA
(
column1 INT,
column2 INT
)

INSERT INTO #tableA
VALUES
(1,1)
,(1, 2)
,(1, 3)
,(2, 1)

SELECT column1,
column2
FROM #tableA

CREATE TABLE #tableB
(
column1 INT,
column2 INT
)

INSERT INTO #tableB
VALUES
(1,1)
,(1, 3)
,(2, 2)

SELECT column1,
column2
FROM #tableB

يك سري ديتاي دلخواه به آن‌ها اضافه شده است. (از روش اضافه كردن چندين ركورد توسط يك عبارت insert كه در اس كيوال سرور 2008 معرفي شده، استفاده گرديده است)
#tableA
column1 column2
----------- -----------
1 1
1 2
1 3
2 1


#tableB
column1 column2
----------- -----------
1 1
1 3
2 2

اكنون مي‌خواهيم ركوردهايي از جدول A را كه در جدول B نيستند، پيدا كنيم. روش متداول انجام اين‌كار در اس كيوال سرور 2000 به صورت زير است:

SELECT column1,
column2
FROM #tableA
WHERE NOT EXISTS (
SELECT *
FROM #tableB
WHERE #tableA.column1 = #tableB.column1
AND #tableA.column2 = #tableB.column2
)

column1   column2
----------- -----------
1 2
2 1
و يا روش زيباتر انجام اين‌كار كه از اس‌كيوال سرور 2005 به بعد معرفي شده، به صورت زير مي‌باشد:
SELECT column1, column2 FROM #tableA
EXCEPT
SELECT column1, column2 FROM #tableB

column1   column2
----------- -----------
1 2
2 1
Except ركوردهاي منحصربفردي از كوئري سمت چپ را كه در كوئري سمت راست وجود ندارند، بر مي‌گرداند.
در اين حالت تعداد ستون‌هاي در نظر گرفته شده براي مقايسه بايد يكسان و يك نوع باشند.
همچنين اگر مي‌خواهيد ركوردهايي از جدول A را كه در جدول B وجود دارند بيابيد، مي‌توان از intersect استفاده كرد.

۱۳۸۸/۰۳/۰۲

نحوه شبيه سازي سرعت‌هاي اتصال پايين جهت آزمايش يك وب سايت


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






همانطور كه در تصوير ملاحظه مي‌كنيد، توسط اين افزونه مي‌توان تنظيمات برگه settings را بر روي يك سري IP‌ و يا سايت و يا به سادگي بر روي كليه ارتباط‌هايي كه به localhost ختم مي‌شوند،‌ اعمال كرد.


۱۳۸۸/۰۳/۰۱

5 دليل براي استفاده از يك ابزار ORM


چرا بايد از ابزارهاي Object relational Mapper يا به اختصار ORM استفاده كرد؟ در اينجا سخن در مورد ORM خاصي نيست. هدف تبليغ يك محصول ويژه هم نمي‌باشد و يك بحث كلي مد نظر است.
كار ابزارهاي ORM خواندن ساختار ديتابيس شما بوده و سپس ايجاد كلاس‌هايي بر اساس اين ساختار ، برقراري ارتباط بين اشياء ايجاد شده و جداول، ويووها، رويه‌هاي ذخيره شده و غيره مي‌باشد. همچنين اين ابزارها امكان تعريف روابط one-to-one, one-to-many, many-to-one, و many-to-many بين اشياء را نيز بر اساس ساختار ديتابيس شما فراهم مي‌كنند.
در ادامه به فوايد استفاده از ORM ها خواهيم پرداخت:

الف) يك ابزار ORM زمان تحويل پروژه را كاهش مي‌دهد

اولين و مهم‌ترين دليلي كه بر اساس آن در يك پروژه، استفاده از ORM حائز اهميت مي‌شود، بحث بالا بردن سرعت برنامه نويسي و كاهش زمان تحويل پروژه به مشتري است. اين كاهش زمان بسته به نوع پروژه بين 20 تا 50 درصد مي‌تواند خود را بروز دهد.
بديهي است ابزارهاي ORM كار شگفت انگيزي را قرار نيست انجام دهند و شما مي‌توانيد تمام آن عمليات ‌را دستي هم به پايان رسانيد؛ اما اجازه دهيد يك مثال كوتاه را با هم مرور كنيم.
براي پياده سازي يك برنامه متداول با حدود 15 تا 20 جدول، حدودا به 30 شيء براي مدل سازي سيستم نياز خواهد بود و برنامه نويسي اين مجموعه بين 5000 تا 10000 سطر كد را به خود اختصاص خواهد داد. بديهي است برنامه نويسي و آزمايش اين سيستم چندين هفته يا ماه به طول خواهد انجاميد.
اما با استفاده از يك ORM ، عمده وقت شما به طراحي سيستم و ايجاد ارتباطات بين اشياء و ديتابيس در طي يك تا دو روز صرف خواهد شد. ايجاد كد بر اساس اين مجموعه و با كمك ابزارهاي ORM ، آني است و با چند كليك صورت مي‌گيرد.


ب) يك ابزار ORM كدي با طراحي بهتر را توليد مي‌كند

ممكن است شما بگوئيد كه كد نويسي من بي‌نظير است و از من بهتر كسي را نمي‌توانيد پيدا كنيد! به تمامي زواياي كار خود مسلطم و نيازي هم به اين‌گونه ابزارها ندارم!
عده‌اي از شما به طور قطع اين‌گونه‌ايد؛ اما نه همه. در يك تيم متوسط، همه نوع برنامه نويس با سطوح مختلفي را مي‌توانيد پيدا كنيد و تمامي ‌‌آن‌ها برنامه نويس‌ها و يا طراح‌هاي آنچنان قابلي هم نيستند. بنابراين امكان رسيدن به كدهايي كه مطابق اصول دقيق برنامه نويسي شيء گرا نيستند و در آن‌ها الگوهاي طراحي به خوبي رعايت نشده، بسيار محتمل است. همچنين در يك تيم زمانيكه از يك الگوي يكسان پيروي نمي‌شود، نتايج نهايي بسيار ناهماهنگ خواهند بود.
در مقابل استفاده از ORM هاي طراحي شده توسط برنامه نويس‌هاي قابل (senior (architect level) engineers) ، كدهايي را بر اساس الگوهاي استاندارد و پذيرفته شده‌ي شيء‌گرا توليد مي‌كنند و همواره يك روند كاري مشخص و هماهنگ را در يك مجموعه به ارمغان خواهند آورد.

ج) نيازي نيست تا حتما يك متخصص دات نت فريم ورك باشيد تا از يك ORM استفاده كنيد

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


د) هنگام استفاده از يك ابزار ORM ، مدت زمان آزمايش برنامه نيز كاهش مي‌يابد

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

ه) استفاده از يك ابزار ORM ، كار برنامه نويسي شما را ساده‌تر مي‌كند

توضيح اين قسمت نياز به ذكر يك مثال دارد. لطفا به مثال زير دقت بفرمائيد:

try {
Employees objInfo = new Employees();
EmployeesFactory objFactory = new EmployeesFactory();

objInfo.EmployeeID = EmployeeID;
objFactory.Load(objInfo);

// code here to use the "objInfo" object
}
catch(Exception ex) {
// code here to handle the exception
}

به نظر شما كار كردن با يك يا چند شيء توليد شده كه نمايانگر ساختار ديتابيس شما هستند و با استفاده از اينترفيس عمومي آن‌ها مي‌توان تمامي اعمال بارگذاري، درج و حذف و غيره را انجام داد، ساده‌تر است يا كار كردن با كوهي از دستورات ADO.Net ؟


برداشتي آزاد از Five Reasons for using an ORM Tool

۱۳۸۸/۰۲/۳۰

ليست كردن ايميل‌هاي موجود در Global address list


Global Address List يا به اختصار GAL و يا همان Microsoft Exchange Global Address Book ، حاوي اطلاعات تمامي كاربران تعريف شده در Exchange server مايكروسافت است و زمانيكه outlook در شبكه به exchange server متصل مي‌شود، كاربران مي‌توانند با كمك آن ليست اعضاء را مشاهده كرده ، يك يا چند نفر را انتخاب نموده و به آن‌ها ايميل ارسال كنند (شكل زير):


نياز بود تا اين ليست تعريف شده در مايكروسافت اكسچنج، با اطلاعات يك ديتابيس مقايسه شوند كه آيا اين اطلاعات مطابق ركوردهاي موجود تعريف شده يا خير.
بنابراين اولين قدم، استخراج email هاي موجود در GAL بود (دسترسي به همين برگه‌ي email address كه در شكل فوق ملاحظه مي‌كنيد از طريق برنامه نويسي) كه خلاصه آن تابع زير است:
جهت استفاده از آن ابتدا بايد يك ارجاع به كتابخانه COM ايي به نام Microsoft Outlook Object Library اضافه شود.

using System.Collections.Generic;
using System.Reflection;
using Microsoft.Office.Interop.Outlook;

namespace GAL
{
//add a reference to Microsoft Outlook 12.0 Object Library
class COutLook
{
public struct User
{
public string Name;
public string Email;
}

public static List<User> ExchangeServerEmailAddresses(string userName)
{
List<User> res = new List<User>();
//Create Outlook application
Application outlookApp = new Application();
//Get Mapi NameSpace and Logon
NameSpace ns = outlookApp.GetNamespace("MAPI");
ns.Logon(userName, Missing.Value, false, true);

//Get Global Address List
AddressLists addressLists = ns.AddressLists;
AddressList globalAddressList = addressLists["Global Address List"];
AddressEntries entries = globalAddressList.AddressEntries;
foreach (AddressEntry entry in entries)
{
ExchangeUser user = entry.GetExchangeUser();
if (user != null && user.PrimarySmtpAddress != null && entry.Name != null)
res.Add(new User
{
Name = entry.Name,
Email = user.PrimarySmtpAddress
});
}

ns.Logoff();

// Clean up.
outlookApp = null;
ns = null;
addressLists = null;
globalAddressList = null;
entries = null;

return res;
}
}
}
و نحوه استفاده از آن هم به صورت زير مي‌تواند باشد:

List<COutLook.User> data = COutLook.ExchangeServerEmailAddresses("nasiri");
foreach (var list in data)
{
//....
}
در اينجا Nasiri نام كاربري شخص در دومين است (كاربر جاري لاگين كرده در سيستم).
تنها نكته‌ي مهم اين كد، مهيا نبودن فيلد ايميل در شيء AdderssEntry است كه بايد از طريق متد GetExchangeUser آن اقدام شود.


۱۳۸۸/۰۲/۲۷

كش كردن اطلاعات غير پويا در ASP.Net - قسمت سوم


در دو قسمت قبل در مورد IIS7 و IIS6 صحبت شد (+ و +).
در تكميل قسمت دوم، يك مورد هم جزو قابليت‌هاي ذاتي IIS6 و همچنين IIS5 است كه مي‌توان آن‌را فعال نمود (اگر دسترسي به سرور داريد) :




تنظيم مدت زمان content expiration ، بدون نياز به برنامه نويسي خاصي، كار اضافه كردن هدر مربوط به مدت زمان كش شدن سمت كلاينت را به محتويات غيرپوياي سايت شما مانند تصاوير ، فايل‌هاي CSS و غيره انجام مي‌دهد. آمارها نشان مي‌دهند كه اين تنظيم، زمان بارگذاري بعدي را بين 50 تا 70 درصد كاهش مي‌دهد.
تنظيم اين قابليت را مي‌توانيد به چك ليست نصب IIS خود اضافه نمائيد.

۱۳۸۸/۰۲/۲۶

banned.h


مطالبي توسط تيم Security Development Lifecycle مايكروسافت منتشر شده مبني بر اينكه آن‌ها هم يك سري از توابع استاندارد زبان C را در كدهاي جديد خود ممنوع كرده‌اند. مستندات آن‌را در مقاله زير مي‌توانيد مشاهده نمائيد:


اخيرا فايل header آن نيز مطابق آخرين به روز رساني‌هاي مورد استفاده منتشر شده است:


استفاده از اين توابع در كدهاي جديد مايكروسافت ممنوع بوده و كدهاي قديمي نيز به مرور اصلاح خواهند شد.

جديدترين تابعي كه به اين ليست اضافه شده ، تابع memcpy است كه سر منشاء نقايص امنيتي زير بوده است:
MS03-030 (DirectX)
MS03-043 (Messenger Service)
MS03-044 (Help and Support)
MS05-039 (PnP)
MS04-011 (PCT)
MS05-030 (Outlook Express)
CVE-2007-3999 (MIT Kerberos v5)
CVE-2007-4000 (MIT Kerberos v5)
...!

#pragma deprecated (memcpy, RtlCopyMemory, CopyMemory)
در اين حالت زمانيكه كد خود را كامپايل نمائيد با اخطار زير مواجه خواهيد شد:
warning C4995: 'memcpy': name was marked as #pragma deprecated
جايگزين آن تابع memcpy_s معرفي شده است و در اين حالت كد قديمي:
char dst[32];
memcpy(dst,src,len);
بايد به كد زير تبديل گردد:
char dst[32];
memcpy_s(dst,sizeof(dst), src,len);
كه يك آرگومان بيشتر دارد و آن هم اندازه‌ي بافر مقصد مورد نظر است.

براي مطالعه بيشتر
Please Join me in welcoming memcpy() to the SDL Rogues Gallery
Unsafe at any speed: Memcpy() banished in Redmond
Good hygiene and Banned APIs
A Look Inside the Security Development Lifecycle at Microsoft

۱۳۸۸/۰۲/۲۳

انتقال فايل‌هاي ديتابيس اس كيوال سرور 2008


روز قبل نياز بود تا فايل‌هاي mdf و ldf ديتابيس‌ها جابجا شوند (يك هارد بزرگتر و از اين مسايل).
براي جابجا كردن اين فايل‌ها هم روش معمول detach و سپس attach است. ابتدا روي ديتابيس كليك راست كرده و detach . حالا فايل‌ها را جابجا مي‌كنيد و سپس attach . يا مي‌شود بك آپ كامل گرفت و بعد ري استور كرد.
عموما هم نمي‌توان ديتابيس در حال استفاده را detach‌ كرد. بايد ديتابيس ابتدا single user شود و بعد مي‌توان اين‌كار را انجام داد.
تا اينجاي كار متداول است. همه چيز به خوبي انجام شد. سپس در لحظه attach ، ديتابيس‌ها به صورت read only اتچ شدند با آيكوني سياه رنگ در management studio . (و رنگ من هم بلافاصله به همين رنگ متمايل شد!)
بعد از مدتي جستجو مشخص شد كه در اس كيوال سرور 2008 براي كاهش سطح حمله به سرور، از يك سري يوزر با دسترسي كم براي نصب اس كيوال سرور استفاده مي‌شود (بسيار هم خوب) و اس كيوال سرور 2008 ، يك سري يوزر مخصوص را هم در حين نصب ايجاد مي‌كند كه به صورت خودكار بر روي پوشه ديتاي شما دسترسي full control دارد براي اينكه بتواند كارش را انجام دهد.
حال اگر شما در جاي ديگري پوشه‌اي درست كرديد و اين ديتابيس‌ها را منتقل نموديد، مجددا پيش از هر كاري بايد اين دسترسي را برقرار كنيد و گرنه اس كيوال سرور مجوز write نخواهد داشت؛ به همين جهت ديتابيس به صورت read only در management studio با رنگ مشكي ظاهر مي‌شود.
نام اين كاربر مخصوص به صورت زير است:

SQLServerMSSQLUser$ComputerName$MSSQLSERVER




پس از برقراري دسترسي هم مشكل برطرف نمي‌شود. بايد دستور زير را نيز اجرا نمود:
ALTER DATABASE myDB SET READ_WRITE
اجراي اين دستور نيز، نياز به حالت single user دارد.

پ.ن.
مي‌توان دسترسي يوزر سرويس اس كيوال سرور 2008 را نيز مانند نگارش‌هاي قبلي به حالت local system تغيير داد (يا هر اكانت ديگري با دسترسي بالا) تا اين مشكلات نباشد؛ ولي بديهي است سطح حمله به سرور نيز به همين اندازه افزايش مي‌يابد.

۱۳۸۸/۰۲/۲۲

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


اس كيوال سرور خود را به 2008 ارتقاء داده‌ايد؟ آيا مي‌دانيد كه مجاز هستيد از تمامي امكانات جديد آن در ديتابيس‌هاي موجود خود استفاده كنيد يا خير؟
به همين منظور ابتدا كوئري زير را اجرا نمائيد:
Use master;
SELECT name, compatibility_level FROM sys.databases
ليست ديتابيس‌هاي موجود به همراه درجه سازگاري آن‌ها نمايش داده مي‌شود. هر كدام كه درجه سازگاري مساوي با 100 نداشت يعني مجاز به استفاده از تمامي امكانات سرور جديد نيست و با آن همانند يك ديتابيس قديمي بر اساس درجه سازگاري آن رفتار مي‌شود.
تفسير اين اعداد مطابق اطلاعات زير است:
60 = SQL Server 6.0
65 = SQL Server 6.5
70 = SQL Server 7.0
80 = SQL Server 2000
90 = SQL Server 2005
100= SQL Server 2008
براي تغيير اين درجه سازگاري مي‌توان از يكي از دستورات T-SQL زير استفاده كرد:

-- Old way:
EXEC sp_dbcmptlevel 'AdventureWorks', 100

-- New way:
ALTER DATABASE 'AdventureWorks' SET COMPATIBILITY_LEVEL = 100 ;
كه روش اول منسوخ شده در نظر گرفته شده و روش دوم پيشنهاد مي‌شود.
براي مثال كوئري زير عبارات Alter مورد نظر را جهت ارتقاء ديتابيس‌هاي موجود كه درجه سازگاري آن‌ها 100 نيست (هنوز به اس كيوال سرور 2008 ارتقاء پيدا نكرده‌اند)، توليد مي‌كند:

SELECT 'ALTER DATABASE ' + NAME + ' SET COMPATIBILITY_LEVEL = 100;' TSQL
FROM sys.databases
WHERE compatibility_level <> 100


۱۳۸۸/۰۲/۲۱

كاهش پهناي باند مصرفي يك سايت ASP.Net


مطلبي را ديروز در وبلاگ آقاي صحرايي ديدم در مورد بهبود كارآيي برنامه‌ها و سايت‌هاي ASP.Net ، كه يكي از موارد آن "فاصله بین تگ ها را تا حد ممکن از بین ببرید" بود.
براي پياده سازي آن به صورت زير مي‌توان عمل كرد:
using System.Text.RegularExpressions;

//حذف فاصله‌هاي خالي
private static readonly Regex REGEX_BETWEEN_TAGS = new Regex(@">\s+<", RegexOptions.Compiled);
private static readonly Regex REGEX_LINE_BREAKS = new Regex(@"\n\s+", RegexOptions.Compiled);

protected override void Render(HtmlTextWriter writer)
{

using (HtmlTextWriter htmlwriter = new HtmlTextWriter(new System.IO.StringWriter()))
{
base.Render(htmlwriter);
string html = htmlwriter.InnerWriter.ToString();

html = REGEX_BETWEEN_TAGS.Replace(html, "> <");
html = REGEX_LINE_BREAKS.Replace(html, string.Empty);

writer.Write(html.Trim());
}
}
اين قطعه كد را در مسترپيج سايت خود قرار دهيد تا به صورت يكسان به كل سايت اعمال شود.

يا روش ديگر اعمال آن سفارشي ساختن ASP.NET pipeline با كمك Response.Filter آن است. براي مشاهده پياده سازي آن لطفا به مقاله زير مراجعه بفرمائيد:

براي بهينه سازي قسمت اعمال regular expressions آن مي‌توان به مقاله "چگونه Regex سريعتري داشته باشيم؟" مراجعه كرد.

۱۳۸۸/۰۲/۲۰

بدست آوردن نام پروسه‌اي كه Clipboard را قفل كرده است


امروز Clipboard‌ سيستم عمل نمي‌كرد و عمليات حياتي copy/paste از كار افتاده بود! پس از كمي جستجو مشخص شد كه به صورت زير مي‌توان نام پروسه‌اي كه Clipboard را باز و قفل كرده و مانع عملكرد ساير برنامه‌ها مي‌شود، بدست آورد:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Diagnostics;

namespace testWinForms87
{
class CTestClipboard
{
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetOpenClipboardWindow();

[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(
IntPtr hWnd,
out uint lpdwProcessId);

public static void TrySetData()
{
try
{
Clipboard.SetData(DataFormats.Text, "وحيد");
}
catch
{
IntPtr hwnd = GetOpenClipboardWindow();
if (hwnd == IntPtr.Zero) return;
uint pid;
GetWindowThreadProcessId(hwnd, out pid);
MessageBox.Show(string.Format("clipboard is locked by: {0}",
Process.GetProcessById((int)pid).Modules[0].FileName));
}
}
}
}
با استفاده از تابع GetOpenClipboardWindow دستگيره پنجره‌اي كه اين‌كار را كرده يافت مي‌شود و سپس با استفاده از GetWindowThreadProcessId مي‌توان id آن پروسه را يافت. سپس با كمك متد Process.GetProcessById امكان بدست آوردن اطلاعات بيشتري از آن پروسه ميسر مي‌گردد.



به نظر اين يك باگ در VPC است.
اگر از MS Virtual PC استفاده مي‌كنيد و اين اتفاق رخ داد، داخل سيستم عاملي كه توسط VPC در حال اجرا است، يك متن ساده را كپي كنيد. سپس به منوي برنامه VPC ، گزينه edit مراجعه كرده و در ادامه گزينه Paste را انتخاب كنيد. به اين صورت بدون نياز به بستن برنامه يا هر عمليات ديگري مشكل برطرف مي‌شود.

۱۳۸۸/۰۲/۱۸

وضعيت آسيب پذيري وب سرورهاي مطرح در سال 2008



به نقل از Secunia كه يكي از مراجع بي‌طرف در اين زمينه به شمار مي‌رود:

Web server Secunia advisories Vulnerabilities Unpatched Secunia advisories

IIS 6
5 4 0%

IIS 7
1 1 0%

Apache 2.0.x
39 23 10%

Apache 2.2.x
10 16 20%



۱۳۸۸/۰۲/۱۷

آشنايي با mocking frameworks - قسمت دوم


استفاده از mocking frameworks :

تعدادي از چارچوب‌هاي تقليد نوشته شده براي دات نت فريم ورك مطابق ليست زير بوده و هدف از آن‌ها ايجاد ساده‌تر اشياء تقليد براي ما مي‌باشد:

Nmock : http://www.nmock.org
Moq : http://code.google.com/p/moq
Rhino Mocks : http://ayende.com/projects/rhino-mocks.aspx
TypeMock : http://www.typemock.com
EasyMock.Net : http://sourceforge.net/projects/easymocknet

در اين بين Rhino Mocks كه توسط يكي از اعضاي اصلي تيم NHibernate به وجود آمده است، در مجامع مرتبط بيشتر مورد توجه است. براي آشنايي بيشتر با آن مي‌توان به اين ويديوي رايگان آموزشي در مورد آن مراجعه نمود (حدود يك ساعت است).



خلاصه‌ا‌ي در مورد نحوه‌ي استفاده از Rhino Mocks :
پس از دريافت كتابخانه سورس باز Rhino Mocks ، ارجاعي را به اسمبلي Rhino.Mocks.dll آن، در پروژه آزمون واحد خود اضافه نمائيد.
يك Rhino mock test با ايجاد شيءايي از MockRepository شروع مي‌شود و كلا از سه قسمت تشكيل مي‌گردد:
الف) ايجاد شيء Mock يا Arrange . هدف از ايجاد شيء mock ، جايگزين كردن و يا تقليد يك شيء واقعي جهت مباحثي مانند ايزوله سازي آزمايشات، بالابردن سرعت آن‌ها و متكي به خود كردن اين آزمايشات مي‌باشد. همچنين در اين حالت نتايج false positive نيز كاهش مي‌يابند. منظور از نتايج false positive اين است كه آزمايش بايد با موفقيت به پايان برسد اما اينگونه نشده و علت آن بررسي سيستمي ديگر در خارج از مرزهاي سيستم فعلي است و مشكل از جاي ديگري نشات گرفته كه اساسا هدف از تست ما بررسي عملكرد آن سيستم نبوده است. كلا در اين موارد از mocking objects استفاده مي‌شود:
- دسترسي به شيء مورد نظر كند است مانند دسترسي به ديتابيس يا محاسبات بسيار طولاني
- شيء مورد نظر از call back استفاده مي‌كند
- شيء مورد آزمايش بايد به منابع خارجي دسترسي پيدا كند كه اكنون مهيا نيستند. براي مثال دسترسي به شبكه.
- شيءايي كه مي‌خواهيم آن‌را تست كنيم يا براي آن آزمايشات واحد تهيه نمائيم، هنوز كاملا توسعه نيافته و نيمه كاره است.
ب) تعريف رفتارهاي مورد نظر يا Act
ج) بررسي رفتارهاي تعريف شده يا Assert

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

public class ImageManagement
{
public string GetImageForTimeOfDay()
{
int currentHour = DateTime.Now.Hour;
return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}

}
آزمايش اين متد، وابسته است به زمان جاري سيستم.

using System;
using NUnit.Framework;

[TestFixture]
public class CMyTest
{
[Test]
public void DaytimeTest()
{
int currentHour = DateTime.Now.Hour;

if (currentHour > 6 && currentHour < 21)
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول روز قابل بررسي است");
}
}

[Test]
public void NighttimeTest()
{
int currentHour = DateTime.Now.Hour;

if (currentHour < 6 || currentHour > 21)
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول شب قابل بررسي است");
}
}

}
براي مثال اگر بخواهيم تصوير ماه را دريافت كنيم بايد تا ساعت 21 صبر كرد. همچنين بررسي اينكه چرا يكي از متدهاي آزمون واحد ما نيز با شكست مواجه شده است نيز نيازمند بررسي زمان جاري است و گاهي ممكن است با شكست مواجه شود و گاهي خير. در اين‌جا با استفاده از يك mock object ، اين وضعيت غيرقابل پيش بيني را با منطقي از پيش طراحي شده جايگزين كرده و آزمون خود را بر اساس آن انجام خواهيم داد.
براي اين‌كار بايد DateTime.Now.Hour را تقليد نموده و اينترفيسي را بر اساس آن طراحي نمائيم. سپس Rhino Mocks كار پياده سازي اين اينترفيس را انجام خواهد داد:

using NUnit.Framework;
using Rhino.Mocks;

namespace testWinForms87
{
public interface IDateTime
{
int GetHour();
}

public class ImageManagement
{
public string GetImageForTimeOfDay(IDateTime time)
{
int currentHour = time.GetHour();

return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}
}

[TestFixture]
public class CMocking
{
[Test]
public void DaytimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();

using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}

using (mocks.Playback())
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}

[Test]
public void NighttimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(1);
}

using (mocks.Playback())
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
}

}
همانطور كه در ابتداي مطلب هم عنوان شد، mocking‌ از سه قسمت تشكيل مي‌شود:

MockRepository mocks = new MockRepository();
ابتدا شيء mocks را از MockRepository كتابخانه Rhino Mocks ايجاد مي‌كنيم تا بتوان از خواص و متدهاي آن استفاده كرد.
سپس اينترفيسي بايد به آن پاس شود تا انتظارات سيستم را بتوان در آن بر پا نمود:

IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);

}
به عبارت ديگر در اينجا به سيستم مقلد خود خواهيم گفت: زمانيكه شيء ساعت را تقليد كردي، لطفا عدد 15 را برگردان.
به اين صورت آزمايش ما بر اساس وضعيت مشخصي از سيستم صورت مي‌گيرد و وابسته به ساعت جاري سيستم نخواهد بود.

همانطور كه ملاحظه مي‌كنيد، روش Test Driven Development بر روي نحوه‌ي برنامه نويسي ما و ايجاد كلاس‌ها و اينترفيس‌هاي اوليه نيز تاثير زيادي خواهد گذاشت. استفاده از اينترفيس‌ها يكي از اصول پايه‌اي برنامه نويسي شيءگرا است و در اينجا مقيد به ايجاد آن‌ها خواهيم شد.

پس از آن‌كه در قسمت mocks.Record ، انتظارات خود را ثبت كرديم، اكنون نوبت به وضعيت Playback مي‌رسد:
using (mocks.Playback())
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);

}
در اينجا روش كار همانند ايجاد متدهاي آزمون واحد متداولي است كه تاكنون با آن‌ها آشنا شده‌ايم و تفاوتي ندارد.
با توجه به اينكه پس از تغيير طراحي متد GetImageForTimeOfDay ، اين متد اكنون از شيء IDateTime به عنوان ورودي استفاده مي‌كند، مي‌توان پياده سازي آن اينترفيس ‌را در آزمايشات واحد تقليد نمود و يا جايي كه قرار است در برنامه استفاده شود، مي‌تواند پياده سازي واقعي خود را داشته باشد و ديگر آزمايشات ما وابسته به آن نخواهد بود:

public class DateTimeController : IDateTime
{
public int GetHour()
{
return DateTime.Now.Hour;
}
}

۱۳۸۸/۰۲/۱۶

خواندني‌هاي 16 ارديبهشت


  • - سايت مهندس مجددا راه اندازي شده و بايد دوباره در آن ثبت نام كرد.

۱۳۸۸/۰۲/۱۵

آشنايي با mocking frameworks (چارچوب‌هاي تقليد) - قسمت اول


اين مطلب در ادامه‌ي مطالب آزمو‌ن‌هاي واحد يا unit testing است.
نوشتن آزمون واحد براي كلاس‌هايي كه با يك سري از الگوريتم‌ها ، مسايل رياضي و امثال آن سر و كار دارند، ساده است. عموما اين نوع كلاس‌ها وابستگي خارجي آنچناني ندارند؛ اما در عمل كلاس‌هاي ما ممكن است وابستگي‌هاي خارجي بسياري پيدا كنند؛ براي مثال كار با ديتابيس، اتصال به يك وب سرويس، دريافت فايل از اينترنت، خواندن اطلاعات از انواع فايل‌ها و غيره.
مطابق اصول آزمايشات واحد، يك آزمون واحد خوب بايد ايزوله باشد. نبايد به مرزهاي سيستم‌هاي ديگر وارد شده و عملكرد سيستم‌هاي خارج از كلاس را بررسي كند.
اين مثال ساده را در نظر بگيريد:
فرض كنيد برنامه شما قرار است از يك وب سرويس ليستي از آدرس‌هاي IP يك كشور خاص را دريافت كند و در يك ديتابيس محلي آن‌ها را ذخيره نمايد. به صورت متداول اين كلاس بايد اتصالي را به وب سرويس گشوده و اطلاعات را دريافت كند و همچنين آن‌ها را خارج از مرز كلاس در يك ديتابيس ثبت كند. نوشتن آزمون واحد براي اين كلاس مطابق اصول مربوطه غير ممكن است. اگر كلاس آزمون واحد آن‌را تهيه نمائيد، اين آزمون، integration test نام خواهد گرفت زيرا از مرزهاي سيستم بايد عبور نمايد.

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

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

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

public static string GetComparisonText(IComparable a, IComparable b)
{
if (a.CompareTo(b) == 1)
return "a is bigger";
if (a.CompareTo(b) == -1)
return "b is bigger";
return "same";
}
در اين مثال چون از IComparable استفاده شده، متد ما از هر نوع داده‌اي جهت مقايسه مي‌تواند استفاده كند. تنها موردي كه براي آن مهم خواهد بود اين است كه a راهي را براي مقايسه با b ارائه دهد.

اكنون با توجه به اين توضيحات، براي ايزوله كردن ارتباط با ديتابيس و وب سرويس در مثال فوق، مي‌توان اينترفيس‌هاي زير را تدارك ديد:
    public interface IEmailSource
{
IEnumerable<string> GetEmailAddresses();
}

public interface IEmailDataStore
{
void SaveEmailAddresses(IEnumerable<string> emailAddresses);

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

جهت تقليد رفتار و عملكرد اين دو اينترفيس، به كلاس‌هاي تقليد زير خواهيم رسيد:

public class MockEmailSource : IEmailSource
{
public IEnumerable<string> EmailAddressesToReturn { get; set; }
public IEnumerable<string> GetEmailAddresses()
{
return EmailAddressesToReturn;
}
}

public class MockEmailDataStore : IEmailDataStore
{
public IEnumerable<string> SavedEmailAddresses { get; set; }
public void SaveEmailAddresses(IEnumerable<string> emailAddresses)
{
SavedEmailAddresses = emailAddresses;
}

}
تا اينجا اولين قدم در مورد ايزوله سازي كلاس‌هايي كه به مرز سيستم‌هاي ديگر وارد مي‌شوند، برداشته شد. اما به مرور زمان مديريت اين اينترفيس‌ها و افزودن رفتارهاي جديد به كلاس‌هاي مشتق شده از آن‌ها مشكل مي‌شود. به همين جهت تا حد ممكن از پياده سازي دستي آن‌ها خودداري شده و روش پيشنهادي استفاده از mocking frameworks است.

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

۱۳۸۸/۰۲/۱۴

مقايسه نتايج الگوريتم‌هاي هش كردن اطلاعات در اس كيوال سرور و دات نت


از اس كيوال سرور 2005 به بعد تابع HashBytes نيز به مجموعه توابع قابل استفاده در دستورات T-SQL اس كيوال سرور اضافه شده است كه الگوريتم‌هاي MD2 | MD4 | MD5 | SHA | SHA1 را پشتيباني مي‌كند. براي مثال:
DECLARE @str1 VARCHAR(4),
@str2 NVARCHAR(4)

--متن يونيكد اينجا ناقص ذخيره مي‌شود
SET @str1 = 'وحيد'

SET @str2 = N'وحيد'

SELECT hashbytes('md5', @str1) --C82A7D721AAE517AD76EF1B871BC33CE

SELECT hashbytes('md5', @str2) --7D883091B80F3CD20B872CADBFDDACDF

اگر اين نتايج را بخواهيم با استفاده از فضاي نام استاندارد System.Security.Cryptography توليد كنيم، بايد به encoding رشته دريافتي حتما دقت داشت؛ در غير اينصورت نتايج يكسان نخواهند بود.
مهم‌ترين encoding هاي پشتيباني شده در دات نت در جدول زير برشمرده شده‌اند:

Encoding تعداد بيت هر كاراكتر

ASCII
هر كاراكتر آن 7 بيت است

UTF7
هر كاراكتر آن 7 بيت است

UTF8
هر كاراكتر آن 8 بيت و يا يك بايت است

Unicode (UTF-16)
هر كاراكتر آن 16 بيت و يا دو بايت است

UTF32
هر كاراكتر آن 32 بيت و يا 4 بايت است


نوع nvarchar در اس كيوال سرور همانند حالت Encoding.Unicode‌ دات نت است و هر كاراكتر آن 2 بايت مي‌باشد.
اين نكته‌ هنگام استفاده از اين توابع بسيار حائز اهميت است. براي مثال اگر تابع HashBytes اس كيوال سرور را بخواهيم در دات نت پياده سازي كنيم، به كلاس زير خواهيم رسيد:

using System.Text;
using System.Security.Cryptography;

class CHash
{
public static string GetMD5Hash(string input, Encoding encoding)
{
byte[] bytes = new MD5CryptoServiceProvider().ComputeHash(encoding.GetBytes(input));
StringBuilder chars = new StringBuilder();
foreach (byte chr in bytes)
{
chars.Append(chr.ToString("x2"));
}
return chars.ToString();
}
}
در اينجا تنها حالت زير با هش توليد شده يك فيلد يا متغير از نوع nvarchar توسط تابع HashBytes اس كيوال سرور معادل است:

string result = CHash.GetMD5Hash("وحيد", Encoding.Unicode);

پ.ن.
احتمالا عده‌اي را ديده‌ايد كه هر چقدر تلاش مي‌كنند با سي شارپ متون ايران سيستم تحت داس را به نمونه‌هاي ويندوزي تبديل كنند، كمتر موفق مي‌شوند؛ علت را با توجه به جدول encoding فوق و عدم اطلاع از آن بهتر مي‌توان بررسي كرد.


۱۳۸۸/۰۲/۱۳

تشخيص نقايص تصاوير صفحات سايت با استفاده از jQuery Ajax


اين مثال شبيه به مثال بررسي وجود نام كاربر با استفاده از jQuery Ajax است كه از ذكر توضيحات مشابه آن، در اينجا خودداري خواهد شد.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestBrokenImages.aspx.cs"
Inherits="testWebForms87.TestBrokenImages" %>

<!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>detecting broken images</title>

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

<script type="text/javascript">
function errorReplace(arg) {
//ارسال پيغام خطا
$.ajax({
type: "POST",
url: "TestBrokenImages.aspx/GetErros",
data: "{'image': '" + arg.src + "','page':'" + location.href + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json"
});
//نمايش تصويري دلخواه بجاي نمونه مفقود
$(arg).attr('src', 'missing.png');
}

//بررسي وضعيت تك تك تصاوير پس از بارگذاري كامل صفحه
$(document).ready(function() {
$(window).bind('load', function() {
$('img').each(function() {
if (!this.complete || (!$.browser.msie && (typeof this.naturalWidth == "undefined" || this.naturalWidth == 0))) {
errorReplace(this);
}
});
})
});
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
<img src="img1.png" />
<img src="img2.png" />
</div>
</form>
</body>
</html>

using System;
using System.IO;
using System.Web.Services;

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

[WebMethod]
public static void GetErros(string image,string page)
{
//ارسال ايميل به مسؤول سايت و يا ذخيره خطاها در ديتابيس
}
}
}

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

اگر موفق به اجراي اين مثال نشديد، براي مثال يك break point داخل متد GetErrors قرار دهيد و برنامه را در حالت ديباگ در ويژوال استوديو شروع كنيد، اگر اتفاق خاصي رخ نداد و به اين break point نرسيديد، احتمالا تنظيمات وب كانفيگ شما مناسب نيست. قسمت مربوط به system.web.extensions ، webServices و jsonSerialization بايد در وب كانفيگ موجود باشد كه VS 2008 اين موارد را به صورت خودكار اضافه مي‌كند.

۱۳۸۸/۰۲/۱۲

خواندني‌هاي 12 ارديبهشت


  • - ليست تازه‌هاي سرويس پك 2 آفيس 2007 (فقط براي نصب، درايوي كه ويندوز بر روي آن نصب است بايد نزديك به 2 گيگ فضاي خالي داشته باشد و گرنه در ميانه نصب، متوقف خواهد شد؛ همانند نصب سرويس پك يك اس كيوال سرور 2008)

۱۳۸۸/۰۲/۱۱

چك ليست امنيتي تنظيمات web.config در ASP.Net


خلاصه‌ي جدول زير يك جمله است: به مهاجم امكان ديباگ برنامه را ندهيد!

تنظيمات نا امن تنظيمات امن
<configuration>
<system.web>
<sessionState cookieless="UseUri">
<configuration>
<system.web>
<sessionState cookieless="UseCookies">
<configuration>
<system.web>
<httpCookies httpOnlyCookies="false">
<configuration>
<system.web>
<httpCookies httpOnlyCookies="true">
<configuration>
<system.web>
<customErrors mode="Off">
<configuration>
<system.web>
<customErrors mode="RemoteOnly">
<configuration>
<system.web>
<trace enabled="true" localOnly="false">
<configuration>
<system.web>
<trace enabled="false" localOnly="true">
<configuration>
<system.web>
<compilation debug="true">
<configuration>
<system.web>
<compilation debug="false">