مطالب
ضمیمه کردن فایل در RavenDb
یکی از مواردی که در بانک‌های اطلاعاتی امروزه بیشتر مورد استفاده قرار میگیرد، ذخیره فایل‌ها در خود دیتابیس، بجای ذخیره نام یا آدرس آن‌ها بر روی دیسک سخت است. از همان ابتدا که Raven به بازار عرضه شد، امکان ذخیره فایل‌های باینری را با استفاده از افزونه هایی که به همراه داشت برای برنامه نویسان مهیا ساخت.  این امکان از طریق کد زیر برای ذخیره یک فایل کفایت میکرد:
using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    store.DatabaseCommands.PutAttachment(key: "file/1",
                                                         etag: null,
                                                         data: System.IO.File.OpenRead(@"D:\Prog\packages.config"),
                                                         metadata: new RavenJObject
                                                         {
                                                            { "Description", "توضیحات فایل" }
                                                         });
                    var question = new Question
                    {
                        By = "users/Vahid",
                        Title = "Raven Intro",
                        Content = "Test....",
                        FileId = "file/1"
                    };
                    session.Store(question);

                    session.SaveChanges();
                }
            }
ولی اگر از نسخه سه بعد RavenDb را استفاده کنید، می‌بینید که متد PutAttachment و دیگر خانواده این متد با ویژگی Obsolete (منسوخ شده) مزین شده‌اند و توصیه شده‌است از این پس از قابلیت جدیدی به نام RavenFs استفاده شود.
RavenFS یک فایل سیستم مجازی توزیع شده‌است و برای فایل‌های بزرگ چند گیگابایتی به طور بهینه‌ای طراحی گردیده‌است تا کارآیی بانک اطلاعاتی را بالا ببرد و وجود فایل‌های تکراری، از بین برود. این سیستم جدید شامل سیستم پیش فرض ایندکس گذاری میباشد و به شما این اجازه را میدهد تا بر روی متادیتاهای یک فایل از قبیل حجم، تاریخ آخرین نگارش و حتی متادیتاهای اختصاصی که شما در حین ذخیره سازی به آن اضافه می‌کنید، به جست‌وجو بپردازد. این سیستم جدید همچنین این امکان را به شما میدهد تا این اطلاعات را بین Node‌ها، با کمترین میزان انتقالات جابجا کنید و دسترسی سریعتری را بین نودهای مختلف داشته باشید.

برای ذخیره سازی یک فایل ابتدا باید یک FileStore را همانند آنچه که برای DocumentStore داشتید تعریف کنید. Url که شامل همان رشته اتصالی بوده و DefaultFileSystem هم  همانند DefaultDatabase که نام دیتابیس در آن ذکر میشد، در اینجا نام فایل سیستم ذکر می‌گردد:
 var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

بعد از آن باید از طریق Store جدید یک سشن ایجاد شود و فایل مورد نظر را در قالب یک استریم بخوانیم:
 var session = fileStore.OpenAsyncSession();
 var stream = File.OpenRead("D:\\Apocalypse.Now.Redux.1979.BDRip.YIFY.mkv");  

توجه داشته باشید که برای کار با فایل سیستم، همه متدهای session به صورت غیرهمزمان بوده و متد همزمانی وجود ندارد. سپس در مرحله بعد میخواهم متادیتاهای شخصی نیز به آن اضافه کنیم:
  var metadata = new RavenJObject
            {
                {"User", "users/1345"},
              {"Director","Francis Ford Coppola" }, 
                {"Year","1979" }
            };

با استفاده از شیء RavenObject میتوانیم در قالب کلید و مقدار، مقادیر خود را ذخیره کنیم و بعد از آن همه موارد بالا که شامل فایل هدر، استریم و متادیتای اختصاصی است را رجیستر کنیم. اگر هم چندین فایل داریم میتوانید آن‌ها را هم در همینجا رجیستر کنید:
session.RegisterUpload("mkv/sample.mkv", stream, metadata);

در مرحله بعدی تغییرات را تایید و عملیات آپلود آغاز میگردد:
await session.SaveChangesAsync();

همانطور که می‌بینید تمامی متدهای کاربردی این سشن به طور غیرهمزمان طراحی شده‌اند.
کلیه عملیاتی که در بالا انجام شد:
    var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

            var session = fileStore.OpenAsyncSession();
             var stream = File.OpenRead("D:\\Apocalypse.Now.Redux.1979.BDRip.YIFY.mkv"); 
            var metadata = new RavenJObject
            {
                {"User", "users/1345"},
               {"Director","Francis Ford Coppola" }, 
                {"Year","1979" }
            };

            session.RegisterUpload("Mkv/sample.mkv", stream, metadata);
            await session.SaveChangesAsync();

حالا اگر به نسخه سرور ravenDb مراجعه کنید می‌بینید که فایل طبق فایل هدر داده شده قرار گرفته است و اطلاعات مربوط به آن ذخیره شده است:



{
    "User": "users/1345",
    "Country": "Iran",
    "City": "Kashan",
    "Raven-Synchronization-History": [
        {
            "Version": 4,
            "ServerId": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4"
        },
        {
            "Version": 5,
            "ServerId": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4"
        }
    ],
    "Raven-Synchronization-Version": "6",
    "Raven-Synchronization-Source": "42d0cccb-103d-4bf0-9f3d-6f635b1c8ba4"
}

برای خواندن هم به شیوه زیر عمل میکنیم:
 از طریق Store ایجاد شده، یک سشن جدید را باز می‌کنیم و فایل مورد نظر را از طریق یکی از متادیتاهای تعریف شده بازیابی میکنیم:
var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

            var session = fileStore.OpenAsyncSession();
            var file = await session.Query()
                   .WhereEquals("Year", "1979")
                   .FirstOrDefaultAsync();
سپس با آدرس دهی فایل هدر، فایل باینری را داخل استریم قرار می‌دهیم:
var stream = await session.DownloadAsync("mkv/"+file.Name);

سپس استریم را روی دیسک سخت دخیره یا به هر مکانی که مد نظر است ارسال می‌کنیم:
   var fs = File.Create("D:\\file2.mkv");
            stream.CopyTo(fs);
            fs.Flush();
            fs.Close();
البته از آنجائیکه عملیات بازیابی توسط بانک اطلاعاتی به صورت غیرهمزمان انجام می‌گیرد، بهتر هست که باقی عملیات هم به صورت غیرهمزمان انجام شود:
await stream.CopyToAsync(fs);

سپس کل کد بازیابی را به شکل زیر می‌نویسیم:
            var fileStore = new FilesStore()
            {
                Url = "http://localhost:8080",
                DefaultFileSystem = "SampleFs"
            };
            fileStore.Initialize();

            var session = fileStore.OpenAsyncSession();
            var file = await session.Query()
                   .WhereEquals("Year", "1979")
                   .FirstOrDefaultAsync();

            var stream = await session.DownloadAsync("mkv/"+file.Name);

            var fs = File.Create("D:\\file2.mkv");

            await stream.CopyToAsync(fs);


مطالب
استفاده از افزونه Typeahead مجموعه Twitter Bootstrap در ASP.NET MVC
با تعدادی از کامپوننت‌های Bootstrap در مطلب «نگاهی به اجزای تعاملی Twitter Bootstrap» آشنا شدید. یکی دیگر از این افزونه‌ها، Typeahead نام دارد که در حقیقت نوعی Autocomplete text box است. در ادامه قصد داریم نحوه استفاده از آن‌را در ASP.NET MVC بررسی کنیم.

استفاده‌ی استاتیک از افزونه Typeahead

منظور از استفاده‌ی استاتیک، مشخص بودن آرایه عناصر و هچنین درج آن به صورت html encoded در صفحه است. برای این منظور، کنترلر برنامه چنین شکلی را خواهد داشت:
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };
            ViewBag.JsonString = new JavaScriptSerializer().Serialize(array);
            return View();
        }
    }
}
در اینجا یک آرایه با تعداد عناصر مشخص، تبدیل به رشته JSON معادل آن شده و توسط ViewBag.JsonString به View ارسال می‌شود.
View متناظر با آن به نحو ذیل با مشخص سازی نوع data-provide (تا به کتابخانه‌ی جاوا اسکریپتی همراه bootstrap اعلام کند از چه افزونه‌ای در اینجا قرار است استفاده شود)، منبع داده data-source و حداکثر تعداد آیتم ظاهر شونده data-items، می‌تواند طراحی شود:
@{
    ViewBag.Title = "Index";    
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8,
                                  data_source = @ViewBag.JsonString
                              })

به این ترتیب، یک چنین خروجی در صفحه درج می‌شود:
<input autocomplete="off" data-items="8" data-provide="typeahead" 
data-source="[&quot;Afghanistan&quot;,&quot;Albania&quot;,&quot;Algeria&quot;,&quot;American Samoa&quot;,&quot;Andorra&quot;,&quot;Angola&quot;,&quot;Anguilla&quot;,&quot;Antarctica&quot;,&quot;Antigua and/or Barbuda&quot;]" 
id="search" name="search" type="text" value="" />
همانطور که ملاحظه می‌کنید دقیقا data-source تهیه شده مطابق نیاز خاص این افزونه، html encoded است. به علاوه هر جایی در htmlAttributes صفحه از under line استفاده شده، در این سمت به صورت خودکار به - ترجمه گردیده است.
اگر هم بخواهیم برای آن یک Html Helper درست کنیم، می‌توان به نحو ذیل عمل کرد:
        public static MvcHtmlString TypeaheadFor<TModel, TValue>(
                this HtmlHelper<TModel> htmlHelper,
                Expression<Func<TModel, TValue>> expression,
                IEnumerable<string> source,
                int items = 8)
        {
            var jsonString = new JavaScriptSerializer().Serialize(source);
            return htmlHelper.TextBoxFor(
                expression,
                new
                {
                    autocomplete = "off",
                    data_provide = "typeahead",
                    data_items = items,
                    data_source = jsonString
                }
            );
        }


استفاده پویا و Ajax ایی از افزونه Typeahead

اگر بخواهیم data-source را به صورت پویا، هربار از بانک اطلاعاتی دریافت و ارائه دهیم، نیاز به کمی اسکریپت نویسی خواهد بود:
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Mvc4TwitterBootStrapTest.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public JsonResult GetNames(string term)
        {
            var array = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };

            var results = array.Where(n =>
                n.StartsWith(term, StringComparison.OrdinalIgnoreCase));

            return Json(results.ToArray(), JsonRequestBehavior.AllowGet);
        }
    }
}
در این حالت، کدهای اکشن متدی که یک عبارت، یا قسمتی از آن را از طریق پارامتر term دریافت و خروجی JSON مناسبی را ارائه می‌کند، همانند متد GetNames فوق خواهد بود.
سپس در تعاریف View، قسمت data-source مرتبط با TextBox حذف و از طریق فراخوانی مستقیم کدهای افزونه typeahead مقدار دهی می‌گردد:
@{
    ViewBag.Title = "Index";
    var url = Url.Action("GetNames", "Home");
}
<h2>
    Typeahead</h2>
@Html.TextBox("search", null, htmlAttributes:
                              new
                              {
                                  autocomplete = "off",
                                  data_provide = "typeahead",
                                  data_items = 8
                              })
@section JavaScript
{
    <script type="text/javascript">
            $(function () {
                $('#search').typeahead({
                    source: function (term, process) {
                        return $.getJSON('@url', { term: term }, function (data) { return process(data); });
                    }
                });
            });
    </script>
}
در اینجا توسط متد getJSON کتابخانه jQuery، مقدار عبارت وارد شده در TextBox جستجو، به آدرس اکشن متد GetNames ارسال و سپس حاصل به source افزونه typeahead انتساب داده می‌شود.
مطالب دوره‌ها
نگاهی به 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 فراخوانی می‌شود نیز این فراخوانی غیرهمزمان است و خصوصا اگر نیاز است رابط کاربری برنامه را در این بین به روز کنید باید به نکات به روز رسانی رابط کاربری از طریق یک ترد دیگر دقت داشت.
مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت دوم - کار با Attributes
عناصر HTML از سه قسمت نام، محتوا و ویژگی‌ها یا attributes تشکیل می‌شوند که دو مورد آخر، اختیاری هستند.
<form action="/rest/login" method="POST">
    <input name="username" required>
    <input type="password" name="password" required>
</form>
در این مثال سه المان form و دو input را مشاهده می‌کنید. تگ المان <form> دارای نام form و تگ المان <input> دارای نام input است.
محتوا یا content به معنای المان‌هایی هستند که درون یک المان قرار می‌گیرند. برای مثال در اینجا المان <form> دارای محتوایی متشکل از دو المان <input> است و دون المان <input> دارای محتوایی نیستند.
از ویژگی‌ها و یا attributes، برای ارائه‌ی اطلاعات بیشتر و یا تعیین حالت عناصر استفاده می‌شود. برای نمونه در اینجا المان <form> دارای دو ویژگی action و method است که برای ارائه‌ی اطلاعاتی بیشتر جهت تعیین روش و محل ارسال اطلاعات به سرور از آن‌ها استفاده می‌شود. همچنین می‌توان ویژگی‌های سفارشی را نیز توسط ویژگی‌های شروع شده‌ی با -data نیز به المان‌ها اضافه کرد که جزئی از استاندارد HTML 5 است. هرچند می‌توان ویژگی کاملا غیرمتعارفی مانند myproject-uuid را نیز به یک المان اضافه کرد. این مورد مشکل خاصی را ایجاد نکرده و صفحه بدون مشکل رندر می‌شود؛ اما یک چنین صفحه‌ای دیگر به عنوان یک صفحه‌ی استاندارد HTML ارزیابی نخواهد شد؛ از این جهت که از ویژگی استفاده کرده‌است که در هیچکدام از استانداردهای HTML ذکر نشده‌است.
آخرین نگارش HTML5 به همراه 4 ویژگی جدید دیگر نیز هست:
- Boolean attribute : مانند ویژگی required که به المان‌های <input> قابل انتساب است. حضور این نوع ویژگی‌ها بدون مقداری در یک المان
 <input name=username required>
به معنای true بودن مقدار آن‌ها است و اگر به طور کامل ذکر نشوند، مقدار false را خواهند داشت.
نمونه‌ی دیگری از این نوع ویژگی‌ها، ویژگی hidden است که به HTML 5.1 اضافه شده‌است و اگر به عنصری اضافه شود، آن المان رندر نخواهد شد.
- unquoted attribute: به این معنا که می‌توان "" را از اطراف مقدار یک ویژگی حذف کرد:
 <input name=username required>
البته با این شرط که این مقدار دارای فاصله، علامت مساوی، مقداری خالی و یا >< نباشد.
- single-quoted and double-quoted attributes: که به معنای استفاده از "" و یا '' جهت تعیین مقدار یک ویژگی است.


تفاوت attributes با خواص المان‌ها چیست؟

Attributes در تعریف تگ HTML یک عنصر ظاهر می‌شوند اما خواص، جزئی از تعریف شیء یک عنصر هستند.
<div class="bold">I'm a bold element</div>
در این مثال المان <div> دارای ویژگی class است. هرچند ویژگی‌ها و خواص دارای مفاهیم یکسانی نیستند، اما در تعریف اشیاء HTML به ازای تمام ویژگی‌های استاندارد، یک خاصیت با نام معادل نیز در نظر گرفته‌است و تغییر مقدار آن‌ها از طریق کد، سبب به روز رسانی مقدار ویژگی‌های متناظر نیز می‌شود.
 <a href="http://www.site.com/blog/">Read the blog</a>
برای مثال اگر بخواهیم مقدار ویژگی href المان فوق را تغییر دهیم، می‌توانیم ابتدا این شیء را یافته و سپس خاصیت href آن‌را به روز رسانی کنیم تا بر روی ویژگی متناظر با آن تاثیرگذار شود:
<script>
   document.querySelector('A').href = 'https://www.dntips.ir';
</script>
هرچند در اکثر موارد تناظری بین نام خواص و ویژگی‌های المان‌ها برقرار است، اما یکسری استثناءهایی هم وجود دارند:
- برای مثال واژه‌ی class در زبان جاوا اسکریپت یک واژه‌ی کلیدی است. به همین جهت در اینجا اگر بخواهیم مقدار ویژگی class را تغییر دهیم باید از خاصیت className آن المان استفاده کنیم.
- مورد دوم ویژگی checked المان‌های radio و checkbox است. این ویژگی فقط در ابتدای کار این المان‌ها به آن‌ها متصل می‌شود:
  <input type="checkbox" checked>

  <script>
      // this does not remove the checked attribute 
      document.querySelector('INPUT').checked = false;
  </script>
با اجرای قطعه کد فوق، هرچند مقدار خاصیت checked این المان false می‌شود، اما سبب حذف خود ویژگی از المان نخواهد شد.
- تمام ویژگی‌های غیراستانداردی که تعریف شوند، دارای خاصیت متناظری در آن المان نخواهند بود، اما به آن‌ها expando properties گفته می‌شود.


یافتن المان‌ها بر اساس ویژگی‌های آن‌ها

از آنجائیکه attribute selectors در استاندارد W3C CSS 2 معرفی شده‌اند، امکان کار با آن‌ها از زمان IE 8.0 میسر بوده‌است و برای کار با آ‌ن‌ها الزاما نیازی به استفاده از jQuery نیست!


یافتن المان‌ها بر اساس نام ویژگی‌ها

  <form action="/submitHandler" method="POST">
      <input name="first-name">
      <input name="last-name" required>
      <input type="email" name="email" required>
      <button disabled>submit</button>
  </form>
در اینجا برای یافتن المان‌هایی که دارای ویژگی‌هایی با نام‌های required و disabled هستند، در جی‌کوئری از CSS 2+ attribute selector string آن‌ها استفاده می‌شود:
 var $result = $('[required], [disabled]');
و در جاوا اسکریپت خالص نیز دقیقا به همان شکل و با استفاده از همان استاندارد است:
 var result = document.querySelectorAll('[required], [disabled]');
که خروجی آن آرایه‌ای از المان‌های last-name، email و دکمه است.

در اینجا باید دقت داشت که این جستجو صرفا بر اساس نام ویژگی‌ها انجام می‌شود؛ حتی اگر این ویژگی دارای مقداری نباشد:
  <div class="bold">I'm bold</div>
  <span class>I'm not</span>
در اینجا ویژگی class دوم دارای مقداری نیست و اگر کوئری ذیل را اجرا کنیم، هر دو المان span و div را دریافت خواهیم کرد:
  var result = document.querySelectorAll('[class]');


یافتن المان‌ها بر اساس نام و مقدار ویژگی‌ها

  <section>
     <h2>Sites</h2>
     <ul>
          <li>
              <a href="https://www.dntips.ir/">dotnettips</a>
          </li>
          <li>
              <a href="https://google.com/">Google</a>
          </li>
      </ul>
  </section>
اگر یافتن المان‌ها صرفا بر اساس نام ویژگی‌های آن‌ها کافی نیست، استفاده از همان روش استاندارد CSS selector string برای یافتن عنصری بر اساس نام و مقدار یک ویژگی نیز میسر است. برای مثال در این حالت در جی‌کوئری خواهیم داشت:
 var $result = $('A[href="https://www.dntips.ir/"]');
معادل این کد در جاوا اسکریپت خالص نیز به همین شکل است؛ اما بدون نیاز به وابستگی خاصی و سریعتر:
 var result = document.querySelectorAll('A[href="https://www.dntips.ir/"]');

و یا اگر اینبار بخواهیم تمام ویژگی‌های کلاسی که دارای مقدار هستند را انتخاب کنیم، روش آن با استفاده از exclusion selector به صورت زیر است:
 var result = document.querySelectorAll('[class]:not([class=""]');


یافتن المان‌ها بر اساس نام و قسمتی از مقدار ویژگی‌ها

  <a href="https://www.dntips.ir/">home page</a>
  <a href="https://www.dntips.ir/postsarchive">articles</a>
  <a href="https://www.dntips.ir/newsarchive">news</a>
در مثال قبل، المان‌هایی را که دارای مقدار ویژگی کاملا مشخصی بودند، یافتیم. اما اگر بخواهیم در قطعه HTML فوق لینک‌هایی را که دارای domain مشخصی هستند بیابیم چطور؟ در اینجا باید از substring attribute selector که جزئی از استاندارد W3C CSS 3 است، استفاده کنیم:
 var result = document.querySelectorAll('A[href*="www.dotnettips.info"]');
در اینجا تمام anchor tagهایی که دارای ویژگی href با مقداری حاوی www.dotnettips.info هستند، یافت خواهند شد.


یافتن المان‌ها بر اساس نام و کلمه‌ای مشخص در مقدار ویژگی‌ها

<div class="one two three">1 2 3</div>
<div class="onetwothree">123</div>
در این مثال می‌خواهیم المانی را بیابیم که کلاس two به آن اعمال شده‌است. برای اینکار از attribute word selector استفاده می‌شود:
 var result = document.querySelectorAll('[class∼=two]');
خروجی این کوئری، لیستی است حاوی اولین div تعریف شده.

در اینجا نوع دیگری از کوئری را هم می‌توان مطرح کرد: آیا المانی مشخص، دارای کلاس two است؟
روش انجام آن در jQuery به صورت زیر است:
 var hasTwoClass = $divEl.hasClass('two');
و در جاوا اسکریپت خالص:
 var hasTwoClass = divEl.classList.contains('two');
DOM API به همراه خاصیتی است به نام classList که امکان یافتن عنصری خاص را در آن توسط متد استاندارد contains میسر می‌کند.
همچنین خاصیت classList به همراه متدهای استاندارد add و remove نیز هست که معادل متدهای addClass و removeClass جی‌کوئری هستند.
divEl.classList.remove('red');
divEl.classList.add('blue');
و یا متد toggle خاصیت classList سبب افزوده شدن کلاسی مشخص و یا فراخوانی مجدد آن سبب حذف آن کلاس می‌شود (معادل متد toggleClass جی‌کوئری است):
// removes "hide" class
divEl.classList.toggle('hide');

// re-adds "hide" class
divEl.classList.toggle('hide');


یافتن المان‌ها بر اساس نام و شروع یا خاتمه‌ی عبارتی مشخص در مقدار ویژگی‌ها

  <img id="cat" src="/images/cat.gif">
  <img id="zebra" src="zebra.gif">
  <a href="#zebra">watch the zebra</a>
  <a href="/logout">logout</a>
در اینجا می‌خواهیم تمام المان‌هایی را که از نوع تصاویر gif هستند، به همراه لینک‌هایی که به صفحه‌ی جاری اشاره می‌کنند، بیابیم:
 var result = document.querySelectorAll('A[href^="#"], [src$=".gif"]');
این کوئری نحوه‌ی استفاده‌ی از starts with attribute value selector یا ^ و ends with attribute value selector یا $ را نمایش می‌دهد. این مثال لینک‌هایی را که با # شروع می‌شوند و همچنین المان‌هایی را که دارای src ختم شده‌ی به gif هستند، پیدا می‌کند.


خواندن مقادیر ویژگی‌ها

 <input type="password" name="user-password" required>
روش خواندن مقدار ویژگی type و بررسی وجود ویژگی required در جی‌کوئری:
// returns "password"
$inputEl.attr('type');

// returns "true"
$inputEl.is('[required]');
و معادل همین قطعه کد در جاوا اسکریپت خالص به صورت زیر است:
// returns "password"
inputEl.getAttribute('type');

// returns "true"
inputEl.hasAttribute('required');
متد getAttribute از سال 1997 به همراه استاندارد W3C DOM Level 1 Core و متد hasAttribute از سال 2000 به همراه استاندارد DOM Level 2 Core معرفی شده‌اند.


تغییر مقدار ویژگی‌ها

 <input name="temp" required>
می‌خواهیم این المان را به نحوی تغییر دهیم که نوع آن email شود، بدون ویژگی required و نام آن به userEmail تغییر یابد.
روش انجام اینکار در جی‌کوئری:
 $inputEl
 .attr('type', 'email') // #1
 .removeAttr('required') // #2
 .attr('name', 'userEmail'); // #3
و با جاوا اسکریپت خالص:
 inputEl.setAttribute('type', 'email'); // #1
inputEl.removeAttribute('required'); // #2
inputEl.setAttribute('name', 'userEmail'); // #3
متدهای استاندارد setAttribute و removeAttribute نیز جزئی از استاندارد W3C DOM Level 1 Core سال 1997 هستند؛ اما مانند jQuery قابلیت ذکر زنجیروار را ندارند که ... مهم نیست.
مطالب
شروع کار با ASP.NET Web API 2
HTTP تنها برای به خدمت گرفتن صفحات وب نیست. این پروتکل همچنین پلتفرمی قدرتمند برای ساختن API هایی است که سرویس‌ها و داده را در دسترس قرار می‌دهند. این پروتکل ساده، انعطاف پذیر و در همه جا حاضر است. هر پلتفرمی که فکرش را بتوانید بکنید کتابخانه ای برای HTTP دارد، بنابراین سرویس‌های HTTP می‌توانند بازه بسیار گسترده ای از کلاینت‌ها را پوشش دهند، مانند مرورگرها، دستگاه‌های موبایل و اپلیکیشن‌های مرسوم دسکتاپ.

ASP.NET Web API فریم ورکی برای ساختن API‌های وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر می‌گرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.


یک پروژه Web API بسازید

در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.

در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.

می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.

افزودن یک مدل

یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی می‌کند. ASP.NET Web API می‌تواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمت‌های دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی داده‌ها را بخواند، می‌تواند آبجکت شما را deserialize کند. اکثر کلاینت‌ها می‌توانند XML یا JSON را تفسیر کنند. بعلاوه کلاینت‌ها می‌توانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.

بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.

کلاس جدیدی در پوشه Models ایجاد کنید.

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

namespace ProductsApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

افزودن یک کنترلر

در Web API کنترلر‌ها آبجکت هایی هستند که درخواست‌های HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت می‌کنند. ما کنترلری خواهیم ساخت که می‌تواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلر‌های MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق می‌شوند.

کنترلر جدیدی در پوشه Controllers ایجاد کنید.

در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.

در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.

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

using ProductsApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;

namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }
}
برای اینکه مثال جاری را ساده نگاه داریم، محصولات مورد نظر در یک آرایه استاتیک ذخیره شده اند. مسلما در یک اپلیکیشن واقعی برای گرفتن لیست محصولات از دیتابیس یا منبع داده ای دیگر کوئری می‌گیرید.

کنترلر ما دو متد برای دریافت محصولات تعریف می‌کند:

  • متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر می‌گرداند.
  • متد GetProductById سعی می‌کند محصولی را بر اساس شناسه تعیین شده پیدا کند.

همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ می‌دهند:

 URI  Controller Method
api/products/  GetAllProducts
 api/products/id/  GetProductById

برای اطلاعات بیشتر درباره نحوه نگاشت درخواست‌های HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.


فراخوانی Web API با جاوا اسکریپت و jQuery

در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی می‌کند. برای ارسال درخواست‌های آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.

در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.

در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.

حال محتوای این فایل را با لیست زیر جایگزین کنید.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Product App</title>
</head>
<body>

  <div>
    <h2>All Products</h2>
    <ul id="products" />
  </div>
  <div>
    <h2>Search by ID</h2>
    <input type="text" id="prodId" size="5" />
    <input type="button" value="Search" onclick="find();" />
    <p id="product" />
  </div>

  <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
  <script>
    var uri = 'api/products';

    $(document).ready(function () {
      // Send an AJAX request
      $.getJSON(uri)
          .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
              // Add a list item for the product.
              $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
          });
    });

    function formatItem(item) {
      return item.Name + ': $' + item.Price;
    }

    function find() {
      var id = $('#prodId').val();
      $.getJSON(uri + '/' + id)
          .done(function (data) {
            $('#product').text(formatItem(data));
          })
          .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
          });
    }
  </script>
</body>
</html>
راه‌های مختلفی برای گرفتن jQuery وجود دارد، در این مثال از Microsoft Ajax CDN استفاده شده. می‌توانید این کتابخانه را از http://jquery.com دانلود کنید و بصورت محلی استفاده کنید. همچنین قالب پروژه‌های  Web API این کتابخانه را به پروژه نیز اضافه می‌کنند.


گرفتن لیستی از محصولات

برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.

تابع getJSON یک درخواست آژاکسی ارسال می‌کند. پاسخ دریافتی هم آرایه ای از آبجکت‌های JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا می‌شود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی می‌کنیم.

$(document).ready(function () {
    // Send an AJAX request
    $.getJSON(apiUrl)
        .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
                // Add a list item for the product.
                $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
        });
});

گرفتن محصولی مشخص

برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.

function find() {
    var id = $('#prodId').val();
    $.getJSON(apiUrl + '/' + id)
        .done(function (data) {
            $('#product').text(formatItem(data));
        })
        .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
        });
}
برای این کار هنوز از getJSON برای ارسال درخواست آژاکسی استفاده می‌کنیم، اما اینبار شناسه محصول را هم به آدرس درخواستی اضافه کرده ایم. پاسخ دریافتی از این درخواست، اطلاعات یک محصول با فرمت JSON است.


اجرای اپلیکیشن

اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.

برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.

اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر می‌گرداند.


استفاده از F12 برای مشاهده درخواست‌ها و پاسخ ها

هنگام کار با سرویس‌های HTTP، مشاهده‌ی درخواست‌های ارسال شده و پاسخ‌های دریافتی بسیار مفید است. برای اینکار می‌توانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر می‌توانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر می‌کند. می‌توانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.

به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، می‌توانید header‌ها و بدنه درخواست‌ها و پاسخ‌ها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header داده‌ها را با فرمت "application/json" درخواست کرده است.

اگر روی برگه Response body کلیک کنید، می‌توانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیت‌های مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار می‌توانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواست‌های جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما می‌دهد.


قدم‌های بعدی

  • برای یک مثال کامل از سرویس‌های HTTP که از عملیات POST,PUT و DELETE پشتیبانی می‌کند به این لینک مراجعه کنید.
  • برای اطلاعات بیشتر درباره طراحی واکنش گرا در کنار سرویس‌های HTTP به این لینک مراجعه کنید، که اپلیکیشن‌های تک صفحه ای (SPA) را بررسی می‌کند.
نظرات مطالب
کار با اسکنر در برنامه های تحت وب (قسمت دوم و آخر)
سلام؛ ممنون از مطلب مفیدتون. من راهی رو گفتیم رفتم wcfservice هم run کردم و درست بود اما با اجرای WinForm  و  زدن آدرس locallhost:8010 چیزی باز نمیشه. کد زیر هم اصلا کار نمیکنه:
 <script>
  $( "#get-scan" ).click(function () {
       var url = 'http://localhost:8010/' ;
       $. get (url, function (data) {
       $( "#img-scanned" ).attr( "src" , "data:image/Jpeg;base64,  " + data.GetScanResult);
    });
  });
پورت رو هم 6019 میذارم بازم همینطور. ممنون میشم راهنمایی کنید.
مطالب
افزودن یک DataType جدید برای نگه‌داری تاریخ خورشیدی - 2
پیش از هرچیز به شما پیش‌نهاد می‌کنم؛ بار دیگر کد سی‌شارپ درس نخست را در پروژه‌ی خود کپی کنید و سپس Publish را بزنید. پس از ارسال آن مطلب، تغییراتی در جهت بهینه‌سازی کد دادم که به نظرم بهتر است شما نیز در پروژه‌ی خود به کار برید.

چرا از این نوع داده استفاده کنیم؟
نخستین پرسشی که ممکن است برای شما پیش بیاید این است که چرا بهتر است از این نوع داده استفاده کنیم. برای پاسخ به این پرسش باید راه‌کارهای گذشته را بررسی کنیم. معمولاً طراحان پایگاه داده‌ها برای استفاده از تاریخ خورشیدی، زمان را به صورت میلادی ثبت می‌کنند؛ سپس با یک scalar-valued function زمان درج شده را به خورشیدی تبدیل می‌کنند. در این صورت می‌توان با یک تابع کوچک دیگر بخش مربوط به ساعت را نیز از همان ستون به دست آورد. در این صورت می‌توانیم از کلیه‌ی متدهای مربوط به DateTime در SQL از جمله افزایش و کاهش و تفاضل دو تاریخ بهره برد. برخی دیگر از طراحان، ستونی از نوع (char(10 در نظر می‌گیرند و تاریخ خورشیدی را به صورت ده‌کاراکتری در آن ذخیره می‌کنند. این روش هرچند نیاز به تبدیل به خورشیدی را ندارد ولی کلیه‌ی مزایایی که در استفاده از DateTime به آن‌ها دسترسی داریم از دست می‌دهیم. افزون بر این جهت نگه‌داری زمان باید یک فیلد دیگر از نوع کاراکتری و یا در نگارش‌های نوین‌تر از نوع time تعریف کنیم. برخی دیگر از هر دو را در کنار هم استفاده می‌کنند و در واقع جهت سرعت بالاتر نمایش و بررسی داده‌ها از طریق محیط SQL Server از فیلد کاراکتری تاریخ خورشیدی و برای مقایسه و بدست آوردن ساعت از فیلد نوع DateTime استفاده می‌کنند.

از نظر فضای اشغال‌شده نوع DataTime، هشت بایت، smalldatetime (در صورت استفاده) 4 بایت و فیلد 10 کاراکتری تاریخ 10 بایت فضا اشغال می‌کند در صورتی که نوع JalaliDate با درنظر گرفتن همه‌ی مزایای انواع داده‌ی استفاده‌شده برای تاریخ، فقط 8 بایت فضا اشغال می‌کند. با استفاده از این نوع به راحتی داده‌ی تاریخ را بر اساس تقویم ایرانی اعتبارسنجی می‌کنید و بخش‌های مختلف زمان از سال تا ثانیه را با یک متد به دست می‌آورید. می‌توانید به راحتی به تاریخ خود زمانی را بیفزایید یا بکاهید و در گزارش‌ها بدون نگرانی از تبدیل درست استفاده کنید. چون کدباز است می‌توانید با کمی حوصله امکانات دیگر مد نظر خود را به آن بیفزایید و از آن در SQL بهره ببرید.

چگونه این نوع داده را حذف کنم!؟
شما می‌توانید به سادگی نوع داده‌ی ایجادشده توسط CLR را در مسیر زیر بیابید و اقدام به حذف آن نمایید:

همان‌طور که مشاهده می‌شود؛ حتی نوع داده‌ی سیستمی hierarchyid که جهت ساختار سلسله‌مراتبی مانند چارت سازمانی یا درخت تجهیزات استفاده می‌شود؛ نیز یک نوع داده‌ی CLR است.

آیا راه دیگری نیز برای افزودن این نوع داده به SQL به جز Publish کردن وجود دارد؟
مانند بسیاری دیگر از گونه‌های پروژه، در اینجا نیز شما یک فایل DLL خواهید داشت. این فایل برپایه‌ی تنظیماتی که شما در قسمت Properties پروژه‌ی خود انجام می‌دهید ساخته می‌شود. پس از تغییر مسیر فایل DLL در دستور زیر توسط یک New Query از Database خود، آن را اجرا کنید:

CREATE ASSEMBLY JalaliDate
FROM 'F:\prgJalaliDate.dll' 
WITH PERMISSION_SET = SAFE;
هم‌چنین در صورت ویرایش‌های دوباره پروژه از دستور زیر استفاده کنید:
ALTER ASSEMBLY JalaliDate
FROM 'F:\prgJalaliDate.dll'
با استفاده از دستورهای زیر می‌توانید از چگونگی درج فایل‌های افزوده شده آگاه شوید:
select * from sys.assemblies
select * from sys.assembly_files
تا اینجا SQL Server، دی‌ال‌ال مربوط به پروژه را شناخته است. برای تعریف نوع داده از دستور زیر بهره ببرید:
CREATE TYPE dbo.JalaliDate 
EXTERNAL NAME JalaliDate.[JalaliDate];
این کار همانند استفاده از گزینه‌ی Publish در Visual Studio است.
هم‌چنین چنان‌چه در SQL Server 2012 از منوی راست‌کلیک پایگاه داده‌ها روی گزینه Tasks و سپس Generate Scripts را انتخاب کنیم، از مشاهده‌ی سند ساخته شده، درخواهیم یافت که حتی دستورهای مربوط به ساخت اسمبلی CLR با تبدیل فایل به کد در Scripts وجود دارد و با اجرای آن در سروری دیگر، انتقال می‌یابد.

GO
/****** Object:  SqlAssembly [prgJalaliDate]    Script Date: 2013/04/30 08:27:00 ب.ظ ******/
CREATE ASSEMBLY [prgJalaliDate]
FROM 0x4D5A90000300000004000000FFFF0000B8000000000000 ..... بقیه‌ی کدها حذف شده
WITH PERMISSION_SET = SAFE

GO
ALTER ASSEMBLY [prgJalaliDate]
ADD FILE FROM 0x4D6963726F736F667420432F432B2B204D534620372E30300D0A1A44530..... بقیه‌ی کدها حذف شده
AS N'prgJalaliDate.pdb'

GO
/****** Object:  UserDefinedType [dbo].[JalaliDate]    Script Date: 2013/04/30 08:27:00 ب.ظ ******/
CREATE TYPE [dbo].[JalaliDate]
EXTERNAL NAME [prgJalaliDate].[JalaliDate]

GO

دنباله دارد ...
مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
در حین کار با برنامه‌های وب، چشم‌پوشی از جاوا اسکریپت عملا ممکن نیست؛ هرچند با Blazor، امکان انجام کارهایی را یافته‌ایم که پیشتر با MVC و یا Razor pages میسر نبودند، اما هیچگاه به تنهایی نمی‌تواند جایگزین کامل جاوا اسکریپت، در تولید برنامه‌های وب باشد. بنابراین ضروری است که نحوه‌ی یکپارچگی جاوا اسکریپت را با برنامه‌های مبتنی بر Blazor، بررسی کنیم.


ایجاد کامپوننت جدید BlazorJS

برای بررسی نحوه‌ی تعامل جاوا اسکریپت و Blazor، در ابتدا کامپوننت جدید Pages\LearnBlazor\BlazorJS.razor را ایجاد کرده:
@page "/BlazorJS"

<h3>BlazorJS</h3>

@code
{
}
و همچنین مدخل منوی آن‌را نیز بر اساس مسیریابی ابتدای فایل این کامپوننت، به فایل Shared\NavMenu.razor اضافه می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="BlazorJS">
        <span class="oi oi-list-rich" aria-hidden="true"></span> BlazorJS
    </NavLink>
</li>


روش فراخوانی کدهای جاوا اسکریپتی از طریق کدهای سی‌شارپ Blazor

فرض کنید می‌خواهیم در حین کلیک بر روی دکمه‌ای مانند دکمه‌ی حذف، ابتدا تائیدیه‌ای را توسط تابع confirm جاوا اسکریپتی، از کاربر اخذ کنیم. روش انجام چنین کاری در برنامه‌های مبتنی بر Blazor به صورت زیر است:
@page "/BlazorJS"

@inject IJSRuntime JsRuntime

<h3>BlazorJS</h3>

<div class="row">
    <button class="btn btn-secondary" @onclick="TestConfirmBox">Test Confirm Button</button>
</div>
<div class="row">
    @if (ConfirmResult)
    {
        <p>Confirmation has been made!</p>
    }
    else
    {
        <p>Confirmation Pending!</p>
    }
</div>

@code {
    string ConfirmMessage = "Are you sure you want to click?";
    bool ConfirmResult;

    async Task TestConfirmBox()
    {
        ConfirmResult = await JsRuntime.InvokeAsync<bool>("confirm", ConfirmMessage);
    }
}
توضیحات:
- در اینجا می‌خواهیم تابع استاندارد confirm جاوا اسکریپتی را از طریق کدهای سی‌شارپ، با کلیک بر روی دکمه‌ی Test Confirm Button، فراخوانی کنیم. به همین جهت onclick@ این دکمه، به متد TestConfirmBox کدهای UI سی‌شارپ این کامپوننت، متصل شده‌است.
- برای دسترسی به توابع جاوا اسکریپتی، نیاز است سرویس توکار IJSRuntime را به کدهای کامپوننت تزریق کنیم که روش انجام آن‌را توسط دایرکتیو inject@ مشاهده می‌کنید. برای دسترسی به این سرویس توکار، نیاز به تنظیمات ابتدایی خاصی نیست و اینکار پیشتر انجام شده‌است.
- سرویس JsRuntime تزریق شده، دو متد مهم InvokeVoidAsync و InvokeAsync را جهت فراخوانی توابع جاوا اسکریپتی به همراه دارد. اگر تابعی، خروجی غیر void داشته باشد، باید از متد InvokeAsync استفاده کرد. برای مثال خروجی تابع استاندارد confirm، از نوع boolean است. بنابراین نوع این خروجی را به صورت یک آرگومان جنریک متد InvokeAsync مشخص کرده‌ایم.
- اولین پارامتر متد InvokeAsync، نام رشته‌ای تابع جاوا اسکریپتی است که قرار است صدا زده شود. پارامترهای اختیاری بعدی که به صورت params object?[]? args تعریف شده‌اند، لیست نامحدود آرگومان‌های ورودی این متد هستند.
- فیلد ConfirmMessage، پیامی را جهت اخذ تائید، تعریف می‌کند که به عنوان پارامتر متد confirm، توسط JsRuntime.InvokeAsync فراخوانی خواهد شد.
- فیلد ConfirmResult، نتیجه‌ی فراخوانی متد confirm جاوا اسکریپتی را به همراه دارد.
- در اینجا روش عکس العمل نشان دادن به خروجی دریافتی از متد جاوااسکریپتی را نیز مشاهده می‌کنید. پس از پایان متد TestConfirmBox که یک متد رویدادگران است، همانطور که در مطلب بررسی «چرخه‌ی حیات کامپوننت‌ها» نیز بررسی کردیم، متد StateHasChanged، در پشت صحنه فراخوانی می‌شود که سبب رندر مجدد UI خواهد شد. بنابراین در حین رندر مجدد UI، بر اساس مقدار جدید ConfirmResult دریافت شده‌ی از کاربر، با تشکیل یک if/else@، می‌توان به نتیجه‌ی تائید یا عدم تائید کاربر، واکنش نشان داد. با این توضیحات در اولین بار نمایش کامپوننت جاری چون مقدار ConfirmResult مساوی false است، پیام زیر را مشاهده می‌کنیم:


اما در ادامه با کلیک بر روی دکمه و تائید پیام ظاهر شده، عبارت زیر ظاهر می‌شود:



روش افزودن یک کتابخانه‌ی خارجی جاوا اسکریپتی به پروژه‌های Blazor

فرض کنید می‌خواهیم پیام‌های برنامه را توسط کتابخانه‌ی معروف جاوا اسکریپتی Toastr نمایش دهیم؛ با این دمو.
مرحله‌ی اول کار با این کتابخانه، دریافت فایل‌های CSS و JS آن است. برای این منظور قصد داریم از برنامه‌ی مدیریت بسته‌های LibMan استفاده کنیم:
dotnet tool install -g Microsoft.Web.LibraryManager.Cli
libman init
libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap
libman install jquery --provider unpkg --destination wwwroot/lib/jquery
libman install toastr --provider unpkg --destination wwwroot/lib/toastr
بنابراین خط فرمان را در ریشه‌ی پروژه گشوده و پنج دستور فوق را اجرا می‌کنیم. دستور اول، ابزار خط فرمان LibMan را نصب می‌کند. دستور دوم، یک فایل libman.json خالی را در این پوشه ایجاد می‌کند و سه دستور بعدی، جی‌کوئری، بوت استرپ و toastr را دریافت و در پوشه‌ی wwwroot/lib قرار می‌دهند. Toastr برای اجرا، نیاز به jQuery نیز دارد.
البته تعاریف مداخل آن‌ها به فایل libman.json نیز اضافه می‌شوند. مزیت آن، اجرای دستور libman restore برای بازیابی و نصب مجدد تمام بسته‌های ذکر شده‌ی در فایل libman.json است.

پس از دریافت بسته‌های سمت کلاینت آن، مداخل مرتبط را به فایل Pages\_Host.cshtml برنامه‌ی Blazor Server اضافه خواهیم کرد (و یا در فایل wwwroot/index.html برنامه‌های Blazor WASM).
<head>
    <base href="~/" />
    <link rel="stylesheet" href="lib/toastr/build/toastr.min.css" />

</head>
<body>
 
    <script src="lib/jquery/dist/jquery.min.js"></script>
    <script src="lib/toastr/build/toastr.min.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>
مدخل فایل css آن‌را در قسمت head و فایل js آن‌را پیش از بسته شدن تگ body تعریف می‌کنیم. در اینجا نیازی به ذکر پوشه‌ی آغازین wwwroot نیست؛ چون base href تعریف شده، به این پوشه اشاره می‌کند.

یک نکته: می‌توان فایل csproj برنامه را به صورت زیر تغییر داد تا کار اجرای دستور libman restore را قبل از build، به صورت خودکار انجام دهد:
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <Target Name="DebugEnsureLibManEnv" BeforeTargets="BeforeBuild" Condition=" '$(Configuration)' == 'Debug' ">
    <!-- Ensure libman is installed -->
    <Exec Command="libman --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="libman is required to build and run this project. To continue, please run `dotnet tool install -g Microsoft.Web.LibraryManager.Cli`, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'libman'. This may take several minutes..." />
    <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="libman restore" />
  </Target>
</Project>


روش فراخوانی یک کتابخانه‌ی خارجی جاوا اسکریپتی در پروژه‌های Blazor

پس از افزودن فایل‌های سمت کلاینت toastr و تعریف مداخل آن در فایل Pages\_Host.cshtml برنامه‌ی Blazor Server جاری، اکنون می‌خواهیم از این کتابخانه استفاده کنیم. یک روش کار با این نوع کتابخانه‌های عمومی و سراسری به صورت زیر است:
- ابتدا فایل خالی جدید wwwroot\js\common.js را ایجاد می‌کنیم.
- سپس تابع عمومی و سراسری ShowToastr را بر اساس امکانات کتابخانه‌ی toastr و مستندات آن، به صورت زیر ایجاد می‌کنیم:
window.ShowToastr = (type, message) => {
  // Toastr don't work with Bootstrap 4.2
  toastr.options.toastClass = "toastr"; // https://github.com/CodeSeven/toastr/issues/599

  if (type === "success") {
    toastr.success(message, "Operation Successful", { timeOut: 20000 });
  }
  if (type === "error") {
    toastr.error(message, "Operation Failed", { timeOut: 20000 });
  }
};
چون تابع ShowToastr به شیء window انتساب داده شده‌است، در سراسر برنامه‌ی جاری قابل دسترسی است.
سطر اول آن هم برای رفع عدم تداخل با بوت استرپ 4x اضافه شده‌است. بوت استرپ 4x به همراه کلاس‌های CSS مشابهی است که نیاز است با تنظیم toastClass به مقداری دیگر، این تداخل را برطرف کرد.

- در ادامه مدخل تعریف فایل wwwroot\js\common.js را به انتهای تگ body فایل Pages\_Host.cshtml اضافه می‌کنیم:
    <script src="lib/jquery/dist/jquery.min.js"></script>
    <script src="lib/toastr/build/toastr.min.js"></script>
    <script src="js/common.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>

در آخر برای آزمایش آن به کامپوننت Pages\LearnBlazor\BlazorJS.razor مراجعه کرده و تابع سراسری ShowToastr را دقیقا مانند روشی که در مورد تابع confirm بکار بردیم، توسط سرویس JsRuntime، فراخوانی می‌کنیم:
@page "/BlazorJS"

@inject IJSRuntime JsRuntime


<div class="row">
    <button class="btn btn-success" @onclick="@(()=>TestSuccess("Success Message"))">Test Toastr Success</button>
    <button class="btn btn-danger" @onclick="@(()=>TestFailure("Error Message"))">Test Toastr Failure</button>
</div>

@code {
    async Task TestSuccess(string message)
    {
        await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message);
    }

    async Task TestFailure(string message)
    {
        await JsRuntime.InvokeVoidAsync("ShowToastr", "error", message);
    }
}
در اینجا دو دکمه، جهت فراخوانی متد ShowToastr با پارامترهای مختلفی تعریف شده‌اند. چون تابع ShowToastr خروجی ندارد، به همین جهت اینبار از متد InvokeVoidAsync استفاده کرده‌ایم. پارامتر اول آن، نام متد ShowToastr است. پارامتر‌های دوم و سوم آن با آرگومان‌های (type, message) تعریف شده‌ی تابع ShowToastr تطابق دارند. به علاوه در این مثال، روش ارسال پارامترها را نیز در onlick@ توسط arrow functions مشاهده می‌کنید.



کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی

می‌توان جهت کاهش تکرار کدهای استفاده از تابع ShowToastr، متدهای الحاقی زیر را برای سرویس IJSRuntime تهیه کرد:
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace BlazorServerSample.Utils
{
    public static class JSRuntimeExtensions
    {
        public static ValueTask ToastrSuccess(this IJSRuntime JSRuntime, string message)
        {
            return JSRuntime.InvokeVoidAsync("ShowToastr", "success", message);
        }

        public static ValueTask ToastrError(this IJSRuntime JSRuntime, string message)
        {
            return JSRuntime.InvokeVoidAsync("ShowToastr", "error", message);
        }
    }
}
و سپس فضای نام آن‌را به فایل Imports.razor_ معرفی نمود تا در تمام کامپوننت‌های برنامه قابل استفاده شوند.
@using BlazorServerSample.Utils
به این ترتیب به فراخوانی‌های ساده شده‌ی زیر خواهیم رسید:
async Task TestSuccess(string message)
{
   //await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message);
   await JsRuntime.ToastrSuccess(message);
}


فراخوانی یک متد عمومی واقع در کامپوننت فرزند از طریق کامپوننت والد

فرض کنید در کامپوننت فرزند Pages\LearnBlazor\LearnBlazor‍Components\ChildComponent.razor که در قسمت‌های قبل آن‌را تکمیل کردیم، متد عمومی زیر تعریف شده‌است:
@inject IJSRuntime JsRuntime


@code {
    // ...

    public async Task TestSuccess(string message)
    {
        await JsRuntime.ToastrSuccess(message);
    }
}
اکنون اگر بخواهیم این متد عمومی را از طریق کامپوننت والد یا دربرگیرنده‌ی آن فراخوانی کنیم، نیاز است از مفهوم جدیدی به نام ref استفاده کرد. برای این منظور به کامپوننت Pages\LearnBlazor\ParentComponent.razor مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
@page "/ParentComponent"

<ChildComponent
    OnClickBtnMethod="ShowMessage"
    @ref="ChildComp"
    Title="This title is passed as a parameter from the Parent Component">
    <ChildContent>
        A `Render Fragment` from the parent!
    </ChildContent>
    <DangerChildContent>
        A danger content from the parent!
    </DangerChildContent>
</ChildComponent>

<div class="row">
    <button class="btn btn-success" @onclick="@(()=>ChildComp.TestSuccess("Done!"))">Show Alert</button>
</div>


@code {
    ChildComponent ChildComp;
    // ...
}
با استفاده از ref@ که به فیلد ChildComp انتساب داده شده‌است، می‌توان ارجاعی از کامپوننت فرزند را (وهله‌ای از کلاس مرتبط با آن‌را) در کامپوننت جاری بدست آورد و سپس از آن جهت فراخوانی متدهای عمومی کامپوننت فرزند استفاده کرد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-11.zip
مطالب
کش کردن اطلاعات غیر پویا در ASP.Net - قسمت دوم

قسمت قبل به IIS7‌ اختصاص داشت که شاید برای خیلی‌ها کاربرد نداشته باشد خصوصا اینکه برنامه نویس‌ها ترجیح می‌دهند به روش‌هایی روی بیاورند که کمتر نیاز به دخالت مدیر سرور داشته باشد؛ یا زمانیکه سایت شما بر روی یک هاست اینترنتی قرار گرفته است عملا شاید دسترسی خاصی به تنظیمات IIS نداشته باشید (مگر اینکه یک هاست اختصاصی را تهیه کنید).
برای IIS6 و ماقبل از آن و حتی بعد از آن!، حداقل دو روش برای کش کردن اطلاعات استاتیک وجود دارد:

الف) استفاده از web resources معرفی شده در ASP.Net 2.0 به بعد
در مورد نحوه‌ی تعریف و بکارگیری web resources می‌توان به مقاله "تبدیل پلاگین‌های jQuery‌ به کنترل‌های ASP.Net" رجوع کرد.


همانطور که در شکل فوق نیز ملاحظه می‌کنید، هدر مربوط به مدت زمان منقضی شدن کش سمت کلاینت یک web resource توسط موتور ASP.Net به صورت خودکار به سال 2010 تنظیم شده است و این مقدار خالی نیست.

ب) افزودن این هدر به صورت دستی

برای این منظور باید در نحوه‌ی ارائه فایل‌های استاتیک دخالت کنیم و این‌کار را با استفاده از یک generic handler می‌توان انجام داد.


کد این generic handler می‌تواند به صورت زیر باشد:

using System;
using System.IO;
using System.Web;
using System.Web.Services;
using System.Reflection;

namespace test1
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class cache : IHttpHandler
{

private static void cacheIt(TimeSpan duration)
{
HttpCachePolicy cache = HttpContext.Current.Response.Cache;

FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, duration);

cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(duration));
cache.SetMaxAge(duration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}

public void ProcessRequest(HttpContext context)
{
string file = context.Request.QueryString["file"];
if (string.IsNullOrEmpty(file))
{
return;
}

string contetType = context.Request.QueryString["contetType"];
if (string.IsNullOrEmpty(contetType))
{
return;
}

context.Response.Write(File.ReadAllText(context.Server.MapPath(file)));

//Set the content type
context.Response.ContentType = contetType;

// Cache the resource for 30 Days
cacheIt(TimeSpan.FromDays(30));
}

public bool IsReusable
{
get
{
return false;
}
}
}
}
توضیحات:
این generic handler دو کوئری استرینگ را دریافت می‌کند؛ file جهت دریافت نام فایل و contetType جهت مشخص سازی نوع محتوایی که باید سرو شود؛ مثلا جاوا اسکریپت یا استایل شیت و امثال آن. سپس زمانیکه محتوا را Response.Write می‌کند، هدر مربوط به کش شدن آن‌را نیز به 30 روز تنظیم می‌نماید.
تابع مربوط به کش کردن اطلاعات از مقاله ASP.NET Ajax Under-the-hood Secrets استخراج شد.

روش استفاده در مورد فایل‌های CSS
بجای تعریف یک فایل CSS در صفحه، به صورت استاندارد، اکنون تعریف متداول را به صورت زیر اصلاح کنید:

<link type="text/css" href="cache.ashx?v=1&file=site.css&contetType=text/css" rel="Stylesheet" />
هر زمانیکه که فایل site.css درخواست می‌شود، باید از فیلتر ما عبور کند و سپس ارائه گردد. در این حین، هدر مربوط به مدت زمان کش شدن سمت کلاینت به آن اضافه می‌شود. از کوئری استرینگ مربوط v هم جهت به روز رسانی‌های بعدی استفاده می‌شود تا اگر تغییری را اعمال کردیم، کلاینت حتما با توجه به آدرس جدید، محتویات جدید را یکبار دیگر دریافت کند. (مرورگر آدرس‌های مشابه را در صورتیکه هدر مربوط به کش شدن آن‌ها تنظیم شده باشد، از کش خواهد خواند و کاری به آخرین تغییرات شما در سرور ندارد)

روش استفاده در مورد فایل‌های JS
<script type="text/javascript" src="cache.ashx?v=1&file=js/jquery-1.3.2.min.js&contetType=application/x-javascript"></script>
اکنون اگر سایت را مجددا با افزونه YSlow بررسی کنیم، می‌توان این هدر جدید را مشاهده کرد:



نظرات مطالب
معرفی Kendo UI
کد زیر رو در ویو اضافه کردم و فایل جی کوئری هم در layout وجود داره
@section JavaScript
{
    
 <!--KendoUI: Web-->
<link href="@Url.Content("~/content/css/kendo/kendo.common.min.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/content/css/kendo/kendo.default.min.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/kendo/kendo.web.min.js")" type="text/javascript"></script>

      <!--KendoUI: DataViz-->
<link href="@Url.Content("~/content/css/kendo/kendo.dataviz.min.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/kendo/kendo.dataviz.min.js")" type="text/javascript"></script>

<!--KendoUI: Mobile-->
<link href="@Url.Content("~/content/css/kendo/kendo.mobile.all.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/kendo/kendo.mobile.min.js")" type="text/javascript"></script>

 <script type="text/javascript">
    $(function () {
        $("#pickDate").kendoDatePicker();
    });
</script>



}

جی کوئری یکبار صدا زده شده ولی متاسفانه خطای زیر رو میده:
TypeError: $(...).kendoDatePicker is not a function

چه مواردی رو باید برای رفع خطا بررسی کنم؟