April 2017
Volume 32 Number 4
[UWP Apps]
Develop Hosted Web Apps for UWP
Many developers and companies create Web interfaces for their products and services for easy discoverability and access. Another platform is the apps platform. Native apps provide richer UX and functionality than Web apps. Now, Project Westminster provides developers a way to convert modern Web sites into native apps.
The Project Westminster bridge enables Web developers to bring their responsive Web applications to the Universal Windows Platform (UWP) by leveraging existing code (see the “Project Westminster in a Nutshell” Windows Developer blog post at bit.ly/2jyhVQo). The idea of this “bridge” is to help reuse existing Web site code and add a layer for UWP-specific code to form the integration points of the Web app with an underlying Windows platform. The blog post discusses a few coding practices to ensure consistent UX.
How can a Web developer integrate a modern Web application to work with, let’s say, Cortana? The answer is through the common denominator, JavaScript. Windows exposes the app framework functionality (Windows Runtime APIs) through JavaScript and the Windows namespace. The resulting apps are referred to as Hosted Web Apps.
In this article, I’ll share what I’ve learned in working with Independent Software Vendors (ISVs) and partners to port their apps to the UWP through the Project Westminster tool. The focus here is to share details on how to best align Web apps for delivering the best UX while running as a platform app.
Getting Started
To get started with the Project Westminster tool, you first need to convert your Web site to the UWP (see the Channel 9 video, “Creating Hosted Web Apps with Project Westminster,” at bit.ly/2jp4srs).
As a preliminary test—to be sure the Web site renders as expected and the presentation looks consistent—you should test it on the Microsoft Edge browser. This will help identify and fix any rendering issues before coding integrations for Windows functionality begin.
Most Web sites have a few common features, which enable users to either complete a specific task (like filling out forms) or take away information for their reference (like downloading a manual). In such scenarios, it’s critical that these functionalities remain intact and the newly generated hosted Web app’s experience is consistent with the original Web site. Some of these scenarios might need to be tested, reviewed and re-factored through code to align with users’ expectations. A few key scenarios are considered in the next sections to demonstrate different classes or objects available to developers while working with the Project Westminster tool and related code workflows. While porting the app to the UWP, it’s a good idea to consider integrating a few native features of the platform for a richer UX and an enhanced app experience. Here are some of the commonly implemented features by app developers:
- Contacts
- Cortana integration
- Live tiles
- Toast notifications
- Camera and microphone
- Photos library
- Rich graphics and Media Windows Runtime Stack
- Sharing content with other apps
In this article, the focus is on integrating UWP features, such as Cortana and Live Tiles, for example, which help with activation of apps through voice-based commands and information delivery to the user. Thus, it enhances the overall UX with support of the UWP apps infrastructure. The latest Windows 10 features for developers Web page (bit.ly/2iKufs6) provides a quick overview of more integration opportunities. The Web page, which is being converted to an app, provides information on the following:
- Web application functionality features
- File downloads
- Session management or single sign-on
- Can go to previous page through back button in a stately way
- UWP integration features
- Live Tiles
- Cortana
These features need to be considered while porting and refactoring to deliver a predictable experience for the app.
File Download Scenario
Most Web applications today enable file downloads for a variety of content. As shown in Figure 1, the default experience of file download in the browser is like clicking a button or hyperlink; the browser begins to download it and also saves the file to (mostly) rootdir:\users\username\Downloads.
Figure 1 Default Download Experience
This happens partly because the browser is a native Win32 application running with full trust privileges and writes the file directly to the Downloads folder.
Now, consider the same scenario when the Web site is running in the context of an app (WWAHost.exe, to be specific) and the download button is clicked. What next? Most likely, nothing will happen and it will appear that the code simply doesn’t work. It might appear that the button isn’t responding or, perhaps, the file download has started, but where is it being saved?
WWAHost.exe is an app container for Web sites, which has a subset of features, compared to the browser. This subset of features includes the rendering capability (mostly presentation through HTML) and Standard script execution.
What you’re working with now is an app. In the apps world, for developers, the file download should be explicitly coded for—otherwise, the remote file is just a URL and it’s unclear what the app/container would do with a remote file URL (I can discuss what might be happening under the hood through specialized debugging tools, however, I won’t go into that for now).
To handle these kinds of scenarios, you need to invoke the appropriate UWP APIs. These APIs can co-exist with the code that makes those functionalities work when running the Web site using a browser. Figure 2 shows the code involved.
Figure 2 File Download JavaScript Code (Larger Files)
(function() {
if (typeof Windows !== 'undefined' &&
typeof Windows.UI !== 'undefined' &&
typeof Windows.ApplicationModel !== 'undefined') {
function WinAppSaveFileBGDownloader() {
// This condition is only true when running inside an app.
// The else condition is effective when running inside browsers.
// This function uses the Background Downloader class to download a file.
// This is useful when downloading a file more than 50MB.
// Downloads continue even after the app is suspended/closed.
if (typeof Windows !== 'undefined' &&
typeof Windows.UI !== 'undefined' &&
typeof Windows.ApplicationModel !== 'undefined') {
var fileSavePicker = new Windows.Storage.Pickers.FileSavePicker();
// Example: You can replace the words EXTENSION and ext with the word PNG.
fileSavePicker.fileTypeChoices.insert(
"EXTENSION file", [".ext"]);
// Insert appropriate file format through code.
fileSavePicker.defaultFileExtension = ".ext";
// Extension of the file being saved.
fileSavePicker.suggestedFileName = "file.ext";
// Name of the file to be downloaded.
fileSavePicker.settingsIdentifier = "fileSavePicker1";
var fileUri =
new Windows.Foundation.Uri("<URL of the file being downloaded>");
fileSavePicker.pickSaveFileAsync().then(function (fileToSave) {
var downloader =
new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
var download = downloader.createDownload(
fileUri,
fileToSave);
download.startAsync().then(function (download) {
// Any post processing.
console.log("Done");
});
});
}
else {
// Use the normal download functionality already implemented for browsers,
// something like <a href="<URL>" />.
}
}
}
})();
You can write the code in Figure 2 on the button click or wherever the file download functionality is expected. The code snippet
uses the BackgroundDownloader class (bit.ly/2jQeuBw), which has lots of benefits, such as persisting downloads in background post-app suspension and the ability to download large files.
The code in Figure 2 actually creates the same experience as a browser would, such as prompting the user to select a file location (fileSavePicker variable), initiating the download (downloader variable) and writing it to the selected location.
Alternatively, you can use the code in Figure 3 for smaller file downloads, which will run as long as the app is running:
Figure 3 File Download JavaScript Code (Smaller Files)
// Use the Windows.Web.Http.HttpClient to download smaller files and
// files are needed to download when the app is running in foreground.
(function() {
if (typeof Windows !== 'undefined' &&
typeof Windows.UI !== 'undefined' &&
typeof Windows.ApplicationModel !== 'undefined') {
function WinAppSaveFileWinHTTP() {
var uri = new Windows.Foundation.Uri("<URL of the file being downloaded>");
var fileSavePicker = new Windows.Storage.Pickers.FileSavePicker();
fileSavePicker.fileTypeChoices.insert("EXT file",
[".ext"]); //insert appropriate file format through code.
fileSavePicker.defaultFileExtension = ".ext";
// Extension of the file being saved.
fileSavePicker.suggestedFileName = "file.ext";
// Name of the file to be downloaded. Needs to be replaced programmatically.
fileSavePicker.settingsIdentifier = "fileSavePicker1";
fileSavePicker.pickSaveFileAsync().then(function (fileToSave) {
console.log(fileToSave);
var httpClient = new Windows.Web.Http.HttpClient();
httpClient.getAsync(uri).then(function (remoteFile) {
remoteFile.content.readAsInputStreamAsync().then(
function (stream) {
fileToSave.openAsync(
Windows.Storage.FileAccessMode.readWrite).then(
function (outputStream) {
Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(
stream, outputStream).then(function (progress, progress2) {
// Monitor file download progress.
console.log(progress);
var temp = progress2;
});
});
});
});
});
}
}
})();
The code in Figure 3 uses the Windows.Web.Http.HttpClient to handle the file download operation (bit.ly/2k5iX2E). If you compare the code snippets in Figure 2 and Figure 3, you’ll notice that the latter requires a bit of advanced coding, which gives you more control over the file download. This way, you’re also able to explore the multiple ways to save a stream to a file using a UWP app.
Also, in the case where the app also processes the files that were downloaded, it’s best to use the FutureAccessList property to cache and access the location where the file was saved (bit.ly/2k5fXn6). This is important because the user can choose to save the downloaded files anywhere on the system (such as in D:\files\ or in C:\users\myuser\myfiles\) and the app container, being a sandbox process, might not be able to directly access the file system without user-initiated actions. This class can be useful to avoid extra steps for the user to open the same file again. To use this class, it’s required to open the file using the FileOpenPicker at least once.
This should help streamline file download functionality in the hosted Web apps and deliver an intuitive UX. (Tip: Make sure to add the file URL domain to the ApplicationContentURI at bit.ly/2jsN1WS to avoid runtime exceptions.)
Session Management
It’s common for most modern apps today to persist data between users’ multiple sessions. As an example, imagine having to log into a frequently used app every time the app is launched. It would be time-consuming if the app is launched multiple times in a day. There are classes within the Windows Runtime framework available to the Web apps, which are converted to UWP apps.
Consider a scenario in which certain parameters need to be persisted between sessions by the same user. For example, consider a string identifying the user and other miscellaneous information such as last session time and other cached items.
The right class to use in this scenario is the Windows.Storage.ApplicationData class. This class has many properties and methods, however, consider the localSettings property for this scenario.
The settings are stored in a key-value pair system. Look at the following sample code for the implementation:
function getSessionData()
{
var applicationData = Windows.Storage.ApplicationData.current;
var localSettings = applicationData.localSettings;
// Create a simple setting.
localSettings.values["userID"] = "user998-i889-27";
// Read data from a simple setting.
var value = localSettings.values["userID"];
}
Ideally, the code to write data into the settings can be written at checkpoints within the app where it’s necessary to capture certain information and then be sent to a remote Web API or service. The code to read the setting can usually be written in the App_activated event handler where the app is launching and the information persisted from the previous app session can be accessed. Note that the name of each setting has a 255-character limit and each setting has a limit of 8K bytes in size.
Voice (Cortana) Integration Scenario
One of the scenarios that your Web site (now an app) can implement—post porting to the UWP—is to integrate Cortana, a voice-based interactive digital assistant on Windows devices.
Consider that the Web site is a travel-based portal and now, ported to a Windows 10 app, one of the use cases where Cortana can be integrated is to show details of a person’s trip. The user can then issue commands such as, “Show trip to London” (or whatever destination), which needs to be configured by app developers, as shown in Figure 4.
Figure 4 Cortana Commands XML
<?xml version="1.0" encoding="utf-8"?>
<VoiceCommands xmlns="https://schemas.microsoft.com/voicecommands/1.1">
<CommandSet xml:lang="en-us" Name="AdventureWorksCommandSet_en-us">
<CommandPrefix> Adventure Works, </CommandPrefix>
<Example> Show trip to London </Example>
<Command Name="showTripToDestination">
<Example> Show trip to London </Example>
<ListenFor RequireAppName=
"BeforeOrAfterPhrase"> show trip to {destination} </ListenFor>
<Feedback> Showing trip to {destination} </Feedback>
<Navigate/>
</Command>
<PhraseList Label="destination">
<Item> London </Item>
<Item> Dallas </Item>
</PhraseList>
</CommandSet>
<!-- Other CommandSets for other languages -->
</VoiceCommands>
Cortana can help achieve tasks with a lot of first-party apps (such as Calendar) and expose APIs to interface with custom apps. Basically, here’s basically how Cortana works with apps:
- Listens to commands as registered in the voicecommands.xml file.
- Activates the relevant app, matching the commands spoken/typed in the Windows Search bar.
- Passes the speech to/text commands to app as variables to let the app process the input from user.
Note: The file name is indicative. It can be named as desired with an XML extension. The Windows Developer article, “Using Cortana to Interact with Your Customers (10 by 10),” at bit.ly/2iGymdE provides a good overview of configuring commands in the XML file and its schema.
The XML code in Figure 4 will help Cortana identify that the Adventure Works app needs to launch when issued commands such as the following:
'Adventure Works, Show trip to London'
'Adventure Works, Show trip to Dallas'
Now, let’s take a look at how the Web code would handle the activation and navigation to the relevant page within the app (or Web site) based on the input. You create a separate JavaScript file for coding this scenario.
The code in Figure 5 should be referred in the <body> section of the caller page/referencing page just before the DOMContentLoaded event triggers. Hence, it’s best to add <script src> just before the <body> element ends in the referencing page.
Figure 5 Voice Commands Handler Code
<!-- In HTML page :
<meta name="msapplication-cortanavcd" content="https://<URL>/vcd.xml" />
-->
(function () {
if (typeof Windows !== 'undefined' &&
typeof Windows.UI !== 'undefined' &&
typeof Windows.ApplicationModel !== 'undefined') {
Windows.UI.WebUI.WebUIApplication.addEventListener("activated", activatedEvent);
}
function activatedEvent (args) {
var activation = Windows.ApplicationModel.Activation;
// Check to see if the app was activated by a voice command.
if (args.kind === activation.ActivationKind.voiceCommand) {
// Get the speech recognition.
var speechRecognitionResult = args.result;
var textSpoken = speechRecognitionResult.text;
// Determine the command type {search} defined in vcd.
switch (textSpoken) {
case "London":
window.location.href =
'https://<mywebsite.webapp.net>/Pages/Cities.html?value=London';
break;
case "Dallas":
window.location.href =
'https://<mywebsite.webapp.net>/Pages/Cities.html?value=Dallas';
break;
...
<other cases>
...
}
}
}
})();
(Note: Because the app is “activated” by Cortana using the “activationKind” as “voiceCommand,” it’s important to register the event handler for this activation. To register app lifecycle events where the app isn’t a native WinJS or C# one, the namespace Windows.UI.WebUI.WebUIApplication is provided to help subscribe to and handle specific events.)
In the code in Figure 5, Cortana APIs will receive the user voice input and populate the SpeechRecognition property of the activation classes. This should help retrieve the converted text in the code and help the app perform relevant actions. In this snippet, you use a switch-case statement to evaluate the textSpoken variable and route the user to the Cities.html page with the value of city appended as querystring.
Clearly, this is just one of the scenarios and, given the routing configuring of each Web site (MVC, REST and so on), the cases will change accordingly. The app is now ready to talk to Cortana.
The (Missing) Back Button
After discussing some of the most-advanced functionalities, let’s look at one of the basic—but important—aspects of the app experience: navigation. Ease of browsing through the app and intuitive navigation hierarchy helps users to predictively experience an app.
This scenario is special because when you port the responsive Web site to a UWP app, the UI behaves as expected. However, you need to provide the app container with more pointers about handling the navigation within the app. The default UX goes like this:
- User launches the app.
- The user browses various sections of the app by clicking hyperlinks or menus on the pages.
- At one point, the user wishes to go to the previous page and clicks the hardware or software back button provided by the Windows OS (there is no back button in the app yet).
- The app exits.
Point No. 4 is an unexpected result and needs to be fixed. The solution is simple, which includes instructing the app container window to go back through regular JavaScript APIs. Figure 6 shows the code for this scenario.
Figure 6 Back-Button Code
(function() {
if (typeof Windows !== 'undefined' &&
typeof Windows.UI !== 'undefined' &&
typeof Windows.ApplicationModel !== 'undefined') {
Windows.UI.Core.SystemNavigationManager.getForCurrentView().
appViewBackButtonVisibility =
Windows.UI.Core.AppViewBackButtonVisibility.visible;
Windows.UI.Core.SystemNavigationManager.getForCurrentView().
addEventListener("backrequested", onBackRequested);
function onBackRequested(eventArgs) {
window.history.back();
eventArgs.handled = true;
}
}
})();
The initial few lines enable the framework-provided back button, which appears on the top of the app and then registers an event handler for the tap/click event. All that you do in the code is access the “window” DOM object and instruct it to go one page back. There’s one thing to remember: When the app is at the bottom of the navigation stack, there’s no further pages available in the history and the app will exit at this point. Additional code needs to be written if custom experience is required to be baked into the app.
Live Tiles
Live Tiles is a feature of the UWP apps that displays crisp information about updates in the app or anything that might be of interest to the user without having to launching the app. A quick example can be viewed by hitting the Start menu on a Windows device. A few Live Tiles would be evident for apps such as News, Money and Sports.
Here are just a few use-case examples:
- For an e-commerce app, a tile can display recommendations or status of your order.
- In a line-of-business app, a tile can display a mini image of your organization’s reports.
- In a gaming app, Live Tiles can display offers, achievements, new challenges and so on.
Figure 7 shows two examples of tiles from some of the Microsoft first-party apps.
Figure 7 Live Tiles Examples
One of the easiest ways to integrate Live Tiles into your hosted Web app is to create a Web API and set up the app code (shown in Figure 8) to poll it every few minutes. The job of the Web API is to send back XML, which will be used to create the tile content for the Windows app. (Note: It’s important that the end user pins the app to the Start menu to experience Live Tiles.)
Figure 8 Simple Live Tiles Code
function enableLiveTile()
{
if (typeof Windows !== 'undefined' &&
typeof Windows.UI !== 'undefined' &&
typeof Windows.ApplicationModel !== 'undefined') {
{
var notification = Windows.UI.Notifications;
var tileUpdater =
notification.TileUpdateManager.createTileUpdaterForApplication();
var recurrence = notification.PeriodicUpdateRecurrence.halfHour;
var url = new Windows.Foundation.Uri("<URL to receieve the XML for tile>");
tileUpdater.startPeriodicUpdate(url, recurrence);
}
}
}
The most important class here is the TileUpdateManager from the Windows.UI.Notifications namespace. This class not only creates a template internally to send to the tile, but also polls the specified URL for tile XML content through startPeriodicUpdate method. The duration of the poll can be set using the PeriodicUpdateRecurrence enumeration to period pull the XML content for Tiles. This approach is more server-driven where the Web API sends the XML code and tile template to the client. This is feasible when the developer has control over the app and the service layers.
Now consider a scenario in which the app receives information from third-party Web APIs such as weather or market research data. In such scenarios, mostly the Web APIs would send standard HTTP responses in terms of body, headers and so on. Here, you can parse the Web API response and then form an XML tile of content in the client-side UWP app code in JavaScript. This gives the app developer more control over the type of templates to display the data. You can also mention the expiration time for the tile through the TileNotification class. The code for this is shown in Figure 9.
Figure 9: Another Option for Live Tiles Creations
function createLiveTile() /* can contain parameters */
{
var notifications = Windows.UI.Notifications,
tile = notifications.TileTemplateType.tileSquare310x310ImageAndText01,
tileContent = notifications.TileUpdateManager.getTemplateContent(tile),
tileText = tileContent.getElementsByTagName('text'),
tileImage = tileContent.getElementsByTagName('image');
// Get the text for live tile here [possibly] from a remote service through xhr.
tileText[0].appendChild(tileContent.createTextNode('Demo Message')); // Text here.
tileImage[0].setAttribute('src','<URL of image>');
var tileNotification = new notifications.TileNotification(tileContent);
var currentTime = new Date();
tileNotification.expirationTime = new Date(currentTime.getTime() + 600 * 1000);
notifications.TileUpdateManager.createTileUpdaterForApplication().
update(tileNotification);
}
Note the TileTemplateType class provides the functionality of creating a square tile template 310 x 310px in size with an image and text. Also, the expiration time for the tile is set to 10 minutes through code, which means that after this time, the Live Tile would revert to the default app tile provided in the app package, unless a new notification arrives in the app in form of push notifications. More information about available tile templates can be found at bit.ly/2k5PDJj.
Wrapping Up
There are a few things to consider while planning the migration from a Web app to a UWP app:
- Test your app for layout and rendering with modern browsers (Microsoft Edge is an example).
- If your Web app is dependent on an ActiveX control or plug-in, ensure an alternative way to make the functionality work when running on modern browsers or as a UWP app.
- Use the SiteScan tool at bit.ly/1PJBcpi to surface recommendations related to libraries and functionality.
- Identify URLs of external resources that your Web site references. These will be required to be added to the ApplicationContentUriRules section of the Appx.manifest file.
Additionally, there are many deeper integrations that can be achieved through the JavaScript Windows object and help light up the app functionality through richer experiences. Contacts, Camera, Microphone, Toast notifications and many features open a window of opportunity for blending your Web site with the app persona. The code in this article has been converted into a project template and made available for developers through GitHub at bit.ly/2k5FlJh.
Sagar Bhanudas Joshi has worked with developers and ISVs on the Universal Windows Platform and Microsoft Azure for more than six years. His role includes working with startups to help them architect, design, and provide on-board solutions and applications to Azure, Windows, and the Office 365 platform. Joshi lives and works in Mumbai, India. Find him on Twitter: @sagarjms.
Thanks to the following Microsoft technical experts who reviewed this article: Sandeep Alur and Ashish Sahu