السلام عليكم ورحمة الله وبركاته
أعزائي نظرا لتكرار الأسئلة المتعلقة بتخزين وإسترجاع الصور من قاعدة البيانات، قمت بكتابة هذا الموضوع (وأنا متأكد أن غيري قد وضح العملية مسبقا) لمحاولة محو أي تسائل أو غموض. قبل أن نبداء العملية دعونا نراجع بعض المفاهيم البدائية في تقنية أو ميكانيكية الويب بشكل عام.
أولا : Request & Response
عندما يقوم الزائر بإدخال عنوان لموقع ما أو عندما تقوم بالضغط على زر معين أو رابط معين، يقوم المتصفح بإرسال طلب (Request) إلى الخادم الذي يستضيف الموقع. بعد ذلك يقوم تطبيق الويب سواء كان ASP أو ASP.NET أو JSP أو PHP بمعاينة هذا الطلب ويبداء بإرسال الإستجابة Response لك (الزائر) وهي غالبا تكون على شاكلة HTML وهذا السبب الذي يجعلك تتصفح مواقع دون أن تحتاج لتتنصيب آلة إفتراضية (Virtual Machine) متطابقة مع اللغة التي أستخدمت لبناء هذه المواقع.بالنسبة لي ASP.NET يمكنك كتابة الـ Response بالشكل الذي تراه مناسبا لك ، فعلى سبيل المثال يمكنك إنشاء صفحة وفي حدث الـ Page_Load أكتب الكود التالي :
protected void Page_Load(object source, EventArgs e) {
Response.Write("أهلا بك في موقعي");
}نلاحظ أنه عندما يتم طلب على الصفحة المذكورة فوق سيكون الرد دائما هو عبارة "أهلا بك في موقعي"، بغض النظر عن كيفية عملية الطلب، هل هي عن طريق رابط أو عن Form أو ما إلى ذلك. دعونا نأخذ مثالا أكثر تعقيدا وهو عبارة عن عرض محتويات جدول مكون من حقلين ID و Name عن طريق إستخدام فقط الـ Response.Write:
protected void Page_Load(object source, EventArgs e) {
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(string));
dt.Columns.Add("Name", typeof(string));
for(int i = 1; i <= 10; i++)
{
DataRow newRow = dt.NewRow();
newRow["ID"] = i;
newRow["Name"] = "Name " + i;
dt.Rows.Add(newRow);
}
// Here we will write our response without using GridView, ListView ... etc
Response.Write("<table cellspacing='0' cellpadding='5' border='1' width='200px' style='margin: 0 auto;'>");
Response.Write("<tr><td>ID</td><td>Name</td></tr>");
foreach(DataRow row in dt.Rows)
Response.Write(string.Format("<tr><td>{0}</td><td>{1}</td></tr>", row["ID"], row["Name"]));
Response.Write("</table>");
}لاحظوا أننا قمنا بتحديد شكل الرد عن طريق كتابة الـ HTML Tags المناسبة ويمكنك فعل ما تريد هنا والإحتمالات لا حصر لها، يمكنك أيضا كتابة محتوى ملف خارجي (موجود في نفس الخادم الذي يحوي تطبيق الويب). ولكن ماذا عن الصور ؟
ثانيا : كيفية عرض الصور في المواقع الإلكترونية بشكل عام
بشكل عام لعرض صورة ما في موقع إلكتروني معين نقوم بإستخدما الـ IMG Tag حيث أنه يحوي على Attribute خاص بإسم ملف الصورة المراد عرضها وهو src، فلنفرض أن هناك مجلد اسمه media ويحوي بداخله صورة باسم x.jpg، لكي نعرضها نكتب الـ HTML التالي
<img src="media/x.jpg" />
ولكن ماذا لو كانت الصورة مخزنة في قاعدة بينات أو في ملف XML (تخزين صورة داخل ملف XML ليس مستحيلا ولكنه لا يعد ممارسة جيدة) ؟
ثالثا : كيفية تخزين صورة داخل قاعدة بيانات
تخزين صورة داخل قاعدة بيانات ليس أمرا عسيرا كما يبدوا، كل ما عليك فعله هو تحديد نوع البيانات المناسب للحقل الذي سيحوي الصورة وهذا يختلف على حسب قاعدة البيانات، ففي MS SQL Server نوع الحقل هو image أو varbinary أما في Oracle و MySql هو blob بالنسبة للـ MS Access وغيرها لن أتطرق لها ولكن أود أن أشير إلى أن المبداء ينطبق على الجميع. بالنسبة للـ .NET لتخزين صورة في قاعدة البيانات يجب أولا تحويل الصورة إلى byte[] وهي مصفوفة من نوع byte ومن ثم إرسال المصفوفة أو إدخالها في جملة الـ INSERT، دعونا نفرض أن لدينا جدول باسم images وبداخلة حقلين id و image_content حيث أن الحقل image_content من نوع image وقاعدة البيانات هي MS SQL Server وأن هناك FileUpload Control باسم uploader، سيكون الكود كالتالي:
protected void InsertImageToDatabase() {
if(uploader.HasFile) {
using(SqlConnection con = new SqlConnectionString("your connection string")) {
SqlCommand cmd = new SqlCommand("insert into images(image_content) values(@image_content)", con);
cmd.Parameters.AddWithValue("image_content", uploader.FileBytes);
con.Open();
cmd.ExecuteNonQuery();
}
}
}الكود السابق واضح ومباشر جدا.
رابعا : كيفية عرض الصور من قاعدة البيانات
لعمل ذلك يجب علينا الإستفادة مما تعلمناه في النقطة الأولى والثانية، في النقطة الأولى رأينا كيف يمكننا إستخدام Response.Write لتشكيل الرد من الخادم على ما نريد. سنقوم بنفس المبداْ ولكن بدلا من أن نستخدم Response.Write سوف نستخدم Response.BinaryWrite حيث أنها تقوم بكتابة بيانات ثنائية (byte) - هل تتذكر نوع البيانات التي حولنا الصورة إليها في النقطة الثالثة - ولكن قبل ذلك يجب علينا أن نعرف أن هناك في عالم الويب شيء اسمة Content Type وهو ما يحدد نوع البيانات للصفحة سواء كانت مجرد نص أو صورة أو ملف Excel أو ما إلى ذلك (لمزيد من المعلومات إذهب إلى google.com وقم بالبحث عن الموضوع http MIME). في النقطة الثانية رأينا كيفية عرض الصورة من ملف بإستخدام الـ IMG Tag، سوف نقوم بنفس الطريقة ولكن بدل من أن يحوي الـ src Attribute مسار ملف الصورة سوف يحوي مسار صفحة aspx ملصقا بها متغير =id? ومن ثم الرقم الخاص بالصورة والذي هو عبارة عن مفتاح أساسي (Primary Key) بحيث عن طريقة سنقوم بإستخلاص الصورة من قاعدة البيانات ومن ثم كتابتها في الصفحة بإستخدام Response.BinaryWrite. بالنسبة للـ =id? الذي تم إلصاقة في مصار صفحة aspx يسمة بـ Query String وهي عبارة عن بيانات إضافية يمكن إرسالها مع طلب (Request) صفحة معينة، كما هو الحال هنا، لاحظ في شريط العنوان في المتصفح الخاص بك وأنت تقراء هذا الموضوع هناك في آخر العنوان يوجد =showtopic? ومن ثم رقم معين، هذا الرقم هو الـ ID الخاص بهذا الموضوع حيث ان هذه الصفحة التي انت فيها الآن تأخذ هذا الرقم ثم تستعلم قاعدة البيانات ومن ثم تأتي بهذا الموضوع وكافة الردود المندرجة تحته. دعونا نستخدم نفس السيناريو الموجود في النقطة الثالثة ولكن هذه المرة سوف نقوم بعرض الصور:
كود الصفحة getimage.aspx والتي ستقوم بكتابة الصورة من قاعدة البيانات
protected void Page_Load(object source, EventArgs e) {
// Check if 'id' is provided with the query string
if(Request["id"] == null) return;
using(SqlConnection con = new SqlConnection("your connection string")) {
// fetching from database
SqlCommand cmd = new SqlCommand("select image_content from images where id=@id", con);
cmd.Parameters.AddWithValue("id", Request["id"]);
con.Open();
// Remeber to cast the data to byte[]
byte[] image_buffer = (byte[])cmd.ExecuteScalar();
// writing the image into the response
Response.Clear();
// setting the appropriate content type
Resonse.ContentType = "image/pjpeg";
// actual writing
Response.BinaryWrite(image_buffer);
// fluah the underlaying io stream
Response.Flush();
// have a nice day :>
}
}كود عرض الصور
<asp:GridView ID="imageGridView" runat="server" DataSourceID="imageDataSource" AutoGenerateColumns="false">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<img src='getimage.aspx?id=<%# Eval("id")' />
</ItemTemplae>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="imageDataSource" runat="server" ConnectionString="your connection string"
SelectCommand="select id from images" />خامسا: أبدا لا تأخذ مدخلات من المستخدم بشكل مسلم
في الواقع نلاحظ أن مدخلات المستخدم تلعب دورا كبير في أكثر الإختراقات المعروفة في عالم الويب ولكنني لن أتطرق إلى هذا حيث أنه خارج نطاق هذا الموضوع. ما يهمنا هنا هو تحديد الحجم الأقصى للصورة المراد رفعها وأن الملف الذي سيتم رفعة هو صورة وليس شيئا آخر. بالنسبة لحجم الصورة فهناك حد أقصى إفتراضي وهو 4MB وهذا هو سبب الـ Exception الذي يظهر لك في حالة رفع ملف أكثر من 4MB. يمكنك تغيير القيمة الإفتراضية عن طريق إدخال التالي في ملف web.config :
<system.web> . . . <httpRuntime maxRequestLength=[number in KB] /> . . . </system.web>
على كل حال يمكن فحص حجم الملف عن طريق إستخدام CustomValidator وذلك عن طريق ربطه بحدث في الـ Server من خلالة تقوم بفحص الـ ContentLength للملف المرفق في الـ FileUpload. اما بالنسبة للتأكد من الملف المراد رفعة هو عبارة عن صورة فقط فهناك طريقتين، الأولى إستخدام RegularExpressionValidator حيث أن الخاصية ValidationExpression ستكون بالشكل التالي
"^(([a-zA-Z]:)|(\\{2}\w+)\$?)(\\(\w[\w].*))(.jpg|.JPG|.gif|.GIF)$" // you can add other extensions as wellوالطريقة الثانية هي عبارة عن إستخدام CustomValidator بحيث يتم ربطة بحدث في الـ server من خلالة تقوم بفحص إمتداد اسم الملف الموجود في الـ FileUpload أو فحص الـ ContentType .
في المرفقات يوجد مشروع كامل يقوم برفع صورة وتخزينها بأبعادها الأصلية وأيضا يأخذ نسخة مصغرة منها بحجم 100 * 100 وذلك حتى يسهل ويخف عرضها وأيضا يتأكد من حجمها وأنها فقط صورة.
أتمنى أن أكون قد أصبت ولم أنسى شيئا وأسأل الله القدير أن يجعل هذا الشرح نافع غير وأن يوفق الجميع