Unity 3.5 RTW منتشر شد
نگاهی به SignalR Hubs
[HubName("messageHub")] public class MessageHub : Hub { public void NotifyAllClients() { Clients.All.Notify(); } }
public partial class Startup { public void Configuration(IAppBuilder appBuilder) { var hubConfiguration = new HubConfiguration() { EnableDetailedErrors = true }; appBuilder.MapSignalR(hubConfiguration); appBuilder.UseCors(CorsOptions.AllowAll); } }
static void Main(string[] args) { const string baseAddress = "http://localhost:9000/"; // "http://*:9000/"; using (var webapp = WebApp.Start<Startup>(baseAddress)) { Console.WriteLine("Start app..."); var hubConnection = new HubConnection(baseAddress); IHubProxy messageHubProxy = hubConnection.CreateHubProxy("messageHub"); messageHubProxy.On("notify", () => { Console.WriteLine(); Console.WriteLine("Notified!"); }); hubConnection.Start().Wait(); Console.WriteLine("Start signalr..."); bool dontExit = true; while (dontExit) { var key = Console.ReadKey(); if (key.Key == ConsoleKey.Escape) dontExit = false; messageHubProxy.Invoke("NotifyAllClients"); } } }
واقعیت این هست که برای چنین برنامهی کوچکی، 15مگابایت حجم زیادی به حساب میآید. بیشتر این حجم به دلیل کتابخانهی کلاسهای پایه (BCL) میباشد که شامل mscorlib.lib، system و Mono.Android هستند. این کلاسها کامپوننتهایی را که برنامهی ما برای اجرا به آنها احتیاج دارند، فراهم میکنند. البته واقعیت این است که برنامهی ما از تمام این امکانات استفاده نمیکند و میتوان از خیلی از آنها صرف نظر کرد.
وقتی شما برنامهای را برای توزیع آماده میکنید، Xamarin پروسهای را که به Linking معروف است، اجرا میکند. در این پروسه کدهایی که استفاده نشدهاند حذف میشوند. به این ترتیب حجم کدهای برنامه را کاهش میدهند. در واقع بخشهایی از BCL را که استفاده نکردهایم از Package نهایی حذف میکند. برای مثال پروژهی "Hello Word"را در نظر بگیرید (پروژهی پیش فرض). به دلیل آنکه ما از کلاسهای خاصی استفاده نکردهایم، مقدار زیادی از کدهای بلااستفادهی BCL حذف میشوند. تصویر زیر حجم برنامه را مشخص میکند:
چه زمانی از Xamarin.Forms استفاده کنیم:
یکی از راههای ایجاد برنامههای بومی برای اندروید و iOS، استفاده از Xamarn.Android و Xamarin.iOS است. راه دیگر آن Xamarin.Forms است که بیشترین قابلیت اشتراک UI را دارا میباشد. در Xamarin.Forms ما میتوانیم از XAML برای ایجاد UI استفاده کنیم. اما کی بهتر است از آن استفاده کنیم و چه وقت خوب نیست؟
مواردی که بهتر است از Xamarin.Forms استفاده کنیم:
- برنامههای ورود اطلاعات (ِData Entry)
- ایجاد نمونههای اولیه
- برنامههایی که به بازهی وسیعی از قابلیتهای بومی دستگاه مورد نظر احتیاج ندارد.
- برنامههایی که اشتراک کد برای ما مهمتر از نمای ظاهری و زیبایی برنامه باشد.
مواردی که بهتر است از Xamarin.Forms استفاده نکنیم:
- برنامه هایی که تعامل زیادی با کاربر دارد.
- تهیهی برنامههایی با ظاهر بسیار زیبا و پر رنگ و لعاب!
- برنامههایی که نیاز به استفادهی از بازهی وسیعی از APIهای بومی را دارند.
- برنامه هایی که در آنها UIهای سفارشی مهمتر از اشتراک کد میباشند.
نگاهی به SignalR Hubs
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
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 یک سری متد و خاصیت را در اختیار کلاسهای مشتق شده از آن قرار میدهد. سادهترین راه برای آشنایی با این متدها و خواص مهیا، کلیک راست بر روی نام کلاس پایه 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
proxies.chat = this.createHubProxy('chat');
تا اینجا ما موفق شدیم اولین 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); } } }
حالت Clients.All به معنای ارسال پیامی به تمام کلاینتهای متصل به هاب ما هستند.
3) روشهای دیگر، استفاده از خاصیت dynamic دیگری به نام Caller است که میتوان بر روی آن متد دلخواهی را تعریف و فراخوانی کرد.
//این دو عبارت هر دو یکی هستند Clients.Caller.hello(msg); Clients.Client(Context.ConnectionId).hello(msg);
در اینجا پیامی صرفا به فراخوان جاری سرویس ارسال میگردد.
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); }
خاصیت 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(); }
تعدادی از این متدها را میتوان جهت مقاصد 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); }
مورد استفاده دیگر این روش، ارسال اطلاعات به کلاینتها از طریق کدهای یک برنامه تحت وب است (که در همان پروژه هاب واقع شده است). برای مثال در یک اکشن متد یا یک روال رویدادگردان کلیک نیز میتوان از GlobalHost.ConnectionManager استفاده کرد.
Portable Class Library برای تولید Assembly مدیریت شده که قابیلت استفاده در اکثر تکنولوژیها نظیر (WP7(Windows Phone 7 و WPF و Silverlight و Windows Store App و حتی XBox را داراست استفاده میشود. این نوع پروژهها میزان استفاده مجدد از کدها رو به حداکثر ممکن میرسونه و در عوض تعداد پروژههای مورد نیاز رو به حداقل. مورد اصلی استفاده از اونها برای تولید Multi-Targeted Application هاست. برای مثال در تولید پروژههای تحت Windows که با تکنولوژی WPF پیاده سازی میشوند پروژه ViewModel و Model رو با این تکنولوژی پیاده سازی میکنند تا در آینده اگر نیاز بود قسمتی از پروژه با استفاده از Silverlight یا Windows Store App انجام بشه بتونیم Model , ViewModel نوشته شده رو به این پروژهها Reference بدیم. تا قبل از برای انجام این کار باید این دو بخش رو دوباره برای هر تکنولوژی بازنویسی میکردیم.
روش استفاده
زمانی که یک پروژه از نوع Portable Class Library رو ایجاد میکنیم باید حداقل 2 یا چند تا از Platformهای مورد نظر رو انتخاب کنیم. به صورت زیر :
بعد از انتخاب گزینه Portable Class Library و زدن کلید Enter فرم زیر ظاهر خواهد شد که در این قسمت باید Platformهای مورد نظر رو انتخاب کنیم.
در اینجا من 2 Platform رو انتخاب کردم. یکی Silverlight و Net 4.5. یعنی این Portable Class Library هم میتونه به پروژه Silverlight و هم به Class library .Net 4.5 رفرنس داده بشه.
حتما میپرسید چه طوری؟
در واقع Microsoft برای انجام این کار فقط یک سری از Referenceهای مشترک بین این 2 Platform رو به پروژه اضافه میکنه و همین موضوع باعث شده کار با این پروژهها نیاز به یکم مهارت داشته باشه.
برای مثال اگر قصد استفاده از تکنولوژی Async&Await رو دارید باید یکم به خودتون زحمت بدید و CTP مورد نظر رو نصب کنید.(حالا اگر از Net4 استفاده میکنید که باید از یک روش دیگه استفاده کنید که در یک مقاله جداگانه براتون توضیح خواهم داد).
به جدول زیر دقت کنید.
Xbox 360 | Windows Phone | Silverlight | Windows Store | .NET Framework | Feature |
√ | √ | √ | √ | √ | Core |
√ | √ | √ | √ | LINQ | |
Only 7.5 | √ | √ | √ | IQueryable | |
√ | √ | Only 4.5 | Dynamic keyword | ||
√ | √ | √ | Managed Extensibility Framework (MEF) | ||
√ | √ | √ | √ | Network Class Library (NCL) | |
√ | √ | √ | √ | Serialization | |
√ | √ | √ | √ | Windows Communication Foundation (WCF) | |
√ | √ | √ |
Only 4.5 | Model-View-View Model (MVVM) | |
√ | √ | Only 4.0.3 and 4.5 | Data annotations | ||
√ | √ | √ | √ | Only 4.0.3 and 4.5 | XLINQ |
در جدول بالا به صورت کامل مشخص شده که در هر پلاتفرم چه چیزی Support میشه. برای مثال Data Annotation فقط در Net4.3 , 4.5 قابل استفاده است.
اسمبلیهای زیر در یک Portable Class Library قابل استفاده هستند.
mscorlib.dll
System.dll
System.Core.dll
System.Xml.dll
System.ComponentModel.Composition.dll
System.Net.dll
System.Runtime.Serialization.dll
System.ServiceModel.dll
System.Xml.Serialization.dll
System.Windows.dll - From Silverlight
پیاده سازی یک مثال عملی
در این مثال قصد دارم یک کلاس برای مدیریت تاریخ شمسی درست کنم. مراحل زیر رو دنبال کنید
یک Portable Class Library به نام Common به پروژه اضافه کنید.
یک کلاس به نام PersianDate به برنامه اضافه کرده به صورت زیر
public class PersianDate<TCalendar> where TCalendar : System.Globalization.Calendar { public PersianDate() { this.CurentCalendar = System.Activator.CreateInstance<TCalendar>(); } public TCalendar CurentCalendar { get; private set; } public string GetDate() { return string.Format( "{0}/{1}/{2}", this.CurentCalendar.GetYear( DateTime.Today ).ToString( "####0000" ) , this.CurentCalendar.GetMonth( DateTime.Today ).ToString( "##00" ) , this.CurentCalendar.GetDayOfMonth( DateTime.Today ).ToString( "##00" ) ); } }
حالا یک پروژه از نوع Console Application به برنامه اضافه کنید و یک Reference به پروژه Common بدید و بعد کلاس زیر رو بنویسید.
public class CustomPersianDate { public CustomPersianDate() { } public static Common.PersianDate<System.Globalization.PersianCalendar> PersianCalendar { get { return _persianCalendar ?? ( _persianCalendar = new Common.PersianDate<System.Globalization.PersianCalendar>() ); } } private static Common.PersianDate<System.Globalization.PersianCalendar> _persianCalendar; }
حالا یک پروژه از نوع Silverlight به برنامه اضافه کنید و یک Reference به پروژه Common بدید و بعد کلاس بالا بدون تغییر بنویسید.
نمای کلی پروژه باید به صورت زیر باشد.
بعد پروژه ConsoleApplication رو به عنوان پروژه StartUp انتخاب کنید و فایل Program رو به صورت زیر تغییر بدید.
class Program { static void Main( string[] args ) { Console.WriteLine( "Today Is ?{0}", CustomPersianDate.PersianCalendar.GetDate() ); Console.ReadLine(); } }
خوب نتیجه به صورت زیر خواهد بود:
برای پروژه Silverlight هم نتیجه قطعا به همین صورت است.
همان طور که دید انتخاب نوع Calendar به خود Applicationها واگذار شد و شما میتونید هر نوع تقویم رو به عنوان TCalendar تعیین کنید.
امیدوارم شما هم مثل من به این تکنولوژی دلچسب علاقه پیدا کرده باشید.