۱۳۸۹/۰۶/۰۸

نحوه‌ي استفاده از كتابخانه‌ي OpenSSL در ويندوز


سؤالي شده به اين مضمون : "یه الگوریتم دارم که بر طبق اون باید اعداد تصادفی خیلی بزرگ تولید کنم، اونها رو جمع و ضرب کنم. اینکه چطوری باید از dll یا lib استفاده کنم رو بلد نیستم. از VS2008 استفاده میکنم..."

سؤال در مورد زبان CPP است. كتابخانه‌ي استاندارد انجام اينگونه عمليات براي زبان‌هاي C و CPP ، كتابخانه‌ي OpenSSL است. البته شايد الان 100 كتابخانه ديگر را هم ليست كنيد، اما كساني كه با مباحث رمزنگاري اطلاعات مدتي كار كرده باشند، بعيد است سر و كارشان به اين كتابخانه نيفتاده باشد و يك استاندارد در اين زمينه به شمار مي‌رود؛ همچنين به دليل سورس باز بودن در اكثر سكوهاي كاري موجود نيز قابل استفاده است. بنابراين فراگيري نحوه‌ي كار كردن با آن يك مزيت به شمار مي‌رود. قسمتي از اين كتابخانه‌ي معظم مرتبط است به كار با اعداد بزرگ. اين مورد را هم جهت استفاده در الگوريتم RSA نياز دارد.
براي استفاده از آن در ويندوز ابتدا بايد OpenSSL را كامپايل كنيد. كار پر دردسري است. به همين جهت يك سايت فقط به اين موضوع اختصاص يافته و هربار آخرين نسخه‌ي OpenSSL را براي ويندوز كامپايل مي‌كند و در اختيار علاقمندان قرار مي‌دهد : +
در حال حاضر يا بايد Win32 OpenSSL v1.0.0a و يا Win64 OpenSSL v1.0.0a را دريافت كنيد (برنامه‌ي شما اگر 64 بيتي كامپايل شود، dll هاي 32 بيتي را نمي‌تواند بارگذاري كند و برعكس).

روش استفاده از كتابخانه‌ي OpenSSL در ويژوال CPP :

الف) ابتدا فايل‌هاي كامپايل شده‌ي فوق را دريافت و نصب كنيد. اكنون براي مثال يك پوشه‌ي OpenSSL-Win32 در كامپيوتر شما با محتويات اين كتابخانه بايد ايجاد شده باشد(اگر نسخه‌ي 32 بيتي را دريافت كرده‌ايد).
سپس به پوشه‌ي OpenSSL-Win32\lib\VC آن مراجعه كنيد. در اينجا فايل‌هاي كتابخانه‌اي جهت استفاده در ويژوال CPP قرار گرفته‌اند. اگر از محتويات پوشه OpenSSL-Win32\lib\VC\static استفاده كنيد، نيازي به توزيع فايل‌هاي DLL اين كتابخانه نخواهيد داشت و اگر از كتابخانه‌هاي OpenSSL-Win32\lib\VC استفاده كنيد، فايل‌هاي dll را نيز حتما بايد به همراه برنامه‌ي خود توزيع نمائيد.
سه نوع فايل در آن وجود دارند. ختم شده به MD ، MT و MDd كه معاني آن‌ها در مورد چند ريسماني بودن يا خير است (برگرفته شده از فايل faq.txt دريافتي):

Single Threaded /ML - MS VC++ often defaults to this for the release version of a new project.
Debug Single Threaded /MLd - MS VC++ often defaults to this for the debug version of a new project.
Multithreaded /MT
Debug Multithreaded /MTd
Multithreaded DLL /MD - OpenSSL defaults to this.
Debug Multithreaded DLL /MDd

ب) جهت سهولت كار، پوشه‌ي OpenSSL قرار گرفته در مسير OpenSSL-Win32\include را در آدرس زير كپي نمائيد:
C:\Program Files\Microsoft Visual Studio 10.0\VC\include
به اين صورت حين استفاده از اين كتابخانه نيازي به مشخص سازي محل قرارگيري فايل‌هاي include نخواهد بود.

ج) اكنون يك پروژه‌ي جديد Visual C++\Win32\Win32 console application را در VS.NET آغاز كنيد؛ براي مثال به نام OpenSSLTest .

د) سپس به منوي پروژه، گزينه‌ي خواص پروژه مراجعه كرده و مطابق تصاوير زير، اين فايل‌هاي كتابخانه‌اي را معرفي كنيد (انتخاب MD يا MT يا MDd بر اساس runtime library انتخاب شده است كه در تصاوير مشخص گرديده):









ه) اكنون يك مثال ساده در مورد ضرب دو عدد بزرگ به صورت زير مي‌تواند باشد:

#include "stdafx.h"
#include <openssl/bn.h>
#include <string.h>


void RotateBytes(unsigned char *in, int n)
{
unsigned char *e=in+n-1;
do {
unsigned char temp=*in;
*in++=*e;
*e-- =temp;
} while(in<e);
}

int _tmain(int argc, _TCHAR* argv[])
{
//دو عدد بزرگ جهت آزمايش
unsigned char testP[] = {0xD1,0x31,0x85,0x4D,0x00,0xD6,0x31,0x97,0x3A,0xFC,0xD2,0x27,0x02,0xEF,0xC2,0xA7};
unsigned char testA[] = {0xC7,0x1B,0x25,0x72,0x03,0xCB,0x72,0x03,0xCF,0x23,0x27,0x2D,0x00,0xD6,0x31,0x98};

//تبديل آرايه‌هاي فوق به فرمت اعداد بزرگ
BIGNUM *a = BN_new();
//it should be in "big-endian" form
RotateBytes(testA, 16);
BN_bin2bn(testA, 16, a);

BIGNUM *p = BN_new();
//it should be in "big-endian" form
RotateBytes(testP, 16);
BN_bin2bn(testP, 16, p);


//ضرب اين دو عدد در هم
BIGNUM *result = BN_new();
BN_CTX *ctx = BN_CTX_new();

BN_mul(result, a, p, ctx);

//نمايش نتيجه
//حاصل از چند بايت تشكيل شده؟
int num = BN_num_bytes(result);
if(num>0)
{
unsigned char *tmpdata;
if((tmpdata=(unsigned char *)malloc(num)))
memset(tmpdata, 0, num);

//تبديل عدد با فرمت اعداد بزرگ به آرايه‌اي از بايت‌ها
BN_bn2bin(result, tmpdata);
RotateBytes(tmpdata, num);

for(int i=0; i<num; i++)
{
if(i%16==0) printf("\n");
printf("%02X ",tmpdata[i]);
}

if(tmpdata) free(tmpdata);
}


//آزاد سازي منابع
BN_free(a);
BN_free(p);
BN_CTX_free(ctx);

return 0;
}


در مورد شرح توابع كتابخانه OpenSSL به اينجا مراجع كنيد : +
علت استفاده از تابع RotateBytes ، تغيير endian ورودي است.

۱۳۸۹/۰۵/۲۹

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


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

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



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

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

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



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




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




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



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

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

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

۱۳۸۹/۰۵/۲۶

فارسي سازي SharePoint Foundation 2010


جهت اطلاع، چند روز قبل پروژه‌اي را در سايت CodePlex ديدم در مورد فارسي سازي و فارسي ساز SharePoint Foundation 2010 :

۱۳۸۹/۰۵/۲۴

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


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


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

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

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



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

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

۱۳۸۹/۰۵/۲۰

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


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

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

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

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

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

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

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

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

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

#region IServiceBehavior Members

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

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

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

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

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

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

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

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


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

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



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

۱۳۸۹/۰۵/۱۸

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


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

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

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

۱۳۸۹/۰۵/۱۶

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


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

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

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

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

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


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

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

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

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

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

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

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

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

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