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 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.
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
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.
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.
TfsBasicAuthenticationAttribute action filter passes the request headers to a
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.
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
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
WorkItemStore.Query method returns a late-bound
IEnumerable that allows the use of
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
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.
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
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
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.)
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
_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
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
select list that is populated (as seen above) with the work item types available in the current project.
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.
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
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.