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

سوال: چگونه این فایل را در Jcenter آپلود کنیم؟
فرآیندی که در این نوشتار قصد داریم دنبال شود شامل مراحل زیر است:
ابتدا کتابخانه‌ی خودمان را روی جی سنتر قرار داده و در صورتیکه علاقه داشته باشیم، آن را به mavenCentral هم انتقال می‌دهیم.

ابتدا نیاز است در سایت bintray ثبت نام کنید و با حساب جدید وارد شوید و گزینه‌ی maven را انتخاب کنید.

سپس روی گزینه‌ی Add New Package کلیک کنید تا یک پکیج جدید را ایجاد کنیم.

در صفحه‌ای که باز می‌شود، اطلاعات مربوط به این پکیج را وارد کنید که عموما شامل نام پکیج، مجوز آن، کلمات کلیدی، لینک گزارش باگ و .. می‌شود. در انتخاب نام پکیج، قانون اجباری یا خاصی وجود ندارد؛ ولی توصیه می‌شود که از حروف کوچک و - استفاده گردد. بعد از پرکردن فیلدهای الزامی، وارد صفحه‌ی جزئیات پکیج می‌شوید که در آن فیلدهای اضافه‌تری نیز وجود دارند که میتوانید در صورت تمایل آن‌ها را پر کنید. همچنین در بالای صفحه لینک به صفحه‌ی اختصاصی این پکیج نیز وجود دارد که در زیر عبارت Edit Package قرار گرفته است.

وارد صفحه‌ی اختصاصی پکیج شوید، می‌بینید که قسمت‌های مختلفی در اختیار شما قرار گرفته است و این مخزن الان آماده و پذیرای کتابخانه‌ی شماست. تا به اینجا، مرحله‌ی کار با jcenter تمام شد. حال به سایت SonaType می‌رویم و در آن هم ثبت نام می‌کنیم.


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

سوال: چگونه این فایل را در SonaType آپلود کنیم؟ 
گام اول: ابتدا باید در سایت ثبت نام کنید. پس به این صفحه رفته و ثبت نام کنید. سپس در یک مرحله‌ی غیرمنطقی باید یک issue توسط سیستم JIRA ایجاد کنید. برای همین گزینه‌ی Creare را در بالای صفحه بزنید. اطلاعات زیر را به ترتیب پر کنید:
Project: Community Support - Open Source Project Repository Hosting

Issue Type: New Project

Summary: مثلا نام پروژه خودتان را بنویسید

یک نام پکیج که سعی کنید کتابخانه‌های هم خانواده این اشتراک را داشته باشند که در یک گروه قرار بگیرند
Group Id: AndroidBreadCrumb.Plus

آدرس جایی که پروژه قرار دارد
Project URL: https://github.com/yeganehaym/AndroidBreadCrumb

//آدرس سیستم کنترل نسخه
SCM url: https://github.com/yeganehaym/AndroidBreadCrumb
بقیه‌ی موارد الزامی نیست. کادر را تایید کنید. در این مرحله باید مدتی منتظر بمانید تا این مخزن را برای شما تایید کنند که طبق مستندات حدود یک هفته‌ای طول می‌کشد. بعد از اینکار باید نام کاربری اکانت خود را در پروفایل سایت Bintray  در برگه‌ی Accounts  قسمت sonatype oss account وارد کنید و پروفایل را آپدیت کنید.

فعال سازی امضای خودکار در Bintray

همانطور که در ابتدای مقاله گفتیم، می‌خواهیم کتابخانه‌ی خود را از طریق jcenter به maven ارسال کنیم. برای همین نیاز داریم که ابتدا کتابخانه‌ی خود را امضا کنیم. برای اینکار باید از طریق GPG یک کلید بسازیم. ساخت کلید به این شیوه، قبلا در مقاله‌ی «ساخت کلیدهای امنیتی با GnuPG» توضیح داده شد و از تکرار آن خودداری می‌کنیم. تنها به ذکر این نکته بسنده می‌کنیم که شما باید یک کلید ساخته و آن را به سرور کلیدها ارسال کنید و سپس کلید متنی عمومی و خصوصی آن را در پروفایل bintray برگه‌ی GPG Signing درج کنید.


به عنوان آخرین کار باید امضای خودکار را فعال کنید. بنابراین نیاز است که به صفحه‌ی اصلی رفته و بر روی maven کلیک کنید و گزینه‌ی Edit را انتخاب کنید. در صفحه‌ی باز شده، تیک GPG sign uploaded files automatically را بزنید.

این تنظیم از این پس بر روی تمامی کتابخانه‌ها اعمال می‌شود.


سوال : چگونه پروژه‌ی اندرویدی خودم را کامپایل کنم؟


فایل  build.gradle پروژه را باز کنید و پلاگین bintray را به آن معرفی کنید:
 dependencies {
        classpath 'com.android.tools.build:gradle:1.2.2'
        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2'
        classpath 'com.github.dcendents:android-maven-plugin:1.2'
    }
بسیار مهم است که نسخه‌ی gradle build tools از نسخه‌ی 1.1.2 به بعد باشد. چون که در نسخه‌های پایین‌تر، یک باگ بسیار حیاتی یافت شده، که از این نسخه به بعد رفع گردیده است. حال فایل local.properties را باز کنید و اطلاعات زیر را وارد کنید:
bintray.user=YOUR_BINTRAY_USERNAME
bintray.apikey=YOUR_BINTRAY_API_KEY
bintray.gpg.password=YOUR_GPG_PASSWORD
نام کاربری که برای اکانت bintray انتخاب کردید را بنویسید. برای ApiKey در قسمت Edit Profile برگه‌ی Api Key در خط دوم می‌توانید آن را ببینید و کلمه‌ی عبور GPG هم همان عبارتی است که در انتهای ساخت کلید دوبار از شما پرسیده شده است.  (در صورتی که اقدامات لازم برای mavenCentral را انجام داده اید خط سوم را  وارد کنید)

در مرحله‌ی بعدی خطوط زیر را بعد از 'Apply Plugin 'com.android.library اضافه کنید و اطلاعاتی که در bintray وارد کرده‌اید را در اینجا وارد کنید:
apply plugin: 'com.android.library'

ext {
    bintrayRepo = 'maven'
    bintrayName = 'AndroidBreadCrumb'

    publishedGroupId = 'com.plus'
    libraryName = 'AndroidBreadCrumb'
    artifact = 'AndroidBreadCrumb'

    libraryDescription = 'create breadcrumb on android to show a path to user and let user to jump on them'

    siteUrl = 'https://github.com/yeganehaym/AndroidBreadCrumb'
    gitUrl = 'https://github.com/yeganehaym/AndroidBreadCrumb'

    libraryVersion = '1.0'

    developerId = 'yeganehaym'
    developerName = 'ali yeganeh.m'
    developerEmail = 'yeganehaym@gmail.com'

    licenseName = 'The Apache Software License, Version 2.0'
    licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
    allLicenses = ["Apache-2.0"]
}
بعد از آن هم در انتهای فایل گریدل ماژول، دو خط زیر را که شامل اسکریپت‌هایی برای راحتی کار است، معرفی کنید:
apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
با اطلاعاتی که بالا تعریف کردیم، کاربر باید شناسه‌ای به شکل زیر در گریدل به کار ببرد تا به کتابخانه‌ی ما دسترسی داشته باشد:
compile 'com.plus:AndroidBreadCrumb:1.0'

آپلود فایل‌ها به مخزن
برای آپلود فایل‌های ماژول به مخزن، ابتدا ترمینال اندروید استودیو را باز کنید و گام‌های زیر را به ترتیب انجام بدهید:
گام اول: با ارسال دستور زیر از صحت کدها و منابع مطمئن می‌شویم:
gradlew install
در صورتیکه خطایی نباشد، پیام موفقیت آمیزی را به شما نمایش می‌دهد:
BUILD SUCCESSFUL
حالا باید فایل‌ها را به سمت سرور ارسال کنیم:
gradlew bintrayUpload
در صورتی که عملیات موفقیت آمیز باشد پیام زیر نمایش می‌یابد:
SUCCESSFUL

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



و قسمت فایل‌ها هم دیگر خالی نیست:




با اینکه کتابخانه‌ی ما روی maven قرار گرفت، ولی هنوز نمی‌توان آن را توسط jcenter استفاده کرد و باید bintray maven را با jcenter هماهنگ نماییم. در حال حاضر استفاده از این کتابخانه بدون سینک به شکل زیر است:

گریدل پروژه
     maven{
            url 'https://dl.bintray.com/yeganehaym/maven'
        }

گریدل ماژول

dependencies {
    compile 'com.plus:AndroidbreadCrumb:1.0'
}
آدرس اولی را می‌توانید با نگاه در صفحه‌ی اختصاصی پکیج ببینید که لینک آن در جلوی نام پکیج وجود دارد و قابلیت کپی کردن لینک هم وجود دارد. اگر روی آن کلیک کنید، می‌توانید مسیر را در صفحه‌ی باز شده ببینید. البته راه مستقیم‌تر اینکه به جای yeganehaym در لینک بالا، نام کاربری خودتان را جایگزین کنید.

برای افزودن کتابخانه‌ی خود به سیستم jcenter با کلیک بر روی گزینه‌ی Add to jcenter می‌توانید به تیم jcenter درخواست دهید که آن را تایید کنند که بعد از درخواست حدود سه ساعت طول می‌کشد تا پاسخ شما را بدهند.




به این ترتیب دیگر نیازی به تعریف یک url به maven نخواهد بود.

برای دیدن این کتابخانه در صفحه jcenter به ترتیب شناسه‌های Group_ID.Artifact.version را دنبال کنید، یعنی برای ما می‌شود:

com/plus/androidbreadcrumb/1.0
نکته اول : این نکته را به یاد داشته باشید که عمل سینک تنها یکبار اتفاق می‌افتد و در زمان‌های بعد، هر تغییری مثل حذف، به روز آوری و ... مستقیما در jcenter بعد از چند دقیقه اعمال می‌شود.

نکته دوم: در صورتی که پکیج خودتان را حذف کنید، چیزی از روی jcenter حذف نمی‌شود. فقط به یاد داشته باشید که برای حذف آن باید ابتدا نسخه‌های مختلف آپلود شده را حذف کنید تا پکیج از جی سنتر هم حذف شود.


ارسال کتابخانه به mavenCentra
 در این مرحله قصد داریم که این کتابخانه را بر روی mavenCentral هم داشته باشیم. اگر قصدش را ندارید از اینجا به بعد را نیازی نیست انجام بدهید و برای اینکار لازم است همه‌ی مراحل بالا انجام گرفته باشد.
قبل از اینکه این عمل ارسال انجام گیرد، باید دو عمل زیر از قبل صورت گرفته باشند:
  1. پکیج شما در jcenter تایید شده باشد.
  2. با مخزن شما در sonatype موافقت شده باشد.

در صورتیکه دو مرحله‌ی بالا صورت گرفته باشند، در صفحه‌ی پکیج اختصاصی، بر روی گزینه‌ی mavenCentral کلیک کنید:

پس از آن باید نام کاربری و کلمه‌ی عبورتان را در SonaType، وارد کنید و گزینه‌ی sync را بفشارید:

در صورتیکه پیام موفقیت در سینک را بدهد، پکیج شما منتقل شده‌است. در غیر این صورت خطای آن را اعلام می‌کند و باید برای رفع آن تلاش کنید تا خطاها از بین بروند. برای اینکه بتوانید این پکیج را در لیست mavenCentral ببینید، مثل همان چیزی که در بالاتر گفته شد، شناسه‌ی گریدل را دنبال کنید.

مطالب دوره‌ها
نگاهی به SignalR Hubs
Hubs کلاس‌هایی هستند جهت پیاده سازی push services در SignalR و همانطور که در قسمت قبل عنوان شد، در سطحی بالاتر از اتصال ماندگار (persistent connection) قرار می‌گیرند. کلاس‌های Hubs بر مبنای یک سری قرار داد پیش فرض کار می‌کنند (ایده Convention-over-configuration) تا استفاده نهایی از آن‌ها را ساده‌تر کنند.
Hubs به نوعی یک فریم ورک سطح بالای RPC نیز محسوب می‌شوند (Remote Procedure Calls) و آن‌را برای انتقال انواع و اقسام داده‌ها بین سرور و کلاینت و یا فراخوانی متدی در سمت کلاینت یا سرور، بسیار مناسب می‌سازد. برای مثال اگر قرار باشد با persistent connection به صورت مستقیم کار کنیم، نیاز است تا بسیاری از مسایل serialization و deserialization اطلاعات را خودمان پیاده سازی و اعمال نمائیم.


قرار دادهای پیش فرض Hubs

- متدهای public کلاس‌های Hubs از طریق دنیای خارج قابل فراخوانی هستند.
- ارسال اطلاعات به کلاینت‌ها از طریق فراخوانی متدهای سمت کلاینت انجام خواهد شد. (نحوه تعریف این متدها در سمت سرور بر اساس قابلیت‌های dynamic اضافه شده به دات نت 4 است که در ادامه در مورد آن بیشتر بحث خواهد شد)


مراحل اولیه نوشتن یک Hub
الف) یک کلاس Hub را تهیه کنید. این کلاس، از کلاس پایه Hub تعریف شده در فضای نام Microsoft.AspNet.SignalR باید مشتق شود. همچنین این کلاس می‌تواند توسط ویژگی خاصی به نام HubName نیز مزین گردد تا در حین برپایی اولیه سرویس، از طریق زیرساخت‌های SignalR به نامی دیگر (یک alias یا نام مستعار خاص) قابل شناسایی باشد. متدهای یک هاب می‌توانند نوع‌های ساده یا پیچیده‌ای را بازگشت دهند و همه چیز در اینجا نهایتا به فرمت JSON رد و بدل خواهد شد (فرمت پیش فرض که در پشت صحنه از کتابخانه معروف JSON.NET استفاده می‌کند؛ این کتابخانه سورس باز به دلیل کیفیت بالای آن، از زمان ارائه MVC4 به عنوان جزئی از مجموعه کارهای مایکروسافت قرار گرفته است).
ب) مسیریابی و Routing را تعریف و اصلاح نمائید.
و ... از نتیجه استفاده کنید.


تهیه اولین برنامه با SignalR

ابتدا یک پروژه خالی ASP.NET را آغاز کنید (مهم نیست MVC باشد یا WebForms). برای سادگی بیشتر، در اینجا یک ASP.NET Empty Web application درنظر گرفته شده است. در ادامه قصد داریم یک برنامه Chat را تهیه کنیم؛ از این جهت که توسط یک برنامه Chat بسیاری از مفاهیم مرتبط با SignalR را می‌توان در عمل توضیح داد.
اگر از VS 2012 استفاده می‌کنید، گزینه SignalR Hub class جزئی از آیتم‌های جدید قابل افزودن به پروژه است (منوی پروژه، گزینه new item آن) و پس از انتخاب این قالب خاص، تمامی ارجاعات لازم نیز به صورت خودکار به پروژه جاری اضافه خواهند شد.


و اگر از VS 2010 استفاده می‌کنید، نیاز است از طریق NuGet ارجاعات لازم را به پروژه خود اضافه نمائید:
 PM> Install-Package Microsoft.AspNet.SignalR
اکنون یک کلاس خالی جدید را به نام ChatHub، به آن اضافه کنید. سپس کدهای آن را به نحو ذیل تغییر دهید:
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalR02
{
    [HubName("chat")]
    public class ChatHub : Hub
    {
        public void SendMessage(string message)
        {
            Clients.All.hello(message);
        }
    }
}
همانطور که ملاحظه می‌کنید این کلاس از کلاس پایه Hub مشتق شده و توسط ویژگی HubName، نام مستعار chat را یافته است.
کلاس پایه Hub یک سری متد و خاصیت را در اختیار کلاس‌های مشتق شده از آن قرار می‌دهد. ساده‌ترین راه برای آشنایی با این متدها و خواص مهیا، کلیک راست بر روی نام کلاس پایه Hub و انتخاب گزینه Go to definition است.
برای نمونه در کلاس ChatHub فوق، از خاصیت Clients برای دسترسی به تمامی آن‌ها و سپس فراخوانی متد dynamic ایی به نام hello که هنوز وجود خارجی ندارد، استفاده شده است.
اهمیتی ندارد که این کلاس در اسمبلی اصلی برنامه وب قرار گیرد یا مثلا در یک class library به نام Services. همینقدر که از کلاس Hub مشتق شود به صورت خودکار در ابتدای برنامه اسکن گردیده و یافت خواهد شد.

مرحله بعد، افزودن فایل global.asax به برنامه است. زیرا برای کار با SignalR نیاز است تنظیمات Routing و مسیریابی خاص آن‌را اضافه نمائیم. پس از افرودن فایل global.asax، به فایل Global.asax.cs مراجعه کرده و در متد Application_Start آن تغییرات ذیل را اعمال نمائید:
using System;
using System.Web;
using System.Web.Routing;

namespace SignalR02
{
    public class Global : HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            // Register the default hubs route: ~/signalr
            RouteTable.Routes.MapHubs();
        }
    }
}

یک نکته مهم
 اگر از ASP.NET MVC استفاده می‌کنید، این تنظیم مسیریابی باید پیش از تعاریف پیش فرض موجود قرار گیرد. در غیراینصورت مسیریابی‌های SignalR کار نخواهند کرد.

اکنون برای آزمایش برنامه، برنامه را اجرا کرده و مسیر ذیل را فراخوانی کنید:
 http://localhost/signalr/hubs
در این حال اگر برنامه را برای مثال با مرورگر chrome باز کنید، در این آدرس، فایل جاوا اسکریپتی SignalR، قابل مشاهده خواهد بود. مرورگر IE پیغام می‌دهد که فایل را نمی‌تواند باز کند. اگر به انتهای خروجی آدرس مراجعه کنید، چنین سطری قابل مشاهده است:
  proxies.chat = this.createHubProxy('chat');
و کلمه chat دقیقا از مقدار معرفی شده توسط ویژگی HubName دریافت گردیده است.

تا اینجا ما موفق شدیم اولین Hub خود را تشکیل دهیم.


بررسی پروتکل Hub

اکنون که اولین Hub خود را ایجاد کرده‌ایم، بد نیست اندکی با زیر ساخت آن نیز آشنا شویم.
مطابق مسیریابی تعریف شده در Application_Start، مسیر ابتدایی دسترسی به SignalR با افزودن اسلش SignalR به انتهای مسیر ریشه سایت بدست می‌آید و اگر به این آدرس یک اسلش hubs را نیز اضافه کنیم، فایل js metadata مرتبط را نیز می‌توان دریافت و مشاهده کرد.

زمانیکه یک کلاینت قصد اتصال به یک Hub را دارد، دو مرحله رخ خواهد داد:
الف) negotiate: در این حالت امکانات قابل پشتیبانی از طرف سرور مورد پرسش قرار می‌گیرند و سپس بهترین حالت انتقال، انتخاب می‌گردد. این انتخاب‌ها به ترتیب از چپ به راست خواهند بود:
 Web socket -> SSE -> Forever frame -> long polling


به این معنا که اگر برای مثال امکانات Web sockets مهیا بود، در همینجا کار انتخاب نحوه انتقال اطلاعات خاتمه یافته و Web sockets انتخاب می‌شود.
تمام این مراحل نیز خودکار است و نیازی نیست تا برای تنظیمات آن کار خاصی صورت گیرد. البته در سمت کلاینت، امکان انتخاب یکی از موارد یاد شده به صورت صریح نیز وجود دارد.
ب) connect: اتصالی ماندگار برقرار می‌گردد.

در پروتکل Hub تمام اطلاعات JSON encoded هستند و یک سری مخفف‌هایی را در این بین نیز ممکن است مشاهده نمائید که معنای آن‌ها به شرح زیر است:
 C: cursor
M: Messages
H: Hub name
M: Method name
A: Method args
T: Time out
D: Disconnect
این مراحل را در قسمت بعد، پس از ایجاد یک کلاینت، بهتر می‌توان توضیح داد.


روش‌های مختلف ارسال اطلاعات به کلاینت‌ها

به چندین روش می‌توان اطلاعاتی را به کلاینت‌ها ارسال کرد:
1) استفاده از خاصیت Clients موجود در کلاس Hub
2) استفاده از خواص و متد‌های dynamic
در این حالت اطلاعات متد dynamic و پارامترهای آن به صورت JSON encoded به کلاینت ارسال می‌شوند (به همین جهت اهمیتی ندارند که در سرور وجود خارجی دارند یا خیر و به صورت dynamic تعریف شده‌اند).
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalR02
{
    [HubName("chat")]
    public class ChatHub : Hub
    {
        public void SendMessage(string message)
        {
            var msg = string.Format("{0}:{1}", Context.ConnectionId, message);
            Clients.All.hello(msg);
        }
    }
}
برای نمونه در اینجا متد hello به صورت dynamic تعریف شده است (جزئی از متدهای خاصیت All نیست و اصلا در سمت سرور وجود خارجی ندارد) و خواص Context و Clients، هر دو در کلاس پایه Hub قرار دارند.
حالت Clients.All به معنای ارسال پیامی به تمام کلاینت‌های متصل به هاب ما هستند.

3) روش‌های دیگر، استفاده از خاصیت dynamic دیگری به نام Caller است که می‌توان بر روی آن متد دلخواهی را تعریف و فراخوانی کرد.
 //این دو عبارت هر دو یکی هستند
Clients.Caller.hello(msg);
Clients.Client(Context.ConnectionId).hello(msg);
انجام اینکار با روش ارائه شده در سطر دومی که ملاحظه می‌کنید، در عمل یکی است؛ از این جهت که Context.ConnectionId همان ConnectionId فراخوان می‌باشد.
در اینجا پیامی صرفا به فراخوان جاری سرویس ارسال می‌گردد.

4) استفاده از خاصیت dynamic ایی به نام Clients.Others
 Clients.Others.hello(msg);
در این حالت، پیام، به تمام کلاینت‌های متصل، منهای کلاینت فراخوان ارسال می‌گردد.

5) استفاده از متد Clients.AllExcept
این متد می‌تواند آرایه‌ای از ConnectionId‌هایی را بپذیرد که قرار نیست پیام ارسالی ما را دریافت کنند.

6) ارسال اطلاعات به گروه‌ها
تعداد مشخصی از ConnectionIdها یک گروه را تشکیل می‌دهند؛ مثلا اعضای یک chat room.
        public void JoinRoom(string room)
        {
            this.Groups.Add(Context.ConnectionId, room);
        }

        public void SendMessageToRoom(string room, string msg)
        {
            this.Clients.Group(room).hello(msg);
        }
در اینجا نحوه الحاق یک کلاینت به یک room یا گروه را مشاهده می‌کنید. همچنین با مشخص بودن نام گروه، می‌توان صرفا اطلاعاتی را به اعضای آن گروه خاص ارسال کرد.
خاصیت Group در کلاس پایه Hub تعریف شده است.
نکته مهمی را که در اینجا باید درنظر داشت این است که اطلاعات گروه‌ها به صورت دائمی در سرور ذخیره نمی‌شوند. برای مثال اگر سرور ری استارت شود، این اطلاعات از دست خواهند رفت.


آشنایی با مراحل طول عمر یک Hub

اگر به تعاریف کلاس پایه Hub دقت کنیم:
    public abstract class Hub : IHub, IDisposable
    {
        protected Hub();
        public HubConnectionContext Clients { get; set; }
        public HubCallerContext Context { get; set; }
        public IGroupManager Groups { get; set; }

        public void Dispose();
        protected virtual void Dispose(bool disposing);
        public virtual Task OnConnected();
        public virtual Task OnDisconnected();
        public virtual Task OnReconnected();
    }
در اینجا، تعدادی از متدها virtual تعریف شده‌اند که تمامی آن‌ها را در کلاس مشتق شده نهایی می‌توان override و مورد استفاده قرار داد. به این ترتیب می‌توان به اجزا و مراحل مختلف طول عمر یک Hub مانند برقراری اتصال یا قطع شدن آن، دسترسی یافت. تمام این متدها نیز با Task معرفی شده‌اند؛ که معنای غیرهمزمان بودن پردازش آن‌ها را بیان می‌کند.
تعدادی از این متدها را می‌توان جهت مقاصد logging برنامه مورد استفاده قرار داد و یا در متد OnDisconnected اگر اطلاعاتی را در بانک اطلاعاتی ذخیره کرده‌ایم، بر این اساس می‌توان وضعیت نهایی را تغییر داد.


ارسال اطلاعات از یک Hub به Hub دیگر در برنامه

فرض کنید یک Hub دوم را به نام MinitorHub به برنامه اضافه کرده‌اید. اکنون قصد داریم از داخل ChatHub فوق، اطلاعاتی را به آن ارسال کنیم. روش کار به نحو زیر است:
        public override System.Threading.Tasks.Task OnDisconnected()
        {
            sendMonitorData("OnDisconnected", Context.ConnectionId);
            return base.OnDisconnected();
        }

        private void sendMonitorData(string type, string connection)
        {
            var ctx = GlobalHost.ConnectionManager.GetHubContext<MonitorHub>();
            ctx.Clients.All.newEvenet(type, connection);
        }
در اینجا با override کردن OnDisconnected به رویداد خاتمه اتصال یک کلاینت دسترسی یافته‌ایم. سپس قصد داریم این اطلاعات را توسط متد sendMonitorData به Hub دومی به نام MonitorHub ارسال کنیم که نحوه پیاده سازی آن‌را در کدهای فوق ملاحظه می‌کنید. GlobalHost.ConnectionManager یک dependency resolver توکار تعریف شده در SignalR است.
مورد استفاده دیگر این روش، ارسال اطلاعات به کلاینت‌ها از طریق کدهای یک برنامه تحت وب است (که در همان پروژه هاب واقع شده است). برای مثال در یک اکشن متد یا یک روال رویدادگردان کلیک نیز می‌توان از GlobalHost.ConnectionManager استفاده کرد.
مطالب
آموزش BrightStarDb (قسمت اول)
در طی این پست ها با مفاهیم NoSql آشنا شدید. همچنین در این دوره مفاهیم و مبانی RavenDb (یکی از بی نقص‌ترین دیتابیس‌های NoSql) بررسی شد. اما قرار است در طی چند پست با یکی دیگر از انواع دیتابیس‌های NoSql  طراحی شده برای دات نت به نام  BrightStarDb یا به اختصار  B*Db آشنا شویم.

*در دنیای NoSql پیاده سازی‌های متفاوتی از دیتابیس‌ها انجام شده است و هر دیتابیس، ویژگی‌ها و مزایا و معایب خاص خودش را دارد. باید قبول کرد که همیشه و همه جا نمی‌توان از یک پایگاه داده NoSql مشخص استفاده نماییم (دلایلی نظیر محدودیت‌های License، هزینه پیاده سازی و...). در نتیجه بررسی یک دیتابیس دیگر با شرایط و توانمندی‌های خاص آن خالی از سود نیست.
از ویژگی مهم این دیتابیس می‌توان به عناوین زیر اشاره کرد.
» این دیتاییس در گروه Graph databases‌ها قرار دارد و از  زبان SPARQL (بخوانید Sparkle) برای  کوئری گرفتن و کار با داده‌ها بهره می‌برد؛
» متن باز و رایگان است
» پشتیبانی از دات نت چهار به بعد؛
» قابل استفاده در Mobile Application - Windows phone 7 , 8؛
» بدون شما (Schema Less) و با قابیلت ذخیره سازی triple و به فرمت RDF
» پشتیبانی از Linq و  OData؛
» پشتیبانی از تراکنش‌ها ؛
» پیاده سازی مدل برنامه به صورت Code First؛
» سرعت بالا جهت ذخیره سازی و لود اطلاعات؛
» نیاز به پیکربندی‌های خاص جهت پیاده سازی ندارد؛
» ارائه افزونه رایگان Polaris جهت کوئری گفتن و نمایش Visual داده ها.
و غیره که در ادامه با آن‌ها آشنا خواهید شد.

در B*Db دو روش برای ذخیره سازی اطلاعات وجود دارد:
» Append Only : در این روش تمامی تغییرات (عملیات نوشتن) در انتهای فایل index اضافه خواهد شد. این روش مزایای زیر را به دنبال خواهد داشت:
  • عملیات نوشتن هیچگاه عملیات خواندن اطلاعات را block نمی‌کند. در نتیجه هر تعداد عملیات خواندن فایل (منظور اجرای کوئری‌های SPQRL است) می‌تواند به صورت موازی بر روی Store‌ها اجرا شود.
  • به دلیل اینکه عمل ویرایش واقعی هیچ گاه انجام نمی‌شود (داده‌ها فقط اضافه خواهند شد) همیشه می‌توانید وضعیت Store را به حالت‌های قبلی بازگردانید.
  • عملیات نوشتن اطلاعات بسیار سریع خواهد بود.
از معایب این روش این است که حجم Store‌ها فقط با افزایش داده‌ها زیاد نمی‌شود، بلکه با هر عملیات ویرایش نیز حجم فایل‌های Store افزایش پیاده خواهد کرد. در نتیجه از این روش فقط زمانی که از نظر فضای هارد دیسک محدودیت ندارید استفاده کنید(روش پیش فرض در B*Db نیز همین حالت است)

» Rewritable : در این روش در هنگام اجرای عملیات نوشتن، ابتدا یک کپی از اطلاعات گرفته میشود. سپس داده‌های مورد نظر به کپی گرفته شده اعمال می‌شوند. تا زمانیکه عملیات نوشتن اطلاعات به پایان نرسد، هر گونه دسترسی به اطلاعات جهت عملیات Read بر روی داده اصلی اجرا می‌شود. با استفاده از این روش عملیات Read و Write هیچ گونه تداخلی با هم نخواهند داشت. (چیزی شبیه به ^)

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

همان طور که پیشتر گفته شد B*Db  برای ذخیره سازی اطلاعات از سند RDF بهره می‌برد. البته با RDF Syntax‌های متفاوت :

هم چنین در B*Db چهار روش برای دست یابی با داده‌ها (پیاده سازی عملیات CRUD) وجود دارد از قبیل:
» B*Db EntityFramewok
» Data Object Layer
» RDF Client Api
» Dynamic API
که هر کدام در طی پست‌های متفاوت بررسی خواهد شد.

بررسی یک مثال با روش B*Db EntityFramework

برای شروع ابتدا یک پروژه جدید از نوع Console Application ایجاد کنید. سپس از طریق Nuget اقدام به نصب Package  زیر نمایید:
pm> install-Package BirghtStarDb
پکیج بالا تمام کتابخانه‌های لازم جهت کار با B*Db را شامل می‌شود. اگر قصد ندارید از افزونه‌های مربوط به EntityFramework و Code First استفاده نمایید می‌توانید Package زیر را نصب نمایید:
PM> Install-Package BirghtStarDbLibs
این پکیج فقط شامل کتابخانه‌های لازم جهت کار با استفاده از SPRQL است.
بعد از نصب پکیج‌های بالا یک فایل Text Template با نام MyEntityContext.tt  نیز به پروژه افزوده خواهد شد. این فایل جهت تولید خودکار مدل‌های برنامه استفاده می‌شود. اما برای این کار لازم است به ازای هر مدل ابتدا یک اینترفیس ایجاد نمایید. برای مثال:
 [Entity]
    public interface IBook
    {
        public int Code { get; set; }
        public string Title { get; set; }
    }
نکته:
» نیاز به ایجاد یک خاصیت به عنوان Id وجود ندارد. به صورت پیش فرض خاصیت Id با نوع string برای هر مدل پیاده سازی می‌شود. اما اگر قصد دارید این نام خاصیت را تغییر دهید می‌توانید به صورت زیر عمل کنید:
[Entity]
    public interface IBook
    {
        [Identifier]
        public string MyId { get;  }
        public int Code { get; set; }   
        public string Title { get; set; }
    }
در مثال بالا خاصیت MyId به جای خاصیت Id در نظر گرفته می‌شود. مزین شدن با Identifier  و همچنین نداشتن متد set را فراموش نکنید. بعد از ایجاد اینترفیس مورد نظر و اجرای Run Custom Tool بر روی فایل Text Template.tt کلاسی به نام Book به صورت زیر ساخته می‌شود:

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

 MyEntityContext context = new MyEntityContext("type=embedded;storesdirectory=c:\brightstar;storename=test");
            var book = context.Books.Create();
            book.Code = 1;
            book.Title = "Test";

            context.Books.Add(book);

            context.SaveChanges();
با یک نگاه می‌توان به شباهت مدل پیاده سازی شده بالا به EntityFramework پی برد. اما نکته مهم در مثال بالا ConnectionString پاس داده شده به Context پروژه است. در B*Db چهار روش برای دستیابی به اطلاعات ذخیره شده وجود دارد:
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر می‌توان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS می‌توان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهنده‌های DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
مطالب دوره‌ها
ایندکس‌ها در RavenDB
RavenDB یک Document database است و در این نوع بانک‌های اطلاعاتی، اسکیما و ساختار مشخصی وجود ندارد. شاید اینطور به نظر برسد، زمانیکه با دات نت کلاینت RavenDB کار می‌کنیم، یک سری کلاس مشخص دات نتی داشته و این‌ها ساختار اصلی کار را مشخص می‌کنند. اما در عمل RavenDB چیزی از این کلاس‌ها و خواص نمی‌داند و این کلاس‌های دات نتی صرفا کمکی هستند جهت سهولت اعمال Serialization و Deserialization اطلاعات. زمانیکه اطلاعاتی را در RavenDB ذخیره می‌کنیم، هیچ نوع قیدی در مورد ساختار نوع سندی که در حال ذخیره است، اعمال نمی‌شود.
خوب؛ اکنون این سؤال مطرح می‌شود که RavenDB چگونه اطلاعاتی را در این اسناد بدون اسکیما جستجو می‌کند؟ اینجا است که مفهوم و کاربرد ایندکس‌ها مطرح می‌شوند. ما در قسمت قبل که کوئری نویسی مقدماتی را بررسی کردیم، عملا ایندکس خاصی را به صورت دستی جهت انجام جستجو‌ها ایجاد نکردیم؛ از این جهت که خود RavenDB به کمک امکانات dynamic indexing آن، پیشتر اینکار را انجام داده است. برای نمونه به سطر ارسال کوئری به سرور، که در قسمت قبل ارائه شد، دقت کنید. در اینجا ارسال کوئری به indexes/dynamic کاملا مشخص است:
Request #   2: GET     - 3,818 ms - <system>   - 200 - /indexes/dynamic/Questions?&query=Title%3ARaven*&pageSize=128

Dynamic Indexes یا ایندکس‌های پویا

ایندکس‌های پویا زمانی ایجاد خواهند شد که ایندکس صریحی توسط برنامه نویس تعریف نگردد. برای مثال زمانیکه یک کوئری LINQ را صادر می‌کنیم، RavenDB بر این اساس و برای مثال فیلدهای قسمت Where آن، ایندکس پویایی را تولید خواهد کرد. ایجاد ایندکس‌ها در RavenDB از اصل عاقبت یک دست شدن پیروی می‌کنند. یعنی مدتی طول خواهد کشید تا کل اطلاعات بر اساس ایندکس جدیدی که در حال تهیه است، ایندکس شوند. بنابراین تولید ایندکس‌های پویا در زمان اولین بار اجرای کوئری، کوئری اول را اندکی کند جلوه خواهند داد؛ اما کوئری‌های بعدی که بر روی یک ایندکس آماده اجرا می‌شوند، بسیار سریع خواهند بود.


Static indexes یا ایندکس‌های ایستا

ایندکس‌های پویا به دلیل وقفه ابتدایی که برای تولید آن‌ها وجود خواهد داشت، شاید آنچنان مطلوب به نظر نرسند. اینجا است که مفهوم ایندکس‌های ایستا مطرح می‌شوند. در این حالت ما به RavenDB خواهیم گفت که چه چیزی را ایندکس کند. برای تولید ایندکس‌های ایستا، از مفاهیم Map/Reduce که در پیشنیازهای دوره جاری در مورد آن بحث شد، استفاده می‌گردد. خوشبختانه تهیه Map/Reduceها در RavenDB پیچیده نبوده و کل عملیات آن توسط کوئری‌های LINQ قابل پیاده سازی است.
تهیه ایندکس‌های پویا نیز در تردهای پس‌زمینه انجام می‌شوند. از آنجائیکه RavenDB برای اعمال Read، بهینه سازی شده است، با ارسال یک کوئری به آن، این بانک اطلاعاتی، کلیه اطلاعات آماده را در اختیار شما قرار خواهد داد؛ صرفنظر از اینکه کار تهیه ایندکس تمام شده است یا خیر.


چگونه یک ایندکس ایستا را ایجاد کنیم؟

اگر به کنسول مدیریتی سیلورلایت RavenDB مراجعه کنیم، حاصل کوئری‌های LINQ قسمت قبل را در برگه‌ی ایندکس‌های آن می‌توان مشاهده کرد:


در اینجا بر روی دکمه Edit کلیک نمائید، تا با نحوه تهیه این ایندکس پویا آشنا شویم:


این ایندکس، یک نام داشته به همراه قسمت Map از پروسه Map/Reduce که توسط یک کوئری LINQ تهیه شده است. کاری که در اینجا انجام شده، ایندکس کردن کلیه سؤالات، بر اساس خاصیت عنوان آن‌ها است.
اکنون اگر بخواهیم همین کار را با کدنویسی انجام دهیم، به صورت زیر می‌توان عمل کرد:
using System;
using System.Linq;
using Raven.Client.Document;
using RavenDBSample01.Models;
using Raven.Client;
using Raven.Client.Linq;
using Raven.Client.Indexes;

namespace RavenDBSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                store.DatabaseCommands.PutIndex(
                name: "Questions/ByTitle",
                indexDef: new IndexDefinitionBuilder<Question>
                {
                    Map = questions => questions.Select(question => new { Title = question.Title } )
                });
            }
        }
    }
}
کار با شیء DatabaseCommands یک DocumentStore شروع می‌شود. سپس توسط متد PutIndex آن می‌توان یک ایندکس جدید را تعریف کرد. این متد نیاز به نام ایندکس ایجاد شده و همچنین حداقل، متد Map آن‌را دارد. برای این منظور از شیء IndexDefinitionBuilder برای تعریف نحوه جمع آوری اطلاعات ایندکس کمک خواهیم گرفت. در اینجا خاصیت Map آن‌را باید توسط یک کوئری LINQ که فیلدهای مدنظر را بازگشت می‌دهد، مقدار دهی کنیم.
برنامه را اجرا کرده و سپس به کنسول مدیریتی تحت وب RavenDB، قسمت ایندکس‌های آن مراجعه کنید. در اینجا می‌توان ایندکس جدید ایجاد شده را مشاهده کرد:


هرچند همین اعمال را در کنسول مدیریتی نیز می‌توان انجام داد، اما مزیت آن در سمت کدها، دسترسی به intellisense و نوشتن کوئری‌های strongly typed است.

روش استفاده از store.DatabaseCommands.PutIndex اولین روش تولید Index در RavenDB با کدنویسی است. روش دوم، بر اساس ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود و مناسب است برای حالتیکه نمی‌خواهید کدهای تولید ایندکس، با کدهای سایر قسمت‌های برنامه مخلوط شوند:
    public class QuestionsByTitle : AbstractIndexCreationTask<Question>
    {
        public QuestionsByTitle()
        {
            Map = questions => questions.Select(question => new { Title = question.Title });
        }
    }
در اینجا با ایجاد یک کلاس جدید و ارث بری از کلاس AbstractIndexCreationTask کار شروع می‌شود. سپس در سازنده این کلاس، خاصیت Map را مقدار دهی می‌کنیم. مقدار آن نیز یک کوئری LINQ است که کار Select فیلدهای شرکت دهنده در کار تهیه ایندکس را انجام می‌دهد.
اکنون برای معرفی آن به برنامه باید از متد IndexCreation.CreateIndexes استفاده کرد. این متد، نیاز به دریافت اسمبلی محل تعریف کلاس‌های تولید ایندکس را دارد. به این ترتیب تمام کلاس‌های مشتق شده از AbstractIndexCreationTask را یافته و ایندکس‌های متناظری را تولید می‌کند.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsByTitle).Assembly, store);
            }
این روش، قابلیت نگهداری و نظم بهتری دارد.


استفاده از ایندکس‌های ایستای ایجاد شده

تا اینجا موفق شدیم ایندکس‌های ایستای خود را با کد نویسی ایجاد کنیم. در ادامه قصد داریم از این ایندکس‌ها در کوئری‌های خود استفاده نمائیم.
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                using (var session = store.OpenSession())
                {
                    var questions = session.Query<Question>(indexName: "QuestionsByTitle")
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);
                    foreach (var question in questions)
                    {
                        Console.WriteLine(question.Title);
                    }
                }
            }
استفاده از ایندکس تعریف شده نیز بسیار ساده می‌باشد. تنها کافی است نام آن‌را به متد Query ارسال نمائیم. اینبار اگر به خروجی کنسول سرور RavenDB دقت کنیم، از ایندکس indexes/QuestionsByTitle بجای ایندکس‌های پویا استفاده کرده است:
Request # 147: GET     -    58 ms - <system>   - 200 - /indexes/QuestionsByTitle?&query=Title%3ARaven*&pageSize=128
        Query: Title:Raven*
        Time: 7 ms
        Index: QuestionsByTitle
        Results: 2 returned out of 2 total.
روش مشخص سازی نام ایندکس با استفاده از رشته‌ها، با هر دو روش store.DatabaseCommands.PutIndex و استفاده از AbstractIndexCreationTask سازگار است. اما اگر ایندکس‌های خود را با ارث بری از AbstractIndexCreationTask ایجاد کرده‌ایم، می‌توان نام کلاس مشتق شده را به صورت یک آرگومان جنریک دوم به متد Query به شکل زیر ارسال کرد تا از مزایای تعریف strongly typed آن نیز بهره‌مند شویم:
                    var questions = session.Query<Question, QuestionsByTitle>()
                                           .Where(x => x.Title.StartsWith("Raven")).Take(128);

ایجاد ایندکس‌های پیشرفته با پیاده سازی Map/Reduce

حالتی را در نظر بگیرید که در آن قصد داریم تعداد عنوان‌های سؤالات مانند هم را بیابیم (یا تعداد مطالب گروه‌های مختلف یک وبلاگ را محاسبه کنیم). برای انجام اینکار با سرعت بسیار بالا، می‌توانیم از ایندکس‌هایی با قابلیت محاسباتی در RavenDB استفاده کنیم. کار با ارث بری از کلاس AbstractIndexCreationTask شروع می‌شود. آرگومان جنریک اول آن، نام کلاسی است که در تهیه ایندکس شرکت خواهد داشت و آرگومان دوم (و اختیاری) ذکر شده، نتیجه عملیات Reduce است:
    public class QuestionsCountByTitleReduceResult
    {
        public string Title { set; get; }
        public int Count { set; get; }
    }

    public class QuestionsCountByTitle : AbstractIndexCreationTask<Question, QuestionsCountByTitleReduceResult>
    {
        public QuestionsCountByTitle()
        {
            Map = questions => questions.Select(question =>
                                                    new
                                                    {
                                                        Title = question.Title,
                                                        Count = 1
                                                    });
            Reduce = results => results.GroupBy(x => x.Title)
                                       .Select(g =>
                                                   new
                                                   {
                                                       Title = g.Key,
                                                       Count = g.Sum(x => x.Count)
                                                   });
        }
    }
در اینجا یک ایندکس پیشرفته را تعریف کرده‌ایم که در آن در قسمت Map، کار ایندکس کردن تک تک عنوان‌ها انجام خواهد شد. به همین جهت مقدار Count در این حالت، عدد یک است. در قسمت Reduce، بر روی نتیجه قسمت Map کوئری LINQ دیگری نوشته شده و تعداد عنوان‌های همانند، با گروه بندی اطلاعات، شمارش گردیده است.
اکنون برای استفاده از این ایندکس، ابتدا توسط متد IndexCreation.CreateIndexes، کار معرفی آن به RavenDB صورت گرفته و سپس متد Query سشن باز شده، دو آرگومان جنریگ را خواهد پذیرفت. اولین آرگومان، همان نتیجه Map/Reduce است و دومین آرگومان نام کلاس ایندکس جدید تعریف شده می‌باشد:
            using (var store = new DocumentStore
            {
                Url = "http://localhost:8080"
            }.Initialize())
            {
                IndexCreation.CreateIndexes(typeof(QuestionsCountByTitle).Assembly, store);

                using (var session = store.OpenSession())
                {
                    var result = session.Query<QuestionsCountByTitleReduceResult, QuestionsCountByTitle>()
                                         .FirstOrDefault(x => x.Title == "Raven") ?? new QuestionsCountByTitleReduceResult();
                    Console.WriteLine(result.Count);
                }
            }
در کوئری فوق چون عملیات بر روی نتیجه نهایی باید صورت گیرد از FirstOrDefault استفاده شده است. این کوئری در حقیقت بر روی قسمت Reduce پیشتر محاسبه شده، اجرا می‌شود.
مطالب
افزونه نویسی برای مرورگرها : فایرفاکس : قسمت اول
در دو مقاله پیشین ^ ، ^ به بررسی نوشتن افزونه در مرورگر کروم پرداختیم و اینبار قصد داریم همان پروژه را برای فایرفاکس پیاده کنیم. پس در مورد کدهای تکراری توضیحی داده نخواهد شد و برای فهم آن می‌توانید به دو مقاله قبلی رجوع کنید. همه‌ی ما فایرفاکس را به خوبی می‌شناسیم. اولین باری که این مرورگر آمد سرو صدای زیادی به پا کرد و بازار وسیعی از مرورگر‌ها را که در چنگ IE بود، به دست آورد . این سر و صدا بیشتر به خاطر امنیت و کارآیی بالای این مرورگر، استفاده از آخرین فناوری‌های تحت وب و دوست داشتنی برای طراحان وب بود. همچنین یکی دیگر از مهمترین ویژگی‌های آن، امکان سفارشی سازی آن با افزونه‌ها extensions یا addon بود که این ویژگی در طول این سال‌ها تغییرات زیادی به خود دیده است. در مورد افزونه نویسی برای فایرفاکس در سطح نت مطالب زیادی وجود دارند که همین پیشرفت‌های اخیر در مورد افزونه‌ها باعث شده خیلی از این مطالب به روز نباشند. اگر در مقاله پیشین فکر می‌کنید که کروم چقدر در نوشتن افزونه جذابیت دارد و امکانات خوبی را در اختیار شما می‌گذارد، الان دیگر وقت آن است که نظر خودتان را عوض کنید و فایرفاکس را نه تنها یک سرو گردن بلکه بیشتر از این حرف‌ها بالاتر بدانید.
شرکت موزیالا برای قدرتمندی و راحتی کار طراحان یک sdk طراحی کرده است است و شما با استفاده از کدهای موجود در این sdk قادرید کارهای زیادی را انجام دهید. برای نصب این sdk باید پیش نیازهایی بر روی سیستم شما نصب باشد:
  • نصب پایتون  2.5 یا 2.6 یا 2.7 که فعلا در سایت آن، نسخه‌ی 2.7 در دسترس هست. توجه داشته باشید که هنوز برای نسخه‌ی 3 پایتون پشتیبانی صورت نگرفته است. 
  • آخرین نسخه‌ی sdk را هم می‌توانید از این آدرس  به صورت zip و یا از این آدرس به صورت tar دانلود کنید و در صورتیکه دوست دارید به سورس آن دسترسی داشته باشید یا اینکه از سورس‌های مشارکت شده یا غیر رسمی استفاده کنید، از این صفحه آن را دریافت کنید.
بعد از دانلود sdk به شاخه‌ی bin رفته و فایل activate.bat را اجرا کنید. موقعی که فایل activate اجرا شود، باید چنین چیزی دیده شود:
(C:\Users\aym\Downloads\addon-sdk-1.17) C:\Users\aym\Downloads\addon-sdk-1.17\bin>
برای سیستم‌های عامل Linux,FreeBSD,OS X دستورات زیر را وارد کنید:
اگر یک کاربر پوسته‌ی bash هستید کلمه زیر را در کنسول برای اجرای activate بزنید:
source bin/activate
اگر کاربر پوسته‌ی بش نیستید:
bash bin/activate
نهایتا باید کنسول به شکل زیر در آید یا شبیه آن:
(addon-sdk)~/mozilla/addon-sdk >
بعد از اینکه به کنسول آن وارد شدید، کلمه cfx را در آن تایپ کنید تا راهنمای دستورات و سوییچ‌های آن‌ها نمایش داده شوند. از این ابزار میتوان برای راه اندازی فایرفاکس و اجرای افزونه بر روی آن، پکیج کردن افزونه، دیدن مستندات و آزمون‌های واحد استفاده کرد.

آغاز به کار
برای شروع، فایل‌های زیادی باید ساخته شوند، ولی نگران نباشید cfx این کار را برای شما خواهد کرد. دستورات زیر را جهت ساخت یک پروژه خالی اجرا کنید:
mkdir fxaddon
cd fxaddon
cfx init
یک پوشه را در مسیری که کنسول بالا اشاره میکرد، ساختم و وارد آن شدم و با دستور cfx init دستور ساخت یک پروژه‌ی خالی را دادم و باید بعد از این دستور، یک خروجی مشابه زیر نشان بدهد:
* lib directory created
* data directory created
* test directory created
* doc directory created
* README.md written
* package.json written
* test/test-main.js written
* lib/main.js written
* doc/main.md written
Your sample add-on is now ready for testing:
try "cfx test" and then "cfx run". Have fun!"
در این پوشه یک فایل به اسم package.json هم وجود دارد که اطلاعات زیر داخلش هست:
{
  "name": "fxaddon",
  "title": "fxaddon",
  "id": "jid1-QfyqpNby9lTlcQ",
  "description": "a basic add-on",
  "author": "",
  "license": "MPL 2.0",
  "version": "0.1"
}
این اطلاعات شامل نام و عنوان افزونه، توضیحی کوتاه در مورد آن، نویسنده‌ی افزونه، ورژن افزونه و ... است. این فایل دقیقا معادل manifest.json در کروم است. در افزونه نویسی‌های قدیم این فایل install.rdf نام داشت و بر پایه‌ی فرمت rdf بود. ولی در حال حاضر با تغییرات زیادی که افزونه نویسی در فایرفاکس کرده‌است، الان این فایل بر پایه یا فرمت json است. اطلاعات package را به شرح زیر تغییر می‌دهیم:
{
  "name": "dotnettips",
  "title": ".net Tips Updater",
  "id": "jid1-QfyqpNby9lTlcQ",
  "description": "This extension keeps you updated on current activities on dotnettips.info",
  "author": "yeganehaym@gmail.com",
  "license": "MPL 2.0",
  "version": "0.1"
}

رابط‌های کاربری
Action Button و Toggle Button
فایل main.js را در دایرکتوری lib باز کنید:
موقعی که در کروم افزونه می‌نوشتیم امکانی به اسم browser action داشتیم که در اینجا با نام action button شناخته می‌شود. در اینجا باید کدها را require کرد، همان کاری در خیلی از زبان‌ها مثلا مثل سی برای صدا  زدن سرآیندها می‌کنید. مثلا برای action button اینگونه است:
var button= require('sdk/ui/button/action');
نحوه‌ی استفاده هم بدین صورت است:
buttons.ActionButton({...});
که در بین {} خصوصیات دکمه‌ی مورد نظر نوشته می‌شود. ولی من بیشتر دوست دارم از شیء دیگری استفاده کنم. به همین جهت ما از یک مدل دیگر button که به اسم toggle button شناخته می‌شود، استفاده می‌کنیم. از آن جا که این button دارای دو حالت انتخاب (حالت فشرده شده) و غیر انتخاب (معمولی و آماده فشرده شدن توسط کلیک کاربر) است، بهترین انتخاب هست.

کد زیر یک toggle button را برای فایرفاکس می‌سازد که با کلیک بر روی آن، صفحه‌ی popup.htm  به عنوان یک پنل روی آن رندر می‌شود:
var tgbutton = require('sdk/ui/button/toggle');
var panels = require("sdk/panel");
var self = require("sdk/self");

var button = tgbutton.ToggleButton({
  id: "updaterui",
  label: ".Net Updater",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onChange: handleChange
});

var panel = panels.Panel({
  contentURL: self.data.url("./popup.html"),
  onHide: handleHide
});

function handleChange(state) {
  if (state.checked) {
    panel.show({
      position: button
    });
  }
}

function handleHide() {
  button.state('window', {checked: false});
}
در سه خط اول، فایل‌هایی را که نیاز است Required شوند، می‌نویسیم و در یک متغیر ذخیره می‌کنیم. اگر در متغیر نریزیم مجبور هستیم همیشه هر کدی را به جای نوشتن عبارت زیر:
 tgbutton.ToggleButton
به صورت زیر بنویسیم:
require('sdk/ui/button/toggle').ToggleButton
که اصلا کار جالبی نیست. اگر مسیرهای نوشته شده را از مبدا فایل zip که اکسترکت کرده‌اید، در دایرکتوری sdk در شاخه lib بررسی کنید، با دیگر موجودیت‌های sdk آشنا خواهید شد.
در خط بعدی به تعریف یک شیء از نوع toggle button به اسم button میپردازیم و خصوصیاتی که به این دکمه داده ایم، مانند یک کد شناسایی، یک برچسب که به عنوان tooltip نمایش داده خواهد شد و آیکن‌هایی در اندازه‌های مختلف که در هرجایی کاربر آن دکمه را قرار داد، در اندازه‌ی مناسب باشد و نهایتا به تعریف یک رویداد می‌پردازیم. تابع handlechange زمانی صدا زده می‌شود که در وضعیت دکمه‌ی ایجاد شده تغییری حاصل شود. در خط بعدی شیء panel را به صورت global میسازیم. شیء self دسترسی ما را به اجزا یا فایل‌های افزونه خودمان فراهم می‌کند که در اینجا دسترسی ما به فایل html در شاخه‌ی data میسر شده است و مقدار مورد نظر را در contentURL قرار می‌دهد. نهایتا هم برای رویداد onhide تابعی را در نظر می‌گیریم تا موقعی که پنجره بسته شد بتوانیم وضعیت toggle button را به حالت قبلی بازگردانیم و حالت فشرده نباشد. چرا که این دکمه تنها با کلیک ماوس به حالت فشرده و حالت معمولی سوییچ میکند. پس اگر کاربر با کلیک بر روی صفحه‌ی مرورگر پنجره را ببندد، دکمه در همان وضعیت فشرده باقی می‌ماند.
همانطور که گفتیم تابع handlechnage موقعی رخ میدهد که در وضعیت دکمه، تغییری رخ دهد و نمیدانیم که این وضعیت فشرده شدن دکمه هست یا از حالت فشرده خارج شده است. پس با استفاده از ویژگی checked بررسی میکنم که آیا دکمه‌ای فشرده شده یا خیر؛ اگر برابر true بود یعنی کاربر روی دکمه، کلیک کرده و دکمه به حالت فشرده رفته، پس ما هم پنل را به آن نشان می‌دهیم و خصوصیات دلخواهی را برای مشخص کردن وضعیت پنل نمایشی به آن پاس می‌کنیم. خصوصیت یا پارامترهای زیادی را می‌توان در حین ساخت پنل برای آن ارسال کرد. با استفاده از خصوصیت position محل نمایش پنجره را مشخص می‌کنیم. در صورتی که ذکر نشود پنجره در وسط مرورگر ظاهر خواهد شد.
تابع onhide زمانی رخ میدهد که به هر دلیلی پنجره بسته شده باشد که در بالا یک نمونه‌ی آن را عرض کردیم. ولی اتفاقی که می‌افتد، وضعیت تابع را با متد state تغییر می‌دهیم و خصوصیت checked آن را false می‌کنیم. بجای پارامتر اولی، دو گزینه را میتوان نوشت؛ یکی window و دیگری tab است. اگر شما گزینه tab را جایگزین کنید، اگر در یک تب دکمه به حالت فشرده برود و به تب دیگر بروید و باعث بسته شدن پنجره بشوید، دکمه تنها در تبی که فعال است به حالت قبلی باز می‌گردد و تب اولی همچنان حالت خود را حفظ خواهد کرد پس می‌نویسیم window تا این عمل در کل پنجره اعمال شود.

Context Menus
برای ساخت منوی کانتکست از کد زیر استفاده می‌کنیم:
var contextMenu = require("sdk/context-menu");

var home = contextMenu.Item({
  label: "صفحه اصلی",
  data: "https://www.dntips.ir/"
});
var postsarchive = contextMenu.Item({
  label: "مطالب سایت",
  data: "https://www.dntips.ir/postsarchive"
});

var menuItem = contextMenu.Menu({
  label: "Open .Net Tips",
  context: contextMenu.PageContext(),
   items: [home, postsarchive],
  image: self.data.url("icon-16.png"),
  contentScript: 'self.on("click", function (node, data) {' +
                 '  window.location.href = data;' +
                 '});'
});
این منو هم مثل کروم دو زیر منو دارد که یکی برای باز کردن صفحه‌ی اصلی و دیگر‌ی برای باز کردن صفحه‌ی مطالب است. هر کدام یک برچسب برای نمایش متن دارند و یکی هم دیتا که برای نگهداری آدرس است. در خط بعدی منوی پدر یا والد ساخته می‌شود که با خصوصیت items، زیر منوهایش را اضافه می‌کنیم و با خصوصیت image، تصویری را در پوشه‌ی دیتا به آن معرفی می‌کنیم که اندازه‌ی آن 16 پیکسل است و دومی هم خصوصیت context است که مشخص می‌کند این گزینه در چه مواردی بر روی context menu نمایش داده شود. الان روی همه چیزی نمایش داده می‌شود. اگر گزینه، SelectionContext باشد، موقعی که متنی انتخاب شده باشد، نمایش می‌یابد. اگر SelectorContext باشد، خود شما مشخص می‌کنید بر روی چه مواردی نمایش یابد؛ مثلا عکس یا تگ p  یا هر چیز دیگری، کد زیر باعث می‌شود فقط روی عکس نمایش یابد:
SelectorContext("img")
کد زیر هم روی عکس و هم روی لینکی که href داشته باشد:
SelectorContext("img,a[href]")
موارد دیگری هم وجود دارند که میتوانید مطالب بیشتری را در مورد آن‌ها در اینجا مطالعه کنید. آخرین خصوصیت باقی مانده، content script است که می‌توانید با استفاده از جاوااسکریپت برای آن کد بنویسید.  موقعی که برای آن رویداد کلیک رخ داد، مشخص شود تابعی را صدا میزند با دو آرگومان؛ گره ای که انتخاب شده و داده‌ای که به همراه دارد که آدرس سایت است و آن را در نوار آدرس درج می‌کند.
آن منوهایی که با متد item ایجاد شده‌اند منوهایی هستند که با کلیک کاربر اجرا می‌شوند؛ ولی والدی که با متد menu ایجاد شده است، برای منویی است که زیر منو دارد و خودش لزومی به اجرای کد ندارد. پس اگر منویی میسازید که زیرمنو ندارد و خودش قرار است کاری را انجام دهد، به صورت همان item بنویسید که پایین‌تر نمونه‌ی آن را خواهید دید.
الان مشکلی که ایجاد می‌شود این است که موقعی که سایت را باز می‌کند، در همان تبی رخ می‌دهد که فعال است و اگر کاربر بر روی صفحه‌ی خاصی باشد، آن صفحه به سمت سایت مقصد رفته و سایت فعلی از دست میرود. روش صحیح‌تر اینست که تبی جدید بار شود و آدرس مقصد در آن نمایش یابد. پس باید از روشی استفاده کنیم که رویداد کلیک توسط کد خود افزونه مدیریت شود، تا با استفاده از شیء tab، یک تب جدید با آدرسی جدید ایجاد کنیم. پس کد را با کمی تغییر می‌نویسیم:
var tabs = require("sdk/tabs");
var menuItem = contextMenu.Menu({
  label: "Open .Net Tips",
  context: contextMenu.PageContext(),
   items: [home, postsarchive],
  image: self.data.url("icon-16.png"),
  contentScript: 'self.on("click", function (node, data) {' +
                 '  self.postMessage(data);' +
                 '});',
 onMessage: function (data) {
     tabs.open(data);
  }
});
با استفاده از postmessage، هر پارامتری را که بخواهیم ارسال می‌کنیم و بعد با استفاده از رویداد onMessage، داده‌ها را خوانده و کد خود را روی آن‌ها اجرا می‌کنیم.
بگذارید کد زیر را هم جهت سرچ مطالب بر روی سایت پیاده کنیم: 
var Url="https://www.dntips.ir/search?term=";
var searchMenu = contextMenu.Item({
  label: "search for",
  context: [contextMenu.PredicateContext(checkText),contextMenu.SelectionContext()],
    image: self.data.url("icon-16.png"),
  contentScript: 'self.on("click", function () {' +
  '  var text = window.getSelection().toString();' +
                 '  if (text.length > 20)' +
                 '   text = text.substr(0, 20);' +
                 '  self.postMessage(text);'+
                '})',
onMessage: function (data) {
     tabs.open(Url+data);
  }
 
});

function checkText(data) {

       if(data.selectionText === null)
           return false;

       console.log('selectionText: ' + data.selectionText);

       //handle showing or hiding of menu items based on the text content.
       menuItemToggle(data.selectionText);

       return true;
};

function menuItemToggle(text){
var searchText="جست و جو برای ";
    searchMenu.label=searchText+text;

};
در ساخت این منو، ما از ContextSelection استفاده کرده‌ایم. بدین معنی که موقعی که چیزی روی صفحه انتخاب شد، این منو ظاهر شود و گزینه‌ی دیگری که در کنارش هست، گزینه contextMenu.PredicateContext وظیفه دارد تابعی که به عنوان آرگومان به آن دادیم را موقعی که منو کانتکست ایجاد شد، صدا بزند و اینگونه میتوانیم بر حسب اطلاعات کانتکست، منوی خود را ویرایش کنیم. مثلا من دوست دارم موقعی که متنی انتخاب می‌شود و راست کلیک می‌کنم گزینه‌ی "جست و جو برای..." نمایش داده شود و به جای ... کلمه‌ی انتخاب شده نمایش یابد. به شکل زیر دقت کنید. این چیزی است که ما قرار است ایجاد کنیم:
در کل موقع ایجاد منو تابع checkText اجرا شده و متن انتخابی را خوانده به عنوان یک آرگومان برای تابع menuItemToggle ارسال می‌کند و به رشته "جست و جو برای" می‌چسباند. در خود پارامترهای آیتم اصلی، گزینه content scrip، با استفاده از جاوااسکریپت، متن انتخاب شده را دریافت کرده و با استفاده از متد postmessage برای تابع  onMessage ارسال کرده و با ساخت یک تب و چسباندن عبارت به آدرس جست و جو سایت، کاربر را به صفحه مورد نظر هدایت کرده و عمل جست و جو در سایت انجام می‌گیرد.

در قسمت آینده موارد بیشتری را در مورد افزونه نویسی در فایرفاکس بررسی خواهیم کرد و افزونه را تکمیل خواهیم کرد
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
سلام، خواهش می‌کنم.
فقط قسمت مجوز سورس دات نت کمی باید ویرایش شود. مثلا MVC مجوز MS-PL دارد، مابقی مجوز فقط خواندنی MS-RSL. ولی در کل سورس آن از لینک داده شده قابل دانلود، بررسی و مطالعه کامل است.
نظرات مطالب
هزینه استفاده از دات نت فریم ورک چقدر است؟
سلام، خواهش می‌کنم.
فقط قسمت مجوز سورس دات نت کمی باید ویرایش شود. مثلا MVC مجوز MS-PL دارد، مابقی مجوز فقط خواندنی MS-RSL. ولی در کل سورس آن از لینک داده شده قابل دانلود، بررسی و مطالعه کامل است.
مطالب
چرخه‌ی حیات یک درخواست در ASP.NET MVC

در Asp.net دو چرخه‌ی حیات مهم وجود دارند که اساس چارچوب MVC را تشکیل می‌دهند :

  1. چرخه‌ی حیات برنامه (Application)؛ از لحظه‌ای که برنامه برای اولین بار اجرا می‌شود و تا لحظه‌ی خاتمه‌ی آن را شامل می‌شود.
  2. چرخه‌ی حیات یک درخواست ( Request مسیری که یک درخواست طی می‌کند، اصطلاحا PipeLine نامیده می‌شود که همان چرخه‌ی حیات یک درخواست نیز هست و از لحظه‌ای که درخواست تحویل asp.net شده، تا زمانیکه درخواست ارسال می‌شود را شامل می‌شود.

تمرکز بنده بیشتر بر روی روند و مسیری است که یک درخواست طی می‌کند و قصد دارم با بهره گیری از کتاب Pro Asp.net Mvc 5 و دیگر منابع، چرخه‌ی حیات درخواست را در برنامه‌های Mvc بررسی کرده و در مقالات آتی ماژولها و هندلرها را بررسی کنم.

در asp.net ، برنامه global فایلهای شامل دو فایل Global.asax , Global.asax.cs است.

فایل Global.asax که هیچ گاه نیاز به ویرایش آن نداریم محتویاتی مانند زیر دارد:

<%@ Application Codebehind="Global.asax.cs" Inherits="YourAppName.MvcApplication" Language="C#" %>
و صرفا فایل code behind   مرتبط را برای asp.net مشخص می‌کند. این نوع مشخص سازی را از وب فرمها به یاد داریم.

در این مقاله منظور از فایل global فایل  Global.asax.cs است که مشتق شده از کلاس System.Web.HttpApplication است:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            ...//
        }
    }
به صورت پیش فرض کدهای بالا ایجاد شده و کلاس MvcApplication شامل متد Application_Start است که نقطه‌ی شروع چرخه‌ی حیات برنامه را مشخص می‌کند. اما متد دیگری به نام Application_End() نیز وج ود دارد که در زمان خاتمه‌ی برنامه فراخوانی خواهد شد و فرصتی را برای آزاد سازی منابع اشغال شده توسط برنامه فراهم می‌آورد .

Asp.net برای پاسخگویی به درخواست‌های واصله، وهله‌هایی از کلاس MvcApplication را می‌سازد ولی این دو متد صرفا در نقاط شروع و پایان برنامه فراخوانی شده و عملا در وهله‌های یاد شده صدا زده نخواهند شد و به جای آنها رویدادهایی را که در ذیل آنها را معرفی می‌کنیم، فراخوانی شده و چرخه‌ی حیات درخواست را برای ما مشخص می‌سازند .

  BeginRequest : به عنوان اولین رویداد، به محض وصول یک درخواست جدید رخ خواهد داد. 

  AuthenticateRequest ,PostAuthenticateRequest : رویداد AuthenticateRequest برای شناسایی کاربر ارسال کننده درخواست، کاربرد دارد و پس از پردازش کلیه‌ی توابع، رویداد PostAuthenticateRequest صدا زده می‌شود.

  AuthorizeRequest :به‌هنگام صدور مجوزهای یک درخواست رخ می‌دهد و مشابه رویداد بالا پس از پردازش کلیه‌ی توابع، رویداد PostAuthorizeRequest صدا زده خواهد شد.  

ResolveRequestCache : پس از صدور مجوزهای یک درخواست در رویداد authorization  زمانیکه ماژولهای کش می‌خواهند اطلاعاتی را از کش سرور مطالبه کنند، رخ می‌دهد و به مانند دو رخداد قبلی، PostResolveRequestCache نیز پس از اتمام پردازش توابع رویداد رخ می‌دهد.

  MapRequestHandler : زمانی که Asp.net می‌خواهد هندلری را برای پاسخگویی به درخواست واصله انتخاب کند رخ می‌دهد و PostMapRequestHandler نیز پس از این انتخاب، تریگر می‌شود.  

AcquireRequestState : جهت بدست آوردن داده‌هایی نظیر سشن و ... مرتبط با درخواست جاری کاربرد داشته و PostAcquireRequestState نیز پس از پردازش توابع رویداد رخ خواهد داد.

  PreRequestHandlerExecute : بالافاصله قبل و همچنین بلافاصله بعد از این که یک هندلر بخواهد درخواستی را پردازش کند، رخ می‌دهد. PostRequestHandlerExecute نیز همانند دیگر رویدادهای گذشته، پس از اتمام پردازش توابع، این رویداد رخ خواهد داد. 

ReleaseRequestState : زمانی رخ می‌دهد که داده‌های مرتبط با درخواست جاری، در ادامه‌ی روند پردازش درخواست مورد نیاز نباشند و پس از پردازش توابع رویداد، PostReleaseRequestState رخ خواهد داد .

UpdateRequestCache : به جهت اینکه ماژولهای مسئول کش، توانایی به روز رسانی داده‌های خود، برای پاسخگویی به درخواستهای بعدی را داشته باشند، این رویداد رخ می‌دهد.

  LogRequest : قبل از انجام عملیات لاگین برای درخواست جاری رخ می‌دهد و پس از پردازش توابع رویداد نیز PostLogRequest تریگر می‌شود.  

  EndRequest : پس از پایان کار پردازش درخواست جاری و مهیا شدن پاسخ مرتبط جهت ارسال به مرورگر تریگر خواهد شد. 

PreSendRequestHeaders : قبل از ارسال HTTP headers به مرورگر این رویداد رخ خواهد داد.

  PreSendRequestContent : بعد از ارسال شدن هدرها و قبل از ارسال محتوای صفحه به مرورگر رخ می‌دهد. 

Error : هر زمان و در هر مرحله از پردازش درخواست، چنانچه خطایی صورت پذیرد این رویداد رخ خواهد داد.

فریم ورک Asp.net   جهت مدیریت بهتر یک درخواست، در تمام مسیر پردازش، رویدادهای بالا را مهیا کرده است. در ادامه نحوه‌ی هندل کردن رویدادهای چرخه‌ی حیات درخواست را در فایل global توضیح می‌دهم. هر چند که استفاده از این فایل بدین منظور، صرفا برای مدیریت مسائل ابتدایی مناسب بوده و در یک پروژه‌ی بزرگ موجب به هم ریختگی فایل global با کدهای زیاد و خوانایی پایین بوده که قابلیت استفاده مجدد در دیگر پروژه‌ها را نیز ندارد.

Asp.net این مشکل را با معرفی ماژولها که در مقالات آتی توضیح خواهم داد، مرتفع کرده است.

در فایل global هر گاه متدی را با پیشوند Application_ و نام یکی از رویدادهای بالا بنویسید Asp.net آن را به عنوان هندلری برای رویداد مذکور می‌شناسد. به عنوان مثال متدی با نام Application_BeginRequest  متد رویداد BeginRequest می‌باشد.

ابتدا یک پروژه‌ی MVC جدید را به نام SimpleApp ایجاد کرده و فایل global آن را مطابق ذیل تغییر می‌دهیم:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace SimpleApp
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        { 
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
        protected void Application_BeginRequest() 
        {
            RecordEvent("BeginRequest");
        }
        protected void Application_AuthenticateRequest() 
        {
            RecordEvent("AuthenticateRequest");
        }
        protected void Application_PostAuthenticateRequest()
        { 
            RecordEvent("PostAuthenticateRequest");
        }
        private void RecordEvent(string name)
        {
            List<string> eventList = Application["events"] as List<string>;
            if (eventList == null) 
            { 
                Application["events"] = eventList = new List<string>(); 
            } 
            eventList.Add(name);
        }
    }
}

در اینجا متدی به نام RecordEvent را در کدهای ذکر شده مشاهده می‌کنید که نام یک رویداد را دریافت و جهت در دسترس قرار دادن در کل برنامه به خاصیت Application از کلاس HttpApplication نسبت داده و متد مذکور را از سه متد دیگر فراخوانی کرده‌ایم. این متدها در زمان رخ دادن رویدادهای BeginRequest, AuthenticateRequest, PostAuthenticateRequest صدا زده خواهند شد.

حال جهت نمایش اطلاعات رویداد نیاز است تغییراتی مشابه ذیل در کنترلر Home ایجاد نماییم.

using System.Web.Mvc;
namespace SimpleApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(HttpContext.Application["events"]);
        }

    }
}

ویوی مرتبط با اکشن متد index را مطابق کدهای ذیل بازنویسی می‌کنیم:

@model List<string>
@{
    ViewBag.Title = "Events List";
 
}
<h5>Events</h5>
<table>
    @foreach (string eventName in Model)
    {    
        <tr>
            <td>@eventName</td>
        </tr>      
    }

</table>
خروجی آن مطابق ذیل خواهد بود :

در این مقاله سعی کردیم ابتدا چرخه‌ی حیات یک Request را فرا گرفته و سپس از طریق فایل global و توسط متدهایی با پیشوند Application_ +نام رویداد (اصطلاحا متدهای ویژه نامیده می‌شوند) چرخه حیات یک درخواست را مدیریت کنیم.
مطالب دوره‌ها
نحوه برقراری ارتباطات بین صفحات، سیستم راهبری و ViewModelها در قالب پروژه WPF Framework
هدف از قالب پروژه WPF Framework ایجاد یک پایه، برای شروع سریع یک برنامه تجاری WPF جدید است. بنابراین فرض کنید که این قالب، هم اکنون در اختیار شما است و قصد دارید یک صفحه جدید، مثلا تغییر مشخصات کاربری را به آن اضافه کنید. کدهای کامل این قابلیت هم اکنون در قالب پروژه موجود است و به این ترتیب توضیح جزئیات روابط آن در اینجا ساده‌تر خواهد بود.

1) ایجاد صفحه تغییر مشخصات کاربر
کلیه Viewهای برنامه، در پروژه ریشه، ذیل پوشه Views اضافه خواهند شد. همچنین چون در آینده تعداد این فایل‌ها افزایش خواهند یافت، بهتر است جهت مدیریت آن‌ها، به ازای هر گروه از قابلیت‌ها، یک پوشه جدید را ذیل پوشه Views اضافه کرد.


همانطور که ملاحظه می‌کنید در اینجا پوشه UserInfo به همراه یک فایل جدید XAML به نام ChangeProfile.xaml، ذیل پوشه Views پروژه ریشه اصلی اضافه شده‌اند.
ChangeProfile.xaml از نوع Page است؛ از این جهت که اگر به فایل MainWindow.xaml که سیستم راهبری برنامه در آن تعبیه شده است مراجعه کنید، یک چنین تعریفی را ملاحظه خواهید نمود:
<CustomControls:FrameFactory
                    x:Name="ActiveScreen"            
                    HorizontalContentAlignment="Stretch"
                    VerticalContentAlignment="Stretch"     
                    NavigationUIVisibility="Hidden"            
                    Grid.Column="1" 
                    Margin="0" />
سورس کامل کنترل سفارشی FrameFactory.cs را در پروژه Infrastructure برنامه می‌توانید مشاهده کنید. FrameFactory در حقیقت یک کنترل Frame استاندارد است که مباحث تزریق وابستگی‌ها و همچنین راهبری خودکار سیستم در آن تعریف شده‌اند.
مرحله بعد، تعریف محتویات فایل ChangeProfile.xaml است. در این فایل اطلاعات انقیاد داده‌ها از ViewModel مرتبط که در ادامه توضیح داده خواهد شد دریافت می‌گردد. مثلا مقدار خاصیت ChangeProfileData.Password، از ViewModel به صورت خودکار تغذیه خواهد شد.
در این فایل یک سری DynamicResource را هم برای تعریف دکمه‌های سبک مترو ملاحظه می‌کنید. کلیدهای متناظر با آن در فایل Icons.xaml که در فایل App.xaml برای کل برنامه ثبت شده است، تامین می‌گردد.


2) تنظیم اعتبارسنجی صفحه اضافه شده
پس از اینکه صفحه جدید اضافه شد، نیاز است وضعیت دسترسی به آن مشخص شود:
/// <summary>
/// تغییر مشخصات کاربر جاری
/// </summary>
[PageAuthorization(AuthorizationType.FreeForAuthenticatedUsers)]
public partial class ChangeProfile
برای این منظور به فایل code behind این صفحه یعنی ChangeProfile.xaml.cs مراجعه و تنها، ویژگی فوق را به آن اضافه خواهیم کرد. ویژگی PageAuthorization به صورت خودکار توسط فریم ورک تهیه شده خوانده و اعمال خواهد شد. برای نمونه در اینجا کلیه کاربران اعتبارسنجی شده در سیستم می‌توانند مشخصات کاربری خود را تغییر دهند.
در مورد نحوه تعیین نقش‌های متفاوت در صورت نیاز، در قسمت قبل بحث گردید.


3) تغییر منوی برنامه جهت اشاره به صفحه جدید
خوب، ما تا اینجا یک صفحه جدید را تهیه کرده‌ایم. در مرحله بعد باید مدخلی را در منوی برنامه جهت اشاره به آن تهیه کنیم.
منوی برنامه در فایل MainMenu.xaml قرار دارد. اطلاعات متناظر با دکمه ورود به صفحه تغییر مشخصات کاربری نیز به شکل ذیل تعریف شده است:
                <Button Style="{DynamicResource MetroCircleButtonStyle}"
                        Height="55" Width="55"  
                        Command="{Binding DoNavigate}"
                        CommandParameter="\Views\UserInfo\ChangeProfile.xaml"
                        Margin="2">
                    <Rectangle Width="28" Height="17.25">
                        <Rectangle.Fill>
                            <VisualBrush Stretch="Fill" Visual="{StaticResource appbar_user_tie}" />
                        </Rectangle.Fill>
                    </Rectangle>
                </Button>
به ازای هر صفحه جدیدی که تعریف می‌شود تنها کافی است CommandParameter ایی مساوی مسیر فایل XAML مورد نظر، در فایل منوی برنامه قید شود. منوی اصلی دارای ViewModel ایی است به نام MainMenuViewModel.cs که در پروژه Infrastructure پیشتر تهیه شده است.
در این ViewModel تعاریف DoNavigate و پردازش پارامتر دریافتی به صورت خودکار صورت خواهد گرفت و سورس کامل آن در اختیار شما است. بنابراین تنها کافی است CommandParameter را مشخص کنید، DoNavigate کار هدایت به آن‌را انجام خواهد داد.


4) ایجاد ViewModel متناظر با صفحه
مرحله آخر افزودن یک صفحه، تعیین ViewModel متناظر با آن است. عنوان شد که اطلاعات مورد نیاز جهت عملیات Binding در این فایل قرار می‌گیرند و اگر به فایل ChangeProfileViewModel.cs مراجعه کنید (نام آن مطابق قرارداد، یک کلمه ViewModel را نسبت به نام View متناظر بیشتر دارد)، چنین خاصیت عمومی را در آن خواهید یافت.


مطابق قراردادهای توکار قالب تهیه شده:
- نیاز است ViewModel تعریف شده از کلاس پایه BaseViewModel مشتق شود تا علاوه بر تامین یک سری کدهای تکراری مانند:
 public abstract class BaseViewModel : DataErrorInfoBase, INotifyPropertyChanged, IViewModel
سبب شناسایی این کلاس به عنوان ViewModel و برقرار تزریق وابستگی‌های خودکار در سازنده آن نیز گردد.
- پس از اضافه شدن کلاس پایه BaseViewModel نیاز است تکلیف خاصیت public override bool ViewModelContextHasChanges را نیز مشخص کنید. در اینجا به سیستم راهبری اعلام می‌کنید که آیا در ViewModel جاری تغییرات ذخیره نشده‌ای وجود دارند؟ فقط باید true یا false را بازگردانید. برای مثال خاصیت uow.ContextHasChanges برای این منظور بسیار مناسب است و از طریق پیاده سازی الگوی واحد کار به صورت خودکار چنین اطلاعاتی را در اختیار برنامه قرار می‌دهد.

در ViewModelها هرجایی که نیاز به اطلاعات کاربر وارد شده به سیستم داشتید، از اینترفیس IAppContextService در سازنده کلاس ViewModel جاری استفاده کنید. اینترفیس IUnitOfWork امکانات ذخیره سازی اطلاعات و همچنین مشخص سازی وضعیت Context جاری را در اختیار شما قرار می‌دهد.
کلیه کدهای کار کردن با یک موجودیت باید در کلاس سرویس متناظر با آن قرار گیرند و این کلاس سرویس توسط اینترفیس آن مانند IUsersService در اینجا باید توسط سازنده کلاس در اختیار ViewModel قرار گیرد.
تزریق وابستگی‌ها در اینجا خودکار بوده و تنظیمات آن در فایل IocConfig.cs پروژه Infrastructure قرار دارد. این کلاس آنچنان نیازی به تغییر ندارد؛ اگر پیش فرض‌های نامگذاری آن‌را مانند کلاس‌های Test و اینترفیس‌های ITest، در لایه سرویس برنامه رعایت شوند.