,

Building a Stock Ticker Application

The main sample for this app demonstrates the three kinds of push notifications: tile, toast, and raw notifications. It is a stock ticker application that allows the user to enter a stock symbol into the phone, after which a cloud service periodically notifies the phone of stock price variations.


Note

When debugging using a phone device (not the emulator) a local WCF service is unreachable from the app. To allow a device to communicate with a local WCF service, configure Visual Studio to use IIS and use a machine name or external IP address in your WCF client configuration.


The server-side component of the sample application consists of a WCF service that allows the phone app to register itself to receive stock price update notifications. The server-side implementation retrieves stock price information periodically from Yahoo! Finance (see Figure 15.9).

Image

FIGURE 15.9 The sample Stock Quoter application monitors stock prices according to stock symbol.

Input controls for the application include a text box, which allows the user to enter a stock symbol to be monitored, a Register button, and an Unregister button. Clicking the Register button causes the ViewModel’s RegisterForPushNotification method to be called. This causes a PushNotificationSubscriber to be initialized and its events subscribed to, as shown in the following excerpt.

void Subscribe()
{
    if (subscriber == null)
    {
        InitializeSubscriber();
    }
    subscriber.Subscribe();
}

void InitializeSubscriber()
{
    subscriber = new PushNotificationSubscriber(
                        channelName, null, RegisterWithCloudService);
    subscriber.HttpNotificationReceived
                                     += subscriber_HttpNotificationReceived;
    subscriber.ChannelUriUpdated += subscriber_ChannelUriUpdated;
    subscriber.ErrorOccurred += subscriber_ErrorOccurred;
}

A delegate is passed to the subscriber, which is used to notify the cloud service of the push notification URI after it is received from the MPNS.

As mentioned earlier in the chapter, the PushNotificationSubscriber handles the creation and opening of the HttpNotificationChannel (see Listing 15.4). When the Subscribe method is called, a channel is retrieved if it already exists; otherwise, a new channel is created. Subscription occurs to the channel’s various events and the channel is opened. The channel then nominates itself to receive tile and toast notifications.

LISTING 15.4. PushNotificationSubscriber.Subscribe Method


public void Subscribe()
{
    try
    {
        channel = HttpNotificationChannel.Find(channelName);
        if (channel == null)
        {
            channel = string.IsNullOrEmpty(serviceName)
                ? new HttpNotificationChannel(channelName)
                : new HttpNotificationChannel(channelName, serviceName);
        }
        else
        {
            channel.UnbindToShellTile();
            channel.UnbindToShellToast();
            channel.Close();
            Subscribe();
            return;
        }

        UnsubscribeFromChannelEvents();
        SubscribeToChannelEvents();

        try
        {
            channel.Open();
        }
        catch (Exception ex)
        {
            channel.UnbindToShellTile();
            channel.UnbindToShellToast();
            channel.Close();
            WaitAndRetryChannelConnect();
            return;
        }

        /* Toast Notifications. */
        if (!channel.IsShellToastBound)
        {
            channel.BindToShellToast();
        }
        /* Tile Notifications. */
        if (!channel.IsShellTileBound)
        {
            channel.BindToShellTile();
        }

        if (channel.ChannelUri != null)
        {
            RegisterChannel(channel.ChannelUri);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Unable to subscribe. " + ex.ToString());
        WaitAndRetryChannelConnect();
    }
}


If something goes wrong during the subscription phase, the subscriber waits 30 seconds and then tries again, as the following excerpt shows:

void WaitAndRetryChannelConnect()
{
    if (retryTimer == null)
    {
        /* Create a timer and have it fire once. */
        retryTimer = new Timer(o => Subscribe(), null, retryDueTimeMs,
            Timeout.Infinite);
    }
    else
    {
        /* The timer is changed to fire once after the period expires. */
        retryTimer.Change(retryDueTimeMs, Timeout.Infinite);
    }
}

PushNotificationSubscriber contains a field of type Action<string> named registerWithServer, whose task is to send the URI of the MPNS subscription to the StockQuoteService cloud service, along with the stock symbol for the stock that the user wants to monitor. By plugging in this behavior you can test the app without relying on the cloud service being present.


Note

The StockQuoteService implementation is for demonstration purposes only and lacks the capability to support multiple concurrent users. In supporting multiple users, it would be advisable to use a database to record each user’s stock symbols, and to use a worker process to periodically query the database and to send notifications only when a price change occurs.


When the StockQuoteService.RegisterForStockQuoteNotifications method is called on the server, a Timer causes tile, toast, and raw notifications to be periodically sent to the subscriber (Listing 15.5).

The StockQuote result is provided by the StockQuoter class, discussed next.

LISTING 15.5. StockQuoteService Class


[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class StockQuoteService : IStockQuoteService
{
    Timer timer;
    bool notify = true;
    int tilePushCount;
    string subscriptionUrl;
    string stockSymbol;
    StockQuote stockQuote;
    bool sendingNotifications;
    const int notificationPeriodMs = 30000;

    public void RegisterForStockQuoteNotifications(
        PushNotificationSubscriberId pushNotificationSubscriberId,
        string stockSymbol)
    {
        subscriptionUrl = pushNotificationSubscriberId.PushNotificationUrl;
        this.stockSymbol = stockSymbol;
        notify = true;

        if (timer == null)
        {
            timer = new Timer(delegate
                                {
                                    /* An exception must not be raised. */
                                      try
                                      {
                                        NotifyWithStockQuote();
                                      }
                                      catch (Exception ex)
                                      {
                                         Debug.WriteLine("Unable to notify.", ex);
                                      }
                                }, null,
                2000 /* 2000 MS = 5 seconds. */, notificationPeriodMs);
        }
    }
    void NotifyWithStockQuote()
    {
        if (!notify || sendingNotifications)
        {
            return;
        }

        if (tilePushCount > 98)
        {
            tilePushCount = 0;
        }

        try
        {
            sendingNotifications = true;

            try
            {
                StockQuoter stockQuoter = new StockQuoter();
                stockQuote = stockQuoter.GetStockQuote(this.stockSymbol);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to retrieve stock quote." + ex);
                return;
            }

            DateTime now = DateTime.Now;
            Debug.WriteLine(
                "Sending notifications at " + now.ToLongTimeString());
            PushNotifier notifier = new PushNotifier();

            string background;
            if (stockQuote.ChangeInPrice > 0)
            {
                background = "PushNotification/Images/StockUp.png";
            }
            else if (stockQuote.ChangeInPrice < 0)
            {
                background = "PushNotification/Images/StockDown.png";
            }
            else
            {
                background = "PushNotification/Images/StockNone.png";
            }

            try
            {
                notifier.SendTileNotification(
                    stockQuote.ToString(),
                    ++tilePushCount,
                    background,
                    subscriptionUrl,
                    PushNotificationPriority.RealTime);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to send tile notifications:" + ex);
            }

            try
            {
                notifier.SendToastNotification("Stock Quote",
                                       stockQuote.ToString(),
                                             subscriptionUrl,
                            PushNotificationPriority.RealTime);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to send toast notification:" + ex);
            }

            try
            {
                notifier.SendRawNotification(stockQuote,
                    subscriptionUrl, PushNotificationPriority.RealTime);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to send raw notification: " + ex);
            }
        }
        finally
        {
            sendingNotifications = false;
        }
    }

    public void UnregisterForStockQuoteNotifications(
        PushNotificationSubscriberId pushNotificationSubscriberId)
    {
        notify = false;
        tilePushCount = 0;
    }
}


The GetStockQuote method creates an HttpWebRequest that is sent to the Yahoo! Finance stock quote URL. This request produces a CSV (comma-separated values) file containing the stock information of interest, including the price of the stock and the change in price for the day. The following excerpt shows the GetStockQuote method:

public StockQuote GetStockQuote(string stockSymbol)
{
    string queryString = string.Format(
                         "s={0}&f=sl1p2d1t1c1hgvba", stockSymbol);
    string url = "http://download.finance.yahoo.com/d/quotes.csv?"                 + queryString;
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

    string responseString;
    using (HttpWebResponse response
                            = (HttpWebResponse)request.GetResponse())
    {
        using (var reader = new StreamReader(
            response.GetResponseStream(), Encoding.ASCII))
        {
            responseString = reader.ReadLine();
        }
    }

    Debug.WriteLine("Stock Quote: " + responseString);

    var cells = responseString.Split(',');
    if (cells.Length < 3)
    {
        throw new Exception("Invalid response.");
    }
    if (cells[1].ToLower() == @" a")
    {
        throw new UnknownStockSymbolException(stockSymbol);
    }

    StockQuote stockQuote = new StockQuote();
    string changeInPrice = cells[2].Replace(""", "").Replace("%", "");
    stockQuote.ChangeInPrice = double.Parse(changeInPrice,
        NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint);
    stockQuote.LastPrice = double.Parse(cells[1].Replace(""", ""));
    stockQuote.StockSymbol = cells[0].Replace(""", "");
    return stockQuote;
}

The information that is sent back from Yahoo! Finance depends on the contents of the query string in the request URL. The format is like so:

http://finance.yahoo.com/d/quotes.csv?s=
                    <stock symbols separated by +>&f=<field identifiers>

For more information regarding the Yahoo! stock data format, see http://bit.ly/C5MPu.

The StockQuote class is a container for the information retrieved from Yahoo! Finance. It is decorated with DataContract and DataMember attributes so that it can be serialized for raw notifications, as shown in the following excerpt:

[Serializable
[DataContract]
public class StockQuote
{
    [DataMember]
    public string StockSymbol { get; set; }

    [DataMember]
    public double LastPrice { get; set; }

    [DataMember]
    public double ChangeInPrice { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} {2}",
                         StockSymbol, LastPrice, ChangeInPrice);
    }
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.131.142.80