Monday, December 26, 2011

Unit Test Initialization and Cleanup for Windows Azure Storage Emulator

My unit tests add and delete entities from local Windows Azure dev storage,  at their most basic. If the Azure Storage emulator isn’t started, the tests don’t fail quickly – they just sit there acting like the test framework is hung. In order to ensure that the tests proceed, I needed to make sure the emulator was started before the tests ran.

 

I started with code found in this thread on StackOverflow. I needed to make sure the Storage Emulator is started before the test classes are called and that the emulator is shut down when the tests are done. I wrote this class and added it as a separate file to my test assembly.

 

More information about CSRun.exe and the Process class are available in MSDN.

 

// -----------------------------------------------------------------------
// <copyright file="AssemblySpecific.cs" company="BerryIntl">
// Berry International 2011
// </copyright>
// -----------------------------------------------------------------------

namespace Wp7AzureMgmt.Dashboard.Test
{
    using System;
    using System.Text;
    using System.Collections.Generic;
    using System.Linq;
    using System.Diagnostics;
    using System.IO;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Class containing assembly specific test initialization and cleanup
    /// </summary>
    [TestClass]
    public class AssemblySpecific
    {
        /// <summary>
        /// Location of csrun.exe - may be different base on install point and azure sdk version
        /// </summary>
        private const string AzureSDKBin = @"C:\Program Files\Windows Azure Emulator\emulator";

        /// <summary>
        /// Code to run before ClassInitialize or TestInitialize
        /// </summary>
        /// <param name="context">TestContext context</param>
        [AssemblyInitialize]
        public static void MyAssemblyInitialize(TestContext context)
        {
            List<Process> processStatus = Process.GetProcessesByName("DSService.exe").ToList();

            if (( processStatus == null ) || ( processStatus.Count == 0 ))
            {
                ProcessStartInfo processStartInfo = new ProcessStartInfo()
                {
                    FileName = Path.Combine(AzureSDKBin, "csrun.exe"),
                    Arguments = "/devstore",
                };
                using (Process process = Process.Start(processStartInfo))
                {
                    process.WaitForExit();
                }
            }
        }

        /// <summary>
        /// Code to run before ClassCleanup or TestCleanup
        /// </summary>
        [AssemblyCleanup]
        public static void MyAssemblyInitialize()
        {
            List<Process> processStatus = Process.GetProcessesByName("DSService.exe").ToList();

            if ((processStatus != null) || (processStatus.Count > 0))
            {
                ProcessStartInfo processStartInfo = new ProcessStartInfo()
                {
                    FileName = Path.Combine(AzureSDKBin, "csrun.exe"),
                    Arguments = "/devstore:shutdown",
                };
                using (Process process = Process.Start(processStartInfo))
                {
                    process.WaitForExit();
                }
            }
        }
    }
}

Thursday, December 8, 2011

Simple Azure Web and Worker Roles–Running the code

This is a continuation of the simple series. Links for other posts in the series are at the bottom of this post. Download Visual Studio Solution here.

 

This series explains the Windows Phone 7 requesting data from a Windows Azure web service. The phone tile and toast are supported by a background agent on the phone while the web service is supported by a continuous worker role on Windows Azure.

Document 1

The phone calls the WebRole REST API from both the phone app and the phone app’s background agent. Each request is different though. The App’s request is fetching a list of items to show in the app while the background agent is fetching the count of items since the last fetch and the very last item. This information will be displayed in the popup toast and the app’s start screen pinned tile.

 

Azure Only

This post will cover the Azure web  and worker roles only. The Windows Phone 7 app-to-Azure communication will be covered in a separate post. Grab the Visual Studio solution available for download. Running the project doesn’t require that you have an Azure account but you will need the Azure SDK installed.

 

Prerequisites

In order to make this simple sample work, you need to have the following installed:

  • Visual Studio (not Express)
  • Azure SDK 1.4

The AzureSample Solution

The Azure Visual Studio solution has several projects:

 

image

 

The Solution Items project contains a text file of blog post URLs that I found helpful. The Azure project is where the Azure configuration settings are stored. Since this sample enters data into Azure Storage tables, you will notice that the Azure project ServiceConfiguration.Local.cscfg file uses local developer storage.

<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="Azure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="1" osVersion="*">
  <Role name="MvcWebRole">
    <Instances count="1" />
    <ConfigurationSettings>
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="1" />
    <ConfigurationSettings>
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

The next project in the AzureSample Solution is the AzureSharedLibrary which is where the main code for the solution is shared between the Web Role and the Worker Role projects. The MvcWebRole project contains both the REST API answering the phone’s requests as well as an HTML page that refreshes to display the top 100 chronological rows in Azure Storage. The last project is the WorkerRole which periodically cycles to enter data into the table.

 

Both the MvcWebRole and the WorkerRole have as little code in them as possible. I did this on purpose so that you could see what I added and how it pulled together from the shared library.

 

What does the Sample Do?

If you let the sample run, you will see the web page refreshes with new data every few seconds. Both the web page (in the web role) and the worker role are inserting information into the Azure Storage table. Granted this isn’t the typical scenario, it is just an example. In a future post, I’ll connect a Windows Phone 7 app and background agent to this AzureSample and they will all work together.

In my current development project, the worker role is polling a 3rd party web site for new information and inserting the information into the Azure Storage table. Then the phone app is requesting that new information either through the phone’s app or background process.

 

Running the Code

Start Visual Studio with elevated permissions: right click on the VS program and select Run as Administrator.

image

 

Make sure the Azure project is set as the start project then start to debug. The Azure emulators should start if they weren’t already. The default web page should display revealing the data inserted by both roles.

 

Viewing the Data

Once the solution is started, the web page for the MvcWebRole will display. After a few seconds of running, it should show some lines of data that include the process name and the UTC date it was entered.

 

image

 

Even though the local storage emulator is used, you can view the data via the Visual Studio server explorer or a third party tool such as Azure Storage Explorer. Both work with the local storage emulator.

 

Visual Studio Server Explorer

image

 

Azure Storage Explorer

image

 

 

Summary

This blog post showed how to allow a web and worker role to access the same Azure Storage table. The web role provides both web page and REST access to the data while the worker role only inserts the data into the table. In the next post, I’ll go through the code for these two roles.

 

Simple Series

Tuesday, November 15, 2011

Simple WP7 Mango App for Background Tasks, Toast, and Tiles: Code Explanation

This is a continuation of this post, explaining how the sample app works.

image

Caution: This sample isn’t meant as an example of best patterns and practices. If you see something in the code you would never do, then don’t.

Solution File Organization

The app has three Visual Studio Projects: the app, the agent, and the shared code library between them.

clip_image001

One of the great things about Mango is that now the built-in Visual Studio Unit test functionality is available. By separating out the meat of the work into the SampleShared project, I can also focus my unit testing there.

SampleApp1

The app has a MainPage, a ViewModel, and the WMAppManifest.xml file modified to note my ExtendedTask of BackgroundServiceAgent.

clip_image002

The Agent is extremely simple with a call to the shared library, an update to the Tile, and a call to pop up Toast.

clip_image003

The meat of the work is in the SampleShared library.

clip_image004

The AgentMgr manages the background agent: add, remove, find, run now. It is only called from the app via these buttons:
· Start Agent,
· Stop Agent,
· Launch For Test.

The BackgroundAgentRESTCall is a containing class for all the true processing I want the agent to do. It is only called from the agent. For this sample app, it just reads and writes to the mutexed isolated storage file.

The FileIsoStorageMutex class is called by both the agent and the app via these buttons:
· Read ISO Storage,
· Write ISO Storage.

The best practices suggests mutex-protected storage:
"For one-direction communication where the foreground application writes and the agent only reads, we recommend using an isolated storage file with a Mutex. We recommend that you do not use IsolatedStorageSettings to communicate between processes because it is possible for the data to become corrupt."

TileNotification updates the app tile pinned to the start screen via the Update Tile button. It is also called by the agent. By putting the tile code in a separate assembly, I avoid the issue that I can’t update the tile from the UI thread. The toast notification is called by the agent.

Debug Tip: Use the Debug.WriteLine() liberally and keep an eye on the Output window. This is where you can see the background agent begin and end.

Dive into Code - App

The three main parts of the code for the app are the MainPage, the ViewModel, and the WMAppManifest. The MainPage uses the ViewModel which in turn calls over to the SampleShared library. The WMAppManifest, however, has a new section and the Visual Studio project creation for the app didn’t add it – you get to do that.

The tasks section and default task were already there. I added the extended task:
<Tasks>
      <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml"/>
      <ExtendedTask Name="BgTask">
        <BackgroundServiceAgent Specifier="ScheduledTaskAgent"
                                Name="Sample Agent" Source="SampleAgent"
                                Type="SampleAgent.SampleAgent" />
      </ExtendedTask>
    </Tasks>
The TextFile1.txt file of the App project includes all the blog posts and web pages I read to research this topic. If you want other code examples, explanations, or related Microsoft MSDN library documentation, use the links found there.

 

Dive into Code - The Agent Code

The agent code may be new to you but my example is short and simple. The default OnInvoke() provided when I created the Visual Studio Background Agent project is the only code in that project.

public class SampleAgent : ScheduledTaskAgent
    {

        public String BackgroundPNG = "background.png";
        public String MainPageURLForToast = "/MainPage.xaml";
        public String ToastTitle = "Mutex:";
        
        /// <summary>
        /// Called by the system when there is work to be done
        /// </summary>
        /// <param name="Task">The task representing the work to be done</param>
        protected override void OnInvoke(ScheduledTask Task)
        {
            Debug.WriteLine("SampleAgent.SampleAgent.OnInvoke");

            //Here is where you put the meat of the work to be done by the agent
            IsoStorageData MutexedData = BackgroundAgentRESTCall.Call();

            //Toast Popups (processname + datetime in minutes)
            ToastNotifications.ShowToast(ToastTitle, MutexedData.LastProcessToTouchFile + "-" + MutexedData.LastTimeFileTouched.ToShortTimeString().ToString(), MainPageURLForToast);

            //Tile Pinned to Start screen (processname + datetime in minutes)
            // "2" signifies sampleagent
            TileNotifications.UpdateTile(MutexedData.LastProcessToTouchFile + "-" + MutexedData.LastTimeFileTouched.ToShortTimeString().ToString(), BackgroundPNG, 2);

            // this makes the agent run in a shorter cycle than 30 minutes
            // if the iso data setting is turned off (from the app)
            if (MutexedData.CycleAgentEveryMinute)
            {
                AgentMgr.RunDebugAgent();
            }

            //Required Background Agent Finisher
            NotifyComplete();
      }

    }

Shared Code library

The real work is in this file so I’m going to go through the four areas: isolated storage, toast, tiles, and agent. This is a great place to put code to grab data from the phone app which the agent doesn’t have access to. There are many unsupported APIs that the background agents can’t use.

Dive into Code - Shared Code - FileIsoStorageMutex.cs

FileIsoStorageMutex is the class that allows the app and agent to read and or write to the same isolated storage, effectively sharing data. Since the agent and app can wind up using the storage at the same time, I protect the file by a named mutex.

private static Mutex Mutex = new Mutex(false, "BackgroundAgentDemo1");


In order to create a mutex, you have to add a using statement of System.Threading which is contained in the mscorlib.Extensions.dll. Add the dll to the references section of the shared project.

In a production app, I would separate out the data. One isolated storage file would contain app-only data and a second, mutexed file would contain the data that is shared between the app and the agent. While I’m sure the mutex will protect the file sufficiently, I wouldn’t want to accidentally overwrite critical app data which might require a round-trip back to the server or input from the customer.

The code has two functions: read and write. Nothing tricky in either.
// Data object serialized to isolated storage
    public class IsoStorageData
    {
        //datetime of last write to iso storage
        public DateTime LastTimeFileTouched {get;set;}

        //app or agent
        public String LastProcessToTouchFile { get; set; }

        //true==continuous (every minute) cycle of agent
        public bool CycleAgentEveryMinute { get; set; }
    }

    // iso storage manager
    public static class MutexedIsoStorageFile
    {
        //named mutex
        private static Mutex Mutex = new Mutex(false, "BackgroundAgentDemo1");

        //name of isolated storage file
        private const String IsoStorageDateFile = "BackgroundAgentDemo1data.txt";

        //read iso storage
        //debug.writeline lets me "see" agent working in VS output window
        public static IsoStorageData Read()
        {
            IsoStorageData IsoStorageData = new IsoStorageData();

            Mutex.WaitOne();

            try
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(IsoStorageDateFile, FileMode.OpenOrCreate, FileAccess.Read, store))
                using (var reader = new StreamReader(stream))
                {
                    if (!reader.EndOfStream)
                    {
                        var serializer = new XmlSerializer(typeof(IsoStorageData));
                        IsoStorageData = (IsoStorageData)serializer.Deserialize(reader);
                    }
                }
            }
            finally
            {
                Mutex.ReleaseMutex();
            }
            Debug.WriteLine("RRR-data.LastProcessToTouchFile=" + IsoStorageData.LastProcessToTouchFile);
            Debug.WriteLine("RRR-data.LastTimeFileTouched=" + IsoStorageData.LastTimeFileTouched.ToString());
            Debug.WriteLine("RRR-data.CycleAgentEveryMinute=" + IsoStorageData.CycleAgentEveryMinute.ToString()); 
           
            return IsoStorageData;
        }
        //write iso storage
        //debug.writeline lets me "see" agent working in VS output window
        public static void Write(IsoStorageData data)
        {
            Debug.WriteLine("WWW-data.LastProcessToTouchFile=" + data.LastProcessToTouchFile);
            Debug.WriteLine("WWW-data.LastTimeFileTouched=" + data.LastTimeFileTouched.ToString());
            Debug.WriteLine("WWW-data.CycleAgentEveryMinute=" + data.CycleAgentEveryMinute.ToString());

            // persist the data using isolated storage
            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
            using (var stream = new IsolatedStorageFileStream(IsoStorageDateFile,
                                                              FileMode.Create,
                                                              FileAccess.Write,
                                                              store))
            {
                var serializer = new XmlSerializer(typeof(IsoStorageData));
                serializer.Serialize(stream, data);
            }
        }

    }

Dive into Code - Shared Code - Tile Notifications

The default tile that is created as part of the app is used by this application. In a production app, I wouldn’t touch the tile from the app, opting instead to either use in-app notifications similar to the top, numeric indicator in the Facebook app or I would depend on the agent to alter the tile in the next 30 minute window. However, this is a sample so the app is directly updating the tile.
public static class TileNotifications
    {
        //update existing tile
        //no secondary tile
        //no animation
        //Title is title displayed on tile
        //background is can be changed but I use default image
        //good example of changed background image is the People Hub tile
        //count is the number to show in the little black circle
        public static void UpdateTile(String title, String backgroundImageUri, int count)
        {
            ShellTile firstTile = ShellTile.ActiveTiles.First();
            var newData = new StandardTileData() 
            { 
                Title = title, 
                BackgroundImage = new Uri(backgroundImageUri, UriKind.Relative),
                Count = count, 
            };

            firstTile.Update(newData);
        }
    }

The code doesn’t add more tiles, it doesn’t do anything with the backside of the tile, and it doesn’t have animations.

Dive into Code - Shared Code - Toast Notifications

Toast is only popped up via the agent but I wanted it in the shared app project so that I could have a single project for unit testing. The default Visual Studio Unit Test functionality is available to use in Mango while it wasn’t in Metro.
public static class ToastNotifications
    {
        //pop up toast
        //Title is the first part of the string - app name or reference key word
        //signifying app
        //Content is the second part of the string
        //Title + Content is how it is displayed
        //NavigationUri is the page inside your app that you want
        //the user taken to when they select/touch/click on your toast pop up
        //it doesn't have to be the main page
        public static void ShowToast(String title, String message, String NavigationURL)
        {
            var toast = new ShellToast
            {
                Title = title,
                Content = message,
                NavigationUri = new System.Uri(NavigationURL, System.UriKind.Relative)
            }; 
            
            toast.Show();
        }
    }


Dive into Code - Shared Code - BackgroundAgentRESTCall

The last class file contains the code that the agent calls to do the work. The code isn’t shared with the app in this sample, but in a production app I would have a single call that deals with notifications which the app and agent would share.
public static class BackgroundAgentRESTCall
    {
        public const String ProcessName = "Agent";

        public static IsoStorageData Call()
        {
            //This is where you put the call to your webservice. This
            //call can/should determine what to display on the tile and/or
            //toast. However, bring back as little as possible. Leave 
            //the bigger capture of data to your app.
 
            //For purposes of this sample, we just write/read iso storage 
            //and return results

            //Read the mutexed iso storage to get mutexedData.CycleAgentEveryMinute 
            IsoStorageData mutexedData = MutexedIsoStorageFile.Read();

            //Current datetime
            DateTime DateTimeNow = DateTime.Now;

            //Put current process and datetime into mutexed iso storage 
            //carry over previous cycle value
            MutexedIsoStorageFile.Write(new IsoStorageData()
            {
                LastProcessToTouchFile = ProcessName,
                LastTimeFileTouched = DateTimeNow,
                CycleAgentEveryMinute = mutexedData.CycleAgentEveryMinute 
            });

            //Read the mutexed iso storage to verify it was written
            mutexedData = MutexedIsoStorageFile.Read();

            return mutexedData;
        }
    }

Summary

This sample showed a simple app using a background agent to update the pinned tile and pop up toast. The .XAP binary and code solution are both available for download.   

Monday, November 14, 2011

A Simple WP7 Mango App for Background Tasks, Toast, and Tiles

Download Binary and Code

The .XAP binary and code solution are both available for download. The app uses a background process that does some work then updates the tile and pops up toast. The app and the agent talk to each other via an isolated storage file protected by a named mutex. The diagram below shows the pieces and how they work together.

This is the app associated with this post.

App/Agent Architecture


clip_image001

The app and agent don’t actually talk to a web service as this is a sample but in a real world solution they both would. The app would do the heavy lifting. The agent would have a quick, light conversation with just enough information to update the tile and pop up toast with meaningful information.

In this sample, you can configure the agent to run repetitively every minute, second, etc. This is usually done for debug/testing. In the usual release situation, the agent runs on the 30 minute cycle.

Start the App

clip_image006

Pin the App to the Start Screen

Make sure to the pin the app to the start screen as that is where the toast and tile changes will appear. Notice no toast or tile changes yet.
clip_image007

Write Iso Storage

To begin, select the “Write Iso Storage” button, which sets the iso storage to have the name of the process that touched it (“App”) and the datetime the storage was written to. You can verify the iso storage data by looking at the UI which reads from the iso storage after the write.

In this sample, I also write to the output windows to show this. This information in the output window helps me keep track of when and who wrote to isolated storage. RRR means reading from storage. WWW means writing to storage.


RRR-data.LastProcessToTouchFile=
RRR-data.LastTimeFileTouched=1/1/0001 12:00:00 AM
RRR-data.CycleAgentEveryMinute=False
WWW-data.LastProcessToTouchFile=App
WWW-data.LastTimeFileTouched=11/14/2011 8:52:23 AM
WWW-data.CycleAgentEveryMinute=False


Update Tile

Let’s update the app tile to show the iso storage data by selecting the “Update Tile” button then select the phone’s start button to go to the start screen and verify the tile has been updated with “App” and a time. The count in the circle at the top of the tile should be 1. It’s an arbitrary number for this sample. “1” is another signifier meaning the app set the tile. “2” means the agent set the tile.
clip_image008

Verify Settings | Applications | Background tasks

Let’s verify that, while the app is running, the background process for this app has not been turned on yet. Select the start button and go to settings| applications | background tasks. The app of “SampleApp1” should not be in the list.

clip_image009

Go back to App

Now, hold down the back button and select the sample app.

clip_image010
So far, the app touched mutexed isolated storage and the tile. The agent hasn’t been started.

Start Agent

The next thing we can do is select the “Start Agent” button. The background agent does three things: 1) updates the iso storage, 2) pops up toast, and 3) updates the existing tile. Notice the app UI hasn’t changed. If you select the “Read Iso Storage” button, you still see no changes.

And a final place to look, notice that the output windows doesn’t show any work from the background agent. The point is that starting the background agent didn’t do any obvious work. Let’s verify the code did something. Go back to settings | application | background tasks.

clip_image011

Great! The app is now in the background task list so it is “on” but hasn’t run yet. By default, this app uses the standard 30 minute cycle for the agent. But we don’t have to wait that long. Go back to the app.

Launch For Test

Select the “Launch For Test” button and then pop back out to the phone’s start screen. Also make sure you can see the output window of Visual Studio. You may have to wait a few seconds to a minute or more, depending on what your phone is doing at the time.

The start screen showed a change to the tile as well as toast popped up.
clip_image012

The output window showed the Agent’s OnInvoke() call, as well as exiting the code at the end.

SampleAgent.SampleAgent.OnInvoke
RRR-data.LastProcessToTouchFile=Agent
RRR-data.LastTimeFileTouched=11/14/2011 9:07:38 AM
RRR-data.CycleAgentEveryMinute=False
WWW-data.LastProcessToTouchFile=Agent
WWW-data.LastTimeFileTouched=11/14/2011 9:09:34 AM
WWW-data.CycleAgentEveryMinute=False
RRR-data.LastProcessToTouchFile=Agent
RRR-data.LastTimeFileTouched=11/14/2011 9:09:34 AM
RRR-data.CycleAgentEveryMinute=False
The thread '<No Name>' (0xe12012a) has exited with code 0 (0x0).
The thread '<No Name>' (0xe270136) has exited with code 0 (0x0).
The thread '<No Name>' (0xfb30142) has exited with code 0 (0x0).
The program '[253296902] Background Task: Managed' has exited with code 0 (0x0).

If you continue waiting, nothing more will happen as the launch was a one-time event. However, we can change that.

Continuous 1min Cycle of Agent

Move back to the app. The text info at the top of the UI should show the same thing the start screen tile and output window showed.

clip_image013

When the agent is started, it is on a 30 minute cycle. When we launch for test, we run the agent once immediately. But if we want the agent to run repeatedly, we need to configure the agent to do that. This sample app does that via the “Continuous 1min Cycle of Agent” checkbox.

Go ahead and check it. The checkbox writes to iso storage the new setting and the agent reads the setting. At the bottom of the OnInvoke() of the agent, the launch is repeated.

Continuous Cycling

The UI changes to say the app touched storage. Now select the “Launch For Test” button again to get the cycle started. Go back to the start screen. Keep an eye on the tile, toast, and Visual Studio output window. The tile and toast should update and the output window should report agent begin and ending. This cycle should repeat until you stop the uncheck the box, stop the agent, stop the debug session, or uninstall the app.

Caution: If you deployed or debugged on your physical phone, instead of the emulator, remember to stop the agent and delete the sample app when you are done. Otherwise, this can be an annoying app with the constant toast pop ups.

The Code

I’ll explain the code in the next blog post.

Wednesday, October 26, 2011

Windows Azure Cache Tip: Use language change to refresh cache

Several times using the Windows Azure Portal, the page has appeared to recycle correctly its 30 second refresh yet the process change I know reported success doesn’t show up. After a forum thread with a MS Support person, I learned to change the language setting at the top instead of log out and log in.

 

30 Second Refresh at the bottom left of the Browser Window

 

image

 

Change Language setting to refresh Portal Cache at the top right of the Browser Window

image

Friday, October 21, 2011

Reinstalling Purchased Windows Phone 7 Apps after Mango Update

I purchased Flux a while back (metro version) and it is my favorite RSS reader. When my phone updated to mango, Flux updated the day after. Flux has been rock solid but the mango version wouldn’t even get past the splash screen. While I could have tried rebooting the phone (turn off, take battery out, put battery back, and turn back on), I knew a fresh install would definitely fix the issue.

 

I uninstalled Flux then, no the phone, went to the Marketplace  and found it again and reinstalled. The interface didn’t say reinstall though, it said “buy.” This is completely misleading. Microsoft knows I purchased the app so I clicked on buy, knowing it really wasn’t a buy but instead a reinstall.

 

  1. Find app in Marketplace.
  2. “Buy” app which is really just a reinstall. You aren’t charged again.

Thursday, October 20, 2011

Windows Phone 7 and Windows Azure

Introduction

This post will explain how Windows Phone 7 and Windows Azure relate to each other. This is in response to an email question I received over on my apps site. The person, apparently, had been assigned the task of “putting his rewards app on Azure.”

 

This post is a 100 level (introductory) explanation.

 

Client/Server

Twenty years ago the client/server conversation was obvious with a physical client machine (perhaps a Windows version) connecting through a wire to a physical server. Usually these were in the same location or in some sort of private wide area network.  But they were just a client and a server.

 

Ten years ago the client/server conversation was a physical client machine connecting through a wire to a physical server that was not in the same building, probably not in the same state or country. But they were just a client and a server.

 

Since then, until Windows Phone and Azure, nothing really changed except the connection, which became wireless.

 

Now, the client/server conversation is a physical phone with a wireless connection to the server. The server is now a cloud account on Windows Azure. You login to your Gmail account and your Facebook account so the idea isn’t foreign. But still just a client and a server.

 

What goes on the Phone?

Your UX. Possibly an asynchronous connection from the phone to the server, if you need a server.  And whatever else you would put in your web app, your Windows application, or your game. Leave the heavy lifting for the server to do.

 

What goes on Azure?

Azure is just the server so it can be a web site, web service,  file store, database, or a running service. Or anything you want to do with code. It’s location changed but not what it does.

 

How are they related?

From a consumer standpoint, they aren’t. Your consumers shouldn’t have to figure out the technology. It should just work.

 

From a developer perspective, each is a different project type in Visual Studio.

 

From an accessibility perspective, they are different as night and day. You control what goes on your Azure account and how often. You want to upload a new deployment every day in a running release system? Go for it. Want to deploy once a month, once a year, or just once at all. Fine, it’s your account. Is your deployment buggy as hell and can barely stay up? All within your control to keep or go.

 

Not the same for the phone. If you go the get-your-app-certified route, not the side-load route, you are at the mercy of the Microsoft Marketplace: their timeframe, their rules. It isn’t bad but it is completely outside of your immediate control. Plan accordingly.

 

How do I feel about my Windows Phone and Azure development?

I love it and I wouldn’t be able to choose between them if I had to. The end to end solution is an amazing thrill.

Monday, October 3, 2011

Windows Azure Phone Apps for Windows Phone 7

I’ve made two apps targeting Windows Azure developers, testers, and managers. If your company uses any Windows Azure products, you should take a look at the apps, WAZUp and WAZDash.

WAZUp allows you to manage the Deployment status and WAZDash reports on recent service issues. If you would like to keep up with my Windows Azure apps, subscribe by RSS.

Saturday, August 20, 2011

Ah, the joys of poor messages, brief documentation and faded memories!

Today, I was at Hack Dev days in Vancouver, B.C. and enjoyed the group and the presentations. Two recent Uni-grads and I teamed up and proceeded to define a project and work away for most of the day.  We were using the TinEye.com API, specifically a collection of 10 million images from Flickr that they have in one of their libraries. We decided to create a Chrome Extension on the API (one of the other dev’s had done one of these recently).

 

It’s been 16 months since I have done any serious JavaScript/Ajax stuff, and that was (in hind-sight) unfortunately against the website where the pages were hosted…

 

The code that consumed most of the day…

The code below should have been up and working in 15 minutes…

    <script type="text/javascript">
        var ApiUrl = 'http://piximilar-rw.hackdays.tineye.com/rest/'
        function myData() {
            var fd = new FormData();
            fd.append("method", "color_search");
            fd.append("colors[0]", "#FFFFFF");
            fd.append("colors[1]", "#F080C0");
            fd.append("weights[0]", "90");
            fd.append("weights[1]", "10");
            fd.append("color_format", "hex");
            fd.append("limit", "10");
            return fd;
        }
        var data = null;
        function testcall() {            
            var xhr = new XMLHttpRequest();
            xhr.open("POST", ApiUrl);
            xhr.onload = function () {
                data = JSON.parse(xhr.responseText);
            }
            xhr.onerror = function () { alert(xhr.status); }
            xhr.send(myData())
        }
    </script>

 

The problem was that the status was a zero(0) with no status message.  Googling XMLHttpRequest and zero status code found many pages describing a similar issue but no clear explanation (or solution).  I installed the latest version of Fiddler Web Debugger, and saw that the call went off fine, and that the service returned the appropriate JSON fine.

 

image

So the call to the API was being correctly done, results were coming back but with a bizarre status code of zero. Asking around the local HACK developers and no one had an answer….  So it was off trying to find a solution, including trying $.ajax () – which provided less information, overriding mime types, changing “POST” to various other REST verbs (GET was rejected by the API)… the typical systematic trying every variation that came to mind. My colleagues were also encountering problems that they could not identify the cause ….

 

Looking at TinEye.com API documentation did not help, it was centric around the use of cURL with no examples that could be easily copied. They supplied some sample pages using FORMS, but our intent was calling if from JavaScript, so it was not particularly helpful…

 

Well, finally the light went on (I was the one that realize the issue), we were trying to do cross domain POSTs. It turned out that their obtuse problems were the same issue.

 

In hind-sight, the waste of time could have been avoided if:

  • There was a statusText message that went with the zero status code (i.e. “Security violation – cross domain post”), or
  • The .send() raised an error message….
    • Seeing the data coming back in Fiddler, lead me down the wrong path – the API appear to be working correctly…
  • The TinEye.com documentation had reminded the reader about the cross-domain posting issue
    • Ideally, the documentation would have sample code on how to create the needed component on your own server in all of the common languages. Having the code ready to cut-and-paste would have improved adoption
    • Personally, I would love to see firms providing API’s contract out to a seasoned technical developer engineer (what I use to be called at Microsoft) who is good at:
      • Technical writing
      • Creating non-trivial engineering code examples that goes the “extra mile” (resulting in faster adoption of the API).
      • Anticipating how novice developers are likely to proceed and making them successful!
      • The Facebook API is THE classic example of how not to do API documentation.
  • I (or my fellow hacks) had bumped up against the cross-domain API post issues recently.

That’s it for today’s RANT…

Wednesday, August 17, 2011

Windows Azure Service Dashboard OPML Feed File

In case you don’t want to add all the RSS feeds on the Windows Azure Service Dashboard, I’ve created an OPML file you can import into your (Google) reader. The file is up on Windows Live Skydrive but in case you can’t get to that, the file contents are below. If you found I’m missing a link or the link has changed, please let me know.

 

<?xml-stylesheet type="text/xsl" href="http://www.microsoft.com/feeds/msdn_opmlpretty.xsl" version="1.0"?>
<opml version="1.1">
  <head>
    <title>Azure Service Dashboard Feeds</title>
  </head>
  <body>
    <outline text="AppFabric Access Control [East Asia]" title="AppFabric Access Control [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSACSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control [North Central US]" title="AppFabric Access Control [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSACSNCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control [North Europe]" title="AppFabric Access Control [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSACSNE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control [South Central US]" title="AppFabric Access Control [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSACSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control [Southeast Asia]" title="AppFabric Access Control [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSACSSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control [West Europe]" title="AppFabric Access Control [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSACSWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control 2.0 [East Asia]" title="AppFabric Access Control 2.0 [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=ACS2EA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control 2.0 [North Central US]" title="AppFabric Access Control 2.0 [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=ACS2NCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control 2.0 [North Europe]" title="AppFabric Access Control 2.0 [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=ACS2NE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control 2.0 [South Central US]" title="AppFabric Access Control 2.0 [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=ACS2SCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control 2.0 [Southeast Asia]" title="AppFabric Access Control 2.0 [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=ACS2SEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Access Control 2.0 [West Europe]" title="AppFabric Access Control 2.0 [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=ACS2WE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Caching [East Asia]" title="AppFabric Caching [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=AFCEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Caching [North Central US]" title="AppFabric Caching [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=AFCNCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Caching [North Europe]" title="AppFabric Caching [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=AFCNE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Caching [South Central US]" title="AppFabric Caching [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=AFCSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Caching [Southeast Asia]" title="AppFabric Caching [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=AFCSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Caching [West Europe]" title="AppFabric Caching [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=AFCWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Portal [Worldwide]" title="AppFabric Portal [Worldwide]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSPortWW" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Service Bus [East Asia]" title="AppFabric Service Bus [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSSBEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Service Bus [North Central US]" title="AppFabric Service Bus [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSSBSNCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Service Bus [North Europe]" title="AppFabric Service Bus [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSSBSNE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Service Bus [South Central US]" title="AppFabric Service Bus [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSSBSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Service Bus [Southeast Asia]" title="AppFabric Service Bus [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSSBSSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="AppFabric Service Bus [West Europe]" title="AppFabric Service Bus [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=NSSBWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Database Manager [East Asia]" title="Database Manager [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DBMEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Database Manager [North Central US]" title="Database Manager [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DBMNCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Database Manager [North Europe]" title="Database Manager [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DBMNE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Database Manager [South Central US]" title="Database Manager [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DBMSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Database Manager [Southeast Asia]" title="Database Manager [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DBMSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Database Manager [West Europe]" title="Database Manager [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DBMWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Database [East Asia]" title="SQL Azure Database [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SAEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Database [North Central US]" title="SQL Azure Database [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SANCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Database [North Europe]" title="SQL Azure Database [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SANE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Database [South Central US]" title="SQL Azure Database [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SASCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Database [Southeast Asia]" title="SQL Azure Database [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SASEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Database [West Europe]" title="SQL Azure Database [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SAWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="SQL Azure Reporting [South Central US]" title="SQL Azure Reporting [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=SARSSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure CDN [Worldwide]" title="Windows Azure CDN [Worldwide]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACDNWW" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Compute [East Asia]" title="Windows Azure Compute [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Compute [North Central US]" title="Windows Azure Compute [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACNCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Compute [North Europe]" title="Windows Azure Compute [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACNE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Compute [South Central US]" title="Windows Azure Compute [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Compute [Southeast Asia]" title="Windows Azure Compute [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Compute [West Europe]" title="Windows Azure Compute [West Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WACWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Connect [Worldwide]" title="Windows Azure Connect [Worldwide]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WAConnectWW" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Marketplace - DataMarket [South Central US]" title="Windows Azure Marketplace - DataMarket [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=DMSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Service Management [Worldwide]" title="Windows Azure Service Management [Worldwide]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASvcMgmtWW" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Storage [East Asia]" title="Windows Azure Storage [East Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Storage [North Central US]" title="Windows Azure Storage [North Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASNCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Storage [North Europe]" title="Windows Azure Storage [North Europe]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASNE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Storage [South Central US]" title="Windows Azure Storage [South Central US]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASSCUS" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Storage [Southeast Asia]" title="Windows Azure Storage [Southeast Asia]" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASSEA" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
    <outline text="Windows Azure Storage [Southeast Asia]" title="" type="rss" xmlUrl="http://www.microsoft.com/windowsazure/support/status/RSSFeed.aspx?RSSFeedCode=WASWE" htmlUrl="http://www.microsoft.com/windowsazure/support/status/servicedashboard.aspx"/>
  </body>
</opml>
<?xml version="1.0" encoding="utf-8"?>