ASP.Net sites, Clean URLs, and URL Routing

Search engines hate query strings.

For example, say you have a website that sells widgets. You have four types of widgets: red, green, blue and black. You have a page titled Inventory.aspx that accepts a query string to show each of the widgets, like this:

www.MyWidgetSite.com/Widgets.aspx?Type=Red

To make this more SEO friendly, you could have a URL like this:

www.MyWidgetSite.com/Widgets/Red

That allows a search engine to index the site easier, and to more easily identify relevant content for search queries.

The old way would have been to actually create those subdirectories and drop a default page into each one, but by using URL Routing you can create an "alias" that the framework can decipher behind the scenes and direct to the correct page.

You'll add a new route to the Global.asax file. Start by adding a using directive:

using System.Web.Routing;

In the Application_Start method, call a new method, passing it a RouteCollection:

RegisterRoutes(RouteTable.Routes);

Then add that method:

void RegisterRoutes(RouteCollection routes)
{
     routes.MapPageRoute(routeName, routeUrl, physicalFile);
}

The parameters are:

  • routeName - a friendly name for the route
  • routeUrl - the new URL pattern than the route will use
  • physicalFile - the original aspx page where you passed the querystring

So for our example above, it would look like:

routes.MapPageRoute("Widgets", "Widgets/{WidgetType}", "~/Widgets.aspx");

That's all that's needed in Global.asax to handle the routing; now you'll construct the new URLs. You could hardcode the URL, like:

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Widgets/Red" Text="All Red Widgets" />

Or you could build the URL using a RouteURL expression:

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="<%$RouteUrl:WidgetType=Red" Text="All Red Widgets" />

Finally, you have to have a way to retrieve the data that you were previously passing via querystring. You can do this via the Page.RouteData property:

Page.RouteData.Values["WidgetType"].ToString();

However, you probably don't want to completely replace the querystring functionality on the landing page - what if a user has a bookmark containing the querystring? Something like this would take care of both situations:

string widgetType = "";

if (Page.RouteData.Values.Count() > 0)
{
     widgetType  = Page.RouteData.Values["WidgetType"].ToString();
}
else
{
     widgetType  = Request.QueryString["Type"];
}

Technorati Tags: ,

oreilly.com - Your tech ebook super store

Using Dynamic Maps in Microsoft BizTalk

Using maps in a Microsoft BizTalk orchestration is pretty straightforward – create your source and destination schemas, map from one to the other, drop a Transform shape onto your orchestration, and configure the source and destination messages. But what if you need to apply different maps based on some property inside the message, or even who the message is coming from?

In our case, we process X12 837 healthcare claim files. In the X12 schemas, the same piece of information can be stored in different locations, and we are unable to enforce a standard, we have to take whatever our clients give us. So, the orchestration has to figure out which map to use to transform the 837 data to our internal schema. We do this using Dynamic Maps.

In this post I’ll demonstrate how to take an input file containing a customer name, and their home and work addresses, and transform using using one of two different maps – one that uses the home address and one that uses work. Yes, this probably isn’t terribly realistic, but it’s fine for a demo!

To begin, create an input schema to represent customer data, like this:

DynamicMapping1

Promote the Map element. This is how we’ll tell the orchestration which map to use. You could also base the decision on a context property (maybe something to do with the party agreement), a part of the filename, etc.

Next, create an output schema like this:

DynamicMapping2

Then create two maps, one using the home address:

DynamicMapping3

…and one using the work address:

DynamicMapping4

Now for the orchestration. Drag a mess of shapes out until you have something that looks like this:

DynamicMapping5

Create two messages, CustomerData and OutputData, using the two schemas created earlier. Create two variables – mapName (System.String) and mapType (System.Type). Now we’ll start configuring all those expression shapes.

In the GetMapName expression, get the name of the map we’re going to use from the Map promoted property:

mapName = CustomerData.Map;

In Rule_1 of the Decide shape, look at mapName:

mapName == “Home”

In the SetMap expression in the left branch, add:

mapType = System.Type.GetType("DynamicMappingBlogPost1.HomeAddressMap, DynamicMappingBlogPost1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=abcdefghijklmnop");

You’ll have to substitute the namespace and map name you used, and you’ll replace the PublicKeyToken after deploying the solution the first time.

The statement in the SetMap expression in the right branch will be almost identical, except for the name of the other map.

In the Message Assignment shape in the left branch, add the following code to do the transformation:

transform (OutputData) = mapType(CustomerData);

You’ll notice that in the right branch, I used an Expression shape rather than a Message Assignment. You can do this either way, but if you use the Expression shape you have to wrap the transform statement inside a construct statement:

construct OutputData
{
    transform (OutputData) = mapType(CustomerData);
}

Deploy the project, and in the Administration Console, right-click one of the maps and choose Properties. On the Assembly line, copy the PublicKeyToken, and paste it into each of the SetMap expressions where it currently has “abcdefghijklmnop”. Deploy the project one more time.

Configure the send and receive ports, and test using a schema like this:

<ns0:Customer xmlns:ns0="http://DynamicMappingBlogPost1.CustomerData_XML">
  <Name>John Smith</Name>
  <Age>42</Age>
  <HomeAddress>
    <Street>1234 Home Street</Street>
    <City>Plano</City>
    <State>TX</State>
    <Zip>75002</Zip>
  </HomeAddress>
  <WorkAddress>
    <Street>567 Work Ave.</Street>
    <City>Dallas</City>
    <State>TX</State>
    <Zip>75240</Zip>
  </WorkAddress>
  <Map>Work</Map>
</ns0:Customer>

Change the Map node from “Work” to “Home” and verify that a different address is mapped each time.

You can download a complete version of this project at: Dynamic Mapping Example.

Technorati Tags:

oreilly.com - Your tech ebook super store

O'Reilly's Cyber Monday Site-wide sale

O’Reilly Books will be having a Cyber Monday sale – 50% off all ebooks and videos, plus orders over $100 can save 60%. The deal expires December 3, 2013 at 11:00am PT.

Link: O’Reilly Cyber Monday Sale - Save 50% on all ebooks and videos at oreilly.com

Book review: The Lego Build-It Book, Volume 1, Amazing Vehicles (Kuipers & Zamboni, O’Reilly Media)

The LEGO Build-It Book, Vol. 1

As a parent of young kids, we of course have Lego... lots and lots of Lego... all over the house. You'd think that would allow for lots of model building where they get more enjoyment out of following a set of instructions rather than figuring out all the mechanics of making a "cool" model. So we can either keep buying new sets ($$$) or find new instructions.

The Lego Build-It Book, Volume 1, Amazing Vehicles is the perfect resource for our situation. In this 132-page book, the authors show you how to build 10 different vehicles from one Lego kit. You can build several types of cars, a rescue truck, go-kart, and more. The instructions are presented in the same style as an "official" Lego manual, with each page showing an inventory of pieces needed and step-by-step instructions.

The base kit used in this book is #5867, the Creator Super Speedster. The set is no longer in stores, but can be found on eBay in the $20-$30 range. If you don't want to buy it there, you have a couple alternatives - you can scrounge through your existing parts (the book has a complete inventory of all parts needed in the front), visit your local Lego store's "Pick a Brick" wall, or buy individual pieces from a site like BrickLink.

My kids have enjoyed building several of the models so far, and they've held their attention to where they don't immediately want to tear it apart and build the next one. The author was a product developer for Lego, responsible for several Technic models, so he seems to have a good grasp of building interesting models.

This book should keep us occupied far longer than a single Lego set of a similar price. You can purchase it from O'Reilly's website:
The LEGO Build-It Book, Vol. 1 .

New web site launched

I’ve written a few posts about working with the eBay API, and I’ve been using it a bit as a part-time seller, so I decided to make the tool I wrote available to anyone.

The site is called Listing Trends (www.listingtrends.com), and it allows potential sellers to research what is currently hot on eBay. You can drill into individual categories and view the tops results by highest number of watchers, bidders, or the highest price.

There are also pages that show listings with a low Buy-It-Now, or listings ending soon with no bids.

The site is totally non-commercial – there’s no advertising on it, nor any affiliate links.

Comments or suggestion about usability or appearance are welcome!

Technorati Tags:

How to preserve state across pages in a Windows 8 app

When designing a Windows 8 app, you need a way to preserve the state of various items as you navigate from page to page. Windows allows this using the pageState dictionary - a dictionary that can contain any serializable object. If you're a web programmer, you can think of this as similar to using a session variable. Today I'll add this to the cascading ListBoxes shown earlier. You can download the code for that project at ListBoxDrillDown.zip.

First, we'll add a navigation button to the page. Put the pnlMain StackPanel and the new button inside another StackPanel, like this:

<StackPanel x:Name="pnlContainer" Grid.Row="2" Orientation="Vertical">
    <Button x:Name="btnSearch" Content="Show" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="40,0,0,0" Click="btnSearch_Click"/>
    <StackPanel x:Name="pnlMain" HorizontalAlignment="Left" Margin="20,10" VerticalAlignment="Top" Orientation="Horizontal"/>
</StackPanel>

Then add the Click method:

private void btnSearch_Click(object sender, RoutedEventArgs e)
{
    if (_categoryID > 0)
    {
        this.Frame.Navigate(typeof(LandingPage));
    }
}

LandingPage isn't actually part of the project yet, so go ahead and add it. Just add a Basic Page called LandingPage.xaml to the project. It won't be used for anything other than the back button control.

Now, if you run the app, after you select at least one item in the first ListBox you'll be able to click the button to navigate to the second page. When you click the back button, however, you return with only the first ListBox showing and nothing selected; anything that you had drilled down into will have to be reselected. Let's fix that.

Locate the SaveState method and add the following code:

List<int> list = new List<int>();

for (int x = 1; x <= _listBoxCount; x++)
{
    ListBox lb = (ListBox)pnlMain.Children[x - 1];
    if (lb.SelectedValue != null)
    {
        list.Add(lb.SelectedIndex);
    }
}

pageState["listBoxSettings"] = list;

Here I'm simply adding all the SelectedIndex values to a List and saving it to pageState.

Now, to retrieve this List once we've navigated away and back to the page. Add a variable to the class:

private List<int> _selectionList = null;

Then add the following code to the LoadState method:

if (pageState != null && pageState.ContainsKey("listBoxSettings"))
{
    _selectionList = (List<int>)pageState["listBoxSettings"];
}

CallGetCategoryInfo(_categoryID);

Here I check to see if the "listBoxSettings" key is populated (since this same method will be called the first time the page is loaded, it could be empty...), and then assign it to the variable. Notice that the CallGetCategoryInfo(_categoryID) statement has moved from the ChooseCategory() method; I want to make sure it's called after pageState has been retrieved.

Next, add this code to the AddListBox() method, right before the ListBox is added to the Panel:

if (_selectionList != null && _selectionList.Count >= _listBoxCount)
{
    listBox1.SelectedIndex = _selectionList[_listBoxCount - 1];
}

When a new ListBox is created, I'm checking to see if the ListBox in that position has a selection in the List variable. If so, set the SelectedIndex. The SelectionChanged event will fire again, and the code will check again to see if another selection has already been made.

At this point you should be able to test the app and verify that everything works. Fire it up, drill all the way down in the ListBoxes, go to the next page, and return. You should see all your selections preserved. There's one last thing that we have to do, though. Try this: Drill down as far as you can go into a category, go to the next page and return. Now, at the top level, select a different category. You'll see the app automatically drill down again! This is because the List is still held in the _selectionList variable, and the app doesn't know the difference between a human clicking and the automatic process that happens when we return to the page. The easy way to fix this is to simply clear the List when clicking in a ListBox to the left of another ListBox (i.e. a higher level). Add this code to the ComboBox_SelectionChanged event handler:

_selectionList.Clear();

...inside the for() loop.

Keep in mind, these settings are only going to be preserved while you're using the app - if it's suspended or terminated, they're not available. If you need to preserve them after the app is terminated, you can use the SuspensionManager.

When you hit the back button, you'll probably notice a delay as each new ListBox appears. This is because it's fetching the contents from the eBay API, just as it did the first time you selected it. If you don't like this, then you could simply store the contents of each ListBox in pageState and repopulate.

You can download the code for this at SaveStateDemo.zip.

Technorati Tags: ,,

Using cascading ListBoxes to display hierarchical data in Windows 8

When you're dealing with hierarchical data (a tree-like data format such as folders that have sub-folders that have subfolders, etc.) you have to come up with an organized way of displaying it. You could create a tree view, similar to how Windows Explorer displays folders, but I wanted to try something different in Windows 8 - cascading ListBoxes. The user selects an items in a ListBox, and then a new ListBox appears to the right showing the sub-categories. This continues as long as there's data left to drill into.

For a data source, I'll use the eBay category list API that I blogged about previously.

Begin by creating a Windows Store project - a Blank App. Add a Basic Page called ChooseCategory.xaml, and delete the MainPage.xaml file that was created with the app. (For this demo you could get away with the blank page, but it's good to have the Grid and the VisualStateGroup automatically added) Open up App.xaml.cs and change this line:

if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))

to call ChooseCategory instead of MainPage. Finally, in App.xaml change the Theme to Light by adding RequestedTheme="Light" to the Application tag. You could leave it dark by default, but then your ListBoxes will look a bit out of place unless you modify their style.

Next I'll add a class file to the project to hold a List that can be bound to each ListBox, as well as hold the method to make the call to the eBay API. This is almost identical to the previous post, with a few minor tweaks.

Here are the two classes to hold the List:

public class Categories
{
    private List<CategoryDetails> _Items = new List<CategoryDetails>();
    public List<CategoryDetails> Items
    {
        get
        {
            return this._Items;
        }
        set
        {
            this._Items = value;
        }
    }
}

public class CategoryDetails
{
    public string categoryName { get; set; }
    public int categoryId { get; set; }
}

You could have more data in these if you wanted, but since this is a ListBox demo I just wanted some simple name/value pairs.

And here is the method that makes the call to the eBay API:

public async Task<Categories> CallGetCategoryInfo(int CategoryID)
{
    HttpClient httpClient = new HttpClient();
    string api_key = "MyKey";
    string searchUrl = "http://open.api.ebay.com/Shopping?callname=GetCategoryInfo";

    string requestUrl = searchUrl + "&appid=" + api_key + "&version=679&siteid=0&CategoryID=" + CategoryID.ToString() + "&IncludeSelector=ChildCategories";

    HttpResponseMessage response = await httpClient.GetAsync(requestUrl);
    System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Load(await response.Content.ReadAsStreamAsync());

    XNamespace ns = "urn:ebay:apis:eBLBaseComponents";

    var query = from categories in doc.Descendants(ns + "Category")
        select new
        {
            CategoryID = categories.Element(ns + "CategoryID").Value,
            CategoryParentID = categories.Element(ns + "CategoryParentID").Value,
            CategoryName = categories.Element(ns + "CategoryName").Value,
            LeafCategory = categories.Element(ns + "LeafCategory").Value,
        };

    List<CategoryDetails> catList = new List<CategoryDetails>();

    Categories c = new Categories();

    foreach (var element in query)
    {
        //The first category returned in the xml will always be either root, or the parent level category
        //I don't want it in the list
        if (element.CategoryID != "-1" && element.CategoryID != CategoryID.ToString())
        {
            CategoryDetails cd = new CategoryDetails();
            cd.categoryId = Convert.ToInt32(element.CategoryID);
            cd.categoryName = (Convert.ToBoolean(element.LeafCategory) ? element.CategoryName : element.CategoryName + " ->");

            catList.Add(cd);
        }
    }

    c.Items = catList;

    return c;
}

Now let's modify ChooseCategory.xaml. Change the AppName:

<x:String x:Key="AppName">Choose Category</x:String>

And add a StackPanel right below the grid that hold the pageTitle TextBlock:

<StackPanel x:Name="pnlMain" HorizontalAlignment="Left" Margin="20,10" Grid.Row="2" VerticalAlignment="Top" Orientation="Horizontal"/>

This is the panel that we'll add the ListBoxes to.

Now for the code-behind in ChooseCategory.xaml.cs. Declare a few variables in the class:

private int _categoryID = -1;
private string _categoryName = "";
private List<CategoryDetails> _cbp = null;
private int _listBoxCount = 0;

Add a method to retrieve the data from eBay and call the method that adds a new ListBox:

private async void CallGetCategoryInfo(int CategoryID)
{
    CategoriesDataSource cds = new CategoriesDataSource();
    Categories c = await cds.CallGetCategoryInfo(CategoryID);
    _cbp = c.Items;

    AddListBox();
}

Now we add a new ListBox if we had some data returned from eBay. We set the DisplayMemberPath and SelectedValuePath to the public properties of the CategoryDetails object, give the ListBox a distinct name (we'll need it later), wire up an event handler, and add the ListBox to the StackPanel:

private void AddListBox()
{
    //If the List count is 0, that means we have drilled all the way down
    if (_cbp.Count > 0)
    {
        _listBoxCount += 1;

        ListBox listBox1 = new ListBox();
        listBox1.DisplayMemberPath = "categoryName";
        listBox1.SelectedValuePath = "categoryId";
        listBox1.ItemsSource = _cbp;
        listBox1.Width = 250;
        listBox1.Name = "CategoryLevel" + _listBoxCount.ToString();
        listBox1.SelectionChanged += ComboBox_SelectionChanged;
        pnlMain.Children.Add(listBox1);
    }
}

Finally we're going to handle selecting an item in the ListBox:

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Selector list = sender as Selector;

    // Delete listboxes to the right if we click back a level
    int clickedCategoryLevel = Convert.ToInt32(list.Name.Replace("CategoryLevel", ""));
    for (int x = _listBoxCount; x > clickedCategoryLevel; x--)
    {
        pnlMain.Children.RemoveAt(x - 1);
    }

    _listBoxCount = clickedCategoryLevel;
    _categoryID = Convert.ToInt32(list.SelectedValue);

    CallGetCategoryInfo(_categoryID);
}

If the user backs up a level (i.e. they have drilled down a few levels, but now want to click something at a higher level and start over instead of drilling down further), then we need to delete any ListBoxes to the right of the one that was just clicked. Then we call CallGetCategoryInfo again to retrieve the data and add a new ListBox.

Finally, add a call to CallGetCategoryInfo in the constructor to start the whole process off:

public ChooseCategory()
{
    this.InitializeComponent();

    CallGetCategoryInfo(_categoryID);
}

Fire up the app and try drilling down and back up. If everything works correctly, you should get results looking similar to this:

CategoryChooser

Notice that some of the lines end with "->". In the CallGetCategoryInfo I check to see whether the category being added to the List is a "Leaf" category - the lowest level of category (you can't drill down any further). If it is, I add that text to give a visual indication that there are sub-categories.

Notice that I didn't do anything with the various VisualStates, so this will really only look good in FullScreenPortrait mode. I haven't really thought about how other modes would work (especially snapped!); this was more of a proof-of-concept.

You can download the entire source code at: http://dl.dropbox.com/u/22528394/ListBoxDrillDown.zip

Technorati Tags: ,,

Retrieving a list of eBay categories in a .Net 4.5 Windows Store App

Previously I wrote a post on how to get the eBay category list using their .Net SDK. I wanted to try doing the same thing in a Windows Store App for Windows 8 using Visual Studio 2012, but quickly ran into compatibility problems since the DLLs are compiled for .Net 2.0. I could have either changed the targetted framework, or attempted to re-write the DLLs, but neither sounded particularly exciting, so I simply decided to fall back to using a simple URL request and parsing it via Linq to XML.

As mentioned in the previous post, the first step is to join the eBay Developer Program. Once you've joined and gotten your app ID, you can try a simple request with this URL:

http://open.api.ebay.com/Shopping?callname=GetCategoryInfo&appid=yourappid8&version=679&siteid=0&CategoryID=-1&IncludeSelector=ChildCategories

Passing CategoryID=-1 returns the top-level categories, like this:

<GetCategoryInfoResponse xmlns="urn:ebay:apis:eBLBaseComponents">
   <Timestamp>2012-12-19T16:03:33.893Z</Timestamp>
   <Ack>Success</Ack>
   <Build>E803_CORE_BUNDLED_15627359_R1</Build>
   <Version>803</Version>
   <CategoryArray>
      <Category>
         <CategoryID>-1</CategoryID>
         <CategoryLevel>0</CategoryLevel>
         <CategoryName>Root</CategoryName>
         <CategoryParentID>0</CategoryParentID>
         <LeafCategory>false</LeafCategory>
      </Category>
      <Category>
         <CategoryID>20081</CategoryID>
         <CategoryLevel>1</CategoryLevel>
         <CategoryName>Antiques</CategoryName>
         <CategoryParentID>-1</CategoryParentID>
         <CategoryNamePath>Antiques</CategoryNamePath>
         <CategoryIDPath>20081</CategoryIDPath>
         <LeafCategory>false</LeafCategory>
      </Category>
(etc.)

You can continue to walk down the category tree by passing the sub-category IDs.

The first step will be to fire up an HttpClient and send an asynchronous GET request to the URL:

private async Task<string> CallGetCategoryInfo(int CategoryID)
{
    httpClient = new HttpClient();
    string searchUrl = "http://open.api.ebay.com/Shopping?callname=GetCategoryInfo";

    string requestUrl = searchUrl + "&appid=" + api_key + "&version=679&siteid=0&CategoryID=" + CategoryID.ToString() + "&IncludeSelector=ChildCategories";

    HttpResponseMessage response = await httpClient.GetAsync(requestUrl);

Next we'll load the response stream into an XDocument and create a Ling query:

    System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Load(await response.Content.ReadAsStreamAsync());

    XNamespace ns = "urn:ebay:apis:eBLBaseComponents";

    var query = from categories in doc.Descendants(ns + "Category")
        select new
        {
            CategoryID = categories.Element(ns + "CategoryID").Value,
            CategoryParentID = categories.Element(ns + "CategoryParentID").Value,
            CategoryName = categories.Element(ns + "CategoryName").Value,
            LeafCategory = categories.Element(ns + "LeafCategory").Value,
        };

We'll add the results of the query to a List of objects. I only needed the category name and ID, so that's all my object has:

public class ComboBoxPairs
{
    public string categoryName { get; set; }
    public int categoryId { get; set; }

    public ComboBoxPairs(string CategoryName, int CategoryId)
    {
        categoryName = CategoryName;
        categoryId = CategoryId;
    }
}

And here I add the objects to the List:

    List<ComboBoxPairs> cbp = new List<ComboBoxPairs>();

    foreach (var element in query)
    {
        //The first category returned in the xml will always be either root, or the parent level category.
        // I don't want it in the list
        if (element.CategoryID != "-1" && element.CategoryID != _categoryID.ToString())
        {
            cbp.Add(new ComboBoxPairs(element.CategoryName, Convert.ToInt32(element.CategoryID)));
        }
    }

Finally I assign the List to a global variable and exit:

    _cbp = cbp;
    return "Finished";

The big difference between this method and using the SDK is that you can only return one level at a time. This is probably a good thing; the web service is responsive enough that there's no noticeable wait to the call, versus a distinct wait (and massive size) for retrieving the entire list. If you did want the entire list for some reason, you can simply drill down - use a category ID of -1 (the root category ID) on the first call to get all the level 1 categories, then use a level 1 category ID on the second call to get all the level 2 children of the specified category ID, and so forth. When you get to LeafCategory = true, you can stop.

If you omit the &IncludeSelector=ChildCategories flag then you'll just get info for the selected category, but you'll also get the version and last update time, like this:

<GetCategoryInfoResponse xmlns="urn:ebay:apis:eBLBaseComponents">
   <Timestamp>2012-12-19T16:37:46.516Z</Timestamp>
   <Ack>Success</Ack>
   <Build>E803_CORE_BUNDLED_15627359_R1</Build>
   <Version>803</Version>
   <CategoryArray>
      <Category>
         <CategoryID>-1</CategoryID>
         <CategoryLevel>0</CategoryLevel>
         <CategoryName>Root</CategoryName>
         <CategoryParentID>0</CategoryParentID>
         <LeafCategory>false</LeafCategory>
      </Category>
   </CategoryArray>
   <CategoryCount>1</CategoryCount>
   <UpdateTime>2012-09-11T02:42:04.000Z</UpdateTime>
   <CategoryVersion>102</CategoryVersion>
</GetCategoryInfoResponse>

If you're storing the information locally you can then compare to determine whether the category has changed; if it hasn't, skip the full query and move on to the next.

Here's the full code:

private async Task<string> CallGetCategoryInfo(int CategoryID)
{
    // http://open.api.ebay.com/Shopping?callname=GetCategoryInfo&appid=appID&version=679&siteid=0&CategoryID=-1&IncludeSelector=ChildCategories

    httpClient = new HttpClient();
    string searchUrl = "http://open.api.ebay.com/Shopping?callname=GetCategoryInfo";

    string requestUrl = searchUrl + "&appid=" + api_key + "&version=679&siteid=0&CategoryID=" + CategoryID.ToString() + "&IncludeSelector=ChildCategories";

    HttpResponseMessage response = await httpClient.GetAsync(requestUrl);
    System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Load(await response.Content.ReadAsStreamAsync());

    XNamespace ns = "urn:ebay:apis:eBLBaseComponents";

    var query = from categories in doc.Descendants(ns + "Category")
        select new
        {
            CategoryID = categories.Element(ns + "CategoryID").Value,
            CategoryParentID = categories.Element(ns + "CategoryParentID").Value,
            CategoryName = categories.Element(ns + "CategoryName").Value,
            LeafCategory = categories.Element(ns + "LeafCategory").Value,
        };

    List<ComboBoxPairs> cbp = new List<ComboBoxPairs>();

    foreach (var element in query)
    {
        //The first category returned in the xml will always be either root, or the parent level category
        //I don't want it in the list
        if (element.CategoryID != "-1" && element.CategoryID != _categoryID.ToString())
        {
            cbp.Add(new ComboBoxPairs(element.CategoryName, Convert.ToInt32(element.CategoryID)));
        }
    }

    _cbp = cbp;
    return "Finished";
}

public class ComboBoxPairs
{
    public string categoryName { get; set; }
    public int categoryId { get; set; }

    public ComboBoxPairs(string CategoryName, int CategoryId)
    {
        categoryName = CategoryName;
        categoryId = CategoryId;
    }
}

Technorati Tags: ,,

Android–Finding your SDK debug certificate MD5 fingerprint using Keytool

I recently upgraded to a new development machine, which means the certificate used to sign my applications during debug changed. Under most circumstances you’ll never notice a difference, but if you’re developing apps using Google’s Maps API you’ll find that your old API key no longer works with the new certificate fingerprint. 

Google's instructions walk you through retrieving the MD5 fingerprint of your SDK debug certificate - the certificate that you’re probably signing your apps with before publishing, but it doesn't talk much about the Keytool command.

The thing to remember is that Keytool is part of Java, not the Android SDK, so you'll never find it searching through your Android and Eclipse directories. Mine is located in C:\Program Files\Java\jdk1.7.0_02\bin so you should find yours somewhere similar.

From a command prompt, navigate to this directory and type:

keytool -v -list -keystore "C:/Documents and Settings/<user name>/.android/debug.keystore"

That’s assuming the path to your debug certificate is in the typical location. If this doesn’t work, you can find out where it’s located in Eclipse by clicking Window –> Preferences –> Android –> Build.

There's no need to use the additional commands shown on Google's page. You'll be prompted for a password, just hit enter. The last line shown, Certificate fingerprint, is the key you'll give Google to generate your new Maps API key.

Technorati Tags:

Packt Publishing Thousand Titles Campaign–free eBook!

Packt Publishing is celebrating the release of its 1000th title by offering web site members their choice of free eBook. They are also opening their online library for a week for free to members, to allow you to research your choice. 

If you’re not already a member, signing up is free, but you have to do so by September 30th to get the free eBook.

To sign up, just go to www.packtpub.com/login.