How to create and consume an app service

Learn how to write a Universal Windows Platform (UWP) app that can provide services to other UWP apps, and how to consume those services.

  • What you need to know
  • Create a new app service provider project
  • Add an app service extension to package.appxmanifest
  • Create the app service
  • Write the code for the app service
  • Deploy the service app and get the package family name
  • Write a client to call the app service
  • Debug the app service
  • Debug the client

What you need to know

Technologies

Prerequisites

  • Windows 10
  • Microsoft Visual Studio 2015

Instructions

Step 1: Create a new app service provider project

In this how-to, we'll create everything in one solution for simplicity.

  • In Visual Studio 2015, create a new UWP app project and name it AppServiceProvider. (In the New Project dialog box, select Templates > Other Languages > Visual C# > Windows > Windows Universal > Blank app (Windows Universal)). This will be the app that provides the app service.

Step 2: Add an app service extension to package.appxmanifest

In the AppServiceProvider project's Package.appxmanifest file, add the following AppService extension to the <Application> element. This example advertises the com.Microsoft.Inventory service and is what identifies this app as an app service provider. The actual service will be implemented as a background task. The app service app exposes the service to other apps. We recommend using a reverse domain name style for the service name.

... 
<Applications>
    <Application Id="App"
      Executable="$targetnametoken$.exe"
      EntryPoint="AppServiceProvider.App">
      <Extensions>
        <uap:Extension Category="windows.appService" EntryPoint="MyAppService.Inventory">
          <uap:AppService Name="com.microsoft.inventory"/>
        </uap:Extension>
      </Extensions>
      ...
    </Application>
</Applications>

The Category attribute identifies this application as an app service provider.

The EntryPoint attribute identifies the class that implements the service, which we'll implement next.

Step 3: Create the app service

  1. An app service is implemented as a background task. This enables a foreground application to invoke an app service in another application to perform tasks behind the scenes. Add a new Windows Runtime Component project to the solution (File > Add > New Project) named MyAppService. (In the Add New Project dialog box, choose Installed > Other Languages > Visual C# > Windows > Windows Universal > Windows Runtime Component (Windows Universal)

  2. In the AppServiceProvider project, add a reference to the MyAppService project.

  3. In the MyappService project, add the following using statements to the top of Class1.cs:

    using Windows.ApplicationModel.AppService;
    using Windows.ApplicationModel.Background;
    using Windows.Foundation.Collections;
    
  4. Replace the stub code for Class1 with a new background task class named Inventory:

    public sealed class Inventory : IBackgroundTask
    {
        private BackgroundTaskDeferral backgroundTaskDeferral;
        private AppServiceConnection appServiceconnection;
        private String[] inventoryItems = new string[] { "Robot vacuum", "Chair" };
        private double[] inventoryPrices = new double[] { 129.99, 88.99 };
    
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            this.backgroundTaskDeferral = taskInstance.GetDeferral(); // Get a deferral so that the service isn't terminated.
            taskInstance.Canceled += OnTaskCanceled; // Associate a cancellation handler with the background task.
    
            // Retrieve the app service connection and set up a listener for incoming app service requests.
            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            appServiceconnection = details.AppServiceConnection;
            appServiceconnection.RequestReceived += OnRequestReceived;
        }
    
        private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
        }
    
        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (this.backgroundTaskDeferral != null)
            {
                // Complete the service deferral.
                this.backgroundTaskDeferral.Complete();
            }
        }
    }
    

    This class is where the app service will do its work.

    Run() is called when the background task is created. Because background tasks are terminated after Run completes, the code takes a deferral so that the background task will stay up to serve requests.

    OnTaskCanceled() is called when the task is canceled. The task is cancelled when the client app disposes the AppServiceConnection, the client app is suspended, the OS is shut down or sleeps, or the OS runs out of resources to run the task.

Step 4: Write the code for the app service

OnRequestedReceived() is where the code for the app service goes. Replace the stub OnRequestedReceived() in MyAppService's Class1.cs with the code from this example. This code gets an index for an inventory item and passes it, along with a command string, to the service to retrieve the name and the price of the specified inventory item. Error handling code has been removed for brevity.

private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
    // Get a deferral because we use an awaitable API below to respond to the message
    // and we don't want this call to get cancelled while we are waiting.
    var messageDeferral = args.GetDeferral();

    ValueSet message = args.Request.Message;
    ValueSet returnData = new ValueSet();

    string command = message["Command"] as string;
    int? inventoryIndex = message["ID"] as int?;

    if ( inventoryIndex.HasValue &&
         inventoryIndex.Value >= 0 &&
         inventoryIndex.Value < inventoryItems.GetLength(0))
    {
        switch (command)
        {
            case "Price":
            {
                returnData.Add("Result", inventoryPrices[inventoryIndex.Value]);
                returnData.Add("Status", "OK");
                break;
            }

            case "Item":
            {
                returnData.Add("Result", inventoryItems[inventoryIndex.Value]);
                returnData.Add("Status", "OK");
                break;
            }

            default:
            {
                returnData.Add("Status", "Fail: unknown command");
                break;
            }
        }
    }
    else
    {
        returnData.Add("Status", "Fail: Index out of range");
    }

    await args.Request.SendResponseAsync(returnData); // Return the data to the caller.
    messageDeferral.Complete(); // Complete the deferral so that the platform knows that we're done responding to the app service call.
}

Note that OnRequestedReceived() is async because we make an awaitable method call to SendResponseAsync in this example.

A deferral is taken so that the service can use async methods in the OnRequestReceived handler. It ensures that the call to OnRequestReceived does not complete until it is done processing the message. SendResponseAsync is used to send a response alongside the completion. SendResponseAsync does not signal the completion of the call. It is the completion of the deferral that signals to SendMessageAsync that OnRequestReceived has completed.

App services use a ValueSet to exchange information. The size of the data you may pass is only limited by system resources. There are no predefined keys for you to use in your ValueSet. You must determine which key values you will use to define the protocol for your app service. The caller must be written with that protocol in mind. In this example, we have chosen a key named "Command" that has a value that indicates whether we want the app service to provide the name of the inventory item or its price. The index of the inventory name is stored under the "ID" key. The return value is stored under the "Result" key.

An AppServiceClosedStatus enum is returned to the caller to indicate whether the call to the app service succeeded or failed. An example of how the call to the app service could fail is if the OS aborts the service endpoint, resources are exceeded, and so forth. You can return additional error information via the ValueSet. In this example, we use a key named "Status" to return more detailed error information to the caller.

The call to SendResponseAsync returns the ValueSet to the caller.

Step 5: Deploy the service app and get the package family name

The app service provider app must be deployed before you can call it from a client. You will also need the package family name of the app service app in order to call it.

  • One way to get the package family name of the app service application is to call Windows.ApplicationModel.Package.Current.Id.FamilyName from within the AppServiceProvider project (for example, from public App() in App.xaml.cs) and note the result. To run AppServiceProvider in Microsoft Visual Studio, set it as the startup project in the Solution Explorer window and run the project.
  • Another way to get the package family name is to deploy the solution (Build > Deploy solution) and note the full package name in the output window (View > Output). You must remove the platform information from the string in the output window to derive the package name. For example, if the full package name reported in the output window was "9fe3058b-3de0-4e05-bea7-84a06f0ee4f0_1.0.0.0_x86__yd7nk54bq29ra", you would extract "1.0.0.0_x86__" leaving "9fe3058b-3de0-4e05-bea7-84a06f0ee4f0_yd7nk54bq29ra" as the package family name.

Step 6: Write a client to call the app service

  1. Add a new blank Windows Universal app project to the solution (File > Add > New Project) named ClientApp. (In the Add New Project dialog box, choose Installed > Other languages > Visual C# > Windows > Windows Universal > Blank App (Windows Universal)).

  2. In the ClientApp project, add the following using statement to the top of MainPage.xaml.cs:

    using Windows.ApplicationModel.AppService;
    
  3. Add a text box and a button to MainPage.xaml.

  4. Add a button click handler for the button and add the keyword async to the button handler's signature.

  5. Replace the stub of your button click handler with the following code. Be sure to include the inventoryService field declaration.

    private AppServiceConnection inventoryService;
    private async void button_Click(object sender, RoutedEventArgs e)
    {
        // Add the connection.
        if (this.inventoryService == null)
        {
            this.inventoryService = new AppServiceConnection();
    
            // Here, we use the app service name defined in the app service provider's Package.appxmanifest file in the <Extension> section. 
            this.inventoryService.AppServiceName = "com.microsoft.inventory";
    
            // Use Windows.ApplicationModel.Package.Current.Id.FamilyName within the app service provider to get this value.
            this.inventoryService.PackageFamilyName = "replace with the package family name";
    
            var status = await this.inventoryService.OpenAsync();
            if (status != AppServiceConnectionStatus.Success)
            {
                button.Content = "Failed to connect";
                return;
            }
        }
    
        // Call the service.
        int idx = int.Parse(textBox.Text);
        var message = new ValueSet();
        message.Add("Command", "Item");
        message.Add("ID", idx);
        AppServiceResponse response = await this.inventoryService.SendMessageAsync(message);
        string result = "";
    
        if (response.Status == AppServiceResponseStatus.Success)
        {
            // Get the data  that the service sent  to us.
            if (response.Message["Status"] as string == "OK")
            {
                result = response.Message["Result"] as string;
            }
        }
    
        message.Clear();
        message.Add("Command", "Price");
        message.Add("ID", idx);
        response = await this.inventoryService.SendMessageAsync(message);
    
        if (response.Status == AppServiceResponseStatus.Success)
        {
            // Get the data that the service sent to us.
            if (response.Message["Status"] as string == "OK")
            {
                result += " : Price = " + response.Message["Result"] as string;
            }
        }
    
        button.Content = result;
    }
    

    Replace the package family name in the line this.inventoryService.PackageFamilyName = "replace with the package family name"; with the package family name of the AppServiceProvider project that you obtained in [Step 5: Deploy the service app and get the package family name].

    The code first establishes a connection with the app service. The connection will remain open until you dispose this.inventoryService. The app service name must match the AppService Name attribute that you added to the AppServiceProvider project's Package.appxmanifest file. In this example, it is <uap:AppService Name="com.microsoft.inventory"/>.

    A ValueSet named message is created to specify the command that we want to send to the app service. The example app service expects a command to indicate which of two actions to take. We get the index from the textbox in the ClientApp, and then call the service with the "Item" command to get the description of the item. Then, we make the call with the "Price" command to get the item's price. The button text is set to the result.

    Because AppServiceResponseStatus only indicates whether the operating system was able to connect the call to the app service, we check the "Status" key in the ValueSet we receive from the app service to ensure that it was able to fulfill the request.

  6. In Visual Studio, set the ClientApp project to be the startup project in the Solution Explorer window and run the solution. Enter the number 1 into the text box and click the button. You should get "Chair : Price = 88.99" back from the service.

If the app service call fails, check the following in the ClientApp:

  1. Verify that the package family name assigned to the inventory service connection matches the package family name of the AppServiceProvider app. See: button_Click()this.inventoryService.PackageFamilyName = "...";).
  2. In button_Click(), verify that the app service name that is assigned to the inventory service connection matches the app service name in the AppServiceProvider's Package.appxmanifest file. See: this.inventoryService.AppServiceName = "com.microsoft.inventory";.
  3. Ensure that the AppServiceProvider app has been deployed (In the Solution Explorer, right-click the solution and choose Deploy).

Step 7: Debug the app service

  1. Ensure that the entire solution is deployed before debugging because the app service provider app must be deployed before the service can be called. (In Visual Studio, Build > Deploy Solution).
  2. In the Solution Explorer, right-click the AppServiceProvider project and choose Properties. From the Debug tab, change the Start action to Do not launch, but debug my code when it starts.
  3. In the MyAppService project, in the Class1.cs file, set a breakpoint in OnRequestReceived().
  4. Set the AppServiceProvider project to be the startup project and press F5.
  5. Start ClientApp from the Start menu (not from Visual Studio).
  6. Enter the number 1 into the text box and press the button. The debugger will stop in the app service call on the breakpoint in your app service.

Step 8: Debug the client

  1. Follow the instructions in the preceding step to debug the app service.
  2. Launch ClientApp from the Start menu.
  3. Attach the debugger to the ClientApp.exe process (not the ApplicationFrameHost.exe process). (In Visual Studio, choose Debug > Attach to Process....)
  4. In the ClientApp project, set a breakpoint in button_Click().
  5. The breakpoints in both the client and the app service will now be hit when you enter the number 1 into the text box of the ClientApp and click the button.

Remarks

This example provides a simple introduction to creating an app service and calling it from another app. The key things to note are the creation of a background task to host the app service, the addition of the windows.appservice extension to the app service provider app's Package.appxmanifest file, obtaining the package family name of the app service provider app so that we can connect to it from the client app, and using Windows.ApplicationModel.AppService.AppServiceConnection to call the service.

Full code for MyAppService

using System;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;

namespace MyAppService
{
    public sealed class Inventory : IBackgroundTask
    {
        private BackgroundTaskDeferral backgroundTaskDeferral;
        private AppServiceConnection appServiceconnection;
        private String[] inventoryItems = new string[] { "Robot vacuum", "Chair" };
        private double[] inventoryPrices = new double[] { 129.99, 88.99 };

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            this.backgroundTaskDeferral = taskInstance.GetDeferral(); // Get a deferral so that the service isn't terminated.
            taskInstance.Canceled += OnTaskCanceled; // Associate a cancellation handler with the background task.

            // Retrieve the app service connection and set up a listener for incoming app service requests.
            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            appServiceconnection = details.AppServiceConnection;
            appServiceconnection.RequestReceived += OnRequestReceived;
        }

        private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            // Get a deferral because we use an awaitable API below to respond to the message
            // and we don't want this call to get cancelled while we are waiting.
            var messageDeferral = args.GetDeferral();

            ValueSet message = args.Request.Message;
            ValueSet returnData = new ValueSet();

            string command = message["Command"] as string;
            int? inventoryIndex = message["ID"] as int?;

            if (inventoryIndex.HasValue &&
                 inventoryIndex.Value >= 0 &&
                 inventoryIndex.Value < inventoryItems.GetLength(0))
            {
                switch (command)
                {
                    case "Price":
                        {
                            returnData.Add("Result", inventoryPrices[inventoryIndex.Value]);
                            returnData.Add("Status", "OK");
                            break;
                        }

                    case "Item":
                        {
                            returnData.Add("Result", inventoryItems[inventoryIndex.Value]);
                            returnData.Add("Status", "OK");
                            break;
                        }

                    default:
                        {
                            returnData.Add("Status", "Fail: unknown command");
                            break;
                        }
                }
            }
            else
            {
                returnData.Add("Status", "Fail: Index out of range");
            }

            await args.Request.SendResponseAsync(returnData); // Return the data to the caller.
            messageDeferral.Complete(); // Complete the deferral so that the platform knows that we're done responding to the app service call.
        }


        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (this.backgroundTaskDeferral != null)
            {
                // Complete the service deferral.
                this.backgroundTaskDeferral.Complete();
            }
        }
    }
}