Don't miss any opportunity to market your Windows Phone apps! Each one of your apps can serve as an ad for your other apps. Learn how to add a listing of everything you have published in Marketplace to each of your apps. Even better, it will always be up to date!

Introduction

I've written a number of Windows Phone apps, and each time I've wanted to let people know about my other apps. While I could mention the other apps somewhere, I wouldn't want to update all of my apps each time I release a new one. It finally occurred to me that I could use the listing of apps directly from Marketplace instead. This provides me with an easy-to-parse XML feed (Atom) of my apps with all of the info that I need. Armed with that, it wasn't too much work to create a user control to let me drop in the list of apps anytime I need it.

This code doesn't require a physical phone, but it isn't very useful if you don't have a Marketplace account! Ideally, you should have several published apps under your account for this to make much sense. Once you have it in place though, all of your apps will always show your complete list without any special updates.

If you don't have the software installed, go to create.msdn.com, then click Download the free tools to download the Windows Phone Developer Tools (or use the direct download link provided above this Introduction section). This code is written for the Windows Phone Developer Tools 7.1 (Mango). It’s is a mostly online install, and it’s pretty big so expect it to take some time. Even if you don’t have any development tools, this will give you Visual Studio Express, Blend, and XNA Game Studio. If you have the full-version tools already, it will add new templates.

Project Basics

The intention is to create a user control to display a list of apps from a given publisher (preferably yourself!). This user control will be implemented as a ListBox with individual apps showing up visually similar to the way they do in Marketplace. Touching an app in the list should bring the user directly to the appropriate Marketplace page. It should be as easy as adding a project reference, adding the control to a XAML page, and setting the publisher (this could potentially be set by reading the WMAppManifest.xml file).

Marketplace Data

At first glance, getting Marketplace data programmatically isn't an option. Sadly, there's no API for this. Fortunately, there's a solution! If you're using the Zune app to browse publishers and apps, you can watch the network traffic using Fiddler (http://fiddler2.com/fiddler2/). What's nice is that everything you do in Zune results in a simple HTTP request for the data, which is returned as an XML stream. A simple WebRequest object can do a DownloadStringAsync call to get the data, then the SyndicationFeed class can load and parse it. There are extension elements for rating, release date, and price. Even nicer, is that the image thumbnail can be resized server-side based on the URL query string. This makes it super easy to get exactly what we need.

The server of interest is catalog.zune.net, and the URL format is:

/v3.2/en-US/apps?q=PublisherName&clientType=WinMobile 7.1&store=zest

The "q" parameter just needs to be set to the publisher name of interest. Remember to URL encode yours if you have spaces in it.

Note the "clientType" parameter. If you change it to WinMobile 7.0 you will get NoDo (pre-Mango) apps only. You may or may not have anything show up for that, but it's not likely you'll want that list.

The "store" parameter is set to "zest" but we'll just have to take this one on faith! I'm not aware of any other options here. This could vary by country, but I don't have any data on that.

This will return all apps for Mango. This returns feed information starting with a header:

<?xml version="1.0" encoding="utf-8"?> <a:feed xmlns:a="http://www.w3.org/2005/Atom" xmlns:os=http://a9.com/-/spec/opensearch/1.1/ xmlns="http://schemas.zune.net/catalog/apps/2008/02"> <a:link rel="self" type="application/atom+xml" href="/v3.2/en-US/apps?q=Arian+T.+Kulp&store=zest&clientType=WinMobile+7.1" /> <os:startIndex>1</os:startIndex> <os:totalResults>5</os:totalResults> <os:itemsPerPage>5</os:itemsPerPage> <a:updated>2012-01-30T04:21:41.9702941Z</a:updated> <a:title type="text">List Of Items</a:title> <a:id>tag:catalog.zune.net,2012-01-30:/apps</a:id>

You can pretty much ignore this section. The important stuff comes next! Each Atom entry is one app, along with every bit of metadata that you need to make an interesting view:

<a:entry> <a:updated>2012-01-30T04:21:41.9858942Z</a:updated> <a:title type="text">Metro Lockscreen Creator</a:title> <a:id>urn:uuid:0f5eaaa8-e75e-4a04-a5f9-24db1c176a6b</a:id> <sortTitle>Metro Lockscreen Creator</sortTitle> <releaseDate>2011-07-26T14:30:39.987Z</releaseDate> <version>1.3.0.0</version> <averageUserRating>7.254902</averageUserRating> <userRatingCount>51</userRatingCount> <averageLastInstanceUserRating>5.2</averageLastInstanceUserRating> <lastInstanceUserRatingCount>5</lastInstanceUserRatingCount> <image> <id>urn:uuid:cb46389d-6d50-4be0-b4ff-75c968f301ea</id> </image> <categories> <category> <id>windowsphone.toolsandproductivity</id> <title>tools + productivity</title> <isRoot>True</isRoot> </category> </categories> <tags> <tag>apptag.independent</tag> </tags> <offers> <offer> <offerId>urn:uuid:b8d5cc60-0839-4bcb-b26e-e85ba3e92c8d</offerId> <mediaInstanceId>urn:uuid:2ff871ea-cf8a-488e-b613-286052b90a13</mediaInstanceId> <clientTypes> <clientType>WinMobile 7.1</clientType> </clientTypes> <paymentTypes> <paymentType>Credit Card</paymentType> <paymentType>Mobile Operator</paymentType> </paymentTypes> <store>Zest</store> <price>0</price> <displayPrice>$0.00</displayPrice> <priceCurrencyCode>USD</priceCurrencyCode> <licenseRight>Purchase</licenseRight> </offer> </offers> <publisher> <id>Arian T. Kulp</id> <name>Arian T. Kulp</name> </publisher> </a:entry>

From this block you can get title, release date, rating average and count, category, image ID (easily converted to URL), and price (per market). The System.ServiceModel.Syndication.SyndicationFeed class can read from an XMLReader object and it handles everything for you. Dealing with the custom Marketplace namespace can lead to some slight complication, but fortunately that's simplified as well.

Syndicated Data

For standard Atom elements (the ones with the a: namespace here), you can use properties of the SyndicationItem class. For some reason, the SyndicationItem class isn't actually available in the Windows Phone libraries. I'm not sure why this is the case, but it turns out that you can use the desktop version without a problem. Just add a reference to the "C:\Program Files (x86)\Microsoft SDKs\Silverlight\v3.0\Libraries\Client\System.ServiceModel.Syndication" assembly. You might get a warning when you add it, but it will work fine. If you download the accompanying code, you'll get all the assemblies you need in it.

The SyndicationItem class gets you properties like Title and Id. The other properties are all custom types in the "http://schemas.zune.net/catalog/apps/2008/02" namespace. From these properties, you are interested in shortDescription, userRatingCount, averageUserRating, version, releaseDate, displayPrice, and priceCurrencyCode. Instead of worrying about namespaces, since you know they are custom elements, you can use the ElementExtensions collection on the SyndicationItem and query the OuterName property. A simple extension method makes this easy:

Visual Basic

Private Shared Function GetExtensionElementValue(Of T)(item As SyndicationItem, extensionElementName As String) As T Dim f = (From ee In item.ElementExtensions Where ee.OuterName = extensionElementName).FirstOrDefault() Return If(f Is Nothing, Nothing, f.GetObject(Of T)()) End Function

Visual C#

private static T GetExtensionElementValue<T>(SyndicationItem item, string extensionElementName) { var f = item.ElementExtensions.Where(ee => ee.OuterName == extensionElementName).FirstOrDefault(); return f == null ? default(T) : f.GetObject<T>(); }

With this method in place, you can create most of a new MarketplaceApp object with the following code:

Visual Basic

Dim app = New MarketplaceApp() With { _ .Id = id, _ .AppLink = "http://windowsphone.com/s?appid=" & Convert.ToString(id), _ .Title = item.Title.Text, _ .ShortDescription = GetExtensionElementValue(Of String)(item, "shortDescription"), _ .UserRatingCount = GetExtensionElementValue(Of Integer)(item, "userRatingCount"), _ .Version = GetExtensionElementValue(Of String)(item, "version"), _ .AverageUserRating = GetExtensionElementValue(Of Double)(item, "averageUserRating") / 2.0, _ .ReleaseDate = GetExtensionElementValue(Of String)(item, "releaseDate"), _ .PrimaryImageUrl = Root & "/v3.2/en-US/apps/" & Convert.ToString(id) & "/primaryImage?width=95&height=95&resize=true", _ .DisplayPrice = "Unknown" _ }

Visual C#

var app = new MarketplaceApp { Id = id, AppLink = "http://windowsphone.com/s?appid=" + id, Title = item.Title.Text, ShortDescription = GetExtensionElementValue<string>(item, "shortDescription"), UserRatingCount = GetExtensionElementValue<int>(item, "userRatingCount"), Version = GetExtensionElementValue<string>(item, "version"), AverageUserRating = GetExtensionElementValue<double>(item, "averageUserRating") / 2.0, ReleaseDate = GetExtensionElementValue<string>(item, "releaseDate"), PrimaryImageUrl = Root + "/v3.2/en-US/apps/" + id + "/primaryImage?width=95&height=95&resize=true" };

Where it gets a little bit tricky is the "offers" block. This is an XML block within the overall Item block. This requires that you shift over to an XmlReader method of parsing. This works by reading the XML sequentially, stopping at elements of interest. You start by finding the "offers" element, but instead of using the GetObject method, you use GetReader. From there, you use a while loop to visit every node, and grab the value if it's of XmlNodeType.Element and either displayPrice or priceCurrencyCode.

Visual Basic

Dim offers = (From ee In item.ElementExtensions Where ee.OuterName = "offers").FirstOrDefault().GetReader() ' TODO: Restrict to current country's offer If offers.ReadToFollowing("offer") Then While offers.Read() If offers.NodeType = XmlNodeType.Element Then If offers.Name = "displayPrice" Then app.DisplayPrice = offers.ReadElementContentAsString() ElseIf offers.Name = "priceCurrencyCode" Then app.PriceCurrencyCode = offers.ReadElementContentAsString() End If End If End While End If

Visual C#

var offers = item.ElementExtensions. Where(ee => ee.OuterName == "offers").FirstOrDefault().GetReader(); // TODO: Restrict to current country's offer if (offers.ReadToFollowing("offer")) { while(offers.Read()) { if (offers.NodeType == XmlNodeType.Element) { if (offers.Name == "displayPrice") app.DisplayPrice = offers.ReadElementContentAsString(); else if (offers.Name == "priceCurrencyCode") app.PriceCurrencyCode = offers.ReadElementContentAsString(); } } }

Notice the "TODO" block. If you are offering your app for sale at different prices in different markets, this will only grab the first offer. Ideally, this should grab the offer for the user's location, but that's another topic!

Creating the Control

Creating a user control makes it easy to add your list of apps anywhere. I like to add it to a PivotItem in my About page. There are really three things to do:

Download the XML Parse the XML into model objects Bind the collection of items to a list

Of course, before you can show anything, you'll want to create a template to serve as the ItemTemplate in the control. This will use databinding to point back at the properties of the marketplace entries to display the name, price, image, etc. This can be created inline within the ListBox markup, or in the UserControl.Resources block (as it is in this case). The root element is a Grid. The Image shows the app's icon, and the StackPanel displays the title, price, and rating information.

<DataTemplate x:Key="AppTemplate"> <Grid x:Name="LayoutRoot" Margin="0,0,0,4" Width="450" Background="Transparent"> <Grid.ColumnDefinitions> <ColumnDefinition Width="95"/> <ColumnDefinition Width="1*" /> </Grid.ColumnDefinitions> <Image Source="{Binding PrimaryImageUrl}" Margin="0,0,20,10" /> <StackPanel Grid.Column="1"> <TextBlock Text="{Binding Title}" /> <Controls1:RatingControl Max="5" Total="{Binding UserRatingCount}" Score="{Binding AverageUserRating}" /> <TextBlock Text="{Binding DisplayPrice}" /> </StackPanel> </Grid> </DataTemplate>

With the template complete, you just need a ListBox with the ItemTemplate set to the above template.

<Grid> <ListBox x:Name="listBox" ItemsSource="{Binding Apps}" ItemTemplate="{StaticResource AppTemplate}"SelectionChanged="ListBox_SelectionChanged" /> <StackPanel x:Name="LoadingView" HorizontalAlignment="Center" VerticalAlignment="Center"Visibility="Collapsed"> <TextBlock TextWrapping="Wrap" Text="Loading list of apps..." d:LayoutOverrides="Height"FontWeight="Black"/> <ProgressBar Margin="0,0,0,6" Height="5" IsIndeterminate="True"/> </StackPanel> <StackPanel x:Name="ErrorView" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed"> <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Sorry, there was an error reading the list of apps." FontStyle="Italic"/> </StackPanel> </Grid>

The other elements are used to display a loading message and an error message. Switching between the list, loading, or error message is accomplished using VisualStateManager with the "Normal," "Loading," or "Error" states accordingly.

The only other methods needed in the code-behind are to handle selection and caching. When an item is selected, you create a MarketplaceDetailTask object, set the ContentIdentifier to the app's unique ID, and call Show(). Set the SelectedIndex property back to -1 to prevent a problem where a user can't press the same item twice in the row.

The caching is important so the app doesn't cause data access on every single visit to the control. It's currently set to reload once per day, but this could be changed (or better yet, made configurable). Even if it's more than a day, if there's an error downloading the data it will always fallback to cache.

Visual Basic

Dim items As IEnumerable(Of MarketplaceApp) = Nothing If IsolatedStorageSettings.ApplicationSettings.Contains("PublisherAppsControls.Items") Then items = DirectCast(IsolatedStorageSettings.ApplicationSettings( _ "PublisherAppsControls.Items"), IEnumerable(Of MarketplaceApp)) ' Use the cache if within a day... Dim dt = CType(IsolatedStorageSettings.ApplicationSettings( _ "PublisherAppsControls.Items.DateTime"), DateTime) If DateTime.Now.Subtract(dt).TotalDays < 1 Then ctrl.Apps.Clear() For Each i In items ctrl.Apps.Add(i) Next Return End If End If

Visual C#

IEnumerable<MarketplaceApp> items = null; if (IsolatedStorageSettings.ApplicationSettings.Contains("PublisherAppsControls.Items")) { items = (IEnumerable<MarketplaceApp>) IsolatedStorageSettings.ApplicationSettings["PublisherAppsControls.Items"]; // Use the cache if within a day... var dt = (DateTime)IsolatedStorageSettings. ApplicationSettings["PublisherAppsControls.Items.DateTime"]; if (DateTime.Now.Subtract(dt).TotalDays < 1) { ctrl.Apps.Clear(); foreach (var i in items) ctrl.Apps.Add(i); return; } }

Next Steps

I haven't tested this app against publishers with large numbers of apps. I'm not sure if they would all come back in the response or if there would be more requests to make. It would also be good to filter out the currently running app. This could be obtained from the WMAppManifest.xml file. As mentioned in the article, it would also be good to restrict the offer to the current country, but I really don't know how to do that without using the location API, which seems too heavy-handed.

It would be nice to create a general-purpose library for Zune Marketplace data. There are some good starts out there from the likes of Brandon Watson and Jesse Liberty, a CodePlex library, and even another Coding4Fun project, but much of that is based on general catalog data, or specialized for music entries. Even better would be if Microsoft can put out their own API! That would ensure a better experience if/when formats change over time.

Conclusion

XML has made the world of data so much easier to consume! As soon as I saw structured data as the foundation for Zune I knew this would be a pretty easy project. Being able to advertise all of your apps in each of your apps provides a great way to promote with a minimum of effort.

About the Author

Arian Kulp is a software developer living in Western Oregon. He creates samples, screencasts, demos, labs, and articles, speaks at programming events, and enjoys hiking and other outdoor adventures with his family.