بدء العمل مع Azure الجزء 2

تحدث في المقالة السابقة عن بناء VM باستخدام Azure  سنتحدث في هذه المقالة عن بناء موقع ويب استخدام Azure على الرغم من أنه يمكنك استخدام VM التي قمنا ببنائها مسبقا كموقع لاستضافة الموقع الخاص بك , إلى أن Azure يتيح لك خيار بناء استضافة لموقع ويب دون الحاجة لبناء VM .

بدء العمل

لبناء موقع ويب ادخل على لوحة التحكم الخاصة بك على موقع Azure اضغط على الرابط من القائمة في اليمين Web Site , كما في الصورة في الاسفل

image 

اضغط على New من شريط المهام في الاسفل يمكنك انشاء موقع بثلاثة طرق مختلفة أذا كنت تعتمد على IIS يمكنك فقط الضغط على Quick Create , أو اذا كنت ترغب بخيارات اخرى مثل PHP او Java أو غيرها , يمكنك اختيار From Callery 

image

يمكنك الاختيار من خلال مجموعة كبيرة جدا من الأدوات سواء Blog او أنظمة ادارة المحتوى ,  أو منتديات أو انظمة Wiki و غيرها من الخيارات , في هذه التجربة في الحقيقة أود تجربة Better CMS لذلك سأختار Better CMS

image

اضغط على السهم التالي و عبئ النموذج التالي رابط الموقع و معلومات قاعدة البيانات ثم اضغط على التالي و قم بكتابة بيانات خادم قاعدة البيانات بمجرد الانتهاء  يمنكن الان استعراض الموقع الخاص بك على الرابط التي حددته في خالتي هنا سأقوم باستعراض عبر الرابط التالي :

 image

الأن بالعودة لـ Azure يمكنك مراقبة الموقع و حجم الاستهلاك و كمية استهلاك المعالج , الصورة :

image

تستطيع اضافة مهمة ويب جديدة من خلال WebJOBS أو يمكنك تعديل الاعدادات من خلال Config حيث يمكنك تعديل خصائص .Net أو الـ PHP او Java وغيرها من الاعدادات , يمكنك زيادة حجم الـ RAMS او قاعدة البيانات من خلال الـ SCALE , يمكنك الوصول إلى قاعدة البيانات من خلال LINKED RESOURCES يمكنك ادارة قاعدة البيانات من خلال موقع SilverLight

image

1DataBase

بدء العمل مع Azure

إذا  كنت لا تعمل ما هو Azure فهذا يعني أنك بعيد تمام عن التقنية منذ ما يقارب 10 سنوات , عموما Azure هي منصة الـ Cloud الخاصة بـ Mictosoft , سأتحدث في هذه المقالة و مجموعة مقالات قادمة عن Windows Azure , لن أتحدث في هذه المقالات عن الجزء التسويقي من Azure حيث سأركزا كثيرا على الجوانب التقنية بشكل صرف , فلن تجد في هذا المقالات اي حديث عن الاسعار و المميزات و قيم الاشتراك و ما شابه.

متى سأكون بحاجة لـ Azure VM

هناك العديد من الحالات التي ستكون فيها بحاجة إلى استخدام VM , مثلا عند تقديم حلول تقنية تعتمد على وجود العميل  على الشبكة الخاصة بك , مثل برامج الـ Proxy أو برامج الـ Server Base و غيرها من التطبيقات, عندما تكون بحاجة إلى نشر تطبيقات تحتاج إلى نوعا خاصا الإعدادات لا تستطيع أن توفره الاستضافات التقليدية, أضف إلى أن تكلفة Azure كـ VM أرخص بكثير من تكلفة الاستضافة العادية إذا ما قورنت بحجم الامكانيات التي تستطيع عملها من خلال توفر VM مربوط بشبكة الانترنت. أيضا يمكنك استخدام Azure  في عمليات الـ TEST لتلك التطبيقات التي تحتاج لتواصل فيما بعض خارج بيئة التطوير مثلا تطبيقات الـ Chat  تطبيقات التي تحاج  إلى وجود انترنت دائما .أيضا يمكنك استخدام Azure VM من اجل العمل على بناء تطبيقات File Cloud من OwnCloud أو استضافة مشاريع خاصة مثل Wordpress و غيرها.

بدء العمل

لحسن الحظ توفر Micosoft  خيار التجربة لمدة 30 يوما بشكل مجاني تستطيع من خلاله تجربة جميع امكانيات Azure VM لبدء العمل عليك التسجيل مع Azure ستحتاج في عملية التسجيل إلى بطاقة Visa  لا تقلق لن يتم خصم أي مبلغ سوى مبلغ التحقق من صحة البطاقة هو دولار واحد فقط . بعد علمية التسجيل ستتواجه بعدها إلى لوحة التحكم في Azure  في القائمة على اليسار اختر Virtual machines في البداية لن تكون لديك اية واحدة , لذلك يجب عليك صناعة واحدة جديدة.

 

image

اضغط على New من اسفل القائمة ستواجه خيارات أحدمها Quick Create و الأخر From Gallery في البداية سنختار Quick Create سيطلب منك المعالج تحديد اسم الـ DNS و تحديد نسخة الـ Windows أيضا اسم المستخدم و كلمة المرور الخاصة بـ RDP , الصورة في الأسفل :

image

يمكنك تحديد نسخة الـ VM التي تريد العمل عليها سواء Windows او Linux او غيرها , أيضا يمكنك تحديد حجم الـ VM من المعالح و الذاكرة و بما أنك في فترة تجريبية مجانية فختر أكثر حجم أي حجم أكبر حجم تستطع استخدامه في الاشتراك التجريبي وهو 56 GB كـ Memory و A7 معالح. بعد تجهيز الاعدادات الخاصة اضغط على Create a Virtual Machine و انتظر لدقائق معدودة حتى ينتهي بمجرد الانتهاء من صناعة VM يمكنك الان التواصل مع هذه الـ VM من خلال RDP بمجرد تحول حالة الـ VM إلى Runing يمكنك الان التواصل معاها من خلال الضغط على زر Connect في الاسفل بعد تحدد الـ VM طبعا, بعد تحميل الملف اضغط عليه سيفتح لك RDP ادعل اسم المستخدم و كلمة المرور التي قمت بكتابتها في البداية. و مبروك عليك VM.

image

يمكنك ايضا اختيار بناء VM جاهزة للعمل من نافذة From Gallery بدلا عن الخيار Create quick 

image

نقل الملفات من جهة إلى أخرى

مرحبا إن عملية نقل الملفات ليست جديدة نهائيا على شخص يقوم بعمليات الربط و التكامل بين التطبيقات أو الجهات المختلفة, حسنا لديك مهمة جديدة تطلب منك نقل ملف أو اكثر من جهة معينة إلى أخرى هذه الملفات ربما يزيد حجمها عن 40 او حتى 60 ميجا , هذه الملفات يتم توليدها من تطبيق أو تطبيقات مختلفة لجهة محددة او جهات اخرى, ربما يكون أول حل يخطر على بالك في هذه الحالة استخدام الـ Web Service حيث يقوم مطور التطبيق X باستدعاء  الـ Web Service ممرا لها الملف كا Byte مثلا أو ربما كا Stream, ثم تقوم هذه الخدمة بتواصل مع العميل من خلال خدمة يقدمها ايضا العميل التي تستقبل هي بدورها محتويات هذا الملف.

في الحقيقة لا بأس في هذا الحل كفكرة طبعا بشكل نظري المشكلة في هذه الفكرة هو استخدام الـ HTTP كوسيلة لنقل الملفات بالإضافة إلى حاجة المطور X او غيره إلى تعديل التطبيقات الخاصة به من التواصل مع هذه الخدمة , أضف إلى حاجة العميل إلى تطوير خدمة خاصة به من أجل الحصول على  الملفات المختلفة, من الناحية التقنية هذا الحل مكلف و مستهلك للجهد و الوقت و غير مجدي خصوصا إذا راعينا فكرة انتقال الملف كـ Byte من ثلاث طبقات تقريبا قبل ان يصل للعميل على شبكة HTTP , أضف إلى استخدام الـ HTTP لرفع أو تحميل ملف بحجم 60 ميجا بايت ليس بالأمر البسيط.

لحل لهذه مشكلة يجب أن نضع بعض أهداف و توقعات مستقبلة:

  • يجب كتابة أقل شفرة ممكنة من قبل الطرفين سواء مطور التطبيق او العميل لتبادل الملفات فيما بينهم.
  • يجب أن يتم تبادل الملفات بأسرع وقت.
  • يجب أن يتم مراعة اختلاف التقنيات و التي قد تشكل بطيء شديد في طريقة التواصل بين التطبيقات في مشكلة مشابه.
  • يجب أن تراعي هنا أن حل مثل هذا ريما يستخدم في مشاريع أخرى لذلك يجب عليك تطوير تطبيق يتمتع بقدر كافي من المرونة لكي يستخدم مع تطبيقات اخرى بشكل جيد .
  • يجب أن تكون التكلفة متدنية و يفضل ان لا توجد تكلفة على الاطلاق.

تقليل الشفرة ليس بالأمر الهين, خصوصا اذا كان هدفنا في هذا التطبيق هو تقليل الشفرة لدرجة ان المطور X او العميل لا يقوم احدمها بكتابة اي شفرة تذكر, اذا فإن من سيقوم بعملية نقل الملف من التطبيق X إلى خادم العميل هو ادارة الربط و التكامل و ليس المطور أو العميل و هذا منطقي تمام.

لتبادل الملفات بأسرع وقت ممكن لا يوجد أفضل من استخدام TCP و ليس HTTP , حيث سنقوم بتطوير خدمة WCF مبنية على TCP مع استخدام Streaming  و التي تقوم بفتح web socket مع العميل لنقل الملف إلى Bit by bit كما هو حاصل مثلا مع Youtube حيث تستخدم TCP و ليس و HTTP لعرض مقاطع الـ Video على المستخدمين.

إن اعتماد حل واحد من قبل طرف واحد هو بتأكيد أفضل من اعتماده من قبل أطراف مختلفة ما أقصده هو بما أنني قررت أن أتولى عملية نقل الملف من عميل إلى اخر دون تدخل أحدمها فلن أحتاج هنا إلى تقنية سوى التي اقوم انا بتطويرها فلا يهمني بيئات التطوير المختلفة أو خلافه.

أخيرا سنقوم ببنا التطبيق بشكل عملي بحيث يمكن اعادة استخدماها مرارا و تكرارا دون تداخل بين اي تطبيق و أخر أو عملي و أخر و لذلك تستخدما WCF التقليدية لن يجدي نفعا في هذه الحالة.

الفكرة تتلخص بتطبيقين الأول ( المُرسل ) من الجهة الخاصة بك حيث يقوم هذا التطبيق مراقبة مجلد معين متفق عليه بين التطبيق X الخاص بالمطور و الخدمة التي تقوم بمراقبة هذا المجلد, التطبيق الثاني ( المُستقبل )  يثبت على خواد العميل حيث يستقبل هذا التطبيق الملفات الموجودة على المجلد السابق ذكرة من خلال التطبيق الأول ( المُرسل ) ثم يقوم بكتابة هذا الملف على مجلد معين عند العميل و الذي بدوره يقوم بفعل ما يرد بهذا الملف دون ادنى تدخل من قبل المنظمة الخاصة بك, فالمهمة انتهت بتوصيل الملف و التأكد من انه وصل فعلا, الصورة التالية قمت بكتابتها أثناء العصف الدهني لإجاد حل مناسب لهذه المشكلة :

WP_20150108_001

 

الخطوة الأولى: بناء المُستقبل:

بناء المرسل ليس بالأمر الصعب و تقريبا لا توجد به فكرة جديدة كليا, كل ما في الامر سنقوم ببناء WCF تعمل على TCP تحتوي على طريقة واحدة بعنوان Put , تستقبل هذه الطريقة كائن من النوع FileTransferRequest و الذي بدوره يحتوي على محتوى الملف المرسل من قبل التطبيق الخاص بالمطور عن طريق المُرسل الخاص بنا, , الشفرة لمشروع المُستقبل:

IFileTransfer.cs

namespace FileTransferFramework.Client
{
    using System.IO;
    using System.ServiceModel;
 
    [ServiceContract]
    public interface IFileTransfer
    {
        [OperationContract]
        FileTransferResponse Put(FileTransferRequest fileToPush);
    }
}

حيث أن الكائن FileTransferRequest  سيكون بشكل التالي /


FileTransferRequest.cs



namespace FileTransferFramework.Client
{
    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.ServiceModel;
 
    [DataContract]
    /// <summary>
    /// Transfer Request Object
    /// </summary>
    public class FileTransferRequest
    {
        [DataMember]
        /// <summary>
        /// Gets or sets File Name
        /// </summary>
        public string FileName { get; set; }
 
        [DataMember]
        public byte[] Content { get; set; }
    }
}

تعيد هذه الطريقة كائن من النوع FileTransferResponse  و هو بالشكل التالي:


FileTransferResponse.cd



 
 
namespace FileTransferFramework.Client
{
    using System;
    using System.Runtime.Serialization;
 
    /// <summary>
    /// File Response Object
    /// </summary>    
    public class FileTransferResponse
    {
        [DataMember]
        /// <summary>
        /// Gets or sets File Name
        /// </summary>
        public string FileName { get; set; }
 
        [DataMember]
        /// <summary>
        /// Gets or sets Created at
        /// </summary>
        public DateTime CreateAt { get; set; }
 
        [DataMember]
        /// <summary>
        /// Gets or sets Message
        /// </summary>
        public string Message { get; set; }
        
        [DataMember]
        /// <summary>
        /// Gets or sets Response Status
        /// </summary>               
        public string ResponseStatus { get; set; }        
    }
}

في حين سيكون شكل التصنيف FileTransfer بالشكل التالي:


FileTransfer.cs



using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
 
namespace FileTransferFramework.Client
{
     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
    public class FileTransfer : IFileTransfer
    {
        /// <summary>
        /// Push File to the client
        /// </summary>
        /// <param name="fileToPush">file you want to Push</param>
        /// <returns>a file transfer Response</returns>
        public FileTransferResponse Put(FileTransferRequest fileToPush)
        {
            FileTransferResponse fileTransferResponse = this.CheckFileTransferRequest(fileToPush);
            if(fileTransferResponse.ResponseStatus == "FileIsValed")
            {
                try
                {
                    this.SaveFileStream(System.Configuration.ConfigurationManager.AppSettings["SavedLocation"].ToString() + "\\" + fileToPush.FileName, new MemoryStream(fileToPush.Content));
                    return new FileTransferResponse
                    {
                        CreateAt = DateTime.Now,
                        FileName = fileToPush.FileName,
                        Message = "File was transfered",
                        ResponseStatus = "Successful"
                    };
                }
                catch (Exception ex)
                {
                    return new FileTransferResponse
                    {
                        CreateAt = DateTime.Now,
                        FileName = fileToPush.FileName,
                        Message = ex.Message,
                        ResponseStatus = "Error"
                    };
                }
            }
 
            return fileTransferResponse;
        }


/// <summary>
/// Check From file Transfer Object is not null 
/// and all properties is set
/// </summary>
/// <param name="fileToPush">file to check</param>
/// <returns>File Transfer Response</returns>
private FileTransferResponse CheckFileTransferRequest(FileTransferRequest fileToPush)
{
    if (fileToPush != null)
    {
        if (!string.IsNullOrEmpty(fileToPush.FileName))
        {
            if (fileToPush.Content !=null)
            {
                return new FileTransferResponse
                {
                    CreateAt = DateTime.Now,
                    FileName = fileToPush.FileName,
                    Message = string.Empty,
                    ResponseStatus = "FileIsValed"
                };
            }
 
            return new FileTransferResponse
            {
                CreateAt = DateTime.Now,
                FileName = "No Name",
                Message = " File Content is null",
                ResponseStatus = "Error"
            };
        }
 
        return new FileTransferResponse
        {
            CreateAt = DateTime.Now,
            FileName = "No Name",
            Message = " File Name Can't be Null",
            ResponseStatus = "Error"
        };
    }
 
    return new FileTransferResponse
    {
        CreateAt = DateTime.Now,
        FileName = "No Name",
        Message = " File Can't be Null",
        ResponseStatus = "Error"
    };
}
 
/// <summary>
/// Write the Stream in the hard drive
/// </summary>
/// <param name="filePath">path to write the file in</param>
/// <param name="stream">stream to write</param>
private void SaveFileStream(string filePath, Stream stream)
{
    try
    {
        if(File.Exists(filePath))
        {
            File.Delete(filePath);
        }
 
        var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
        stream.CopyTo(fileStream);
        fileStream.Dispose();
    }
    catch(Exception ex)
    {
        throw ex;
    }
}
    }
}

شفرة التصنيف FileTransfer ابسط من المتوقع كل ما في الامر اقوم بكتابة محتويات الملف الذي تستقبله الطريقة Put و اضعه في مجلد ما على حسب اختيار المستخدم, نأتي الان للجزء الاخر من القصة حيث يجيب عليك هنا أن تفتح  المجال للمُرسل بأن يرسل ملفات ذات الحجام كبيرة و ذلك عن طريق ملف app.config  , الشفرة :



<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="DbConnection" value="Data Source=10.10.5.133;Initial Catalog=FileTransferFrwork;Integrated Security=True"/>
    <add key="FolderToWatch" value="C:\Users\ALGHABBAN\Desktop\UploadFiles\"/>
  </appSettings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>     
        <bindings>
            <netTcpBinding>
                <binding name="NetTcpBinding_IFileTransfer" transferMode="Streamed"                     
              transactionFlow="false"  transactionProtocol="OleTransactions"
              hostNameComparisonMode="StrongWildcard" listenBacklog="10"
              maxBufferPoolSize="79623599" maxBufferSize="4967295" maxConnections="10"
              maxReceivedMessageSize="79623599">
                  <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="555555555"
                      maxBytesPerRead="4096" maxNameTableCharCount="79623599" />
                  <reliableSession ordered="true" 
                      enabled="false" />
                  <security mode="None">
                    <message clientCredentialType="None"/>
                    <transport clientCredentialType="None"/>
                  </security>
                </binding>              
            </netTcpBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://192.168.1.108:3021/streamserver" binding="netTcpBinding"
                bindingConfiguration="NetTcpBinding_IFileTransfer" contract="ClientFileTransferServiceReference.IFileTransfer"
                name="NetTcpBinding_IFileTransfer">               
            </endpoint>
        </client>      
    </system.serviceModel>
</configuration>

لاحظ في الاعدادات اني أستخدم TCP على الـ IP 192.168.1.10 أيضا لاحظ بانني سحمت بنقل ملفات حتى 79 ميجا بايت, من المستخدم إلى الخادم و هذه اشياء طبعا تختلف من عميل إلى أخر أيضا لاحظ انني استخدم Streaming بدلا عن Buffered مثلا , أخيرا في الطريقة Main أقوم بتشغيل هذه خدمة:



static void Main(string[] args)
{
    try
    {               
        using (var serviceHost = new ServiceHost(typeof(FileTransfer)))
        {
            serviceHost.Open();
            Console.WriteLine("Client Started..");
            Console.ReadKey();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        Console.ReadKey();
    }
}

بناء تطبيق المٌرسل


لا يوجد اي شيء جديد في تطبيق المٌرسل كل ما في الأمر أنني سأقوم بإنشاء FileWatcher لمراقبة مجلد ما نتفق انا و المطور عليه , يقوم المُرسل بمراقبة هذا المجلد و بمجرد وجود ملف جديد يقوم التطبيق عمل Conusume للـ Web Services التي قمت ببنائها سابقا في التطبيق المُستقبل, حسنا يبدأ كل شيء من الطريقة Main هذه المرة , حيث أقوم فيها بإنشاء كائن جديد من النوع FileSystemWatcher و اعالج الحدث Created  , الشفرة:



/// <summary>
/// main Program 
/// </summary>
/// <param name="args">args of the file</param>
static void Main(string[] args)
{
    try
    {                
        FileSystemWatcher fileWatcher = new FileSystemWatcher(System.Configuration.ConfigurationManager.AppSettings["FolderToWatch"].ToString());
        fileWatcher.Created += FileWatcher_Created;
        fileWatcher.EnableRaisingEvents = true;
        Console.WriteLine("Watcher Started...");
    }
    catch (Exception ex)
    {
        Console.Write(ex.Message);
        new Logger().Create("Service not started", DateTime.Now, "Error", ex.Message);
    }
    
    Console.ReadKey();
}

و في الحدث FileWacher_Created و الذي يتم اطلاقة بمجرد وجود ملف جديد أثناء سوف يتم عمل Fire لهذا الحدث و الذي سيقوم بتحقق أولا بأن الملف غير مغلق او مستخدم من قبل تطبيق اخر عندها أقوم بإنشاء كائن من النوع FileTransferRequest مع تمرير اسم الملف و محتويات من Byte , ثم اقوم باستدعاء الطريقة الـ Put , ثم استخدم الكائن الـFileTransferResponse لمعرفة ماذا حدث على الملف , في حالة الفشل في الارسال بسبب قطع الاتصال او لوجود مشكلة عنده العميل اقوم بمسح الملف من المجلد الرئيسي و وضع في مجلد اخر , الشفرة :



/// <summary>
/// File Watcher when Created
/// </summary>
/// <param name="sender">sender object</param>
/// <param name="e">e object</param>
private static void FileWatcher_Created(object sender, FileSystemEventArgs e)
{
    FileTransferResponse response = null;
    try
    {
        if (IsFileLocked(e.FullPath) == false)
        {
            DateTime startAt = DateTime.Now;
            FileTransferRequest createdFile = new FileTransferRequest()
            {
                FileName = e.Name,
                Content = File.ReadAllBytes(e.FullPath)
            };
 
            response = new FileTransferClient().Put(createdFile);
 
            if (response.ResponseStatus != "Successful")
            {
                MoveToFailedFolder(e);
            }
            else
            {
                if (File.Exists(e.FullPath))
                {
                    File.Delete(e.FullPath);
                }
            }
 
            Console.WriteLine(response.ResponseStatus + " at: " + DateTime.Now.Subtract(startAt).ToString() + " Size:-> " + createdFile.Content.Length);
            new Logger().Create(e.Name, DateTime.Now, response.ResponseStatus, response.Message);
        }
    }
    catch (System.ServiceModel.CommunicationException ex)
    {
        MoveToFailedFolder(e);
        Console.WriteLine(ex.Message);
        new Logger().Create(e.Name, DateTime.Now, "Error", ex.Message);
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.Message);
        if(response != null)
        {
            new Logger().Create(e.Name, DateTime.Now, response.ResponseStatus, response.Message);
        }
        else
        {
            new Logger().Create(e.Name, DateTime.Now, "Error", ex.Message);
        }
    }
}

فيديو يوضح فكرة عمل التطبيق لجهازين على نفس الشبكة عبر الـ Wifi , الفيديو:




أخيرا صورة توضح سرعة الارسال مقارنة فيما اذا تم اعتماد على HTTP , في الصورة عملية نقل ما يقارب 70 ميجا دفعه واحدة و تلاحظ أن 70 ميجا تأخذ متوسط 23 ثانية لتنقل من جهاز إلى أخر .


 Capture