Bagikan melalui


Tutorial: Siaran Server dengan ASP.NET SignalR 1.x

oleh Patrick Fletcher, Tom Dykstra

Peringatan

Dokumentasi ini bukan untuk versi terbaru SignalR. Lihat ASP.NET Core SignalR.

Tutorial ini menunjukkan cara membuat aplikasi web yang menggunakan ASP.NET SignalR untuk menyediakan fungsionalitas siaran server. Siaran server berarti bahwa komunikasi yang dikirim ke klien dimulai oleh server. Skenario ini memerlukan pendekatan pemrograman yang berbeda dari skenario peer-to-peer seperti aplikasi obrolan, di mana komunikasi yang dikirim ke klien dimulai oleh satu atau beberapa klien.

Aplikasi yang akan Anda buat dalam tutorial ini mensimulasikan ticker stok, skenario umum untuk fungsionalitas siaran server.

Komentar pada tutorial dipersilakan. Jika Anda memiliki pertanyaan yang tidak terkait langsung dengan tutorial, Anda dapat mempostingnya ke forum ASP.NET SignalR atau StackOverflow.com.

Gambaran Umum

Paket NuGet Microsoft.AspNet.SignalR.Sample menginstal sampel aplikasi ticker stok yang disimulasikan dalam proyek Visual Studio. Di bagian pertama tutorial ini, Anda akan membuat versi aplikasi yang disederhanakan dari awal. Di sisa tutorial, Anda akan menginstal paket NuGet dan meninjau fitur dan kode tambahan yang dibuatnya.

Aplikasi stock ticker adalah perwakilan dari semacam aplikasi real-time di mana Anda ingin secara berkala "mendorong," atau menyiarkan, pemberitahuan dari server ke semua klien yang terhubung.

Aplikasi yang akan Anda bangun di bagian pertama tutorial ini menampilkan kisi dengan data stok.

Versi awal StockTicker

Secara berkala server memperbarui harga stok secara acak dan mendorong pembaruan ke semua klien yang terhubung. Di browser, angka dan simbol di Perubahan dan % kolom berubah secara dinamis sebagai respons terhadap pemberitahuan dari server. Jika Anda membuka browser tambahan ke URL yang sama, semuanya menampilkan data yang sama dan perubahan yang sama pada data secara bersamaan.

Tutorial ini berisi bagian-bagian berikut:

Catatan

Jika Anda tidak ingin bekerja melalui langkah-langkah membangun aplikasi, Anda dapat menginstal paket SignalR.Sample dalam proyek Empty ASP.NET Web Application baru, dan membaca langkah-langkah ini untuk mendapatkan penjelasan tentang kode. Bagian pertama dari tutorial mencakup subset kode SignalR.Sample, dan bagian kedua menjelaskan fitur utama dari fungsionalitas tambahan dalam paket SignalR.Sample.

Prasyarat

Sebelum memulai, pastikan Anda menginstal Visual Studio 2012 atau 2010 SP1 di komputer Anda. Jika Anda tidak memiliki Visual Studio, lihat ASP.NET Unduhan untuk mendapatkan Visual Studio 2012 Express for Web gratis.

Jika Anda memiliki Visual Studio 2010, pastikan NuGet diinstal.

Membuat proyek

  1. Dari menu File klik Proyek Baru.

  2. Dalam kotak dialog Proyek Baru , perluas C# di bawah Templat dan pilih Web.

  3. Pilih templat Aplikasi Web Kosong ASP.NET , beri nama proyek SignalR.StockTicker, dan klik OK.

    Kotak dialog Proyek Baru

Menambahkan Paket SignalR NuGet

Menambahkan Paket SignalR dan JQuery NuGet

Anda dapat menambahkan fungsionalitas SignalR ke proyek dengan menginstal paket NuGet.

  1. Klik Alat | Manajer Paket NuGet | Konsol Manajer Paket.

  2. Masukkan perintah berikut di manajer paket.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Paket SignalR menginstal sejumlah paket NuGet lainnya sebagai dependensi. Setelah penginstalan selesai, Anda memiliki semua komponen server dan klien yang diperlukan untuk menggunakan SignalR dalam aplikasi ASP.NET.

Menyiapkan kode server

Di bagian ini Anda menyiapkan kode yang berjalan di server.

Membuat kelas Stock

Anda mulai dengan membuat kelas model Stock yang akan Anda gunakan untuk menyimpan dan mengirimkan informasi tentang stok.

  1. Buat file kelas baru di folder proyek, beri nama Stock.cs, lalu ganti kode templat dengan kode berikut:

    using System;
    
    namespace SignalR.StockTicker
    {
        public class Stock
        {
            private decimal _price;
    
            public string Symbol { get; set; }
    
            public decimal Price
            {
                get
                {
                    return _price;
                }
                set
                {
                    if (_price == value)
                    {
                        return;
                    }
    
                    _price = value;
    
                    if (DayOpen == 0)
                    {
                        DayOpen = _price;
                    }
                }
            }
    
            public decimal DayOpen { get; private set; }
    
            public decimal Change
            {
                get
                {
                    return Price - DayOpen;
                }
            }
    
            public double PercentChange
            {
                get
                {
                    return (double)Math.Round(Change / Price, 4);
                }
            }
        }
    }
    

    Dua properti yang akan Anda tetapkan saat membuat saham adalah Simbol (misalnya, MSFT untuk Microsoft) dan Harga. Properti lain bergantung pada bagaimana dan kapan Anda menetapkan Harga. Saat pertama kali Anda menetapkan Harga, nilai akan disebarluaskan ke DayOpen. Waktu berikutnya ketika Anda menetapkan Harga, nilai properti Ubah dan PercentChange dihitung berdasarkan perbedaan antara Harga dan DayOpen.

Membuat kelas StockTicker dan StockTickerHub

Anda akan menggunakan SignalR Hub API untuk menangani interaksi server-ke-klien. Kelas StockTickerHub yang berasal dari kelas SignalR Hub akan menangani penerimaan koneksi dan panggilan metode dari klien. Anda juga perlu mempertahankan data stok dan menjalankan objek Timer untuk secara berkala memicu pembaruan harga, secara independen dari koneksi klien. Anda tidak dapat menempatkan fungsi ini di kelas Hub, karena instans Hub bersifat sementara. Instans kelas Hub dibuat untuk setiap operasi di hub, seperti koneksi dan panggilan dari klien ke server. Jadi mekanisme yang menyimpan data stok, memperbarui harga, dan menyiarkan pembaruan harga harus berjalan di kelas terpisah, yang akan Anda beri nama StockTicker.

Penyiaran dari StockTicker

Anda hanya ingin satu instans kelas StockTicker berjalan di server, jadi Anda harus menyiapkan referensi dari setiap instans StockTickerHub ke instans StockTicker singleton. Kelas StockTicker harus dapat disiarkan ke klien karena memiliki data stok dan memicu pembaruan, tetapi StockTicker bukan kelas Hub. Oleh karena itu, kelas StockTicker harus mendapatkan referensi ke objek konteks koneksi SignalR Hub. Kemudian dapat menggunakan objek konteks koneksi SignalR untuk disiarkan ke klien.

  1. Di Penjelajah Solusi, klik kanan proyek dan klik Tambahkan Item Baru.

  2. Jika Anda memiliki Visual Studio 2012 dengan pembaruan ASP.NET dan Web Tools 2012.2, klik Web di bawah Visual C# dan pilih templat item Kelas SignalR Hub . Jika tidak, pilih templat Kelas .

  3. Beri nama kelas baru StockTickerHub.cs, lalu klik Tambahkan.

    Menambahkan StockTickerHub.cs

  4. Ganti kode templat dengan kode berikut:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        [HubName("stockTickerMini")]
        public class StockTickerHub : Hub
        {
            private readonly StockTicker _stockTicker;
    
            public StockTickerHub() : this(StockTicker.Instance) { }
    
            public StockTickerHub(StockTicker stockTicker)
            {
                _stockTicker = stockTicker;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stockTicker.GetAllStocks();
            }
        }
    }
    

    Kelas Hub digunakan untuk menentukan metode yang dapat dipanggil klien di server. Anda mendefinisikan satu metode: GetAllStocks(). Ketika klien awalnya terhubung ke server, klien akan memanggil metode ini untuk mendapatkan daftar semua saham dengan harga mereka saat ini. Metode ini dapat dijalankan secara sinkron dan kembali IEnumerable<Stock> karena mengembalikan data dari memori. Jika metode harus mendapatkan data dengan melakukan sesuatu yang akan melibatkan menunggu, seperti pencarian database atau panggilan layanan web, Anda akan menentukan Task<IEnumerable<Stock>> sebagai nilai pengembalian untuk mengaktifkan pemrosesan asinkron. Untuk informasi selengkapnya, lihat ASP.NET Panduan SIGNALR Hubs API - Server - Kapan harus dijalankan secara asinkron.

    Atribut HubName menentukan bagaimana Hub akan dirujuk dalam kode JavaScript pada klien. Nama default pada klien jika Anda tidak menggunakan atribut ini adalah versi camel-cased dari nama kelas, yang dalam hal ini adalah stockTickerHub.

    Seperti yang akan Anda lihat nanti saat membuat kelas StockTicker, instans singleton kelas tersebut dibuat di properti Instans statisnya. Instans singleton StockTicker tetap dalam memori tidak peduli berapa banyak klien yang terhubung atau terputus, dan instans tersebut adalah apa yang digunakan metode GetAllStocks untuk mengembalikan informasi stok saat ini.

  5. Buat file kelas baru di folder proyek, beri nama StockTicker.cs, lalu ganti kode templat dengan kode berikut:

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        public class StockTicker
        {
            // Singleton instance
            private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
            private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
            private readonly object _updateStockPricesLock = new object();
    
            //stock can go up or down by a percentage of this factor on each change
            private readonly double _rangePercent = .002;
    
            private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250);
            private readonly Random _updateOrNotRandom = new Random();
    
            private readonly Timer _timer;
            private volatile bool _updatingStockPrices = false;
    
            private StockTicker(IHubConnectionContext clients)
            {
                Clients = clients;
    
                _stocks.Clear();
                var stocks = new List<Stock>
                {
                    new Stock { Symbol = "MSFT", Price = 30.31m },
                    new Stock { Symbol = "APPL", Price = 578.18m },
                    new Stock { Symbol = "GOOG", Price = 570.30m }
                };
                stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));
    
                _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    
            }
    
            public static StockTicker Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
    
            private IHubConnectionContext Clients
            {
                get;
                set;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stocks.Values;
            }
    
            private void UpdateStockPrices(object state)
            {
                lock (_updateStockPricesLock)
                {
                    if (!_updatingStockPrices)
                    {
                        _updatingStockPrices = true;
    
                        foreach (var stock in _stocks.Values)
                        {
                            if (TryUpdateStockPrice(stock))
                            {
                                BroadcastStockPrice(stock);
                            }
                        }
    
                        _updatingStockPrices = false;
                    }
                }
            }
    
            private bool TryUpdateStockPrice(Stock stock)
            {
                // Randomly choose whether to update this stock or not
                var r = _updateOrNotRandom.NextDouble();
                if (r > .1)
                {
                    return false;
                }
    
                // Update the stock price by a random factor of the range percent
                var random = new Random((int)Math.Floor(stock.Price));
                var percentChange = random.NextDouble() * _rangePercent;
                var pos = random.NextDouble() > .51;
                var change = Math.Round(stock.Price * (decimal)percentChange, 2);
                change = pos ? change : -change;
    
                stock.Price += change;
                return true;
            }
    
            private void BroadcastStockPrice(Stock stock)
            {
                Clients.All.updateStockPrice(stock);
            }
    
        }
    }
    

    Karena beberapa utas akan menjalankan instans kode StockTicker yang sama, kelas StockTicker harus threadsafe.

    Menyimpan instans singleton di bidang statis

    Kode ini menginisialisasi bidang _instance statis yang mendukung properti Instans dengan instans kelas, dan ini adalah satu-satunya instans kelas yang dapat dibuat, karena konstruktor ditandai sebagai privat. Inisialisasi malas digunakan untuk bidang _instance, bukan karena alasan performa tetapi untuk memastikan bahwa pembuatan instans adalah threadsafe.

    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
    public static StockTicker Instance
    {
        get
        {
            return _instance.Value;
        }
    }
    

    Setiap kali klien terhubung ke server, instans baru kelas StockTickerHub yang berjalan di utas terpisah mendapatkan instans singleton StockTicker dari properti statis StockTicker.Instance, seperti yang Anda lihat sebelumnya di kelas StockTickerHub.

    Menyimpan data stok dalam ConcurrentDictionary

    Konstruktor menginisialisasi koleksi _stocks dengan beberapa data stok sampel, dan GetAllStocks mengembalikan saham. Seperti yang Anda lihat sebelumnya, koleksi saham ini pada gilirannya dikembalikan oleh StockTickerHub.GetAllStocks yang merupakan metode server di kelas Hub yang dapat dipanggil klien.

    private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
    private StockTicker(IHubConnectionContext clients)
    {
        Clients = clients;
    
        _stocks.Clear();
        var stocks = new List<Stock>
        {
            new Stock { Symbol = "MSFT", Price = 30.31m },
            new Stock { Symbol = "APPL", Price = 578.18m },
            new Stock { Symbol = "GOOG", Price = 570.30m }
        };
        stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));
    
        _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    }
    
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stocks.Values;
    }
    

    Koleksi saham didefinisikan sebagai jenis ConcurrentDictionary untuk keamanan utas. Sebagai alternatif, Anda dapat menggunakan objek Kamus dan secara eksplisit mengunci kamus saat Anda membuat perubahan padanya.

    Untuk aplikasi sampel ini, tidak apa-apa untuk menyimpan data aplikasi dalam memori dan kehilangan data saat instans StockTicker dibuang. Dalam aplikasi nyata, Anda akan bekerja dengan penyimpanan data back-end seperti database.

    Memperbarui harga saham secara berkala

    Konstruktor memulai objek Timer yang secara berkala memanggil metode yang memperbarui harga saham secara acak.

    _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    
    private void UpdateStockPrices(object state)
    {
        lock (_updateStockPricesLock)
        {
            if (!_updatingStockPrices)
            {
                _updatingStockPrices = true;
    
                foreach (var stock in _stocks.Values)
                {
                    if (TryUpdateStockPrice(stock))
                    {
                        BroadcastStockPrice(stock);
                    }
                }
    
                _updatingStockPrices = false;
            }
        }
    }
    
    private bool TryUpdateStockPrice(Stock stock)
    {
        // Randomly choose whether to update this stock or not
        var r = _updateOrNotRandom.NextDouble();
        if (r > .1)
        {
            return false;
        }
    
        // Update the stock price by a random factor of the range percent
        var random = new Random((int)Math.Floor(stock.Price));
        var percentChange = random.NextDouble() * _rangePercent;
        var pos = random.NextDouble() > .51;
        var change = Math.Round(stock.Price * (decimal)percentChange, 2);
        change = pos ? change : -change;
    
        stock.Price += change;
        return true;
    }
    

    UpdateStockPrices dipanggil oleh Timer, yang melewati null dalam parameter status. Sebelum memperbarui harga, kunci diambil pada objek _updateStockPricesLock. Kode memeriksa apakah utas lain sudah memperbarui harga, dan kemudian memanggil TryUpdateStockPrice pada setiap saham dalam daftar. Metode TryUpdateStockPrice memutuskan apakah akan mengubah harga saham, dan berapa banyak untuk mengubahnya. Jika harga saham diubah, BroadcastStockPrice dipanggil untuk menyiarkan perubahan harga saham ke semua klien yang terhubung.

    Bendera _updatingStockPrices ditandai sebagai volatil untuk memastikan bahwa akses ke bendera tersebut adalah threadsafe.

    private volatile bool _updatingStockPrices = false;
    

    Dalam aplikasi nyata, metode TryUpdateStockPrice akan memanggil layanan web untuk mencari harga; dalam kode ini menggunakan generator angka acak untuk membuat perubahan secara acak.

    Mendapatkan konteks SignalR sehingga kelas StockTicker dapat disiarkan ke klien

    Karena perubahan harga berasal dari sini di objek StockTicker, ini adalah objek yang perlu memanggil metode updateStockPrice pada semua klien yang terhubung. Di kelas Hub Anda memiliki API untuk memanggil metode klien, tetapi StockTicker tidak berasal dari kelas Hub dan tidak memiliki referensi ke objek Hub apa pun. Oleh karena itu, untuk menyiarkan ke klien yang terhubung, kelas StockTicker harus mendapatkan instans konteks SignalR untuk kelas StockTickerHub dan menggunakannya untuk memanggil metode pada klien.

    Kode mendapatkan referensi ke konteks SignalR saat membuat instans kelas singleton, meneruskan referensi tersebut ke konstruktor, dan konstruktor menempatkannya di properti Klien.

    Ada dua alasan mengapa Anda ingin mendapatkan konteks hanya sekali: mendapatkan konteks adalah operasi yang mahal, dan mendapatkannya sekali memastikan bahwa urutan pesan yang dimaksudkan yang dikirim ke klien dipertahankan.

    private readonly static Lazy<StockTicker> _instance =
        new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
    private StockTicker(IHubConnectionContext clients)
    {
        Clients = clients;
    
        // Remainder of constructor ...
    }
    
    private IHubConnectionContext Clients
    {
        get;
        set;
    }
    
    private void BroadcastStockPrice(Stock stock)
    {
        Clients.All.updateStockPrice(stock);
    }
    

    Mendapatkan properti Klien dari konteks dan meletakkannya di properti StockTickerClient memungkinkan Anda menulis kode untuk memanggil metode klien yang terlihat sama seperti di kelas Hub. Misalnya, untuk menyiarkan ke semua klien, Anda dapat menulis Clients.All.updateStockPrice(stock).

    Metode updateStockPrice yang Anda panggil di BroadcastStockPrice belum ada; Anda akan menambahkannya nanti saat menulis kode yang berjalan pada klien. Anda dapat merujuk ke updateStockPrice di sini karena Clients.All bersifat dinamis, yang berarti ekspresi akan dievaluasi saat runtime. Ketika panggilan metode ini dijalankan, SignalR akan mengirim nama metode dan nilai parameter ke klien, dan jika klien memiliki metode bernama updateStockPrice, metode tersebut akan dipanggil dan nilai parameter akan diteruskan ke dalamnya.

    Clients.All berarti mengirim ke semua klien. SignalR memberi Anda opsi lain untuk menentukan klien atau grup klien mana yang akan dikirim. Untuk informasi selengkapnya, lihat HubConnectionContext.

Mendaftarkan rute SignalR

Server perlu mengetahui URL mana yang akan dicegat dan diarahkan ke SignalR. Untuk melakukannya, Anda akan menambahkan beberapa kode ke file Global.asax .

  1. Di Penjelajah Solusi, klik kanan proyek, lalu klik Tambahkan Item Baru.

  2. Pilih templat item Kelas Aplikasi Global , lalu klik Tambahkan.

    Menambahkan global.asax

  3. Tambahkan kode pendaftaran rute SignalR ke metode Application_Start:

    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
    }
    

    Secara default, URL dasar untuk semua lalu lintas SignalR adalah "/signalr", dan "/signalr/hubs" digunakan untuk mengambil file JavaScript yang dihasilkan secara dinamis yang menentukan proksi untuk semua Hub yang Anda miliki di aplikasi Anda. Metode MapHubs mencakup kelebihan beban yang memungkinkan Anda menentukan URL dasar yang berbeda dan opsi SignalR tertentu dalam instans kelas HubConfiguration .

  4. Tambahkan pernyataan penggunaan di bagian atas file:

    using System.Web.Routing;
    
  5. Simpan dan tutup file Global.asax , dan buat proyek.

Anda sekarang telah selesai menyiapkan kode server. Di bagian berikutnya Anda akan menyiapkan klien.

Menyiapkan kode klien

  1. Buat file HTML baru di folder proyek, dan beri nama StockTicker.html.

  2. Ganti kode templat dengan kode berikut:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>ASP.NET SignalR Stock Ticker</title>
        <style>
            body {
                font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
                font-size: 16px;
            }
            #stockTable table {
                border-collapse: collapse;
            }
                #stockTable table th, #stockTable table td {
                    padding: 2px 6px;
                }
                #stockTable table td {
                    text-align: right;
                }
            #stockTable .loading td {
                text-align: left;
            }
        </style>
    </head>
    <body>
        <h1>ASP.NET SignalR Stock Ticker Sample</h1>
    
        <h2>Live Stock Table</h2>
        <div id="stockTable">
            <table border="1">
                <thead>
                    <tr><th>Symbol</th><th>Price</th><th>Open</th><th>Change</th><th>%</th></tr>
                </thead>
                <tbody>
                    <tr class="loading"><td colspan="5">loading...</td></tr>
                </tbody>
            </table>
        </div>
    
        <!--Script references. -->
        <!--Reference the jQuery library. -->
        <script src="/Scripts/jquery-1.8.2.min.js" ></script>
        <!--Reference the SignalR library. -->
        <script src="/Scripts/jquery.signalR-1.0.1.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <!--Reference the StockTicker script. -->
        <script src="StockTicker.js"></script>
    </body>
    </html>
    

    HTML membuat tabel dengan 5 kolom, baris header, dan baris data dengan satu sel yang mencakup 5 kolom. Baris data menampilkan "memuat..." dan hanya akan ditampilkan sesaat ketika aplikasi dimulai. Kode JavaScript akan menghapus baris tersebut dan menambahkan baris tempatnya dengan data stok yang diambil dari server.

    Tag skrip menentukan file skrip jQuery, file skrip inti SignalR, file skrip proksi SignalR, dan file skrip StockTicker yang akan Anda buat nanti. File skrip proksi SignalR, yang menentukan URL "/signalr/hubs", dihasilkan secara dinamis dan mendefinisikan metode proksi untuk metode pada kelas Hub, dalam hal ini untuk StockTickerHub.GetAllStocks. Jika mau, Anda dapat membuat file JavaScript ini secara manual dengan menggunakan Utilitas SignalR dan menonaktifkan pembuatan file dinamis dalam panggilan metode MapHubs.

  3. Penting

    Pastikan bahwa referensi file JavaScript di StockTicker.html sudah benar. Artinya, pastikan bahwa versi jQuery di tag skrip Anda (1.8.2 dalam contoh) sama dengan versi jQuery di folder Skrip proyek Anda, dan pastikan bahwa versi SignalR di tag skrip Anda sama dengan versi SignalR di folder Skrip proyek Anda. Ubah nama file dalam tag skrip jika perlu.

  4. Di Penjelajah Solusi, klik kanan StockTicker.html, lalu klik Atur sebagai Halaman Mulai.

  5. Buat file JavaScript baru di folder proyek dan beri nama StockTicker.js..

  6. Ganti kode templat dengan kode berikut:

    // A simple templating method for replacing placeholders enclosed in curly braces.
    if (!String.prototype.supplant) {
        String.prototype.supplant = function (o) {
            return this.replace(/{([^{}]*)}/g,
                function (a, b) {
                    var r = o[b];
                    return typeof r === 'string' || typeof r === 'number' ? r : a;
                }
            );
        };
    }
    
    $(function () {
    
        var ticker = $.connection.stockTickerMini, // the generated client-side hub proxy
            up = '▲',
            down = '▼',
            $stockTable = $('#stockTable'),
            $stockTableBody = $stockTable.find('tbody'),
            rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{DayOpen}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>';
    
        function formatStock(stock) {
            return $.extend(stock, {
                Price: stock.Price.toFixed(2),
                PercentChange: (stock.PercentChange * 100).toFixed(2) + '%',
                Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down
            });
        }
    
        function init() {
            ticker.server.getAllStocks().done(function (stocks) {
                $stockTableBody.empty();
                $.each(stocks, function () {
                    var stock = formatStock(this);
                    $stockTableBody.append(rowTemplate.supplant(stock));
                });
            });
        }
    
        // Add a client-side hub method that the server will call
        ticker.client.updateStockPrice = function (stock) {
            var displayStock = formatStock(stock),
                $row = $(rowTemplate.supplant(displayStock));
    
            $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
                .replaceWith($row);
            }
    
        // Start the connection
        $.connection.hub.start().done(init);
    
    });
    

    $.connection mengacu pada proksi SignalR. Kode mendapatkan referensi ke proksi untuk kelas StockTickerHub dan memasukkannya ke dalam variabel ticker. Nama proksi adalah nama yang ditetapkan oleh atribut [HubName]:

    var ticker = $.connection.stockTickerMini
    
    [HubName("stockTickerMini")]
    public class StockTickerHub : Hub
    

    Setelah semua variabel dan fungsi didefinisikan, baris kode terakhir dalam file menginisialisasi koneksi SignalR dengan memanggil fungsi mulai SignalR. Fungsi mulai dijalankan secara asinkron dan mengembalikan objek jQuery Deferred, yang berarti Anda dapat memanggil fungsi selesai untuk menentukan fungsi yang akan dipanggil ketika operasi asinkron selesai..

    $.connection.hub.start().done(init);
    

    Fungsi init memanggil fungsi getAllStocks di server dan menggunakan informasi yang dikembalikan server untuk memperbarui tabel stok. Perhatikan bahwa secara default, Anda harus menggunakan casing unta pada klien meskipun nama metode berkapit pascal di server. Aturan camel-casing hanya berlaku untuk metode, bukan objek. Misalnya, Anda merujuk ke stok. Simbol dan stok. Harga, bukan stock.symbol atau stock.price.

    function init() {
        ticker.server.getAllStocks().done(function (stocks) {
            $stockTableBody.empty();
            $.each(stocks, function () {
                var stock = formatStock(this);
                $stockTableBody.append(rowTemplate.supplant(stock));
            });
        });
    }
    
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
    

    Jika Anda ingin menggunakan casing pascal pada klien, atau jika Anda ingin menggunakan nama metode yang sama sekali berbeda, Anda dapat menghias metode Hub dengan atribut HubMethodName dengan cara yang sama seperti Anda mendekorasi kelas Hub itu sendiri dengan atribut HubName.

    Dalam metode init, HTML untuk baris tabel dibuat untuk setiap objek stok yang diterima dari server dengan memanggil formatStock untuk memformat properti objek stok, dan kemudian dengan memanggil supplant (yang didefinisikan di bagian atas StockTicker.js) untuk mengganti tempat penampung dalam variabel rowTemplate dengan nilai properti objek stok. HTML yang dihasilkan kemudian ditambahkan ke tabel stok.

    Anda memanggil init dengan meneruskannya sebagai fungsi panggilan balik yang dijalankan setelah fungsi mulai asinkron selesai. Jika Anda memanggil init sebagai pernyataan JavaScript terpisah setelah panggilan dimulai, fungsi akan gagal karena akan segera dijalankan tanpa menunggu fungsi mulai selesai membuat koneksi. Dalam hal ini, fungsi init akan mencoba memanggil fungsi getAllStocks sebelum koneksi server dibuat.

    Ketika server mengubah harga saham, server memanggil updateStockPrice pada klien yang terhubung. Fungsi ini ditambahkan ke properti klien dari proksi stockTicker untuk membuatnya tersedia untuk panggilan dari server.

    ticker.client.updateStockPrice = function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock));
    
        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        }
    

    Fungsi updateStockPrice memformat objek stok yang diterima dari server ke dalam baris tabel dengan cara yang sama seperti dalam fungsi init. Namun, alih-alih menambahkan baris ke tabel, baris stok saat ini ditemukan dalam tabel dan mengganti baris tersebut dengan yang baru.

Menguji aplikasi

  1. Tekan F5 untuk menjalankan aplikasi dalam mode debug.

    Tabel stok awalnya menampilkan "memuat..." baris, kemudian setelah penundaan singkat data saham awal ditampilkan, dan kemudian harga saham mulai berubah.

    Loading

    Tabel saham awal

    Tabel stok menerima perubahan dari server

  2. Salin URL dari bilah alamat browser dan tempelkan ke satu atau beberapa jendela browser baru.

    Tampilan saham awal sama dengan browser pertama dan perubahan terjadi secara bersamaan.

  3. Tutup semua browser dan buka browser baru, lalu buka URL yang sama.

    Objek singleton StockTicker terus berjalan di server, sehingga tampilan tabel stok menunjukkan bahwa saham terus berubah. (Anda tidak melihat tabel awal dengan angka perubahan nol.)

  4. Tutup browser.

Aktifkan pencatatan log

SignalR memiliki fungsi pengelogan bawaan yang dapat Anda aktifkan pada klien untuk membantu pemecahan masalah. Di bagian ini Anda mengaktifkan pengelogan dan melihat contoh yang menunjukkan bagaimana log memberi tahu Anda metode transportasi mana yang digunakan SignalR:

Untuk koneksi tertentu, SignalR memilih metode transportasi terbaik yang didukung server dan klien.

  1. Buka StockTicker.js dan tambahkan baris kode untuk mengaktifkan pengelogan segera sebelum kode yang menginisialisasi koneksi di akhir file:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  2. Tekan F5 untuk menjalankan proyek.

  3. Buka jendela alat pengembang browser Anda, dan pilih Konsol untuk melihat log. Anda mungkin harus me-refresh halaman untuk melihat log Signalr yang menegosiasikan metode transportasi untuk koneksi baru.

    Jika Anda menjalankan Internet Explorer 10 di Windows 8 (IIS 8), metode transportasinya adalah WebSockets.

    Konsol IE 10 IIS 8

    Jika Anda menjalankan Internet Explorer 10 di Windows 7 (IIS 7.5), metode transportasinya adalah iframe.

    Konsol IE 10, IIS 7.5

    Di Firefox, instal add-in Firebug untuk mendapatkan jendela Konsol. Jika Anda menjalankan Firefox 19 di Windows 8 (IIS 8), metode transportasinya adalah WebSockets.

    Firefox 19 IIS 8 Websockets

    Jika Anda menjalankan Firefox 19 di Windows 7 (IIS 7.5), metode transportasi adalah peristiwa yang dikirim server.

    Konsol Firefox 19 IIS 7.5

Menginstal dan meninjau sampel StockTicker lengkap

Aplikasi StockTicker yang diinstal oleh paket NuGet Microsoft.AspNet.SignalR.Sample mencakup lebih banyak fitur daripada versi yang disederhanakan yang baru saja Anda buat dari awal. Di bagian tutorial ini, Anda menginstal paket NuGet dan meninjau fitur baru dan kode yang mengimplementasikannya.

Menginstal paket SignalR.Sample NuGet

  1. Di Penjelajah Solusi, klik kanan proyek dan klik Kelola Paket NuGet.

  2. Dalam kotak dialog Kelola Paket NuGet , klik Online, masukkan SignalR.Sample di kotak Cari Online , lalu klik Instal dalam paket SignalR.Sample .

    Menginstal paket SignalR.Sample

  3. Dalam file Global.asax , komentari RouteTable.Routes.MapHubs(); baris yang Anda tambahkan sebelumnya dalam metode Application_Start.

    Kode di Global.asax tidak lagi diperlukan karena paket SignalR.Sample mendaftarkan rute SignalR dalam file App_Start/RegisterHubs.cs :

    [assembly: WebActivator.PreApplicationStartMethod(typeof(SignalR.StockTicker.RegisterHubs), "Start")]
    
    namespace SignalR.StockTicker
    {
        public static class RegisterHubs
        {
            public static void Start()
            {
                // Register the default hubs route: ~/signalr/hubs
                RouteTable.Routes.MapHubs();
            }
        }
    }
    

    Kelas WebActivator yang dirujuk oleh atribut assembly disertakan dalam paket WebActivatorEx NuGet, yang diinstal sebagai dependensi dari paket SignalR.Sample.

  4. Di Penjelajah Solusi, perluas folder SignalR.Sample yang dibuat dengan menginstal paket SignalR.Sample.

  5. Di folder SignalR.Sample , klik kanan StockTicker.html, lalu klik Atur Sebagai Halaman Mulai.

    Catatan

    Menginstal paket SignalR.Sample NuGet mungkin mengubah versi jQuery yang Anda miliki di folder Skrip Anda. File StockTicker.html baru yang diinstal paket di folder SignalR.Sample akan sinkron dengan versi jQuery yang diinstal paket, tetapi jika Anda ingin menjalankan file StockTicker.html asli Anda lagi, Anda mungkin harus memperbarui referensi jQuery di tag skrip terlebih dahulu.

Menjalankan aplikasi

  1. Klik F5 untuk menjalankan aplikasi.

    Selain kisi yang Anda lihat sebelumnya, aplikasi ticker stok penuh menunjukkan jendela pengguliran horizontal yang menampilkan data stok yang sama. Ketika Anda menjalankan aplikasi untuk pertama kalinya, "pasar" "ditutup" dan Anda melihat kisi statis dan jendela ticker yang tidak bergulir.

    Layar StockTicker dimulai

    Saat Anda mengklik Buka Pasar, kotak Ticker Saham Langsung mulai menggulir secara horizontal, dan server mulai secara berkala menyiarkan perubahan harga saham secara acak. Setiap kali harga saham berubah, kisi Tabel Saham Langsung dan kotak Ticker Saham Langsung diperbarui. Ketika perubahan harga saham positif, saham ditunjukkan dengan latar belakang hijau, dan ketika perubahan negatif, saham ditampilkan dengan latar belakang merah.

    Aplikasi StockTicker, pasar terbuka

    Tombol Tutup Pasar menghentikan perubahan dan menghentikan pengguliran ticker, dan tombol Reset mengatur ulang semua data stok ke status awal sebelum perubahan harga dimulai. Jika Anda membuka lebih banyak jendela browser dan membuka URL yang sama, Anda akan melihat data yang sama diperbarui secara dinamis pada saat yang sama di setiap browser. Saat Anda mengklik salah satu tombol, semua browser merespons dengan cara yang sama secara bersamaan.

Tampilan Live Stock Ticker

Tampilan Live Stock Ticker adalah daftar yang tidak diurutkan dalam elemen div yang diformat menjadi satu baris menurut gaya CSS. Ticker diinisialisasi dan diperbarui dengan cara yang sama seperti tabel: dengan mengganti tempat penampung dalam <string templat li> dan secara dinamis menambahkan <elemen li> ke <elemen ul> . Pengguliran dilakukan dengan menggunakan fungsi animasi jQuery untuk memvariasikan margin-kiri daftar yang tidak diurutkan dalam div.

HTML ticker saham:

<h2>Live Stock Ticker</h2>
<div id="stockTicker">
    <div class="inner">
        <ul>
            <li class="loading">loading...</li>
        </ul>
    </div>
</div>

Saham ticker CSS:

#stockTicker {
    overflow: hidden;
    width: 450px;
    height: 24px;
    border: 1px solid #999;
    }

    #stockTicker .inner {
        width: 9999px;
    }

    #stockTicker ul {
        display: inline-block;
        list-style-type: none;
        margin: 0;
        padding: 0;
    }

    #stockTicker li {
        display: inline-block;
        margin-right: 8px;   
    }

    /*<li data-symbol="{Symbol}"><span class="symbol">{Symbol}</span><span class="price">{Price}</span><span class="change">{PercentChange}</span></li>*/
    #stockTicker .symbol {
        font-weight: bold;
    }

    #stockTicker .change {
        font-style: italic;
    }

Kode jQuery yang membuatnya menggulir:

function scrollTicker() {
    var w = $stockTickerUl.width();
    $stockTickerUl.css({ marginLeft: w });
    $stockTickerUl.animate({ marginLeft: -w }, 15000, 'linear', scrollTicker);
}

Metode tambahan di server yang dapat dipanggil klien

Kelas StockTickerHub menentukan empat metode tambahan yang dapat dipanggil klien:

public string GetMarketState()
{
    return _stockTicker.MarketState.ToString();
}

public void OpenMarket()
{
    _stockTicker.OpenMarket();
}

public void CloseMarket()
{
    _stockTicker.CloseMarket();
}

public void Reset()
{
    _stockTicker.Reset();
}

OpenMarket, CloseMarket, dan Reset dipanggil sebagai respons terhadap tombol di bagian atas halaman. Mereka menunjukkan pola satu klien yang memicu perubahan status yang segera disebarluaskan ke semua klien. Masing-masing metode ini memanggil metode di kelas StockTicker yang memengaruhi perubahan status pasar dan kemudian menyiarkan status baru.

Di kelas StockTicker, status pasar dipertahankan oleh properti MarketState yang mengembalikan nilai enum MarketState:

public MarketState MarketState
{
    get { return _marketState; }
    private set { _marketState = value; }
}

public enum MarketState
{
    Closed,
    Open
}

Setiap metode yang mengubah status pasar melakukannya di dalam blok kunci karena kelas StockTicker harus threadsafe:

public void OpenMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Open)
        {
            _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
            MarketState = MarketState.Open;
            BroadcastMarketStateChange(MarketState.Open);
        }
    }
}

public void CloseMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState == MarketState.Open)
        {
            if (_timer != null)
            {
                _timer.Dispose();
            }
            MarketState = MarketState.Closed;
            BroadcastMarketStateChange(MarketState.Closed);
        }
    }
}

public void Reset()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Closed)
        {
            throw new InvalidOperationException("Market must be closed before it can be reset.");
        }
        LoadDefaultStocks();
        BroadcastMarketReset();
    }
}

Untuk memastikan bahwa kode ini adalah threadsafe, bidang _marketState yang mendukung properti MarketState ditandai sebagai volatil,

private volatile MarketState _marketState;

Metode BroadcastMarketStateChange dan BroadcastMarketReset mirip dengan metode BroadcastStockPrice yang sudah Anda lihat, kecuali metode tersebut memanggil metode berbeda yang ditentukan di klien:

private void BroadcastMarketStateChange(MarketState marketState)
{
    switch (marketState)
    {
        case MarketState.Open:
            Clients.All.marketOpened();
            break;
        case MarketState.Closed:
            Clients.All.marketClosed();
            break;
        default:
            break;
    }
}

private void BroadcastMarketReset()
{
    Clients.All.marketReset();
}

Fungsi tambahan pada klien yang dapat dipanggil server

Fungsi updateStockPrice sekarang menangani kisi dan tampilan ticker, dan menggunakan jQuery.Color untuk berkedip warna merah dan hijau.

Fungsi baru di SignalR.StockTicker.js mengaktifkan dan menonaktifkan tombol berdasarkan status pasar, dan menghentikan atau memulai pengguliran horizontal jendela ticker. Karena beberapa fungsi ditambahkan ke ticker.client, fungsi perluasan jQuery digunakan untuk menambahkannya.

$.extend(ticker.client, {
    updateStockPrice: function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock)),
            $li = $(liTemplate.supplant(displayStock)),
            bg = stock.LastChange === 0
                ? '255,216,0' // yellow
                : stock.LastChange > 0
                    ? '154,240,117' // green
                    : '255,148,148'; // red

        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        $stockTickerUl.find('li[data-symbol=' + stock.Symbol + ']')
            .replaceWith($li);

        $row.flash(bg, 1000);
        $li.flash(bg, 1000);
    },

    marketOpened: function () {
        $("#open").prop("disabled", true);
        $("#close").prop("disabled", false);
        $("#reset").prop("disabled", true);
        scrollTicker();
    },

    marketClosed: function () {
        $("#open").prop("disabled", false);
        $("#close").prop("disabled", true);
        $("#reset").prop("disabled", false);
        stopTicker();
    },

    marketReset: function () {
        return init();
    }
});

Penyiapan klien tambahan setelah membuat koneksi

Setelah klien membuat koneksi, ia memiliki beberapa pekerjaan tambahan yang harus dilakukan: cari tahu apakah pasar terbuka atau ditutup untuk memanggil fungsi marketOpened atau marketClosed yang sesuai, dan melampirkan panggilan metode server ke tombol.

$.connection.hub.start()
    .pipe(init)
    .pipe(function () {
        return ticker.server.getMarketState();
    })
    .done(function (state) {
        if (state === 'Open') {
            ticker.client.marketOpened();
        } else {
            ticker.client.marketClosed();
        }

        // Wire up the buttons
        $("#open").click(function () {
            ticker.server.openMarket();
        });

        $("#close").click(function () {
            ticker.server.closeMarket();
        });

        $("#reset").click(function () {
            ticker.server.reset();
        });
    });

Metode server tidak dikabeli ke tombol sampai setelah koneksi dibuat, sehingga kode tidak dapat mencoba memanggil metode server sebelum tersedia.

Langkah berikutnya

Dalam tutorial ini Anda telah mempelajari cara memprogram aplikasi SignalR yang menyiarkan pesan dari server ke semua klien yang terhubung, baik secara berkala maupun sebagai respons terhadap pemberitahuan dari klien mana pun. Pola penggunaan instans singleton multi-utas untuk mempertahankan status server juga dapat digunakan dalam skenario game online multi-pemain. Misalnya, lihat game ShootR yang didasarkan pada SignalR.

Untuk tutorial yang menunjukkan skenario komunikasi peer-to-peer, lihat Memulai SignalR dan Pembaruan Real-Time dengan SignalR.

Untuk mempelajari konsep pengembangan SignalR yang lebih canggih, kunjungi situs berikut untuk kode sumber dan sumber daya SignalR: