معرفی ADO و کاربرد آن در ASP

معرفی ADO و کاربرد آن در ASP

ارتباط ASP با بانک های اطلاعاتی از طریق واسطی بنام ADO انجام می گیرد. با استفاده از رابط فوق می توان به انواع داده های ذخیره شده بسادگی، در کمترین زمان و صرفا بکمک یک مسیر دستیابی پیدا کرد. به همین دلیل می توان ادعا کرد که ADO ساده ترین روشی است که تاکنون برای ذخیره و بازیابی داده ها بوجود آمده است.ADO نسخه 5 / 2 دارای پنج شئ اساسی با نام : Connection , Command, RecordSet , Record , Stream است. دو شی Record و Stream در نسخه قبلی ADO وجود نداشتند. در این مقاله به بررسی برخی از اشیاء عمده ADO و متدهای مربوطه آنها خواهیم پرداخت.

شئ Connection
قبل از هر گونه ذخیره و بازیابی داده ها از بانک اطلاعاتی می بایست با ایجاد و مقدار دهی اولیه یک
Connection زمینه لازم جهت ارتباط با بانک اطلاعاتی را بوجود آورد. در ADO با استفاده از شی Connection یک ارتباط با بانک اطلاعاتی ایجاد و پس از انجام عملیات دلخواه در رابطه با بانک اطلاعاتی و در زمانیکه به وجود آن دیگر نیاز نباشد، می توان آن را حذف کرد. Open کردن یک ارتباط به بانک اطلاعاتی پروژه ای واحد با نام ( ADODB(ActiveX Data Objects Database بوده که خود شامل تمامی اشیاء ADO است. برای ایجاد یک Connection نظیر سایر اشیاء ASP از متد Server.CreateObject استفاده می گردد.

Dim Conn
Set Conn = Server.CreateObject("ADODB.Connection")

بصورت پیش فرض Connection فقط خواندنی می باشند، شما می توانید وضعیت فوق را تغییر داده و در صورت نیاز و بکمک صفات مربوط به شی فوق ارتباطاتی از نوع خواندنی / نوشتنی و یا فقط نوشتنی ایجاد نمائید. در زمان استفاده از ADO می توان از مجموعه ای ثوابت که دربردارنده مقادیر متفاوتی برای آیتم های متفاوت می باشند و پیشاپیش تعریف شده اند استفاده کرد. این ثوابت در فایلی با نام Adovbs.inc قرار دارند. بمنظور استفاده از ثوابت فوق در صفحات ASP خود، می بایست فایل فوق را بکمک دستور Include به برنامه های خود ملحق نمائیم.

<!-- #INCLUDE FILE="adovbs.inc" -->

اگر فایل adovbs.inc را توسط ادیتوری نظیر Notepad فعال نمائید، ثوابت تعریف شده را بصورت مجموعه ای از گروهها مشاهده خواهید کرد. پس از ایجاد یک Connection با بانک اطلاعاتی می توان نوع ( Mode ) ارتباط را بکمک استفاده از ثوابت تعریف شده مشخص نمود. در صورتیکه بخواهیم از بانک اطلاعاتی صرفا اطلاعاتی را بخوانیم از ثابت adModeRead ، فقط اطلاعاتی را در بانک اطلاعاتی بنویسیم از ثابت adoModeWrite و در نهایت در صورتیکه قصد داریم بطور همزمان اطلاعاتی را از بانک اطلاعاتی خوانده و در آن اطلاعات جدیدی را نیز بنویسیم از ثابت adoModeReadWrite استفاده می گردد. توصیه می گردد با توجه به نوع رفتاری که با بانک اطلاعاتی خواهیم داشت آن را باز نمائیم چراکه در صورتیکه صرفا قصد خواندن و یا نوشتن را در یک بانک اطلاعاتی داشته باشیم و نخواهیم دو عملیات را با هم انجام دهیم، باز کردن بانک اطلاعاتی بصورت هم خواندنی و هم نوشتنی ( adoModeReadWrite ) سرعت دستیابی به بانک اطلاعاتی را کاهش خواهد داد.

ConnectionString
پس از تعیین
Mode، می بایست صفت Connectionstring مربوط به شی Connection را مقدار دهی مناسب نمود. صفت فوق دارای چندین بخش بوده که می بایست مشخص گردند: نام Provider، نام سرویس دهنده بانک اطلاعاتی، نام بانک اطلاعاتی که قصد استفاده از آن را دارید، User Id لازم جهت اتصال به بانک اطلاعاتی (UID)، رمز عبور برای کاربرخاص ( PWD ).

هر یک از بخش های فوق توسط علامت ";" از هم جدا می شوند. مثلا در ساده ترین حالت می توانید از یک Data Source Name یا DSN یا مربوط به ODBC، یک User ID، و یک رمز عبور جهت اتصال به بانک اطلاعاتی استفاده نمائید. یک DSN خود شامل نام Provider، نام سرویس دهنده بانک اطلاعاتی و نام بانک اطلاعاتی بوده و دیگر نیازی به مشخص نمودن مجدد آنها نخواهد بود. به مثال زیر توجه فرمائید.

Dim Conn
Set Conn = Server.Create0bject("ADODB.Connection")
Conn.Mode = adModeReadWrite
Conn.ConnectionString = "DSN=myDSN;UID=Javad;PWD=7474;"

روش فوق بهترین حالت ایجاد یک ارتباط نیست زیرا DSN بصورت پیش فرض از MSDASQL Provider استفاده می کند. اما JET OLEDB Provider بمراتب سریعتر و قابلیت های بیشتری را دارا است. بنابراین می توان جهت ارتباط با بانک اطلاعاتی مطابق زیر عمل نمود:

Dim Conn, ConnStr
ConnStr= "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + Server.MapPath(Path2DB)
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Mode = adModeReadWrite
Conn.ConnectionString = ConnStr

ConnectionString شامل نام Provider، نام سرویس دهنده بانک اطلاعاتی و نام بانک اطلاعاتی است. در مثال فوق از تابع Server.MapPath برای ترجمه مسیر مجازی به مسیر واقعی استفاده شده است. مثلا یک بانک اطلاعاتی موجود درwww.Test.com/test.mdb می تواند بصورت واقعی در مسیر E:webMydatabaseTest.mdb قرار گرفته باشد. تابع Server.MapPath آدرس نوع اول را به آدرس نوع دوم ترجمه خواهد کرد. توجه داشته باشیم که شی ADODB.Connection نیازمند آگاهی از مسیر واقعی بانک اطلاعاتی است. فراموش نکنیم که قبل از باز نمودن Connection، تمامی تغییرات لازم بر روی صفت های مربوطه اعمال گردد. در صورتیکه بخواهیم پس از ایجاد ارتباط با بانک اطلاعاتی یک یا چند صفت مرتبط را تغییر دهیم، می بایست در ابتدا ارتباط خود را با بانک اطلا عاتی قطع (Connection، بسته گردد) و در ادامه تغییرات لازم را از طریق صفات مربوطه اعمال و مجددا Connection را برقرار نمود.

بمنظور باز نمودن یک Connection از متد Open مربوط به شئ Connection استفاده می گردد.

متد Connection.Open
در صورتیکه متد فوق بدون خطا اجرا گردد، یک ارتباط کاری ( عملیاتی) با بانک اطلاعاتی ایجاد شده است.

متد Connection.Execute
برای بازیابی داده ها از یک بانک اطلاعاتی توسط
ADO سه روش عمده وجود دارد. تمامی آنها در مرحله اول نیاز به ارتباط با بانک اطلاعاتی خواهند داشت. ساده ترین حالت استفاده از متد Execute مربوط به شی Connection است. متد فوق سه پارامتر را خواهد داشت:- یک دستور SQL، یا Query، Table، View یا نام یک Stored Procedure که توسط پارامتر CommandText مشخص خواهد شد.
• یک متغیر با نام
RecordsAffected، که شامل تعداد رکوردهائی خواهد بود که با توجه به Query اجرا شده توسط متد Execute مشخص می شود.
• یک ثابت اختیاری با نام
CommandTypeEnum که به بانک اطلاعاتی خواهد گفت چه نوع عبارت و یا Query را می خواهید اجرا نمائید و اینکه یک شی Recordset را برگرداند یا خیر.

پس از اجرای متد Execute ( به هر روش ممکن )، رکوردهای برگردانده شده در شئ RecordSet برگردانده خواهند شد. در صورتیکه از دستور SELECT استفاده شود رکوردهای برگردانده شده در شی RecordSet قرار خواهند گرفت. اما زمانیکه از دستورات INSERT و یا UPDATE استفاده گردد رکوردی باز گردانده نخواهد شد.

شئ RecordSet
از شی
RecordSet جهت نگهداری رکوردها یا جدول دریافتی استفاده می گردد. این شئ نظیر یک جدول بانک اطلاعاتی دارای سطرها و ستون هائی است. در حقیقت شئ فوق یک تصور مجازی از یک جدول خواهد داد و بصورت فیزیکی یک جدول نیست. چراکه مقادیر مورد نظری که در ستون های این شئ قرار می گیرد ممکن است حاصل ترکیب (Join) چندین جدول دیگر باشند.

متد RecordSet.Open
در صورتیکه در هنگام استفاده از شئ
RecordSet به هر نوع Cursor ( اشاره گری به هر یک از سطرهای موجود در جدول ) نیاز داشته باشید ( صرفا نه یک Cursor که بصورت Forward-Only و یا Read-Only باشد ) می بایست به جای استفاده از متد Execute مربوط به شی Connection، مستقیما بانک اطلاعاتی را باز کنید. شئ RecordSet نیز دارای متدی با نام Open است که چندین پارامتر را بعنوان پارامتر اخذ می کند.

Recordset.Open CommandText, Connection|ConnectionString, Cursor-Type, LockType, Options

CommandText شامل SQL query بوده، Connection|ConnectionString شامل یک رجوع به شئ Connection باز شده و یا یک پارامتر معتبر Connectionstring است. پارامتر CursorType مقدار خود را از ثابتی با نام adCursorTypeEnm مطابق زیر اخذ خواهد کرد:

adopenForwardOnly: یک Cursor که فقط امکان حرکت بسمت جلو را دارد، برمی گرداند. ( پیش فرض). اگر نوع Cursor را مشخص ننمائید، ADO همواره Cursor از این نوع را بر می گرداند. همانگونه که از نام آن مشخص است، صرفا می توان بسمت جلو در شئ RecordSet حرکت نمود.
AdOpenKeyset: یک Cursor از نوع Keyset را برمی گرداند. در چنین حالتی می توان جهت و نوع حرکت را به هر نوع دلخواه انتخاب نمود (اولین رکورد، آخرین رکورد، بسمت جلو، بسمت عقب...). سرویس دهنده برای هر یک از سطرهای موجود در شئ RecordSet پس از اجرای Query یک Bookmark، ایجاد می کند. این Bookmark ها تا زمانیکه شئ Recordset حیات دارد تغییر نخواهند کرد بنابراین در صورتی که یک رکورد جدید توسط کاربر دیگری در بانک اطلاعاتی در آن زمان درج گردد، آن رکورد جدید برای ما قابل رؤیت نخواهد بود.
adOpenDynamic: یک Cursor پویا را برمی گرداند. این نوع Cursor مشابه Keyset است با این تفاوت که امکان مشاهده رکوردهای جدید نیز وجود خواهد داشت. یک Cursor پویا بصورت پیوسته رکوردهای جدید و یا تغییر یافته را بررسی و حاصل را در شی RecordSet بصورت پویا منعکس خواهد کرد.
adOpenStatic: یک Cursor ایستا بهمراه تعداد ثابتی از رکوردها را برمی گرداند. در چنین حالتی تغییرات و درج رکوردهای جدید در بانک در همان لحظه مشاهده نخواهد شد مگر اینکه مجددا درخواست بازیابی اطلاعات از بانک صادر شود.

پارامتر LockType، به ADO خواهد گفت که چگونه با مسئله Lock در بانک اطلاعاتی رفتار نماید. در حالت کلی می بایست برای اعمال تغییرات و یا درج رکوردهای جدید، پیش بینی های لازم را انجام داد. چراکه Lock ایجاد شده توسط یک کاربر می تواند باعث بروز مسائلی برای سایر کاربران گردد. مقدار این پارامتر مطابق زیر خواهد بود:

adLockReadOnly: فقط خواندنی، امکان تغییر داده ها وجود نخواهد داشت.
adLockPessimistic: قویترین نوع Lock است. رکوردهائی که بدین صورت Lock خواهند شد توسط سایر کاربران قابل دستیابی نخواهند بود. رکوردها زمانیکه سرویس دهنده آنها را برمی گرداند Lock شده و تا زمانیکه شئ RecordSet
وجود دارد، امکان استفاده از این رکوردها برای سایرین وجود نخواهد داشت. 
adLockoptimistic: این نوع Lock صرفا در زمان بهنگام سازی یک رکورد اعمال شده و بلافاصله رکورد از حالت Lock آزاد می گردد. بنابراین در مراحلی که لازم است یک رکورد تغییر یابد می توان از این نوع Lock استفاده کرد. و پس از اعمال تغییرات بصورت اتوماتیک، Lock آزاد خواهد شد.
AdLockBatchOptimistic: این نوع Lock مشابه Optimistic است با این تفاوت که آنها برای بهنگام سازی Batch مورد استفاده قرار می گیرند. در این حالت مجموعه ای از رکوردها در یک مقطع زمانی بهنگام سازی خواهند شد ( بجای اینکه هر کورد بهنگام سازی گردد ). با توجه به نوع نرم افزار طراحی شده و میزان محاوره ای بودن آن، می توان تصمیم به استفاده از این نوع Lock و یا Optimistic نمود.

آخرین پارامتر متد Recordset.Open، پارامتراختیاری flag است. که نوع Query را مشخص خواهد کرد ( Table,View,Stored Procedure ).

متد Move
پس از باز نمودن یک شئ
RecordSet، با استفاده ازمتد Move، می توان در طول سطرهای موجود در شئ RecordSet با توجه به نوع cursor حرکت کرد. شئ RecordSet دارای یک صفت با نام RecordCount بوده که تعداد رکوردهای موجود در شئ RecordSet را مشخص می کند. تصور کنید که شئ RecordSet نظیر یک جدول است با یک سطر خالی در ابتدا و یک سطر خالی در انتهای آن و اشاره گر ی که در هر لحظه به یک رکورد اشاره می کند. در واقع با استفاده از متد Move، موقعیت اشاره گر فوق تغییر خواهد کرد ( اشاره گر حرکت خواهد کرد) . شئ RecordSet دارای دو متد دیگر با نام EOF و BOF است که زمان حضور اشاره گر در ابتدا و یا انتهای رکوردها را مشخص خواهند کرد. این دو متد از نوع Boolean ( درست / نادرست ) هستند.

While NOT RecordSet.EOF در این مقاله به بررسی برخی مثال های کاربردی از نحوه ارتباط ASP و ADO برای ایجاد وب سایت های متکی بر بانک های اطلاعاتی خواهیم پرداخت. قبل از پرداختن به مثال های مربوطه، در ابتدا لازم است که یک بانک اطلاعاتی نمونه بهمراه یک جدول را تعریف کنیم. به کمک Access (نوع نسخه آن هر چیزی می تواند باشد) یک بانک اطلاعاتی با نام Students.mdb ایجاد و در آن صرفا یک جدول با نام Student که شامل فیلدهای اطلاعاتی زیر است، را تعریف می کنیم.

ID: شماره دانشجوئی یک دانشجو را در خود نگهداری خواهد کرد. فیلد فوق Primary Key خواهد بود.
FirstName: نام دانشجو را در خود نگهداری خواهد کرد.
LastName: نام خانوادگی دانشجو را در خود ذخیره خواهد کرد. 
DateOfBirth: تاریخ تولد یک دانشجو را در خود نگهداری کرد.
Email: آدرس پست الکترونیکی دانشجو را در خود ذخیره خواهد کرد.

مثال ۱- بازیابی اطلاعات از بانک اطلاعاتی دانشجویان
در این مثال جهت ارتباط با بانک اطلاعاتی از
ODBC استفاده نشده است. برای بازیابی اطلاعات از بانک اطلاعاتی فوق از SQL SELECT استفاده می شود. متن برنامه بازیابی اطلاعات از بانک اطلاعاتی دانشجویان بصورت زیر است.

<HTML>
<HEAD><TITLE>Student Records</TITLE></HEAD>
<BODY>
<%
Dim DBSet DB = Server.CreateObject ("ADODB.Connection")
DB.Open ("PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + "C:MyDbStudents.mdb")
Dim RSSet RS = Server.CreateObject ("ADODB.Recordset")
RS.Open "SELECT * FROM Students", DB

If RS.EOF And RS.BOF Then
    Response.Write "There are 0 records."
Else
    RS.MoveFirst
    While Not RS.EOF
        Response.Write RS.Fields ("FirstName")
        Response.Write RS.Fields ("LastName")
        Response.Write RS.Fields ("Email")
        Response.Write "<HR>"
        RS.MoveNext
    Wend
End If
%>
</BODY>
</HTML>

مثال ۲- افزودن یک رکورد جدید
در این مثال رکوردهای جدید را در بانک اطلاعاتی دانشجویان ذخیره خواهیم کرد. برای درج رکوردهای جدید در بانک اطلاعاتی می توان از
INSERT INTO استفاده کرد. یکی دیگر از روش های درج رکورد در بانک اطلاعاتی استفاده از شئ RecordSet است که بمراتب ساده تر مورد قبلی گفته شده است. در مثال فوق برای درج رکوردها در بانک اطلاعاتی از مورد دوم استفاده شده است. متن برنامه درج رکورد جدید در بانک اطلاعاتی دانشجویان بصورت زیر است.

<HTML>
<HEAD>
<TITLE>Student Records</TITLE>
</HEAD>
<BODY>
<%
Dim DBSet DB = Server.CreateObject ("ADODB.Connection")
DB.Mode = adModeReadWrite
DB.Open ("PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + C:MyDbStudents.mdb")
Dim RSSet RS = Server.CreateObject ("ADODB.Recordset")
RS.Open "Students", DB, adOpenStatic, adLockPessimistic
RS.AddNew
RS ("FirstName") = "name"
RS ("LastName") = "family"
RS ("Email") = "name @domain.com"
RS ("DateOfBirth") = CDate("25 Mar, 2002")
RS.Update
%>
</BODY>
</HTML>

در مثال فوق رکورد دانشجوئی با نام "name" و نام خانوادگی "family" بهمراه آدرس پست الکترونیکی مربوطه و تاریخ تولد آن در بانک اطلاعاتی دانشجویان درج خواهد شد. اقلام اطلاعاتی مربوط به درج یک رکورد جدید را می توان با طراحی یک فرم از کاربران اخذ و پس از تکمیل و ارسال فرم مربوطه، مقادیر متناظر در هر یک از فیلدهای موجود در فرم اخذ اطلاعات به مقادیر مربوطه در بانک اطلاعاتی نسبت داده شود (کار بسیار ساده ای است ! امتحان کنید).

مثال ۳- بهنگام سازی رکوردهای بانک اطلاعاتی دانشجویان
در این مثال با نحوه بهنگام سازی رکوردهای موجود در بانک اطلاعاتی دانشجویان آشنا خواهیم شد. متن برنامه بهنگام سازی رکورد دانشجوئی در بانک اطلاعاتی دانشجویان بصورت زیر است.

<HTML>
<HEAD>
<TITLE>Student Records</TITLE>
</HEAD>
<BODY>
<%
Dim DBSet DB = Server.CreateObject ("ADODB.Connection")
DB.Mode = adModeReadWrite
DB.Open ("PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + "C:MyDbStudents.mdb")
Dim RSSet RS = Server.CreateObject ("ADODB.Recordset")
RS.Open "SELECT * FROM Students WHERE FirstName = ''name", DB, adOpenStatic, adLockPessimistic
RS ("Email") = "name@domain.com"
RS ("DateOfBirth") = CDate("26 Mar, 2001")
RS.Update
%>
</BODY>
</HTML>

در برنامه فوق رکورد دانشجوئی با نام "name" انتخاب و فیلدهای آدرس پست الکترونیکی (Email) و تاریخ تولد (DateIfBirth) آن تغییر و حاصل مجددا در بانک اطلاعاتی دانشجویان ثبت شده است.

مثال ۴- حذف یک رکورد
در این مثال با نحوه حذف یک رکورد در بانک اطلاعاتی دانشجویان آشنا خواهیم شد. متن برنامه حذف یک رکورد از بانک اطلاعاتی دانشجویان بصورت زیر است.

<HTML>
<HEAD>
<TITLE>Student Records</TITLE>
</HEAD>
<BODY>
<%
Dim DBSet DB = Server.CreateObject ("ADODB.Connection")
DB.Mode = adModeReadWrite
DB.Open ("PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" + "C:MyDbStudents.mdb")
DB.Execute ("DELETE * FROM Students WHERE FirstName = 'name'")
%>
</BODY>
</HTML>

در مثال فوق رکورد دانشجوئی با نام "name" از بانک اطلاعاتی دانشجویان حذف شده است. در زمان حذف یک رکورد از بانک اطلاعاتی به دو نکته مهم می بایست توجه کرد: اولا امکان معروف Undo وجود ندارد. بنابراین رکوردی که حذف می گردد، امکان برگرداندن آن وجود ندارد. ثانیا اگر در زمان اجرای DELETE ، عبارت WHERE حذف گردد، تمامی رکوردهای موجود در جدول حذف خواهند گردید.

متدهای Sort و Search
با استفاده از متدهای شئ
RecordSet، می توان عملیات مرتب سازی (Sort) و جستجو (Search) را انجام داد. برای مرتب سازی یک مجموعه از رکوردها نام فیلد مورد نظری را که می خواهیم مرتب سازی بر اساس آن انجام شود را به صفت Sort نسبت خواهیم داد. مثلا اگر بخواهیم مجموعه ای از رکوردها را که توسط اجرای SELECT * FROM PhoneBook برگردانده شده است را براساس نام خانوادگی (LastName) مرتب نمائیم، می توان دستور RecordSet.Sort = "LastName" را استفاده کرد. برای مرتب سازی رکوردها براساس بیش از یک فیلد بصورت RecordSet.Sort = "LastName, FirstName" عمل می نمائیم. برای مرتب سازی رکوردها بصورت صعودی ( پیش فرض است )، یا نزولی بصورت RecordSet.Sort = "LastName, FirstName DESC" عمل می کنیم.

برای جستجوی یک رکورد بخصوص در بین مجموعه رکوردهای برگردانده شده از متد Find شئ RecordSet استفاده می گردد. شرط جستجو را بصورتی که در یک SQL Where است، مشخص خواهیم کرد و پس از اجرای متد Find، شئ RecordSet به اولین موردی که پیدا کرده است اشاره خواهد کرد و در صورتیکه رکوردی پیدا نشود،EOF ارزش درست را پیدا خواهد کرد. متد Find بغیر از شرطی که نظیر SQL WHERE برای آن مشخص می کنیم، می تواند دارای سه پارامتر اختیاری دیگر باشد:

SkipRecords: تعداد رکوردهائی را که می بایست قبل از عملیات جستجو، صرفنظر (Skip) نماید، مشخص خواهد کرد.
SearchDirection: جهت حرکت جستجو در مجموعه رکوردها را مشخص خواهد کرد. adSearchForward ( بسمت جلو) ویا adSearchBackward( از آخر بسمت اول )
Start: تعداد رکوردهائی را که می بایست برای جستجو شروع کرد مشخص می کند.

شئ Field
با اینکه تاکنون ما به شئ
RecordSet بصورت یک جدول نگاه می کردیم، اما این صرفا یک مدل فرضی برای راحتی تصور آن است. شئ RecordSet در حقیقت شامل یک بردار دو بعدی از شئ Field است. شئ Field شامل داده است. بنابراین هر شئ از این نوع دارای یک اندازه، یک مقدار و یک نوع بخصوص است. شئ فوق همچنین دارای مجموعه ای از صفات است. در اکثر موارد شاید نیازی نباشد که با صفات و متدهای شئ فوق مستقیما کار کنیم، اما مطالعه آنها برای موارد پیش بینی شده در طراحی صفحات ASP که قصد ارتباط با بانک های اطلاعاتی از طریق ADO را دارند، توصیه می گردد.

نظرات 1 + ارسال نظر
امیر چهارشنبه 15 شهریور‌ماه سال 1385 ساعت 10:53 http://amir-perspolisi.blogsky.com

سلام وبلاگ با حالی داری
دیدم حالا که اینقدر مایه گذاشتی نامردیه نظر نداده برم اگه دوست داشتی به وبلاگ داشت یه سری بزن تا به هم لینک بدیم
قربانت امیر

ما ارادت داریم
ممنون از کرمه توون
خوش باشین

برای نمایش آواتار خود در این وبلاگ در سایت Gravatar.com ثبت نام کنید. (راهنما)
ایمیل شما بعد از ثبت نمایش داده نخواهد شد