UWP with Desktop Extension – Part 3

desktop_yellow

If you haven’t read part 1 or part 2 of this series yet, please start there first. I have covered the “Hello World” version of a UWP with Desktop extension and explained how to launch different process types and pass in arguments. Now the next set of questions I want to answer in this post are:

  • How to establish a communication channel between the components?
  • How to send requests and receive responses from either side?
  • How to handle process exit scenarios?

TL; DR;

Short answers: An AppServiceConnection can be used to connect between the components and to allow bi-directional communication via ValueSets. Typically you want to host the AppService in-proc with your UWP app in this type of scenario.

Just show me the code on github …
https://github.com/StefanWickDev/UWP-FullTrust/tree/master/UWP_FullTrust_3

Let me run your sample from the Microsoft Store …
https://www.microsoft.com/store/apps/9NR0G35Q2F65

Declaring the AppService

The first step is to declare the AppService extension in our manifest, which is what will provide us with a two-way communication pipe between our components:

<Extensions>
 <uap:Extension Category="windows.appService">
  <uap:AppService Name="SampleInteropService" />
 </uap:Extension>
 <desktop:Extension Category="windows.fullTrustProcess" Executable="FullTrust\FullTrust.exe" />
</Extensions>

For full details on AppServices in the Universal Windows Platform our developer documentation has this good read: https://docs.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service

Establishing the Connection

Now in order to establish the connection, let’s take a look at our WPF app. One of the first things it does during launch is to establish the connection to the AppService, and then retain the instance of the AppServiceConnection for the lifetime of the connection. We are also hooking up the event handlers here to receive requests and to get notified when the connection gets closed:

// MainWindow.xaml.cs in WPF project
private async void InitializeAppServiceConnection()
{
 connection = new AppServiceConnection();
 connection.AppServiceName = "SampleInteropService";
 connection.PackageFamilyName = Package.Current.Id.FamilyName;
 connection.RequestReceived += Connection_RequestReceived;
 connection.ServiceClosed += Connection_ServiceClosed;

 AppServiceConnectionStatus status = await connection.OpenAsync();
 if (status != AppServiceConnectionStatus.Success)
 {
  // something went wrong ...
  MessageBox.Show(status.ToString());
  this.IsEnabled = false;
 } 
}

On the other side in the UWP, this will now trigger the OnBackgroundActivated event, and we’ll grab a reference to the AppServiceConnection instance there as well and hold on to it.

In our case the UWP is already running in the foreground, but note that it doesn’t have to: Client processes can also connect to the AppService if the foreground app is not running. It would activate the UWP in the background and establish the AppServiceConnection. The UWP process would then continue running in the background for as long as the connection is active.

// App.xaml.cs in UWP project
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
 base.OnBackgroundActivated(args);

 if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
 {
  // only accept connections from callers in the same package
  if (details.CallerPackageFamilyName == Package.Current.Id.FamilyName)
  {
   // connection established from the fulltrust process
   AppServiceDeferral = args.TaskInstance.GetDeferral();
   args.TaskInstance.Canceled += OnTaskCanceled;

   Connection = details.AppServiceConnection;
   AppServiceConnected?.Invoke(this, args.TaskInstance.TriggerDetails as AppServiceTriggerDetails);
  }
 }
}

Sending Requests and Responses

Now that the connection has been established and either side keeps a reference to the AppServiceConnection instances, we can send requests from either side, and respond to them accordingly.

Scenario 1 – UWP sends request to desktop extension

UWP apps don’t have access to the registry. Let’s assume for an LOB enterprise scenario I need to read a certain reg key. The UWP can use a desktop extension to get the job done, by sending an AppServiceRequest that contains the requested key. The desktop extension can then query the registry and send the name/value pairs back to the UWP as an AppServiceResponse.

screenshot8

// MainPage.Xaml.cs in UWP project
private async void btnClick_ReadKey(object sender, RoutedEventArgs e)
{
 ValueSet request = new ValueSet();
 request.Add("KEY", tbKey.Text);
 AppServiceResponse response = await App.Connection.SendMessageAsync(request);

// display the response key/value pairs
 tbResult.Text = "";
 foreach (string key in response.Message.Keys)
 {
 tbResult.Text += key + " = " + response.Message[key] + "\r\n";
 }
}

This triggers the registry reading code in the WPF app. The implementation can be found here on github in the ‘Connection_RequestReceived’ event handler. In a real production scenario you wouldn’t implement this extension as a windowed WPF app of course, but instead use a headless background process. But since we need UI for scenario #2, I went with WPF …

Scenario 2 – Desktop extension sends request to UWP

To show the reverse communication flow I am just picking a dummy scenario: I will send two double values from WPF to UWP and let the UWP app respond with the sum of both:

screenshot9

// MainWindow.xaml.cs in WPF project
private async void Button_Click(object sender, RoutedEventArgs e)
{
 // ask the UWP to calculate d1 + d2
 ValueSet request = new ValueSet();
 request.Add("D1", d1);
 request.Add("D2", d2);
 AppServiceResponse response = await connection.SendMessageAsync(request);
 double result = (double)response.Message["RESULT"];
 tbResult.Text = result.ToString();
}
// in MainPage.Xaml.cs in UWP project
private async void AppServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
 double d1 = (double)args.Request.Message["D1"];
 double d2 = (double)args.Request.Message["D2"];
 double result = d1 + d2;
 
 ValueSet response = new ValueSet();
 response.Add("RESULT", result);
 await args.Request.SendResponseAsync(response);

// log the request in the UI for demo purposes
 await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
 {
 tbRequests.Text += string.Format("Request: {0} + {1} --> Response = {2}\r\n", d1, d2, result);
 });
}

 

Handling Process Exit Scenarios

Now what happens if one side of the communication pipeline goes away, for whatever reason? It’s up to the app to handle this and decide what should happen. Generally speaking there are three options: try to relaunch the companion component, or keep going without the companion (if it’s non-essential), or failfast to terminate yourself. For our demo I will implement two scenarios:

Scenario 1 – UWP will relaunch closed WPF

// MainPage.xaml.cs in UWP project
private async void MainPage_AppServiceDisconnected(object sender, EventArgs e)
{
 await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, ()=>
 {
  // disable UI to access the connection
  btnRegKey.IsEnabled = false;

  // ask user if they want to reconnect
  Reconnect();
 }); 
}

private async void Reconnect()
{
 if (App.IsForeground)
 {
  MessageDialog dlg = new MessageDialog("Connection to desktop process lost. Reconnect?");
  UICommand yesCommand = new UICommand("Yes", async (r) =>
  {
   await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
  });
  dlg.Commands.Add(yesCommand);
  UICommand noCommand = new UICommand("No", (r) => { });
  dlg.Commands.Add(noCommand);
  await dlg.ShowAsync();
 }
}

Scenario 2 – WPF will terminate itself when UWP closes

// MainWindow.xaml.cs in WPF project
private void Connection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
{
 // connection to the UWP lost, so we shut down the desktop process
 Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
 {
  Application.Current.Shutdown();
 })); 
}

This is it for part 3 of this series. Hopefully I answered most of the question around communication between the app and its extension in this sample. If not, please leave comments here, hit me up on Twitter or post your questions on Stackoverflow. If they are tagged with ‘UWP’ they will show on my radar.

Next up: Part 4 – Submitting to the Microsoft Store

Advertisements

36 comments

  1. Hello Stefan,

    Thank you for this lesson.

    At this moment I develop an extension for Microsoft Edge based on native messaging. Could you please consider this official example? https://github.com/MicrosoftEdge/MicrosoftEdge-Extensions-Demos/blob/master/SecureInput/NativeMessagingHostInProcess/App.xaml.cs

    So my question is how to handle multiple connections to the app correctly? In this sample there is very strange synchronization in the OnBackgroundActivated method and no such a thing in other methods…

    Well I did some experiments and it seems that OnBackgroundActivated is always executed by only one thread, but RequestReceived and ServiceClosed event handlers for different connections can be executed by several threads and the same time… Thus a correct synchronization for dictionaries is required.

    Could you please shed more light on this?

    Like

    1. Yes, that’s correct. The event handlers may fire on different threads and things will get more complicated if you have more than one client for your AppService. This would be a good thing to cover in an additional, dedicated sample.

      Like

  2. Hi Stefan,

    Great lessons for desktop extension!

    I am having UWP app written in C++, and I want to add desktop extension, again written in C++. Your example from part 2 shows it is possible to have UWP with C++. My question is, can I
    establish “channel” in between for information exchange? Concept of AppService is great, but as far as I saw, it can’t be used from pure C++. Or, there is something else you can propose?

    Regards,
    Aleksandar

    Like

  3. Hello Stefan,

    Really great article on how to communicate between UWP and Windows Application.
    I have implemented WPF application as a service and UWP application as a client, following all above steps I’m able to read Registry, able to check the Process using “System.Diagnostics.Process” from a service.

    The only question I had in mind though, will Microsoft Store allow/accept my application when I will be submitting it to Store to verify and publish the same, as we are checking other applications registry and checking for the Process which is not allowed in UWP application.

    Thanks in advance!

    Like

  4. Hello Stefan,

    Really great article on how to communicate between UWP and Windows Application.
    I have implemented WPF application as a service and UWP application as a client, following all above steps I’m able to read Registry, able to check the Process using “System.Diagnostics.Process” from a service.

    Only question I had in mind though, will Microsoft Store allow/accept my application when I will be submitting it to Store to verify and publish the same, as we are checking other applications registry and checking for the Process which is not allowed in UWP application.

    Thanks in advance!

    Like

      1. Sorry for the late reply, I just returned from vacation.

        What you describe is allowed and can be accepted in the Windows Store, unless you do other things in the app that would violate Store policy. Hope this helps.

        Thanks,
        Stefan

        Like

  5. Hi Stefan,

    I am using visual studio 2017 with SDK 16299 installed, but when I build UWP_FullTrust_3, I am having a warning “warning MSB3245: Could not resolve this reference. Could not locate the assembly “Windows” and some errors

    error CS0234: The type or namespace name ‘ApplicationModel’ does not exist in the namespace ‘Windows’ (are you missing an assembly reference?)
    error CS0234: The type or namespace name ‘Collections’ does not exist in the namespace ‘Windows.Foundation’

    Do you know what’s wrong about it? Where can I add reference to assembly “Windows”.

    BR,
    CY

    Like

  6. Me again…
    In my Win32 Winforms app, I’m trying to open the connection using

    status = Await connection.OpenAsync()

    the error list shows
    Await requires that the type ‘IAsyncOperation(Of AppServiceConnectionStatus)’ have a suitable GetAwaiter method.

    Think that I can do what I want in a WinForms VB app but not sure 🙂

    Like

      1. Just realized it’s a NuGet package but the package wouldn’t install since it’s supposedly not compatible since the app targets .NET Framework v.4.7.2

        Like

      2. I know it’s not exactly obvious, but you can just add the reference to the DLL located in \Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll

        Like

  7. Stefan, Great Stuff!

    My question is more related to the capabilities of the AppService. We have a Windows service that we want to talk to from a UWP app. We can use any IPC mechanism as we have control over it. Our current environment is Service WPF app using WCF over a ManedPipe. Is an AppService able to use any of the current MS IPC mechanisms to communicate to a Windows Service, or is a Windows service able to create and use a connection to an AppService like you show in this article?

    Thanks for any insights you might have

    Like

  8. Sort of new question… I’ve got a UWP app that exposes an AppService & houses a BackgroundTask. The AppService is working fine and accepting messages from external apps via an AppServiceConnection. The BackgroundTask is registered and getting triggered when it should via OnBackgroundActivated. The problem I’m having is talking back to the external app from the BackgroundTask. The task code is in App.xaml.vb and the AppService is in class VB file that’s all part of the same solution. The AppServiceConnection is live in the class code but is inaccessible in the BackgroundTask. Not sure if what I’m doing is possible or makes sense

    Like

    1. Yes, definitely possible. You just need to get a hold of the AppServiceConnection so that your task can access it. If your task is implemented in App.xaml.vb it should be very easy as I assume this is also where your OnBackgroundActivated handler is. In that event handler you can get to the AppServiceConnection via the event args (it’s on a property a couple of levels deep: args->TaskInstance->Details->AppServiceConnection) and then store it in a class level variable. Take a look at this OnBackgroundActivated implementation (C#): https://github.com/Microsoft/DesktopBridgeToUWP-Samples/blob/master/Samples/UWP%20Office%20Interop/DataGrid2Excel/App.xaml.cs

      Liked by 1 person

      1. Looks good.. the trigger for my background task is a toast notification so the args I get are ToastNotificationActionTriggerDetail args but I can cast them as AppServiceTriggerDetails args also but not sure if that’s going to work

        Like

      2. What I meant was: grab the AppServiceConnection (from the AppServiceTriggerDetail) when the external app connects. Then keep that instance around in a class level variable. This way, when your toast notification comes in, you can use the connection to communicate with your external app. Does this make sense? Let me know if I misunderstood your scenario.

        Like

  9. When I declare the connection as such:
    Public NotInheritable Class Notify
    Implements IBackgroundTask

    Shared AppServiceDeferral As BackgroundTaskDeferral = Nothing
    Public Shared Connection As AppServiceConnection = Nothing

    Public Sub Run(taskInstance As IBackgroundTaskInstance) Implements IBackgroundTask.Run

    AppServiceDeferral = taskInstance.GetDeferral()
    AddHandler taskInstance.Canceled, AddressOf OnTaskCanceled
    Dim details As AppServiceTriggerDetails
    details = taskInstance.TriggerDetails
    Connection = details.AppServiceConnection
    AddHandler Connection.RequestReceived, AddressOf AppServiceConnection_RequestReceived

    End Sub

    and then reference it in my OnBackgroundActivated routine as such:

    Protected Overrides Async Sub OnBackgroundActivated(args As BackgroundActivatedEventArgs)
    MyBase.OnBackgroundActivated(args)
    Dim deferral As BackgroundTaskDeferral = args.TaskInstance.GetDeferral()
    Dim conn As AppServiceConnection = NotifyComponent.Notify.Connection

    I get this error upon compiling:
    Type ‘NotifyComponent.Notify’ contains externally visible field ‘Windows.ApplicationModel.AppService.AppServiceConnection NotifyComponent.Notify.Connection’. Fields can be exposed only by structures.

    Like

    1. I am not a VB expert, but I think you are declaring the Connection property as an instance property, so you will need the actual Instance of Notify to access it. This seems more complicated that it should be. I would just keep track of the AppServiceConnection in the App class and expose it from there (like I do in the example I pointed you to).

      Like

      1. The only problem with that is that the connection is created in the Notify class with is the entry point for the AppService and it doesn’t look like I can put the entry point for the AppService in the App class

        Like

      2. How do you currently declare the AppService in your appxmanifest? Make sure you run the AppService in-proc with the UWP app (unless you have a good reason not to do so). Then your AppService entrypoint is OnBackgroundActivated in the App class and all of this gets very easy (as described in the sample code I linked earlier).

        Like

  10. Here’s the relevant part

    If I can move the AppService into the UWP app that probably would clean stuff up. Currently the extension is in the same solution but a different project class file. I tried to quickly move the AppService into the UWP app into app.xaml.vb but the connection from the client app didn’t work at all. I changed the EntryPoint to something like App.Notify or something similar.

    Like

    1. The manifest part didn’t get through somehow. The key point is to declare it without specifying an entry point. Then the AppService will be activated in the same process as the app, via the OnBackgroundActivated event.

      Like

  11. Leaving out the Entry Point fixed just about everything. Thanks. Now the only ‘problem’ now is that when I click on a toast notification, but not the button on it, it brings up the UWP app’s splash screen and doesn’t hit the OnBackgroundActivated event even if I click on the toast from the Action Center. I’m guessing that another launch event gets hit.

    Like

    1. Looks like clicking on the body of a toast the app creates sends it through the OnActivated event while clicking on the button in the toast sends it through the OnBackground event

      Like

      1. This is unrelated to the topic of AppService and Desktop Extension. The activation type of your toast (foreground vs background) depends on what you specify in your app code for the toast. You can read up on this here:

        https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/send-local-toast#handling-activation-1
        https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-schema#toastactivationtype

        Like

  12. I got so wrapped up in the other stuff that I forgot that toast stuff has foreground & background. Now that program flow is working OK, it’s toast configuration that’s the problem… except that ToastHeader doesn’t allow for Background activation, only ToastButton, etc 🙂

    Like

    1. Yep, that’s how notifications work system-wide. And users are trained to expect that clicking on the body will open the foreground experience. They would likely think your app is broken if it wouldn’t behave the same way.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s