It’s time to dive into Windows Store application development. I’ve developed several tools for the Team Foundation Server (TFS) ecosystem, so I think an appropriate first project that will intersect my existing skill set would be a TFS Work Item browser.
The Plan
The app will be a simple work item browser. After entering your TFS server connection information, you’ll be presented with a list of Team Projects. Select a Team Project and browse through Work Items. This will allow me to explore navigation, paging, presentation, and other new idioms in the WinRT platform.
There is an interesting wrinkle here: the TFS API client assemblies are not usable from WinRT. A brief attempt at hitting the TFS ASMX services directly proved too frustrating to waste time on - after all, the goal is to push my Windows Store app skills. This led me to the decision to host a TFS proxy on AppHarbor that would provide a simple ASP.NET Web API endpoint for retrieving Work Items, which should be simple to access from WinRT. Another option for proxying to TFS could have been the OData Service for Team Foundation Server, except that it is configured to connect only to a single server. I want my app’s users to be able to connect an arbitrary TFS server, which would require them to set up the OData service themselves. I opted instead to stand up a small service that allows connection to any TFS server and provides just enough functions for my app.
You can find the source for the TfsWorkItems work item browser and TfsProxy API proxy at my Github profile.
The app itself is available for testing in this ZIP archive. Extract, and then run the Add-AppDevPackage.ps1
PowerShell script to install the app.
Web API Proxy
As described above, in order to get to the TFS data from WinRT, I will stand up a simple Web API service with methods for retrieving Team Projects and Work Items. I created a Web API project and added controllers named ProjectsController
and WorkItemsController
. Along the way, I am using the handy Postman REST Client extension for Chrome to hit my service methods as I build them out. Fiddler or curl would be just as effective.
TFS Connection
The first thing I need is a connection to TFS - this requires a TFS URI, username, and password. Both of my controllers will require a connection, so I will compose this dependency to avoid cluttering all of the API calls with the TFS connection information. This will be implemented via an action filter that provides a TFS Connection in the current HttpContext.
My TfsBasicAuthenticationAttribute
action filter passes the request headers to a UserDataPrincipal
(IPrincipal
) that provides a factory method named InitFromHeaders
. This method handles the parsing of the connection information from the request headers. If no principal is returned, or if connecting to TFS with the given connection information fails, an HTTP 401 Unauthorized response is returned. When a connection issue occurs, the specific reason for failure is provided in the response content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Note that in the SetUnauthorizedResponse method, I am adding the WWW-Authenticate header with the realm set to the TFS URL. Responding with a combination of HTTP 401 and this header is the standard for negotiating Basic Authentication. The WinJS.xhr wrapper for XMLHttpRequest handles this negotiation by automatically prompting for username and password in a modal popup, and then re-issuing the request with the Authorization header correctly encoded for Basic Authentication.
The UserDataPrincipal
class used here implements IPrincipal
and in its InitFromHeaders
method, extracts the username, password, and TFS URL from the HTTP headers. The username and password are retrieved from the HTTP Authorization header using the Basic Authentication standard, and the TFS URL is expected in a separate HTTP header named TfsUrl. Upon authenticating with the TFS Configuration Server, its instance is stored in the current HTTP context. The UserDataPrincipal
class also supplies an ICredentialsProvider
to the TfsConfigurationServer
constructor, which is the interface for providing the domain credentials.
Retrieving the Team Projects list
After applying the TfsBasicAuthorization
attribute to the ProjectsController
, I have access to the TfsConfigurationServer
for retrieving information about Team Projects on the server. The highest level of organization within Team Foundation Server is a “Project Collection” that contains one or more Team Projects. Project Collections are accessed by by querying the configuration server’s CatalogNode
for children with resource type ProjectCollection
. For each Team Project Collection node, I get its WorkItemStore
service and iterate its Projects
collection looking for Team Projects where the authorized user has Work Item read rights. This method returns a list of TeamProjectInfo
data transfer objects. Note that for each Team Project, I am including a list of the available Work Item Types. This will be leveraged for filtering in the app.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Retrieving a Work Items list
When the app user selects a Team Project, we want to display a list of Work Items in the project. This is implemented in the WorkItemsController
on the Web API proxy service. The Get
method requires the collectionId
and projectName
parameters, and returns a page of 10 work items, in the form of a WorkItemInfo
data transfer object. The optional page
parameter allows retrieving a particular page of work items, and the optional workItemType
parameter allows filtering by work item type (e.g. Bug, Requirement, Task, etc).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
The WorkItemStore.Query
method returns a late-bound IEnumerable
that allows the use of Skip
and Take
for efficient paging. The WorkItemInfoBuilder
helper class takes care of mapping the TFS WorkItem
class to the WorkItemInfo
data transfer object.
The Windows Store App
Now that we have a proxy that makes the TFS API accessible from WinRT, it’s time to get started on the Windows Store app. I began with the JavaScript Split App project template. This template provides a basic app frame and bootstrapper (default.html / default.js), navigation handler (navigator.js), data provider (data.js), and provides two screens for interaction: an “items” screen and a “split” screen. The data provider is set up to return static, hard-coded sample data.
The home screen of the app is the “items” screen, which would be more appropriately named “groups.” The items screen presents a list of top-level item groups as horizontally-scrolling tiles. Selecting an item group tile navigates to the “split” screen that presents a list-detail view of the individual items within the selected group. The overall shape of this data and navigation scheme (groups containing items) meshes well with Team Projects containing lists of Work Items. I plan to break one more level out of the hierarchy: Team Projects are found within Team Project Collections (as seen above), so rather than the simple grid view on the main screen, I will use a grouped grid view with the team projects grouped by collection.
Connection Preferences
Before I can replace the sample data with live TFS data, I need to be able to provide the proxy with the URL of the TFS server. This is going to be a per-user setting, so I will add a settings command for displaying a flyout where the user can set the TFS URL. The user will invoke the Charms sidebar, and then click Settings to see my app’s settings, which will include this custom command. Adding a settings command is accomplished by handling the WinJS.Application.onsettings
event as seen here:
1 2 3 4 |
|
This adds a command to the settings flyout labeled “Connection” which will navigate to a flyout with the provided href. The markup for a flyout is simple - a top-level div
inside the body for the flyout itself, with a back button and a label/input for the TFS URL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
In the JavaScript code-behind for the flyout, I added event listeners for blur
and keyup
on the tfsUrl
input. The standard for Windows Store apps is for settings to take effect immediately, so when the user focuses away from the input or presses the Enter key, I will immediately store and react to the change.
Getting the Team Projects list
Now that the user has a way to specify the TFS URL, I can finally go fetch some live data. Windows Store apps are expected to launch as instantly as possible, so blocking while loading data at startup is not an option — control needs to be returned to the UI thread right away. In the data.js
data provider, I will set up the Data
namespace with an empty Team Projects list, then make an asynchronous call to fetch the Team Projects list from the proxy. The UI will bind to the team projects list so that once the asynchronous call returns and the list is filled, the UI binding will trigger it to be updated with the populated list.
Here you can see the definition of the Data namespace. Note that the Windows.ApplicationModel.DesignMode.designModeEnabled
property is checked to determine if the code is being invoked in design mode. This is to allow the use of sample data when editing the views in Blend, but to fetch real data from the network when the app is actually running. When in design mode, Data.dataService
is set to the static SampleDataService
class; when not in design mode, Data.dataService
is set to an instance of the WebDataService
class. The call to Data.loadProjects()
will return immediately to avoid blocking app startup, as you will see in the next code sample.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
The WebDataService
class provides access to the proxy for retrieving Team Projects and Work Items. It leverages the WinJS.xhr
method to make asynchronous HTTP requests to the proxy. Note that if the Settings.tfsUrl
property is not set, no call to the service is made and the list remains empty.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
The following sequence occurs when making the request to the proxy:
- WinJS.xhr makes the initial GET request to /api/projects with no authentication information
- The custom Web API ActionFilter responds with HTTP 401 and the WWW-Authenticate header
- WinRT recognizes the Basic auth negotiation, prompts for credentials, and retries the request
- For the remainder of the app session, WinRT remembers the entered credentials and includes them in future requests to the same domain
Once the request is successfully completed, the JSON response is parsed and the TeamProjectInfo
records are pushed into the list, triggering a data-binding refresh.
Displaying the Team Projects list
Note in the Data namespace above, the groupedProjects
property is provided. This is a grouped view of the Team Projects, with the Team Project Collection name used as the group key. In the items page, I made some modifications so that Team Projects would be displayed in groups by their collection. I also simplified the item template markup to simply display the Team Project name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
There is not much code at all in the codebehind for this page, simply setting up the data binding and hooking the oniteminvoked
event to trigger navigation to the Work Item list for the selected item. Because the ListView layout type has been set to WinJS.UI.GridLayout
in the markup, the items in the list are automatically displayed in groups when the groupDataSource
of the ListView is set.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
So, putting it all together, here’s what the app’s home screen looks like:
Getting and Displaying Work Items
Selecting a Team Project on the items screen navigates to the split screen, with the project
property of the options parameter set to the selected project. This is transformed by the framework into the arguments to the ready
function of the split page. The split page has a lot more functionality than the items page – it needs to support:
- Selection of a work item to display its details
- Browsing to the next page of work items (a page of 10 at a time is returned by the proxy)
- Browsing to the preview page of work items
- Filtering the list of work items by type (e.g. Bug, Requirement, Task, etc.)
The ready
function takes care of binding the App Bar commands, filling the filtering select control with work item types, and initiating the fetch of Work Items from the proxy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
The _getWorkItems
function is triggered when the view is first loaded, as well as when a different page or filter is requested. It passes the parameters for page number and work item type to the proxy, and then fills the data-bound work item list with the response. The work item list and detail sections are faded out while processing. Code in the default.js
bootstrapper hooks to the Data
namespace’s processing event and shows an indeterminate progress bar and status message when the event is raised. By fading out the primary sections, we allow the progress bar to be easily seen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Note the use of the _isSingleColumn
utility function - depending on the orientation or snapped state of the app, the view may be filled with the article details - in that case, we immediately load the current article details after the work items are retrieved; when in full landscape view, we set the selection in the list view knowing that our _selectionChanged
handler will be triggered for loading the article.
The markup for the split page provides formatting for the work item list and detail view. It is not heavily modified from the Split App template, although the item images have been removed and additional binding fields have been added for the most important Work Item properties. At the bottom of the display area, a ListView is used to display a generic list of field name-value pairs - this is because work item templates can be heavily customized, and it is not possible to anticipate the names of the fields a user could define.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
The split.html
markup also provides the App Bar for the page (defining the page and filter commands) as well as the flyout that is displayed when the filter command is invoked. The flyout simply contains a label
and select
list that is populated (as seen above) with the work item types available in the current project.
Wrapping Up
So, there you have it - a (very simple) TFS Work Item browser. This was a great learning exercise: it pushed my JavaScript skills and was a nice tour of some of the common features of the platform. While I’m happy with the insights I’ve gained into WinRT and with this app as a sample, I think it could take a more dynamic approach to browsing and viewing work items. The work item detail display does not seem to me to conform to the Windows Store design guidelines. I would like to consult with some of my UX colleagues for ideas about how to present the work item details in a way that is natural to the platform.
Future Plans
Here are some other potential ways to extend the app in the future
- Work Item Queries: returning a list of work items sorted ascending by ID is not very useful - most TFS users access work items using pre-defined work item queries.
- Content URIs: handle the vsts content URI for loading an individual work item.
- Leverage the Work Item Type definition to determine (and possibly reflect the layout definition of) the important custom work item fields.
- Display the Work Item details in a format more appropriate to a Windows Store app.
- Search, Sharing, and Print contracts.
- Allow simple modification actions, like assignment and state change; or, allow full editing.
Miscellaneous Tips
I ran into some issues along the way that don’t fit right in with the narrative above, but are worth sharing.
Tip 1: Secure XHR against IIS
Windows Store apps require all HTTPS server certificates to be trusted. I hit this message when attempting to use WinJS.xhr
against my local IIS Express-hosted service:
SCRIPT7002: XMLHttpRequest: Network Error 0x800c0019, Security certificate required to access this resource is invalid.
I found a helpful blog post (see Step 7) that described how to install the IIS Express certificate in the local store so that it is trusted by the Windows Store app. In the deployed environment, this won’t be an issue, since AppHarbor supplies an HTTPS certificate from an already-trusted authority.
Tip 2: Fiddler with WinRT apps
By default, WinRT security prevents Fiddler from intercepting network traffic from Windows Store apps. This post explained how to work around the issue.
Tip 3: Dynamic content in InnerHTML
The Description and History work item fields will often contain HTML content. WinRT will throw a security error if you attempt to set the innerHTML
property of an element to a string with certain attributes set. This article explains techniques for dealing with potentially dangerous content. The particular technique that I used was the window.toStaticHTML
method for cleaning text before assigning it to an innerHTML
property.
Tip 4: Deploying to and Debugging on Surface
To auto-deploy and test the app on my Microsoft Surface, directly from Visual Studio 2012, I followed the steps provided in this article. My colleague Rocky Lhotka also has a good article about packaging an app with a PowerShell script to allow distributing an app package for short-term side-loaded testing.