مطالب
AngularJS #4
در این قسمت قصد دارم تا یک سیستم ارسال دیدگاه را به کمک Angular پیاده سازی کنم. هدف از این مثال؛ آشنایی با چند Directive توکار Angular و همچنین آموختن چگونگی کار با سرویس http$ برای ارتباط با سرور است.
کدهای HTML زیر را در نظر بگیرید:
<div ng-app="myApp">
    <div ng-controller="CommentCtrl">

        <div ng-repeat="comment in comments">
            <div style="float:right;cursor:pointer;" ng-click="remove(comment.Id,$index);">X</div>
            <a href="#">
                <img style="width:32px;" ng-src="/Content/user.gif" alt="{{comment.Name}}">
            </a>
            <div>
                <h4>{{comment.Name}}</h4>
                {{comment.CommentBody}}
            </div>
        </div>

        <div>
            <form action="/Comment/Add" method="post">
                <div>
                    <label for="Name">Name</label>
                    <input id="Name" type="text" name="Name" ng-model="comment.Name" placeholder="Your Name" />
                </div>
                <div>
                    <label for="Email">Email</label>
                    <input id="Email" type="text" name="Email" ng-model="comment.Email" placeholder="Your Email" />
                </div>
                <div>
                    <label for="CommentBody">Comment</label>
                    <textarea id="CommentBody" name="CommentBody" ng-model="comment.CommentBody" placeholder="Your Comment"></textarea>
                </div>
                <button type="button" ng-click="addComment()">Send</button>
            </form>
        </div>

    </div>
</div>
خب از ابتدا ساختار را مورد بررسی قرار می‌دهم و موارد ناآشنای آن را توضیح می‌دهم:

ng-app: خاصیت ng-app جز خواص پیش فرض HTML نیست و یک خاصیت سفارشی است که توسط Angular به صورت پیش فرض تعریف شده است. این خاصیت به Angular می‌گوید که کدام بخش از DOM باید توسط Angular مدیریت و پردازش شود. در اینجا div ای که با خاصیت ng-app مزین شده است به همراه تمامی عناصر فرزند آن توسط موتور پردازش گر DOM توکار مورد پردازش قرار گرفته و اصطلاحا کامپایل می‌شود. بله! اینجا از لفظ کامپایل شدن برای بیان این فرآیند استفاده کردم. هیچ کدام از این Directive‌های سفارشی به خودی خود برای مرورگر قابل تفسیر نیست و اینجاست که Angular وارد عمل شده و این Directive‌ها را به کدهای HTML و جاوا اسکریپت که برای مرورگر قابل فهم است تبدیل می‌کند. به همین جهت با ng-app مشخص می‌کنیم که کدام بخش از DOM باید توسط Angular تفسیر و مدیریت شود.شاید این سوال برای شما مطرح شده باشد که در مثال قبلی ng-app مقداری نداشت و برای تگ html تعریف شده بود. پاسخ این است که در مثال قبلی چون برنامه‌ی ما دارای یک ماژول بیشتر نبود می‌توانستیم از مقدار دهی ng-app صرف نظر کنیم؛ اما در این مثال ما قصد داریم کمی هم مفهوم ماژول را در Angular بررسی کنیم. در نتیجه در این مثال برنامه‌ی ما از ماژولی به نام myApp تشکیل شده است. دلیل اینکه در این مثال ng-app بر روی یک div تعریف شده است این است که همین قسمت از DOM توسط Angular تفسیر شود برای ما کفایت می‌کند. هنگامی ng-app را بر روی html تعریف می‌کنیم که قصد داشته باشیم کل صفحه توسط Angular تفسیر شود.
 
ng-controller: در Angular کنترلر‌ها تابع سازنده‌ی کلاس‌های ساده‌ی جاوا اسکریپتی هستند که به کمک آن‌ها بخشی از صفحه را مدیریت می‌کنیم. این که کدام بخش از صفحه توسط کدام کلاس کنترل و مدیریت شود، توسط ng-controller مشخص می‌شود. در اینجا هم عنصری که با ng-controller مشخص شده به همراه تمامی فرزندانش، توسط کلاس جاوا اسکریپتی به نام CommentCtrl مدیریت می‌شود. در حقیقت ما به کمک ng-controller مشخص می‌کنیم که کدام قسمت از View توسط کدام Controller مدیریت می‌شود. مرسوم است که در Angular نام کنترلرها با Ctrl خاتمه یابد.
   
ng-repeat: همه‌ی نظرات دارای یک قالب html یکسان هستند که به ازای داده‌های متفاوت تکرار شده اند. اگر می‌خواستیم نظرات را استفاده از موتور نمایشی Razor نشان دهیم از یک حلقه‌ی foreach استفاده می‌کردیم. خبر خوب این است که ng-repeat هم دقیقا به مانند حلقه‌ی foreach عمل می‌کند.در اینجا عبارت comment in comments دقیقا برابر با آن چیزی است که در یک حلقه‌ی foreach می‌نوشتیم. Comments در اینجا یک لیست به مانند آرایه ای از comment هست که در کنترلر مقدار دهی شده است. پس اگر با حلقه‌ی foreach مشکلی نداشته باشید با مفهوم ng-repeat هم مشکلی نخواهید داشت و دقیقا به همان شکل عمل می‌نماید.
     
ng-click: همان طور که گفتیم Directive‌های تعریف شده می‌توانند یک event سفارشی نیز باشند. ng-click هم یک Directive تو کار است که توسط Angular به صورت پیش فرض تعریف شده است. کاملا مشخص است که یک تابع به نام remove تعریف شده است که به هنگام کلیک شدن، فراخوانی می‌شود. دو پارامتر هم به آن ارسال شده است. اولین پارامتر Id دیدگاه مورد نظر است تا به سرور ارسال شود و از پایگاه داده حذف شود. دومین پارامتر index$ است که یک متغیر ویژه است که توسط Angular در هر بار اجرای حلقه‌ی ng-repeat مقدارش یک واحد افزایش می‌یابد. index$ هم به تابع remove ارسال می‌شود تا بتوان فهمید در سمت کلاینت کدام نظر باید حذف شود.
 
ng-src: از این Directive برای مشخص کردن src عکس‌ها استفاده می‌شود. البته در این مثال چندان تفاوتی بین ng-src و src معمولی وجود ندارد. ولی اگر آدرس عکس به صورت Content/{{comment.Name}}.gif می‌بود دیگر وضع فرق می‌کرد. چرا که مرورگر با دیدن آدرس در src سعی به لود کردن آن عکس می‌کند و در این حالت در لود کردن آن عکس با شکست روبرو می‌شود. ng-src سبب می‌شود تا در ابتدا آدرس عکس توسط Angular تفسیر شود و سپس آن عکس توسط مرورگر لود شود.
   
{{comment.Name}}: آکلودهای دوتایی برای انقیاد داده (Data Binding) با view-model استفاده می‌شود. این نوع اقیاد داده در مثال‌های قبلی مورد بررسی قرار گرفته است و نکته‌ی بیشتری در اینجا مطرح نیست.
   
ng-model: به کمک ng-model می‌توان بین متن داخل textbox و خاصیت شی مورد نظر انقیاد داده بر قرار کرد و هر دو طرف از تغییرات یکدیگر آگاه شوند. به این عمل انقیاد داده دوطرفه (Two-Way Data-Binding) می‌گویند.برای مثال textbox مربوط به نام را به comment.Name و textbox مربوط به email را به comment.Email مقید(bind) شده است. هر تغییری که در محتوای هر کدام از طرفین صورت گیرد دیگری نیز از آن تغییر با خبر شده و آن را نمایش می‌دهد.
   
تا به اینجای کار قالب مربوط به HTML را بررسی کردیم. حال به سراغ کدهای جاوا اسکریپت می‌رویم:
var app = angular.module('myApp', []);

app.controller('CommentCtrl', function ($scope, $http) {

    $scope.comment = {};

    $http.get('/Comment/GetAll').success(function (data) {

        $scope.comments = data;

    })

    $scope.addComment = function () {

        $http.post("/Comment/Add", $scope.comment).success(function () {

            $scope.comments.push({ Name: $scope.comment.Name, CommentBody: $scope.comment.CommentBody });

            $scope.comment = {};

        });
    };

    $scope.remove = function (id, index) {

        $http.post("/Comment/Remove", { id: id }).success(function () {

            $scope.comments.splice(index, 1);

        });
    };

});
در تعریف ng-app اگر به یاد داشته باشید برای آن مقدار myApp در نظر گرفته شده بود. در اینجا هم ما به کمک متغیر سراسری angular که توسط خود کتابخانه تعریف شده است، ماژولی به نام myApp را تعریف کرده ایم. پارامتر دوم را فعلا توضیح نمی‌دهم، ولی در این حد بدانید که برای تعریف وابستگی‌های این ماژول استفاده می‌شود که من آن را برابر یک آرایه خالی قرار داده ام.
در سطر بعد برای ماژول تعریف شده یک controller تعریف کرده ام. شاید دفعه‌ی اول است که تعریف کنترلر به این شکل را مشاهده می‌کنید. اما چرا به این شکل کنترلر تعریف شده و به مانند قبل به شکل تابع سازنده‌ی کلاس تعریف نشده است؟
پاسخ این است که اکثر برنامه نویسان از جمله خودم دل خوشی از متغیر سراسری ندارند. در شکل قبلی تعریف کنترلر، کنترلر به شکل یک متغیر سراسری تعریف می‌شد. اما استفاده از ماژول برای تعریف کنترلر سبب می‌شود تا کنترلرهای ما روی هوا تعریف نشده باشند و هر یک در جای مناسب خود باشند. به این شکل مدیریت کدهای برنامه نیز ساده‌تر بود. مثلا اگر کسی از شما بپرسد که فلان کنترلر کجا تعریف شده است؛ به راحتی می‌گویید که در فلان ماژول برنامه تعریف و مدیریت شده است.
در تابعی که به عنوان کنترلر تعریف شده است، دو پارامتر به عنوان وابستگی درخواست شده است. scope که برای ارتباط با view-model و انقیاد داده به کار می‌رود و http$  که برای ارتباط با سرور به کار می‌رود. نمونه‌ی مناسب هر دوی این پارامترها توسط سیستم تزریق وابستگی تو کار angular در اختیار کنترلر قرار می‌گیرد.
قبلا چگونگی استفاده از scope$ برای اعمال انقیاد داده توضیح داده شده است. نکته‌ی جدیدی که مطرح است چگونگی استفاده از سرویس http$ برای ارتباط با سرور است. سرویس http $   دارای 4 متد put ، post ، get و delete است.
واقعا استفاده از این سرویس کاملا واضح و روشن است. در متد addComment وقتی که دیدگاه مورد نظر اضافه شد، به آرایه‌ی کامنت‌ها یک کامنت جدید می‌افزاییم و چون انقیاد داده دو طرفه است، بالافاصله دیدگاه جدید نیز در view به نمایش در می‌آید.کار تابع remove هم بسیار ساده است. با استفاده از index ارسالی، دیدگاه مورد نظر را از آرایه‌ی کامنت‌ها حذف می‌کنیم و ادامه‌ی کار توسط انقیاد داده دو طرفه انجام می‌شود.
همان طور که مشاهده می‌شود مفاهیم انقیاد داده دو طرفه و تزریق وابستگی خودکار سرویس‌های مورد نیاز، کار با angularjs را بسیار ساده و راحت کرده است. اصولا در بسیاری از موارد احتیاجی به باز اختراع چرخ نیست و کتابخانه‌ی angular آن را برای ما از قبل تدارک دیده است.
   
کدهای این مثال ضمیمه شده است. این کدها در Visual Studio 2013 و به کمک ASP.NET MVC 5 و Entity Framework 6 نوشته شده است. سعی شده تا مثال نوشته شده به واقعیت نزدیک باشد. اگر دقت کنید مدل کامنت در مثالی که نوشتم به گونه ای است که دیدگاه‌های چند سطحی به همراه پاسخ هایش مد نظر بوده است. به عنوان تمرین نمایش درختی این گونه دیدگاه‌ها را به کمک Angular انجام دهید. کافیست Treeview in Angular را جست و جو کنید؛ مطمئنا به نتایج زیادی می‌رسید. گرچه در مثال ضمیمه شده اگر جست و جو کنید من پیاده سازیش را انجام دادم. هدف از جست و جو در اینترنت مشاهده این است که بیشتر مسائل در Angular از پیش توسط دیگران حل شده است و احتیاجی نیست که شما با چالش‌های جدیدی دست و پنجه نرم کنید.
پس به عنوان تمرین، دیدگاه‌های چند سطحی به همراه پاسخ که نمونه اش را در همین سایتی که درحال مشاهده آن هستید می‌بینید را به کمک AngularJS پیاده سازی کنید.
  
در مقاله‌ی بعدی چگونگی انتقال منطق تجاری برنامه از کنترلر به لایه سرویس و چگونگی تعریف سرویس جدید را مورد بررسی قرار می‌دهم.
   

نظرات مطالب
ارسال انواع بی نام (Anonymous) بازگشتی توسط Entity framework به توابع خارجی
 من میخام جداول زیر رو به روش بالا با هم join و استفاده کنم

  var query = pro.types.Select(ty => new
       {
           typID = ty.type_id,
           typ_nam = ty.type_nam,
           cat_names = ty.categories.Select(cat => new
           {
               catgID = cat.cat_id,
               catg_name = cat.cat_nam,
               items = pro.items.Select(itm => new
               {
                   item_ID = itm.item_id,
                   item_nam = itm.item_nam,
                   item_path = itm.url,
                   coments = pro.comments.Select(cmm => new
                   {
                       comm_title = cmm.comment_nam,
                       comm_mail = cmm.mail,
                       comm_text = cmm.text,
                   })

               })


           })

       }).ToList();

 bool l = myfunc(query);

  protected bool myfunc(object q)
    {
        var cquery = CreateGenericListFromAnonymous(q,
            new
            {
                typID = 0,
                typ_nam = string.Empty,
                cat_names = CreateEmptyAnonymousIEnumerable(new
                {
                    catgID = 0,
                    catg_name = string.Empty,
                    items = CreateEmptyAnonymousIEnumerable(new
                    {
                        item_ID = 0,
                        item_name = string.Empty,
                        item_path = string.Empty,
                        coments = CreateEmptyAnonymousIEnumerable(new
                        {
                            comm_title = string.Empty,
                            comm_mail = string.Empty,
                            comm_text = string.Empty,

                        })
                    })
                })
            });
        foreach (var item in cquery)
        {
            int ID = item.typID;
            string type_title = item.typ_nam;
            foreach (var Cname in item.cat_names)
            {
                int categoryID = Cname.catgID;
                string category_name = string.Empty;

                foreach (var Iname in Cname.items)
                {
                    int ItmID = Iname.item_ID;
                    string ItmName = Iname.item_name;
                    string ItmPath = Iname.item_path;
                    foreach (var cm in Iname.coments)
                    {
                        string com_title = cm.comm_title;
                        string com_mail = cm.comm_mail;
                        string com_text = cm.comm_text;
                    }
                }
            }
        }
        return true;
    }

مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت دوم - تعیین نوع‌های پیشرفته‌تر props
در قسمت قبل با معرفی نوع props توسط TypeScript، مجبور به تکمیل اجباری تک تک آن‌ها شدیم؛ اما در React می‌توان props را به صورت اختیاری و یا با مقادیری پیش‌فرض نیز تعریف کرد.


روش تعیین props پیش‌فرض توسط TypeScript

اگر بخواهیم توسط روش‌های خود React، مقادیر پیش‌فرض props را تعیین کنیم، می‌توان از defaultProps به صورت زیر با تعریف یک شیء جاوا اسکریپتی از پیش مقدار دهی شده، استفاده کرد:
Head.defaultProps = {
   title: "Hello",
   isActive: true
};
اما در حالت استفاده‌ی از TypeScript و یا حتی نگارش ES6 آن (React در حالت پیش‌فرض آن)، می‌توان مقادیر پیش‌فرض props را با مقدار دهی مستقیم متغیرهای حاصل از Object Destructuring آن، تعیین کرد:
type Props = {
  title: string;
  isActive: boolean;
};

export const Head = ({ title = "Hello", isActive = true }: Props) => {
در اینجا هر متغیری که با مقداری پیش‌فرض، مقدار دهی شده باشد، اختیاری در نظر گرفته شده و اگر دارای مقدار پیش‌فرضی نباشد، باید به صورت اجباری در حین تعریف المان این کامپوننت، ذکر شود.
در این حالت انتظار داریم که در حین استفاده و تعریف المان کامپوننت Head، اگر برای مثال ویژگی isActive را ذکر نکردیم، کامپایلر TypeScript خطایی را گزارش نکند؛ که اینطور نیست:


هنوز هم در اینجا می‌توان خطای عدم تعریف خاصیت isActive را مشاهده کرد. برای رفع این مشکل، به صورت زیر عمل می‌کنیم:
type Props = {
  title: string;
  isActive?: boolean;
};

export const Head = ({ title, isActive = true }: Props) => {
در حین تعریف یک type، اگر خاصیتی با علامت ? ذکر شود، به معنای اختیاری بودن آن است. همچنین در اینجا مقدار پیش‌فرض title را هم حذف کرده‌ایم تا تعریف آن اجباری شود. بنابراین در typeها، تمام خواص اجباری هستند؛ مگر اینکه توسط ? به صورت اختیاری تعریف شوند. این مورد هم مزیتی است که در ابتدای طراحی props یک کامپوننت، باید در مورد اختیاری و یا اجباری بودن آن‌ها بیشتر فکر کرد. همچنین نیازی به استفاده از روش‌های غیراستانداردی مانند Head.defaultProps خود React نیست. ذکر مقدار پیش‌فرض متغیرهای حاصل از Object Destructuring، جزئی از جاوااسکریپت استاندارد است و یا مشخص سازی خواص اختیاری در TypeScript، فقط مختص به پروژه‌های React نیست و در همه جا به همین شکل کاربرد دارد.
اکنون با تعریف isActive?: boolean، دیگر شاهد نمایش خطایی در حین تعریف المان Head، بدون ذکر خاصیت isActive، نخواهیم بود.


تعریف انواع و اقسام نوع‌های props

تا اینجا نوع‌های ساده‌ای مانند string و boolean و همچنین نحوه‌ی تعریف اجباری و اختیاری آن‌ها را بررسی کردیم. در ادامه یک نمونه‌ی کامل‌تر را مشاهده می‌کنید:
type User = {
  name: string;
};

type Props = {
  title: string;
  isActive?: boolean;
  count: number;
  options: string[];
  status: "loading" | "loaded";
  thing: {};
  thing2: {
    name: string;
  };
  user: User;
  func: () => void;
};
- در ابتدا نوع‌های متداولی مانند number و string ذکر شده‌اند.
- سپس نحوه‌ی تعریف آرایه‌ای از رشته‌ها را مشاهده می‌کنید.
- یا می‌توان مقدار یک خاصیت را تنها به مقادیری خاص محدود کرد؛ مانند خاصیت status در اینجا و اگر در حین مقدار دهی این خاصیت، از مقدار دیگری استفاده شود، تایپ‌اسکریپت، خطایی را صادر می‌کند.
- در ادامه سه روش تعریف اشیاء تو در تو را مشاهده می‌کنید؛ خاصیت thing از نوع یک شیء خالی تعریف شده‌است (بجای آن می‌توان از نوع object هم استفاده کرد). خاصیت thing2 از نوع یک شیء که دارای خاصیت رشته‌ای name است، تعریف شده و یا بهتر است این نوع تعاریف را به یک type مستقل دیگر مانند User منتقل کرد و سپس از آن جهت تعیین نوع خاصیتی مانند user استفاده نمود.
- در اینجا حتی می‌توان یک خاصیت را که از نوع یک تابع است، تعریف کرد. در این تعریف، void نوع خروجی آن است.


روش تعریف props تابعی در TypeScript

برای بررسی روش تعریف نوع توابع ارسالی از طریق props، ابتدا کامپوننت جدید src\components\Button.tsx را ایجاد می‌کنیم. سپس آن‌را به صورت زیر تکمیل خواهیم کرد:
import React from "react";

type Props = {
  // onClick(): string;  method returns string
  // onClick(): void  method returns nothing;
  // onClick(text: string): void; method with params
  // onClick: () => void; function returns nothing
  onClick: (text: string) => void; // function with params
};

export const Button = ({ onClick }: Props) => {
  return <button onClick={() => onClick("hi")}>Click Me</button>;
};
در این کامپوننت، متغیر onClick حاصل از Object Destructuring شیء props دریافتی، یک تابع است که قرار است با کلیک بر روی دکمه‌ای که در این کامپوننت قرار دارد، پیامی را به کامپوننت والد ارسال کند.
با توجه به تعریف { onClick }، در همان لحظه، خطای any بودن نوع آن از طرف TypeScript گزارش داده می‌شود. بنابراین نوع جدید Props را ایجاد کرده و برای onClick، نوع متناسبی را تعریف می‌کنیم. در اینجا 4 روش مختلف تعریف نوع function را در TypeScript مشاهده می‌کنید؛ دو حالت آن با ذکر پرانتزها و درج امضای متد انجام شده و دو حالت دیگر به کمک arrow functions پیاده سازی شده‌اند.
برای نمونه آخرین حالت تعریف شده از روش arrow functions استفاده می‌کند که متداول‌ترین روش تعریف نوع توابع است (چون عنوان می‌کند که نوع onClick، یک تابع است و آن‌را شبیه به یک متد معمولی نمایش نمی‌دهد) که در آن در ابتدا امضای پارامترهای این تابع مشخص شده‌اند و در ادامه پس از <=، نوع خروجی این تابع تعریف شده‌است که void می‌باشد (این تابع چیزی را بر نمی‌گرداند).

در آخر، تعریف المان آن‌را به صورت زیر به فایل src\App.tsx اضافه می‌کنیم که onClick آن یک مقدار را دریافت کرده و سپس آن‌را در کنسول نمایش می‌دهد.
البته خروجی از نوع void، در اینجا بسیار معمول است؛ چون هدف از این نوع توابع بیشتر ارسال مقادیری به کامپوننت در برگیرنده‌ی آن‌ها است (مانند value در اینجا) و اگر برای مثال خروجی رشته‌ای را داشته باشند، باید در حین درج و تعریف المان آن‌ها، برای نمونه یک "return "value1 را هم در انتهای کار قرار داد که عملا استفاده‌ای ندارد و بی‌معنا است:
import { Button } from "./components/Button";
import { Head } from "./components/Head";
// ...

function App() {
  return (
    <div className="App">
      <Head title="Hello" />
      <Button
        onClick={(value) => {
          console.log(value);
        }}
      />
  // ...
مطالب
بررسی چند کتابخانه آپلود با پشتیبانی از DragDrop
برای یکی از پروژه‌ها نیاز به یک آپلودر داشتم که قابلیت  Drag&Drop را نیز داشته باشد و در ضمن پیاده سازی آسانی هم داشته باشد. در این بین به تعدادی از کتابخانه‌های جی کوئری می‌پردازیم.
FileDrop
اولین کتابخانه‌ای که با آن آشنا شدم و از آن استفاده کردم، کتابخانه‌ی FileDrop است که بسیار ساده و در عین حال قابلیت‌های خوبی را می‌دهد و از فناوری Filereader  (+) در Html5 برای اینکار استفاده می‌کند. مرورگرهای کروم، فایرفاکس 3.6 به بعد، IE10 به بعد و Opera 12 به بعد از آن پشتیبانی می‌کنند.
فایل‌های مورد نیاز را از اینجا دانلود کنید . فایل اسکریت آن را ابتدا صدا بزنید:
 <script src="~/scripts/jquery.filedrop.js" type="text/javascript"></script>
سپس المان‌های زیر را به کد HTML خود اضافه کنید:
 <div id="dropZone">فایل برنامه را به داخل این کادر بکشانید</div>
        <br>
        فایل یا فایل‌های آپلود شده:
        <ul id="uploadResult"></ul>
تگ اول، محلی است که فایل‌ها به سمت آن درگ و روی آن دراپ می‌شوند که از این به بعد به آن محل آپلود می‌گوییم. المان بعدی جهت گزارش فایل‌هایی است که آپلود شده‌اند. با آپلود شدن هر تعداد فایل، اسم آن به لیست اضافه می‌گردد.
کدهای css زیر را هم به صفحه اضافه کنید تا محل آپلود زیباتر شود:
        .files {
            min-height: 42px;
            background: #CCC none repeat scroll 0% 0%;
            border-top: 1px solid #FFF;
            margin: 11px 0px;
            padding: 11px 13px;
            border-radius: 6px;
        }

        #dropZone.mouse-over {
            background-color: #1d4257;
        }

کد جی کوئری زیر را به صفحه اضافه کنید:
      $('#dropZone').filedrop({
                url: uploadAddress,
                paramname: 'files',
                maxFiles: 1,
                dragOver: function() {
                    $('#dropZone').addClass('mouse-over');
                },
                dragLeave: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                drop: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                afterAll: function() {
                    $('#dropZone').html('آپلود با موفقیت انجام شد');
                },
                uploadFinished: function(i, file, response, time) {
                    $('#uploadResult').append('<li>' + file.name + '</li>');
                }
            });
ابتدا پلاگین جی کوئری را روی تگ مربوطه اعمال می‌کنیم و سپس پارامترها را با مشخصات زیر اعمال می‌کنیم:
Url  آدرسی که قرار است فایل‌ها به آن سمت ارسال شوند. 
 Paramname  در سمت سرور باید فایل‌ها را با استفاده از این نام پارامتر دریافت  کنید.
 maxFiles  تعداد فایلهایی که میتوان با درگ و دراپ کردن روی آن به دست آورد. در بالا به یک فایل محدود شده است.
 dragOver  این رویداد زمانی اجرا خواهد شد که اشاره گر با حالت درگ کرده فایل‌ها را به محل آپلود آورده است.
 dragLeave  موقعی که ماوس از محل آپلود خارج می‌شود
 drop  موقعی که شما فایل‌ها را روی محل  آپلود رها می‌کنید.
 afterAll  بعد از اینکه همه کارها تمام شد اجرا می‌شود.(آخرین رویداد)
 uploadFinished  کار آپلود به پایان رسیده است. در مثال بالا پس از پایان آپلود، نام فایل آپلود شده را به کاربر نشان داده‌ایم.

نحوه‌ی دریافت آن در سمت سرور, در یک اکشن متد به صورت زیر است:

[HttpPost]
        public virtual ActionResult UpdateApp(IEnumerable<HttpPostedFileBase>files)
        {
            foreach (HttpPostedFileBase file in files)
            {
                string filePath = Path.Combine(TempPath, file.FileName);
                file.SaveAs(filePath);
            }

            return Json(new {state = "success", message = "با موفقیت عملیات ارسال فایل انجام شد"}, JsonRequestBehavior.AllowGet);
        }

در اکشن متد بالا ما فایل‌ها را از طریق نام پارامتر files که مشخص کرده بودیم، به عنوان یک لیست شمارشی دریافت می‌کنیم.  کدها بالا برای ساده‌ترین راه اندازی ممکن کفایت می‌کنند.

این موارد از اصلی‌ترین‌ها هستند که به کار می‌آیند. به غیر این‌ها یک سری خصوصیات اضافه‌تری هم برای آن وجود دارد.


fallback_id 
 اگر دوست دارید این آپلودر را نیر به یک آپلودر معمولی اتصال دهید از این شناسه استفاده کنید.
 withCredentials   با استفاده از کوکی‌ها یک درخواست cross-origin ایجاد می‌کند.
 data  اگر دوست دارید به همراه فایل‌ها اطلاعات دیگری هم به همراه آن ارسال و پست شوند از این طریق اقدام نمایید. می‌تواند در قالب یک متغیر باشد یا خروجی یک تابع.
data: {
        param1: 'value1',           
        param2: function(){
            return calculated_data; 
        }
 headers برای ارسال مقدار اضافه‌تر در هدر درخواست به کار میرود و صدا زدن آن همانند کد data می‌باشد. 
 error   در صورتیکه در فرایند آپلود خطایی رخ دهد، اجرا می‌گردد. نحوه‌ی کدنویسی آن و بررسی خطاهای آن به شرح زیر است:
error: function(err, file) {
        switch(err) {
            case 'BrowserNotSupported':
                alert('مرورگر از این فناوری پشتیبانی نمی‌کند')
                break;
            case 'TooManyFiles':
                // قصد آپلود همزمان فایل‌های بیشتری از حد مجاز تعیین شده دارید
                break;
            case 'FileTooLarge':
                //حداقل حجم یکی از فایل‌ها از حجم مجاز تعیین شده بیشتر است
                //برای دسترسی به نام آن فایل از کد زیر استفاده کنید
              //file.name
                break;
            case 'FileTypeNotAllowed':
                // نوع حداقل یکی از فایل‌ها با نوع‌ها مشخص شده ما یکی نیست
                break;
            case 'FileExtensionNotAllowed':
                // پسوند حداقل یکی از فایل‌ها مورد تایید نیست
                break;
            default:
                break;
        }
    }
 allowedfiletypes   نوع فایل‌های مجاز را تعیین می‌کند:
allowedfiletypes: 
['image/jpeg','image/png','image/gif']
 allowedfileextensions   پسوند فایل هایی که برای آپلود مجاز هستند را معرفی می‌کند.
allowedfileextensions:
 ['.apk','.jar']
 maxfilesize   حداکثر حجم مجاز برای هر فایل که به مگابایت بیان می‌شود.
 docOver   این رویداد زمانی اجرا می‌شود که فایل‌های درگ شده شما وارد محیط یا پنجره مرورگر می‌شود.
 uploadStarted 
 این رویداد زمانی اجرا میگردد که فرایند آپلود هر فایل به طور جداگانه در حال آغاز شدن است:
متغیر i در کد زیر شامل اندیس فایلی است که آپلودش آغاز شده است و این اندیس از صفر آغاز می‌شود.
متغیر file دسترسی شما را به اطلاعات یک فایل باز میکند مانند نام فایل.
متغیر len تعداد فایل هایی را که کاربر در محل آپلود رها کرده است، باز میگرداند.
function(i, file, len){

    },
uploadFinished 
با اتمام آپلود هر فایل، این رویداد فراخوانی می‌گردد. دو پارامتر اول آن، همانند سابق هستند. پارامتر response خروجی json ایی را که در سمت سرور برگرداندیم، به ما باز می‌گرداند. پارامتر بعدی، زمانی را که برای آپلود طول کشیده است، بر می‌گرداند.
 function(i, file, response, time) {
    }
progressUpdated 
این رویداد برای نمایش پیشرفت یک آپلود مناسب است که آخرین پارامتر آن یک عدد صحیح از پیشرفت فایل را بر می‌گرداند.
function(i, file, progress) {
    },
globalProgressUpdated 
این رویداد میزان پیشرفت کلیه فایل‌ها را به درصد باز می‌گرداند:
function(progress) {
        $('#progress div')
.width(progress+"%"); }
speedUpdated 
سرعت آپلود هر فایل را با کیلوبیت بر ثانیه مشخص می‌کند.
function(i, file, speed) {
    }
rename
در صورتی که قصد تغییر نام فایل ارسالی را دارید می‌توانید از این رویداد استفاده کنید. پارامتر name، نام اصلی فایل را بر می‌گرداند که می‌توانید آن را دستکاری کنید و نام جدیدی را به عنوان خروجی برگردانید. نمونه کاربردی از این رویداد
 rename: function(name) {
    }

beforeEach 
این رویداد قبل از آپلود هر فایل آغاز می‌گردد و برگرداندن مقدار false در آن باعث جلوگیری و کنسل شدن آپلود آن فایل می‌گردد.
function(file) {
    }

beforeSend 
پارامترهای اولی تکراری هستند ولی آخرین پارامتر یک تابع done را می‌توان به آن پاس کرد که قبل از اجرای کل عملیات آپلود صدا زده می‌شود.
 function(file, i, done) {
    }
رویدادی به اسم queuefiles هم هست تعداد فایل‌هایی را که میتوانند به طور موازی و همزمان آپلود گردند، مشخص می‌کند. ولی دراین حالت maxfiles مورد استفاده قرار نمی‌گیرد. جهت بررسی یک مثال عملی و همچنین کدهای سمت سرور در PHP میتوانید از این آموزش استفاده کنید.
با تستی که به صورت لوکال رو آن انجام دادم به نظر نمی‌رسد برای فایل‌های با حجم متوسط به بالا مناسب باشد و برای فایل‌های با حجم کم مناسب می‌باشد. یک فایل 8 مگابایتی در حالت لوکال 9 ثانیه آپلود آن زمان برد و برای فایل‌های بزرگتر، فایرفاکس دیالوگ Stop Script را نشان داد.

PlUpload
این کتابخانه متن باز هم بسیار کارآمد و ساده و قابل انعطاف است و مثالهای آماده زیادی دارد. سایت سابسن هم در بخش آپلود زیرنویس‌ها از این کتابخانه استفاده می‌کند. از آنجا که آموزش این کتابخانه در سایت جاری آمده است از ذکر نکات بیشتر در مورد آن خودداری می‌نماییم.

Bootstrap FileStyle
اگر از قالب بوت استراپ استفاده می‌کنید و دوست دارید روی المان input file  قدیمی، ولی به شکلی مدرن کار کنید این کتابخانه هم فراموش نشود.

DropZoneJS
این کتابخانه به نسبت DropFile امکانات بیشتری را دارد و در سایت اختصاصی آن مثال‌ها و مستندات خوبی قرار گرفته است. در ساده‌ترین حالت آن ابتدا فایل کتابخانه  را صدا زده و سپس تگ فرم را به آن نسبت دهید:
<script src="https://rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script>

<form action="/upload-target" class="dropzone"></form>
ولی اگر بخواهید آن را به سمت سرور ارسال کنید و  از آنجا آن را کنترل کنید، کد فرم را به شکل زیر تغییر دهید:
ابتدا بسته‌ی نیوگت آن را صدا بزنید:
Install-Package dropzone

با نصب این کتابخانه یک سری فایل CSS هم به سیستم اضافه می‌شود که می‌توانید برای استایل دهی هر چه بیشتر از آن بهره ببرید. کد فرم را به شکل زیر تغییر دهید:
    <form action="~/Home/SaveUploadedFile" method="post" enctype="multipart/form-data" class="dropzone" id="dropzoneForm" style="width: 50px; background: none; border: none;">
        <div class="fallback">
            <input name="file" type="file" multiple />
            <input type="submit" value="Upload" />
        </div>
    </form>
تگی که با کلاس fallback مزین شده است موقعی به کار می‌آید که مرورگر از این کتابخانه پشتیبانی نکرده و مجبور به نمایش یک آپلود معمولی می‌شویم.
با استفاده از کدنویسی هم می‌توان یک المان را به یک آپلودر تبدیل کرد:
var myDropzone = new Dropzone("div#myId", { url: "/file/post"});

//============ OR ====================
$("div#myId").dropzone({ url: "/file/post" });
همانطور که می‌بینید الزامی برای اینکه از یک تگ فرم استفاده کنید ندارد.
برای کانفیگ آپلودرهایی که از طریف المانهای Html ایجاد می‌شوند، می‌توان از کد زیر استفاده کرد و یک تنظیم عمومی برای تمامی آپلودرهای html آن صفحه ایجاد کرد.
Dropzone.options.myId= {
  paramName: "file", //نام پارامتری که فایل از طریق آن انتقال می‌بابد
  maxFilesize: 2, // MB
  accept: function(file, done) {
    if (file.name == "justinbieber.jpg") {
      done("Naha, you don't.");
    }
    else { done(); }
  }
};
تابع بالا یک آرگومان از نوع file را برگردانده و اگر این تابع، تابع done را با پارامتری رشته‌ای صدا بزند، عملیات آپلود آن فایل کنسل شده و پیام خطایی را نمایش می‌دهد و در صورتیکه بدون پارامتر صدا زده شود، عمل آپلود بدون مشکل انجام می‌شود.
ازآنجا که این کتابخانه از تنظیمات وسیعی استفاده می‌کند و از حوصله‌ی این مقاله خارج است، بهتر هست که صفحه‌ی مستندات آن را که کامل هم هست، مطالعه بفرمایید. از سری قابلیت‌هایی که پشتیبانی می‌کند: موارد پوشش داده شده در FileDrop، ساخت layout، ایجاد صف، متد حذف و اضافه و از این قبیل، ایجاد تصویر تمبر مانند و ...

یک نکته تکمیلی در مورد آپلود: در ASP.net به طور پیش فرض نهایت حجم فایل آپلودی 4 مگابایتی تعیین شده است که میتوانید آن را از طریق web.config تغییر دهید:
<configuration>
    <system.web>
        <httpRuntime maxRequestLength="1048576" />
    </system.web>
</configuration>
برای IIS 7 به بعد هم از تکه کد زیر استفاده کنید:
<system.webServer>
   <security>
      <requestFiltering>
         <requestLimits maxAllowedContentLength="1073741824" />
      </requestFiltering>
   </security>
 </system.webServer>
در هر دو کد بالا نهایت حجم بر روی یک گیگابایت تعیین شده است که maxRequestLength به صورت کیلوبایت و maxAllowContentLength به صورت بایت تعیین شده است. توصیه می‌شود هر دو شکل آن را وارد کنید. به خصوص که IIS Express از کد ابتدایی استفاده می‌کند و بخواهید نتیجه‌ی آن را در تست‌ها ببینید.

مطالب
با HttpHandler بیشتر آشنا شوید
در  مقاله قبل توضیح دادیم که وظیفه httphandler رندر و پردازش خروجی یک درخواست هست؛ حالا در این مقاله قصد داریم که مفهوم httphandler را بیشتر بررسی کنیم.

HttpHandler
برای تهیه‌ی یک httphandler، باید کلاسی را بر اساس اینترفیس IHttpHandler پیاده سازی کنیم و بعدا آن را در web.config برنامه معرفی کنیم. برای پیاده سازی این اینترفیس، به یک متد به اسم ProcessRequest با یک پارامتر از نوع HttpContext و یک پراپرتی به اسم IsReusable نیاز داریم که مقدار برگشتی این پراپرتی را false بگذارید؛ بعدا خواهم گفت چرا اینکار را می‌کنیم. نحوه‌ی پیاده‌سازی یک httphandler به شکل زیر است:
public class MyHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {        
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
با استفاده از شیء context می‌توان به دو شیء httpresponse و httprequest دسترسی داشت. تکه کد زیر مثالی است در مورد نحوه‌ی تغییر در محتوای سایت:
public class MyHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        HttpResponse response = context.Response;
        HttpRequest request = context.Request;

        response.Write("Every Page has a some text like this");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
بگذارید همین کد ساده را در وب کانفیگ معرفی کنیم:
<system.web>
  <httpHandlers>
      <add verb="*" path="*.aspx" type="MyHttpHandler"/>
  </httpHandlers>
</system.web>
اگر نسخه IIS شما همانند نسخه‌ی من باشد، نباید هیچ تغییری مشاهده کنید؛ زیرا کد بالا فقط در مورد نسخه‌ی IIS6 صدق می‌کند و برای نسخه‌های IIS 7 به بعد باید به شیوه زیر عمل کنید:
<configuration>
  <system.web>
    <httpHandlers>

  <add name="myhttphandler" verb="*" path="*.aspx" type="MyHttpHandler"/>

    </httpHandlers>
  </system.web>
</configuration>
خروجی نهایی باید تنها این متن باشد: Every Page has a some text like this 
گزینه Type که نام کلاس می‌باشد و اگر کلاس داخل یک فضای نام قرار گرفته باشد، باید اینطور نوشت : namespace.ClassName  
گزینه verb شامل مقادیری چون Get,Post,Head,Putو Delete می‌باشد و httphandler را فقط برای این نوع درخواست‌ها اجرا می‌کند و در صورتیکه بخواهید چندتا از آن‌ها را استفاده کنید، با , از هم جدا می‌شوند. مثلا Get,post و درصورتیکه همه‌ی گزینه‌ها را بخواهید علامت * را میتوان استفاده کرد. 
 گزینه‌ی path این امکان را به شما می‌دهد که مسیر و نوع فایل‌هایی را که قصد دارید روی آن‌ها فقط اجرا شود، مشخص کنید و ما در قطعه کد بالا گفته‌ایم که تنها روی فایل‌هایی با پسوند aspx اجرا شود و چون مسیری هم ذکر نکردیم برای همه‌ی مسیرها قابل اجراست. یکی از مزیت‌های دادن پسوند این است که می‌توانید پسوندهای اختصاصی داشته باشید. مثلا پسوند RSS برای فیدهای وب سایتتان. بسیاری از برنامه نویسان به جای استفاده از صفحات aspx از ashx استفاده می‌کنند که به مراتب سبک‌تر از aspx هست و شامل بخش ui نمی‌شود و نتیجه خروجی آن بر اساس کدی که می‌نویسید مشخص می‌شود که میتواند صفحه متنی یا عکس یا xml یا ... باشد. در اینجا در مورد ساخت صفحات ashx توضیح داده شده است. 

  IHttpHandlerFactory
کار این اینترفیس پیاده سازی یک کلاس است که خروجی آن یک کلاس از نوع IHttpHandler هست. اگر دقت کنید در مثال‌های قبلی ما برای معرفی یک هندلر در وب کانفیگ یک سری path را به آن میدادیم و برای نمونه aspx.* را معرفی می‌کردیم؛ یعنی این هندلر را بر روی همه‌ی فایل‌های aspx اجرا کن و اگر دو یا چند هندلر در وب کانفیگ معرفی کنیم و برای همه مسیر aspx را قرار بدهیم، یعنی همه این هندلرها باید روی صفحات aspx اجرا گردند ولی در httphandlerfactory، ما چند هندلر داریم و میخواهیم فقط یکی از آن‌ها بر روی صفحات aspx انجام بگیرد، پس ما یک هندلرفکتوری را برای صفحات aspx معرفی می‌کنیم و در حین اجرا تصمیم می‌گیریم که کدام هندلر را ارسال کنیم.
اجازه بدهید نوشتن این نوع کلاس را آغاز کنیم،ابتدا دو هندلر به نام‌های httphandler1 و httphandler2 می‌نویسیم :
public class MyHttpHandler1 :IHttpHandler
{
   
    public void ProcessRequest(HttpContext context)
    {
        HttpResponse response = context.Response;
        response.Write("this is httphandler1");
    }

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

public class MyHttpHandler2 : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        HttpResponse response = context.Response;
        response.Write("this is httphandler2");
    }

    public bool IsReusable
    {
        get { return false; }
    }
}
سپس کلاس MyFactory را بر اساس اینترفیس IHttpFactory پیاده سازی می‌کنیم و باید دو متد برای آن صدا بزنیم؛ یکی که هندلر انتخابی را بر میگرداند و دیگری هم برای رها کردن یا آزادسازی یک هندلر هست که در این مقاله کاری با آن نداریم. عموما GC دات نت در این زمینه کارآیی خوبی دارد. در قسمت هندلرهای غیرهمزمان به طور مختصر خواهیم گفت که GC چطور آن‌ها را مدیریت می‌کند. کد زیر نمونه کلاسی است که توسط IHttpFactory پیاده سازی شده است:
public class MyFactory : IHttpHandlerFactory
{
    public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTrasnlated)
    {
        
    }

    public void ReleaseHandler(IHttpHandler handler)
    {
        
    }
}
در متد GetHandler چهار آرگومان وجود دارند که به ترتیب برای موارد زیر به کار می‌روند:
 Context یک شی از کلاس httpcontext که دسترسی ما را برای اشیاء سروری چون response,request,session و... فراهم می‌کند.
 RequestType  مشخص می‌کند که درخواست صفحه به چه صورتی است. این گزینه برای مواردی است که verb بیش از یک مورد را حمایت می‌کند. برای مثال دوست دارید یک هندلر را برای درخواست‌های Get ارسال کنید و هندلر دیگر را برای درخواست‌های نوع Post
 URL مسیر مجازی virtual Path صفحه صدا زده شده 
 PathTranslated مسیر فیزیکی صفحه درخواست کننده را ارسال می‌کند. 
متد GetHandler را بدین شکل می‌نویسیم و میخواهیم همه صفحات aspx هندلر شماره یک را انتخاب کنند و صفحات aspx که نامشان با t شروع می‌شوند، هندلر  شماره دو را انتخاب کند:
 public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTrasnlated)
    {
        string handlername = "MyHttpHandler1";
        if(url.Substring(url.LastIndexOf("/")+1).StartsWith("t"))
        {
            handlername = "MyHttpHandler2";
        }

        try
        {
            return (IHttpHandler) Activator.CreateInstance(Type.GetType(handlername));
        }
        catch (Exception e) 
        {
            throw new HttpException("Error: " + handlername, e);
        }
    }

    public void ReleaseHandler(IHttpHandler handler)
    {
        
    }
}
شی Activator که برای ساخت اشیاء با انتخاب بهترین constructor موجود بر اساس یک نوع Type مشخص به کار می‌رود و خروجی Object را می‌گرداند؛ با یک تبدیل ساده، خروجی به قالب اصلی خود باز میگردد. برای مطالعه بیشتر در مورد این کلاس به اینجا و اینجا مراجعه کنید.
نحوه‌ی تعریف factory در وب کانفیگ مانند قبل است و فقط باید در Type به جای نام هندلر نام فکتوری را نوشت. برنامه را اجرا کنید تا نتیجه آن را ببینیم:
تصویر زیر نتیجه صدا زده شدن فایل default.aspx است:

تصویر زیر نتیجه صدا زده شدن فایل Tours_List.aspx است:

AsyncHttpHandlers
برای اینکه کار این اینترفیس را درک کنید بهتر هست اینجا را مطالعه کنید. در اینجا به خوبی تفاوت متدهای همزمان و غیرهمزمان توضیح داده شده است.
متن زیر خلاصه‌ترین و بهترین توضیح برای این پرسش است، چرا غیرهمزمان؟
در اعمالی که disk I/O و یا network I/O دارند، پردازش موازی و اعمال async به شدت مقیاس پذیری سیستم را بالا می‌برند. به این ترتیب worker thread جاری (که تعداد آن‌ها محدود است)، سریعتر آزاد شده و به worker pool بازگشت داده می‌شود تا بتواند به یک درخواست دیگر رسیده سرویس دهد. در این حالت می‌توان با منابع کمتری، درخواست‌های بیشتری را پردازش کرد. 
موقعی که اینترفیس IHttpAsyncHandler را ارث بری کنید (این اینترفیس نیز از IHttpHandler ارث بری کرده است و دو متد اضافه‌تر دارد)، باید دو متد دیگر را نیز پیاده سازی کنید:
 public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object obj)
    {
        
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        
    }
پراپرتی ISResuable هم موقعی که true برگشت بدهد، باعث می‌شود pooling فعال شده و این هندلر در حافظه باقی بماند و تمامی درخواست‌ها از طریق همین یک نمونه اجرا شوند.
به زبان ساده‌تر، این پراپرتی می‌گوید اگر چندین درخواست از طرف کلاینت‌ها برسد، توسط یک نمونه یا instance از هندلر پردازش خواهند شد؛ چون به طور پیش فرض موقعی که تمام درخواست‌های از pipeline بگذرند، هندلر‌ها توسط httpapplication در یک لیست بازیافت قرار گرفته و همه‌ی آن‌ها با null مقداردهی می‌شوند تا از حافظه پاک شوند ولی اگر این پراپرتی true برگرداند، هندلر مربوطه نال نشده و برای پاسخگویی به درخواست‌های بعدی در حافظه خواهد ماند.
مهمترین مزیت این گزینه، این می‌باشد که کاآیی سیستم را بالا می‌برد و اشیا کمتری به GC پاس می‌شوند. ولی یک عیب هم دارد که این تردهایی که ایجاد می‌کند، امنیت کمتری دارند و باید توسط برنامه نویس این امنیت بالاتر رود. این پراپرتی را در مواقعی که با هندلرهای همزمان کار می‌کنید برابر با false بگذارید چون این گزینه بیشتر بر روی هندلرهای غیرهمزمان اثر دارد و هم اینکه بعضی‌ها توصیه می‌کنند که false بگذارید چون GC مدیریت خوبی در مورد هندلرها دارد و هم این که ارزش یافتن باگ در کد را ندارد.
بر میگردیم سراغ کد نویسی هندلر غیر همزمان. در آخرین قطعه کد نوشته شده، ما دو متد دیگر را پیاده سازی کردیم که یکی از آن‌ها BeginProcessRequest  است و خروجی آن کلاسی است که از اینترفیس IAsyncResult  ارث بری کرده است. پس یک کلاس با ارث بری از این اینترفیس می‌نویسیم و در این کلاس نیاز است که 4 پراپرتی را پیاده سازی کنیم که این کلاس به شکل زیر در خواهد آمد:
public class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }
}
متدهای private اجباری نیستند؛ ولی برای ذخیره مقادیر get و set نیاز است. همانطور که از اسامی آن‌ها پیداست مشخص است که برای چه کاری ساخته شده اند.
خب اجازه بدهید یک تابع سازنده به آن برای مقداردهی اولیه این متغیرهای خصوصی داشته باشیم:
   public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }
همانطور که می‌بینید موارد موجود در متد BeginProcessRequest را تحویل می‌گیریم تا اطلاعات درخواستی مربوطه را داشته باشیم و مقدار _Completed را هم برابر با false قرار می‌دهیم. سپس نوبت این می‌رسد که ما درخواست را در صف pool قرار دهیم. برای همین تکه کد زیر را اضافه می‌کنیم: 
   public void StartAsyncWork()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncTask),null);
    }
با اضافه شدن درخواست به صف، هر موقع درخواست‌های قبلی تمام شوند و callback خودشان را ارسال کنند، نوبت درخواست‌های جدیدتر هم میرسد. StartAsyncTask هم متدی است که وظیفه‌ی اصلی پردازش درخواست را به دوش دارد و موقعی که نوبت درخواست برسد، کدهای این متد اجرا می‌گردد که ما در اینجا مانند مثال اول روی صفحه چیزی نوشتیم:
 private void StartAsyncTask(Object workItemState)
    {

        _context.Response.Write("<p>Completion IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");

        _context.Response.Write("Hello World from Async Handler!");
        _completed = true;
        _callback(this);
    }
دو خط اول اطلاعات را چاپ کرده و در خط سوم متغیر _completed را true کرده و در آخر این درخواست را فراخوانی مجدد می‌کنیم تا بگوییم که کار این درخواست پایان یافته‌است؛ پس این درخواست را از صف بیرون بکش و درخواست بعدی را اجرا کن.
نهایتا کل این کلاس را در متد BeginProcessRequest  صدا بزنید:
context.Response.Write("<p>Begin IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
        AsynchOperation asynch = new AsynchOperation(callback, context, obj);
        asynch.StartAsyncWork();
        return asynch;
کل کد مربوطه : (توجه:کدها از داخل سایت msdn برداشته شده است و اکثر کدهای موجود در نت هم به همین قالب می‌نویسند)
public class MyHttpHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object obj)
    {
        context.Response.Write("<p>Begin IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");
        AsynchOperation asynch = new AsynchOperation(callback, context, obj);
        asynch.StartAsyncWork();
        return asynch;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        
    }
    public void ProcessRequest(HttpContext context)
    {
       throw new InvalidOperationException(); 

    }

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

public class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }

    public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }


    public void StartAsyncWork()
    {
        
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncTask),null);

    }
    private void StartAsyncTask(Object workItemState)
    {

        _context.Response.Write("<p>Completion IsThreadPoolThread is " + Thread.CurrentThread.IsThreadPoolThread + "</p>\r\n");

        _context.Response.Write("Hello World from Async Handler!");
        _completed = true;
        _callback(this);
    }

آشنایی با فایل ASHX
در مطالب بالاتر به فایل‌های Ashx اشاره کردیم. این فایل به نام Generic Web Handler شناخته می‌شوند و می‌توانید با Add New Item این نوع فایل‌ها را اضافه کنید. این فایل شامل هیچ UI ایی نمی‌باشد و فقط شامل بخش کد می‌باشد. برای همین نسبت به aspx سبک‌تر بوده و شامل یک directive به اسم  WebHandler@ است.
مایکروسافت در MSDN نوشته است که httphandler‌ها در واقع فرآیندهایی هستند (به این فرایندها بیشتر End Point می‌گویند) که در پاسخ به درخواست‌های رسیده شده توسط asp.net application اجرا می‌شوند و بیشترین درخواست هایی هم که می‌رسد از نوع صفحات Aspx می‌باشد و موقعی که کاربری درخواست صفحه‌ی aspx می‌کند هندلرهای مربوط به page اجرا می‌شوند.
در متن بالا به خوبی روشن هست که ashx به دلیل نداشتن UI، تعداد کمتری از handlerها را در مسیر Pipeline قرار می‌دهند و اجرای آن‌ها سریعتر است. غیر از این دو هندلر aspx و ashx، هندلر توکار دیگری چون asmx که مختص وب سرویس هست و axd مربوط به اعمال trace نیز وجود دارند.

در این لینک که در بالاتر هم درج شده بود یک نمونه هندلر برای نمایش تصویر نوشته است. اگر تصاویرتان را بدین صورت اجرا کنید می‌توان جلوی درخواست‌های رسیده از وب سایت‌های دیگر را سد کرد. برای مثال یک نفر مطالب شما را کپی می‌کند و در داخل وبلاگ یا وب سایتش می‌گذارد و شما در اینجا درخواست‌های رسیده خارج از وب سایت خود را لغو خواهید کرد و تصاویر کپی شده نمایش داده نخواهند شد.
مطالب
توزیع پروژه‌های ASP.NET MVC بدون ارائه فایل‌های View آن
پروژه دیگری از آقای David Ebbo (عضو تیم ASP.NET که پیشتر با پروژه T4 MVC آن در این سایت آشنا شده‌اید)، جهت کامپایل کامل فایل‌های View و ارائه پروژه نهایی ASP.NET MVC بدون نیاز به ارائه پوشه Views آن به نام Razor Generator وجود دارد که در ادامه خلاصه‌ای از نحوه استفاده از آن‌را مرور خواهیم کرد.

الف) ابتدا افزونه Razor Generator را از اینجا دریافت و نصب کنید.
ب) سپس به پروژه MVC خود بسته NuGet زیر را اضافه نمائید:
 PM> Install-Package RazorGenerator.Mvc
در این حالت باید پروژه پیش فرض، همان وب سایت MVC شما انتخاب گردد:


با اضافه کردن این بسته NuGet تغییرات زیر به پروژه جاری اعمال خواهند شد:
  - ارجاعی به اسمبلی RazorGenerator.Mvc.dll به پروژه اضافه خواهد شد.
  - در پوشه App_Start، فایلی به نام RazorGeneratorMvcStart.cs اضافه گردیده و کار تنظیم موتور View مخصوص کار با Viewهای کامپایل شده را انجام می‌دهد.

ج) پس از نصب بسته NuGet یاد شده در همان خط فرمان پاورشل نوگت دستور زیر را صادر کنید:
 PM> Enable-RazorGenerator
و ... همین!

پس از انجام اینکار، دو کار صورت خواهد گرفت:
  - برای تمام Viewهای برنامه، فایل cs متناظری تولید می‌شود که ذیل فایل‌های View قابل مشاهده است.
  - گزینه Custom tool این Viewها نیز به RazorGenerator تنظیم می‌شود.


بدیهی است اگر از دستور Enable-RazorGenerator استفاده نکنید، نیاز خواهید داشت تا تنظیم گزینه Custom tool به RazorGenerator کلیه Viewها را دستی انجام داده و اگر فایل cs متناظر با View تولید نشد روی فایل view کلیک راست کرده و گزینه run custom tool را انتخاب کنید. اما دستور Enable-RazorGenerator کار را بسیار ساده می‌کند.

مزایا:
  - در عمل موتور ASP.NET همین کارها را در زمان اولین بار اجرای Viewها(ی کامپایل نشده) در پشت صحنه انجام می‌دهد. بنابراین با این روش زمان آغاز برنامه سریعتر می‌شود.
  - دیگر نیازی به توزیع فایل‌های cshtml نخواهید داشت.
  - خطایابی Viewها نیز ساده‌تر می‌شود. از این جهت که کامپایل آن‌ها به زمان اجرا موکول نخواهد شد.

یک سری قابلیت‌های دیگر نیز به همراه این پروژه است مانند انتقال Viewها به یک اسمبلی دیگر و یا استفاده از MSBuild برای انجام عملیات که می‌توانید آن‌ها را در Wiki پروژه Razor Generator مطالعه کنید. انتقال Viewها به یک اسمبلی دیگر هرچند در این روش کاملا ممکن شده و کار می‌کند اما صفحه dialog افزودن یک view جدید مهیا در کلیک راست بر روی اکشن متدهای یک کنترلر را غیرفعال می‌کند که در عمل آنچنان جالب نیست.


یک نکته مهم:
اگر در آینده بسته NuGet و افزونه یاد شده را به روز کردید نیاز است دستور زیر را اجرا کنید:
 PM> Redo-RazorGenerator
به این ترتیب بر اساس ساختار و کدهای جدید RazorGenerator، کلیه فایل‌های cs تولید شده مجددا به روز و تولید خواهند شد.


فایل‌های Helper قرار گرفته در پوشه App_Code

اگر HTML Helperهای خود را توسط فایل‌های Razor قرار گرفته در پوشه App_Code تولید می‌کنید، پس از اجرای دستور Enable-RazorGenerator، برای این موارد نیز فایل‌های cs متناظری تولید می‌شود. با این تفاوت که Build Action آن‌ها بر روی Compile قرار ندارند که این مورد را باید دستی تنظیم کنید. همچنین حین استفاده از این توابع کمکی نیاز است فضای نام مرتبط را نیز در ابتدای فایل View خود ذکر کنید مثلا:
 @using MvcViewGenTest2.app_code
البته با استفاده از Razor Generaor دیگر نیازی به استفاده از پوشه App_Code نخواهد بود؛ از این جهت که کار کامپایل خودکار، به زمان اجرا موکول نخواهد شد. بنابراین اینبار در هر جایی که علاقمند بودید می‌توانید این فایل‌های کمکی را تولید و کامپایل کنید. فقط ذکر فضای نام مرتبط را در ابتدای View خود فراموش نکنید.


حذف فایل RazorGeneratorMvcStart.cs
 اگر علاقمند به استفاده از فایل پیش فرض RazorGeneratorMvcStart.cs نیستید و می‌خواهید موتورهای View اضافی را حذف کنید، ابتدا فایل RazorGeneratorMvcStart.cs را حذف کرده و سپس در فایل global.asax.cs تغییر زیر را اعمال نمائید:
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.WebPages;
using RazorGenerator.Mvc;

namespace MvcViewGenTest2
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            // Adding PrecompiledMvcEngine
            var engine = new PrecompiledMvcEngine(typeof(MvcApplication).Assembly);
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(engine);
            VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
        }
    }
}
مطالب دوره‌ها
نکاتی درباره برنامه نویسی دستوری(امری)
در این فصل نکاتی را درباره برنامه نویسی دستوری در #F فرا خواهیم گرفت. برای شروع از mutale خواهیم گفت.

mutable
Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک می‌گیریم و برای تغییر مقادیر شناسه‌ها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? "

#2 printfn "%s" phrase

#3 phrase <- "yes, it can."

#4 printfn "%s" phrase


در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change?  
yes, it can.

نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change?  "

#3 phrase <- 1

اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمی‌شود)
Prog.fs(9,10): error: FS0001: This expression has type
int
but is here used with type
string
نکته دوم :ابتدا به مثال زیر توجه کنید.
let redefineX() =
let x = "One"
printfn "Redefining:\r\nx = %s" x
if true then
   let x = "Two"
printfn "x = %s" x
printfn "x = %s" x

در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ می‌کنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده می‌کنیم.
Redefining:
x = One
x = Two
x = One
همان طور که میبینید شناسه دوم x بعد از تعریف دارای مقدار جدید Two بود و بعد از اتمام محدوده(scope) مقدار x دوباره به One تغییر کرد.(بهتر است بگوییم منظور از دستور print x سوم اشاره به شناسه x اول برنامه است). این رفتار مورد انتظار ما در هنگام استفاده از روش تعریف مجدد شناسه هاست. حال به بررسی رفتار muatable در این حالت می‌پردازیم.
let mutableX() =
let mutable x = "One"
printfn "Mutating:\r\nx = %s" x
if true then
   x <- "Two"
printfn "x = %s" x
printfn "x = %s" x
تنها تفاوت در استفاده از mutable keyword و (->) است. خروجی مثال بالا نیز به صورت زیر خواهد بود. کاملا واضح است که مقدار شناسه x بعد از تغییر و اتمام محدوده(scope) هم چنان Two خواهد بود.
Mutating:
x = One
x = Two
x = Two

Reference Cells

روشی برای استفاده از شناسه‌ها به صورت mutable است. با این روش می‌تونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اون‌ها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسه‌ها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده می‌شود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:

 Member Or Field
Description
 Definition
(derefence operator)!
 مقدار مشخص شده را برگشت می‌دهد
 

let (!) r = r.contents 

 (Assignment operator)=: مقدار مشخص شده را تغییر می‌دهد
 

let (:=) r x = r.contents <- x 

ref operator
 یک مقدار را در یک reference cell  جدید کپسوله می‌کند
 

let ref x = { contents = x } 

Value Property
 برای عملیات get  یا set  مقدار مشخص شده
 

member x.Value = x.contents 

 contents record field
 برای عملیات get  یا set  مقدار مشخص شده

let ref x = { contents = x } 

  یک مثال:
let refVar = ref 6

refVar := 50
printfn "%d" !refVar
خروجی مثال بالا 50 خواهد بود.
let xRef : int ref = ref 10

printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)

xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
خروجی مثال بالا:
10
10
11
12 

خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده می‌کنیم که برای یک متغیر مقدار وجود نداشته باشد. option  در #F نوعی است که می‌تواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
از None زمانی استفاده می‌کنیم که option مقدار نداشته باشد و از Some  زمانی استفاده می‌کنیم که option مقدار داشته باشد.
let exists (x : int option) = 
    match x with
    | Some(x) -> true
    | None -> false
در مثال بالا ورودی تابع exists از نوع int و به صورت اختیاری تعریف شده است.(معادل با ?int یا<Nullable<int در #C) در صورتی که x مقدار داشته باشد مقدار true در غیر این صورت مقدار false را برگشت می‌دهد.

چگونگی استفاده  از option
مثال
let rec tryFindMatch pred list =
    match list with
    | head :: tail -> if pred(head)
                        then Some(head)
                        else tryFindMatch pred tail
    | [] -> None

let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است

let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک تابع به نام tryFindMatch داریم با دو پارامتر ورودی. با استفاده از الگوی Matching از عنصر ابتدا تا انتها را در لیست (پارامتر list) با مقدار پارامتر pred مقایسه می‌کنیم. اگر مقادیر برابر بودند مقدار head در غیر این صورت None(یعنی option مقدار ندارد) برگشت داده می‌شود.
یک مثال کاربردی تر
open System.IO
let openFile filename =
    try 
        let file = File.Open (filename, FileMode.Create)
        Some(file)
    with
        | ex -> eprintf "An exception occurred with message %s" ex.Message
                None
در مثال بالا از option‌ها برای بررسی وجود یا عدم وجود فایل‌های فیزیکی استفاده کردم.

Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F می‌پردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name =
   | value1 = integer-literal1
   | value2 = integer-literal2
   ...
یک مثال از تعریف:
type Color =
   | Red = 0
   | Green = 1
   | Blue = 2
نحوه استفاده
let col1 : Color = Color.Red
enums فقط از انواع داده ای sbyte, byte, int16, uint16, int32, uint32, int64, uint16, uint64, char پشتیبانی می‌کند که البته مقدار پیش فرض آن Int32 است. در صورتی که بخواهیم صریحا نوع داده ای را ذکر کنیم به صورت زیر عمل می‌شود.
type uColor =
   | Red = 0u
   | Green = 1u
   | Blue = 2u
let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)

توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاس‌ها بود می‌شود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی می‌کند.
using(var writer = new StreamWriter(filePath))
{
   
}
در #F نیز امکان استفاده از این عبارت با اندکی تفاوت وجود دارد.مثال:
let writeToFile fileName =
    use sw = new System.IO.StreamWriter(fileName : string)
    sw.Write("Hello ")
Units Of Measure
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحد‌های اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحد‌های اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص می‌کنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
یک مثال از تعریف واحد cm:
[<Measure>] type cm
مثالی از تعریف میلی لیتر:
[<Measure>] type ml = cm^3
برای استفاده از این واحد‌ها می‌تونید به روش زیر عمل کنید.
let value = 1.0<cm>
توابع تبدیل واحد ها
قدرت اصلی واحد‌های اندازه گیری  #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر می‌باشد:
[<Measure>] type g                 تعریف واحد گرم
[<Measure>] type kg               تعریف واحد کیلوگرم
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>    تعریف تابع تبدیل
یک مثال دیگر :
[<Measure>] type degC // دما بر حسب سلسیوس
[<Measure>] type degF // دما بر حسب فارنهایت

let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس

let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت
let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس

printfn "Enter a temperature in degrees Fahrenheit." 
let input = System.Console.ReadLine()
let mutable floatValue = 0.
if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود
   then 
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

خروجی مثال بالا :

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.
مطالب دوره‌ها
نگاهی به SignalR Clients
در قسمت قبل موفق به ایجاد اولین Hub خود شدیم. در ادامه، برای تکمیل برنامه نیاز است تا کلاینتی را نیز برای آن تهیه کنیم.
مصرف کنندگان یک Hub می‌توانند انواع و اقسام برنامه‌های کلاینت مانند jQuery Clients و یا حتی یک برنامه کنسول ساده باشند و همچنین Hubهای دیگر نیز قابلیت استفاده از این امکانات Hubهای موجود را دارند. تیم SignalR امکان استفاده از Hubهای آن‌را در برنامه‌های دات نت 4 به بعد، برنامه‌های WinRT، ویندوز فون 8، سیلورلایت 5، jQuery و همچنین برنامه‌های CPP نیز مهیا کرده‌اند. به علاوه گروه‌های مختلف نیز با توجه به سورس باز بودن این مجموعه، کلاینت‌های iOS Native، iOS via Mono و Android via Mono را نیز به این لیست اضافه کرده‌اند.


بررسی کلاینت‌های jQuery

با توجه به پروتکل مبتنی بر JSON سیگنال‌آر، استفاده از آن در کتابخانه‌های جاوا اسکریپتی همانند jQuery نیز به سادگی مهیا است. برای نصب آن نیاز است در کنسول پاور شل نوگت، دستور زیر را صادر کنید:
 PM> Install-Package Microsoft.AspNet.SignalR.JS
برای نمونه به solution پروژه قبل، یک برنامه وب خالی دیگر را اضافه کرده و سپس دستور فوق را بر روی آن اجرا نمائید. در این حالت فقط باید دقت داشت که فرامین بر روی کدام پروژه اجرا می‌شوند:


با استفاده از افزونه SignalR jQuery، به دو طریق می‌توان به یک Hub اتصال برقرار کرد:
الف) استفاده از فایل proxy تولید شده آن (این فایل، در زمان اجرای برنامه تولید می‌شود و یا امکان استفاده از آن به کمک ابزارهای کمکی نیز وجود دارد)
نمونه‌ای از آن‌را در قسمت قبل ملاحظه کردید؛ همان فایل تولید شده در مسیر /signalr/hubs برنامه. به نوعی به آن Service contract نیز گفته می‌شود (ارائه متادیتا و قراردادهای کار با یک سرویس Hub). این فایل همانطور که عنوان شد به صورت پویا در زمان اجرای برنامه ایجاد می‌شود.
امکان تولید آن توسط برنامه کمکی signalr.exe نیز وجود دارد؛ برای دریافت آن می‌توان از طریق NuGet اقدام کرد (بسته Microsoft.AspNet.SignalR.Utils) که نهایتا در پوشه packages قرار خواهد گرفت. نحوه استفاده از آن نیز به صورت زیر است:
 Signalr.exe ghp http://localhost/
در این دستور ghp مخفف generate hub proxy است و نهایتا فایلی را به نام server.js تولید می‌کند.

ب) بدون استفاده از فایل proxy و به کمک روش late binding (انقیاد دیر هنگام)

برای کار با یک Hub از طریق jQuery مراحل ذیل باید طی شوند:
1) ارجاعی به Hub باید مشخص شود.
2) روال‌های رخدادگردان تنظیم گردند.
3) اتصال به Hub برقرار گردد.
4) متدی فراخوانی شود.

در اینجا باید دقت داشت که امکانات Hub به صورت خواص
 $.connection
در سمت کلاینت جی‌کوئری، در دسترس خواهند بود. برای مثال:
 $.connection.chatHub
و نام‌های بکارگرفته شده در اینجا مطابق روش‌های متداول نام گذاری در جاوا اسکریپت، camel case هستند.

خوب، تا اینجا فرض بر این است که یک پروژه خالی ASP.NET را آغاز و سپس فرمان نصب Microsoft.AspNet.SignalR.JS را نیز همانطور که عنوان شد، صادر کرده‌اید. در ادامه یک فایل ساده html را به نام chat.htm، به این پروژه جدید اضافه کنید (برای استفاده از کتابخانه جاوا اسکریپتی SignalR الزامی به استفاده از صفحات کامل پروژه‌های وب نیست).
<!DOCTYPE>
<html>
<head>
    <title></title>
    <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.signalR-1.0.1.min.js" type="text/javascript"></script>
    <script src="http://localhost:1072/signalr/hubs" type="text/javascript"></script>
</head>
<body>
    <div>
        <input id="txtMsg" type="text" /><input id="send" type="button" value="send msg" />
        <ul id="messages">
        </ul>
    </div>
    <script type="text/javascript">
        $(function () {
            var chat;
            $.connection.hub.logging = true; //اطلاعات بیشتری را در جاوا اسکریپت کنسول مرورگر لاگ می‌کند
            chat = $.connection.chat; //این نام مستعار پیشتر توسط ویژگی نام هاب تنظیم شده است
            chat.client.hello = function (message) {
                //متدی که در اینجا تعریف شده دقیقا مطابق نام متد پویایی است که در هاب تعریف شده است
                //به این ترتیب سرور می‌تواند کلاینت را فراخوانی کند
                $("#messages").append("<li>" + message + "</li>");
            };
            $.connection.hub.start(/*{ transport: 'longPolling' }*/); // فاز اولیه ارتباط را آغاز می‌کند

            $("#send").click(function () {
                // Hub's `SendMessage` should be camel case here
                chat.server.sendMessage($("#txtMsg").val());
            });
        });
    </script>
</body>
</html>
کدهای آن‌را به نحو فوق تغییر دهید.
توضیحات:
همانطور که ملاحظه می‌کنید ابتدا ارجاعاتی به jquery و jquery.signalR-1.0.1.min.js اضافه شده‌اند. سپس نیاز است مسیر دقیق فایل پروکسی هاب خود را نیز مشخص کنیم. اینکار با تعریف مسیر signalr/hubs انجام شده است.
<script src="http://localhost:1072/signalr/hubs" type="text/javascript"></script>
در ادامه توسط تنظیم connection.hub.logging سبب خواهیم شد تا اطلاعات بیشتری در javascript console مرورگر لاگ شود.
سپس ارجاعی به هاب تعریف شده، تعریف گردیده است. اگر از قسمت قبل به خاطر داشته باشید، توسط ویژگی HubName، نام chat را برگزیدیم. بنابراین connection.chat ذکر شده دقیقا به این هاب اشاره می‌کند.
سپس سطر chat.client.hello مقدار دهی شده است. متد hello، متدی dynamic و تعریف شده در سمت هاب برنامه است. به این ترتیب می‌توان به پیام‌های رسیده از طرف سرور گوش فرا داد. در اینجا، این پیام‌ها، به li ایی با id مساوی messages اضافه می‌شوند.
سپس توسط فراخوانی متد connection.hub.start، فاز negotiation شروع می‌شود. در اینجا حتی می‌توان نوع transport را نیز صریحا انتخاب کرد که نمونه‌ای از آن را به صورت کامنت شده جهت آشنایی با نحوه تعریف آن مشاهده می‌کنید. مقادیر قابل استفاده در آن به شرح زیر هستند:
 - webSockets
- forverFrame
- serverSentEvents
- longPolling
سپس به رویدادهای کلیک دکمه send گوش فرا داده و در این حین، اطلاعات TextBox ایی با id مساوی txtMsg را به متد SendMessage هاب خود ارسال می‌کنیم. همانطور که پیشتر نیز عنوان شد، در سمت کلاینت، تعریف متد SendMessage باید camel case باشد.

اکنون به صورت جداگانه یکبار برنامه hub را در مرورگر باز کنید. سپس بر روی فایل chat.htm کلیک راست کرده و گزینه مشاهده آن را در مرورگر نیز انتخاب نمائید (گزینه View in browser منوی کلیک راست).

خوب! پروژه کار نمی‌کند! برای اینکه مشکلات را بهتر بتوانید مشاهده کنید نیاز است به JavaScript Console مرورگر خود مراجعه نمائید. برای مثال در مرورگر کروم دکمه F12 را فشرده و برگه Console آن‌را باز کنید. در اینجا اعلام می‌کند که فاز negotiation قابل انجام نیست؛ چون مسیر پیش فرضی را که انتخاب کرده است، همین مسیر پروژه دومی است که اضافه کرده‌ایم (کلاینت ما در پروژه دوم قرار دارد و نه در همان پروژه اول هاب).
برای اینکه مسیر دقیق hub را در این حالت مشخص کنیم، سطر زیر را به ابتدای کدهای جاوا اسکریپتی فوق اضافه نمائید:
 $.connection.hub.url = 'http://localhost:1072/signalr'; //چون در یک پروژه دیگر قرار داریم
اکنون اگر مجدا سعی کنید، باز هم برنامه کار نمی‌کند و پیام می‌دهد که امکان دسترسی به این سرویس از خارج از دومین آن میسر نیست. برای اینکه این مجوز را صادر کنیم نیاز است تنظیمات مسیریابی پروژه هاب را به نحو ذیل ویرایش نمائیم:
using System;
using System.Web;
using System.Web.Routing;
using Microsoft.AspNet.SignalR;

namespace SignalR02
{
    public class Global : HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            // Register the default hubs route: ~/signalr
            RouteTable.Routes.MapHubs(new HubConfiguration
            {
                EnableCrossDomain = true
            });
        }
    }
}
با تنظیم EnableCrossDomain به true اینبار فاز‌های آغاز ارتباط با سرور برقرار می‌شوند:
 SignalR: Auto detected cross domain url. jquery.signalR-1.0.1.min.js:10
SignalR: Negotiating with 'http://localhost:1072/signalr/negotiate'. jquery.signalR-1.0.1.min.js:10
SignalR: SignalR: Initializing long polling connection with server. jquery.signalR-1.0.1.min.js:10
SignalR: Attempting to connect to 'http://localhost:1072/signalr/connect?transport=longPolling&connectionToken…NRh72omzsPkKqhKw2&connectionData=%5B%7B%22name%22%3A%22chat%22%7D%5D&tid=3' using longPolling. jquery.signalR-1.0.1.min.js:10
SignalR: Longpolling connected jquery.signalR-1.0.1.min.js:10
مطابق این لاگ‌ها ابتدا فاز negotiation انجام می‌شود. سپس حالت long polling را به صورت خودکار انتخاب می‌کند.

در برگه شبکه، مطابق شکل فوق، امکان آنالیز اطلاعات رد و بدل شده مهیا است. برای مثال در حالتیکه سرور پیام دریافتی را به کلیه کلاینت‌ها ارسال می‌کند، نام متد و نام هاب و سایر پارامترها در اطلاعات به فرمت JSON آن به خوبی قابل مشاهده هستند.

یک نکته:
اگر از ویندوز 8 (یعنی IIS8) و VS 2012 استفاده می‌کنید، برای استفاده از حالت Web socket، ابتدا فایل وب کانفیگ برنامه را باز کرده و در قسمت httpRunTime، مقدار ویژگی targetFramework را بر روی 4.5 تنظیم کنید. اینبار اگر مراحل negotiation را بررسی کنید در همان مرحله اول برقراری اتصال، از روش Web socket استفاده گردیده است.


تمرین 1
به پروژه ساده و ابتدایی فوق یک تکست باکس دیگر به نام Room را اضافه کنید؛ به همراه دکمه join. سپس نکات قسمت قبل را در مورد الحاق به یک گروه و سپس ارسال پیام به اعضای گروه را پیاده سازی نمائید. (تمام نکات آن با مطلب فوق پوشش داده شده است و در اینجا باید صرفا فراخوانی متدهای عمومی دیگری در سمت هاب، صورت گیرد)

تمرین 2
در انتهای قسمت دوم به نحوه ارسال پیام از یک هاب به هابی دیگر اشاره شد. این MonitorHub را ایجاد کرده و همچنین یک کلاینت جاوا اسکریپتی را نیز برای آن تهیه کنید تا بتوان اتصال و قطع اتصال کلیه کاربران سیستم را مانیتور و مشاهده کرد.


پیاده سازی کلاینت jQuery بدون استفاده از کلاس Proxy

در مثال قبل، از پروکسی پویای مهیای در آدرس signalr/hubs استفاده کردیم. در اینجا قصد داریم، بدون استفاده از آن نیز کار برپایی کلاینت را بررسی کنیم.
بنابراین یک فایل جدید html را مثلا به نام chat_np.html به پروژه دوم برنامه اضافه کنید. سپس محتویات آن‌را به نحو زیر تغییر دهید:
<!DOCTYPE>
<html>
<head>
    <title></title>
    <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.signalR-1.0.1.min.js" type="text/javascript"></script>
</head>
<body>
    <div>
        <input id="txtMsg" type="text" /><input id="send" type="button" value="send msg" />
        <ul id="messages">
        </ul>
    </div>
    <script type="text/javascript">
        $(function () {                        
            $.connection.hub.logging = true; //اطلاعات بیشتری را در جاوا اسکریپت کنسول مرورگر لاگ می‌کند

            var connection = $.hubConnection();
            connection.url = 'http://localhost:1072/signalr'; //چون در یک پروژه دیگر قرار داریم
            var proxy = connection.createHubProxy('chat');

            proxy.on('hello', function (message) {
                //متدی که در اینجا تعریف شده دقیقا مطابق نام متد پویایی است که در هاب تعریف شده است
                //به این ترتیب سرور می‌تواند کلاینت را فراخوانی کند
                $("#messages").append("<li>" + message + "</li>");
            });

            $("#send").click(function () {
                // Hub's `SendMessage` should be camel case here
                proxy.invoke('sendMessage', $("#txtMsg").val());
            });

            connection.start();
        });
    </script>
</body>
</html>
در اینجا سطر مرتبط با تعریف مسیر اسکریپت‌های پویای signalr/hubs را دیگر در ابتدای فایل مشاهده نمی‌کنید. کار تشکیل proxy اینبار از طریق کدنویسی صورت گرفته است. پس از ایجاد پروکسی، برای گوش فرا دادن به متدهای فراخوانی شده از طرف سرور از متد proxy.on و نام متد فراخوانی شده سمت سرور استفاده می‌کنیم و یا برای ارسال اطلاعات به سرور از متد proxy.invoke به همراه نام متد سمت سرور استفاده خواهد شد.


کلاینت‌های دات نتی SignalR
تا کنون Solution ما حاوی یک پروژه Hub و یک پروژه وب کلاینت جی‌کوئری است. به همین Solution، یک پروژه کلاینت کنسول ویندوزی را نیز اضافه کنید.
سپس در خط فرمان پاور شل نوگت دستور زیر را صادر نمائید تا فایل‌های مورد نیاز به پروژه کنسول اضافه شوند:
 PM> Install-Package Microsoft.AspNet.SignalR.Client
در اینجا نیز باید دقت داشت تا دستور بر روی default project صحیحی اجرا شود (حالت پیش فرض، اولین پروژه موجود در solution است).
پس از نصب آن اگر به پوشه packages مراجعه کنید، نگارش‌های مختلف آن‌را مخصوص سیلورلایت، دات نت‌های 4 و 4.5، WinRT و ویندوز فون8 نیز می‌توانید در پوشه Microsoft.AspNet.SignalR.Client ملاحظه نمائید. البته در ابتدای نصب، انتخاب نگارش مناسب، بر اساس نوع پروژه جاری به صورت خودکار صورت می‌گیرد.
مدل برنامه نویسی آن نیز بسیار شبیه است به حالت عدم استفاده از پروکسی در حین استفاده از jQuery که در قسمت قبل بررسی گردید و شامل این مراحل است:
1) یک وهله از شیء HubConnection را ایجاد کنید.
2) پروکسی مورد نیاز را جهت اتصال به Hub از طریق متد CreateProxy تهیه کنید.
3) رویدادگردان‌ها را همانند نمونه کدهای جاوا اسکریپتی قسمت قبل، توسط متد On تعریف کنید.
4) به کمک متد Start، اتصال را آغاز نمائید.
5) متدها را به کمک متد Invoke فراخوانی نمائید.

using System;
using Microsoft.AspNet.SignalR.Client.Hubs;

namespace SignalR02.WinClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var hubConnection = new HubConnection(url: "http://localhost:1072/signalr");
            var chat = hubConnection.CreateHubProxy(hubName: "chat");

            chat.On<string>("hello", msg => {
                Console.WriteLine(msg);
            });

            hubConnection.Start().Wait();

            chat.Invoke<string>("sendMessage", "Hello!");

            Console.WriteLine("Press a key to terminate the client...");
            Console.Read();
        }
    }
}
نمونه‌ای از این پیاده سازی را در کدهای فوق ملاحظه می‌کنید که از لحاظ طراحی آنچنان تفاوتی با نمونه ذهنی جاوا اسکریپتی ندارد.

نکته مهم
کلیه فراخوانی‌هایی که در اینجا ملاحظه می‌کنید غیرهمزمان هستند.
به همین جهت پس از متد Start، متد Wait ذکر شده‌است تا در این برنامه ساده، پس از برقراری کامل اتصال، کار invoke صورت گیرد و یا زمانیکه callback تعریف شده توسط متد chat.On فراخوانی می‌شود نیز این فراخوانی غیرهمزمان است و خصوصا اگر نیاز است رابط کاربری برنامه را در این بین به روز کنید باید به نکات به روز رسانی رابط کاربری از طریق یک ترد دیگر دقت داشت.
مطالب
ساخت DropDownList های مرتبط به کمک jQuery Ajax در MVC
در پروژه‌های زیادی با مواردی مواجه می‌شویم که دو انتخاب داریم ، و یک انتخاب زیر مجموعه انتخاب دیگر است ، مثلا رابطه بین استان‌ها و شهر‌ها ، در انتخاب اول استان انتخاب می‌شود و مقادیر DropDownList  شهر‌ها حاوی ، شهر‌های آن استان می‌شود.
در پروژه‌های WebForm  این کار به کمک Update Panel  انجام می‌شد ، ولی در پروژه‌های MVC  ما این امکان را نداریم و خودمان باید به کمک موارد دیگر این درخواست را پیاده سازی کنیم.
در پروژه ای که فعلا مشغول آن هستم ، با این مورد مواجه شدم ، و دیدم شاید بد نباشد راهکار خودم را با شما در میان بگذارم.
صورت مسئله :
کاربری نیاز به وارد کردن اطلاعات یک شیرالات بهداشتی  (Tap) را دارد ، از DropDownList   اول نوع آن شیر (Type)  را انتخاب می‌کند ، و از DropDownList  دوم ، که شامل گروه‌های آن نوع است ، گروه(Category) مورد نظر را انتخاب می‌کند. (در انتهای همین مطلب ببینید )
ساختار Table‌های دیتابیس به این صورت است: 

رابطه ای از جدول Type به جدول Category.

به کمک Scaffolding یک کنترلر برای کلاس Tap (شیر آب) می‌سازیم ، به طور عادی در فایل Create.chtml مقدار گروه را به صورت DropDown  نمایش می‌دهد، حال ما نیاز داریم که خودمان  DropDown را برای Type  ایجاد کنیم و بعد ارتباط این‌ها را بر قرار کنیم.

تابع اولی Create را این طوری ویرایش می‌کنیم :

        public ActionResult Create()
        {
            ViewBag.Type = new SelectList(db.Types, "Id", "Title");
            ViewBag.Category = new SelectList(db.Categories, "Id", "Title");
            return View();
        }

همان طور که مشخص است ، علاوه بر مقادیر Category  که خودش ارسال می‌کند ، ما نیز مقادیر نوع‌ها را به View  مورد نظر ارسال می‌کنیم.

برای نمایش دادن هر دو DropDownList ویو مورد نظر را به این صورت ویرایش می‌کنیم :

        <div>
            نوع
        </div>
        <div>
            @Html.DropDownList("Type", (SelectList)ViewBag.Type, "-- انتخاب ---", new { id = "rdbTyoe" })
            @Html.ValidationMessageFor(model => model.Category)
        </div>

        <div>
            دسته بندی
        </div>
        <div>
            @Html.DropDownList("Category", (SelectList)ViewBag.Category, "-- انتخاب ---", new { id = "rdbCategory"})
            @Html.ValidationMessageFor(model => model.Category)
        </div>

همان طور که مشاهده می‌کنید ، در اینجا  DropDownList  مربوط به Type که خودمان سمت سرور ،مقادیر آن را پر کرده بودیم نمایش می‌دهیم.

خب شاید تا اینجای کار ، ساده بود ولی میرسیم به اصل مطلب و ارتباط بین این دو DropDownList. (قبل از این قسمت حتما نگاهی به ساختار DropDownList یا همان تگ select بیندازید ، اطلاعات جی کوئری شما در این قسمت خیلی کمک حال شما است)

برای این کار ما از jQuery  استفادی می‌کنیم ، کار به این صورت است که هنگامی که مقدار DropDownList  اول تغییر کرد :

  •  ما Id  آن را به سرور ارسال می‌کنیم.
  •  در آنجا Category  هایی که دارای Type با Id  مورد نظر هستند را جدا می‌کنیم
  • فیلد‌های مورد نیاز یعنی Id  و Title را می‌گیریم
  • و بعد به کمک Json مقادیر را بر می‌گردانیم 
  • و مقادیر ارسالی از سرور را در option‌های DropDownList  دوم (گروه‌ها ) قرار می‌دهم 
در ابتدا متدی در سرور می‌نویسیم ، کار این متد (همان طور که گفته شد) گرفتن Id از نوع Type استو برگرداندن Category‌های مورد نظر. به این صورت :
        public ActionResult SelectCategory(int id)
        {
            var categoris = db.Categories.Where(m => m.Type1.Id == id).Select(c => new { c.Id, c.Title });
            return Json(categoris, JsonRequestBehavior.AllowGet);
        }
ابتدا به کمک Where مقادیر مورد نظر را پیدا می‌کنیم و بعد به کمک Select فیلد هایی مورد استفاده را جدا می‌کنیم و به کمک Json اطلاعات را بر می‌گردانیم. توجه داشته باشید که مقادیری که در categoris  قرار می‌گیرد ، شبیه به مقادیر json  هستند و ما می‌تانیم مستیما از این مقادیر در javascript استفاده کنیم. 
در کلایت (همان طور که گفته شد ) به این نیاز داریم که هنگاهی که مقدار در لیست اول تغییر کرد ، ای دی آن را به تابع بالا بفرستیم و مقادیر بازگشتی را در  DropDownList  دوم قرار دهیم : به این صورت :
            $('#rdbTyoe').change(function () {
                jQuery.getJSON('@Url.Action("SelectCategory")', { id: $(this).attr('value') }, function (data) {
                    $('#rdbCategory').empty();
                    jQuery.each(data, function (i) {
                        var option = $('<option></option>').attr("value", data[i].Id).text(data[i].Title);
                        $("#rdbCategory").append(option);
                    });
                });
            });
توضیح کد :
با کمک تابع .change() از تغییر مقدار آگاه می‌شویم ، و حالا می‌توانیم مراحل کار را انجام دهیم. حالا وقت ارسال اطلاعات ( Id مورد گزینه انتخاب شده در تغییر مقدار ) به سرور است که ما این کار را به کمک jQuery.getJSON انجام می‌دهیم ، در تابع callback باید کار مقدار دهی انجام شود.
به ازای رکورد هایی که برگشت شده است که حالا در data قرار دارد به کمک تابع each لوپ می‌رنیم و هربار این کار را تکرار می‌کنیم.
  • ابتدا یک تگ option می‌سازیم 
  • مقادیر مربوطه شامل Id که باید در attribute مورد نظر value قرار گیرد و متن آن که باید به عنوان text باشد را مقدار دهی می‌کنیم
  • option آماده شده را به DropDownList دومی (Category ) اضافه می‌کنیم.
توجه داشته باشید که هنگامی که کار دریافت اطلاعات با موفقیت انجام شد ، مقادیر داخل DropDownList  دومی ، مربوط به Category را به منظور بازیابی دوباره در  
$('#rdbCategory').empty(); 
خالی می‌کنیم.
نتیجه کار برای من می‌شود این :
قبل از انتخاب :

بعد از انتخاب :

موفق و پیروز باشید
مطالب
شروع کار با Apache Cordova در ویژوال استودیو #5

همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و plugin‌های مربوط به Cordova آشنا خواهیم شد.


تگ متای زیر برای تنظیمات مربوط به viewport است و برای jQuery Mobile توصیه می‌شود.
<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Title</title> 
<meta name="viewport" content="width=device-width, initial-scale=1">
device-width  نشان می‌دهد که می‌خواهیم مقیاس محتوای ما به اندازه‌ی عرض دستگاه(device) مورد نظر باشد و initial-scale هم مقدار زوم را برای Web page ما مشخص می‌کند. شما می‌توانید با مقدار دهی user-scalable=no هم امکان تغییر زوم را به کاربر ندهید. این متا تگ را در تمام صفحات html خود بعد از تگ title قرار دهید.

روال کار jQuery Mobile
برای اینکه بتواند سند HTML ما را برای استفاده‌ی در موبایل بهینه کند، ابتدا آن را لود می‌کند و سپس بر  اجزایی که با ویژگیdata-role علامت گذاری شده‌اند، CSS3 بهینه شده برای موبایل را اعمال می‌کند.


از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.

لیست کامل اتریبیوت‌های -data به همراه مقادیری که می‌پذیرند 

دموی مربوط به ویجت‌ها  

لیست تمام رخدادها 

شما می‌توانید از امکانات Theme Roller برای شخصی سازی تم‌های مورد نیاز استفاده کنید.

لیست کامل کلاس‌های CSS  



Cordova Plugins

از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت  http://plugreg.com/plugins می‌توانید سراغ پلاگین‌های مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins  و تب Core یکسری از پلاگین‌هایی را که در Cordova گنجانده شده است، مشاهده می‌کنید. با کلیک بر روی دکمه‌ی Add می‌توانید آن را دانلود کرده و از API‌های آن استفاده کنید.



برای مثال پلاگین Notification را به پروژه اضافه می‌کنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و  محتوای فایل‌های index.html , custome.js را به شکل زیر در نظر می‌گیرم:


$(function() {
    $("#alert").on('tap', function(event) {
        navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید");
    });

    $("#prompt").on('tap', function(event) {
        navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]);
    });

    function onPrompt(results) {
        navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null);
    }
    $("#confirm").on('tap', function(event) {
        navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]);
    });

    function onConfirm(buttonIndex) {
        navigator.notification.alert(buttonIndex , null);
    }
    $("#beep").on('tap', function(event) {
        navigator.notification.beep(1);
    });

});

رخداد tap زمانی صادر می‌شود که کاربر، دکمه‌ی مورد نظر را لمس کند و یکی از رخداد‌های jQuery Mobile می‌باشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification می‌توانید به متد‌های مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.

برای آشنایی با این پلاگین می‌توانید داکیومنت آن را مطالعه کنید.

در کد بالا با استفاده از متد‌های callback توانسته‌ایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>CordovaApp01</title>
   <meta name="viewport" content="width=device-width, initial-scale=1"/> 
    <!-- CordovaApp01 references -->
    <link href="css/index.css" rel="stylesheet" />
    <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" />
</head>
<body>
<div data-role="page" id="page1">
    <div data-role="header">
        <h2>
            تست پلاگین Notification
        </h2>
    </div>
    <div data-role="content">
        <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
       
        <button data-role="button" id="alert" data-inline="true" >alert</button>
        <button data-role="button" id="confirm" data-inline="true">confirm</button>
        <button data-role="button" id="beep" data-inline="true" >beep</button>
        <button data-role="button" id="prompt" data-inline="true" >prompt</button>

    </div>
    <div data-role="footer">
        <h2>من فوتر هستم</h2>
    </div>
</div>
    <div data-role="page" id="page2">
        <div data-role="header">
            <h1>Header</h1>
        </div>
        <div data-role="content">
            Content
        </div>
        <div data-role="footer">
            <h1>Footer</h1>
        </div>
    </div>
<!-- Cordova reference, this is added to your app when it's built. -->
    <script src="scripts/jquery-2.1.3.min.js"></script>
    <script src="cordova.js"></script>
    <script src="scripts/platformOverrides.js"></script>
    <script src="scripts/index.js"></script>
    <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script>
    <script src="scripts/custom.js"></script>
</body>
</html>

در کد بالا 4 تا button دیده می‌شود که ویژگی data-role آنها مقدار button در نظر گرفته شده‌است تا توسط jQuery Mobile به عنوان button شناخته شوند و استایل‌های لازم بر روی آن‌ها اعمال گردد. قرار است طبق کد js ایی که نوشته‌ایم، با لمس کردن هر کدام از دکمه‌ها، notification هایی نمایش داده شوند.


برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینه‌ی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:

Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01
Error448throw e;CordovaApp-01
Error449^CordovaApp-01
Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
مشخصا خطا، مبنی بر پیدا نشدن دستگاه خارجی است. برای رفع این مشکل می‌بایست شبیه ساز youwave را به ویژوال استودیو وصل کنیم. برای این منظور دستور زیر را در cmd اجرا کنید.
adb connect localhost:5558

بعد از آن اگر پروژه را اجرا کنید، فایل apk. پروژه بر روی شبیه ساز نصب شده و اجرا خواهد شد. با کلیک بر روی دکمه‌ی confirm تصویری به شکل زیر قابل مشاهده خواهد بود:


علاوه بر این ما در سند HTML خود در بالا، یک page و یک تگ a قرار داده‌ایم. 
 <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
data-role: با مقدار button در نظر گرفته شده است؛ لذا به شکل 4 دکمه دیگر رندر خواهد شد.
data-transition: با مقدار pop در نظر گرفته شده است که مشخص کننده‌ی افکت ظاهر شدن صفحه‌ای است که قرار است بار گذاری شود.
data-rel: مشخص می‌کند که صفحه‌ی مورد نظر من به صورت دیالوگ باز شود.
data-icon: با استفاده از این ویژگی می‌توان icon مورد نظر خود را برای المنت در نظر گرفت.
data-inline: برای به خط کردن دکمه‌ها کنار هم استفاده می‌شود.
با لمس کردن این دکمه، نتیجه به شکل زیر خواهد بود:

در مقاله‌ی بعد، به مباحث Database در Cordova خواهیم پرداخت.

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