The previous posts detailed how file and protocol associations allow Windows Store Apps and Desktop Apps to activate each other. There is one drawback in both mechanisms: the end user switches back and forth from Desktop and Modern UI mode when each application gets activated.
Let’s take a simple example: imagine that you want to build a calculator with a Modern UI that relies on a Desktop App to process the computations. The latter will implements a custom protocol/file association to receive the requests sent by the former. This WSA will implements its own custom protocol/file association to be notified back with the result of the processing. However, the screen will switch from the WSA to the DA when the request is sent and from the DA to the WSA when the computed result is send back. This is clearly not acceptable for the user.
WARNING: all these techniques are violating the point 3.1 from the Windows Store Requirements that states: “Your app may only depend on software listed in the Windows Store“. Therefore, you should not use them for a WSA that you plan to publish into the Windows Store because it will be rejected.
Silent activation architecture
The notion of “silent activation” behind the title of this post hides several technics required to let a WSA send requests to a DA without switching the user back and from between Modern UI and the Desktop.
- The DA should be notified when the WSA creates a request but without leaving the Modern UI
- The DA should run behind the scene and the WSA should know if this is not the case
- The WSA should be notified when the DA has computed the result
Here is the solution I’m proposing for a line of business application
- The DA registers a file system watcher for .wsaCalculator files in the WSA local data folder
- The WSA creates a .wsaCalculator file in its local application data and waits for the DA to process the request
- The DA activates the WSA and passes the request/result as a query string to its custom protocol
- The WSA has a timeout to detect if the DA is not processing requests
DA – How to detect that a request has been issued?
Because I don’t want the WSA to activate the DA in order to process a request, I need to find another way to communicate with it. I simply create a file with a specific extension into the WSA local data folder and wait for the result. Based on the package family name
I know where these files are stored on the file system:
So, the DA creates a
FileSystemWatcher to be notified when such a file gets created and then processes the request.
Why not creating the request files in the Documents library? Well… I do not want to add garbage files without any meaning into the user folder; this is only implementation details that should not be visible to the user. In addition, the WSA would have needed to be associated to the extension without any good reason to be activated when the user double-click one of the files.
DA – How to send the result back to the WSA?
The request parameters are read one line after the other from the file by the DA which then executes the computation. The next step to send the result back to the WSA is easy: just activates it with its associated protocol and pass the request/result as a query string as shown in the previous post.
On the other side, the WSA receives the query string in the
ProtocolActivatedEventArgs.Uri.Query passed to its application
OnActivated override. The result is extracted from the string and the UI is updated accordingly. However, what if the DA never answers? The WSA will wait forever!
WSA – How to detect DA presence?
In order to avoid this kind of situation, you need to ensure that the DA runs. You could activate the DA when the WSA starts but with a UI glitch when Windows switches back and forth. It is also possible to ask Windows to automatically start the DA when the user logs in. However, it is still possible that the DA contains a bug that would end up to an unhandled exception and would be terminated by Windows. The simpler solution is to start a timer just after the request file is created and if it expires, the WSA knows that something went wrong.
You can see an implementation of this two steps request in
MainPage.OnActivated where a “ping” request is created and a
PeriodicTimer is started.
The code triggered by the timer expiration runs in a worker thread from which it is not possible to update the UI. This is why it needs to be run asynchronously by the application dispatcher. This code tests whether or not the
PingWasAnswered field has been set by the
OnActivated application override.
Wrap-up – building an async/await kind of API with the RequestManager
This architecture is working fine but if you want to issues several different requests at the same time, it is just not scalable. Here is the kind of code I would like to write:
string result = await app._requestManager.SendRequestAsync(request);
if (result == null)
// the DA processes the request and returns result
RequestManager class implements a
SendRequestAsync method that makes everything simple.
Each request has a guid that is used in two ways:
- as filename of the .wsaCalculator file that contains the parameters
- as a key in a dictionary to be able to map a request to the result computed by the DA when it will come back. In addition to the expected result, the
RequestInfoassociated to the key in the dictionary provides a
ManualResetEvent is created non-signalled and associated to the request in the dictionary. Next, the guid.wsaCalculator file is filled with the parameters to be processed by the DA. Instead of using a timer, the code simply waits for the event to be signalled or of the given timeout expires. Since I don’t want to freeze the UI thread by waiting for the event, this code is executed by a
bool hasTimedOut = await Task.Run<bool>(() => mre.WaitOne(Timeout));
When the DA activates the WSA with the result, the
OnActivated override calls the
CompleteRequest RequestManager method to find the
RequestInfo associated to the processed request received in the query string of the custom protocol. The computation is stored in the
Result property and the
ManualResetEvent stored in the
EventToSignal property is set to let the
Task return false.
Last but not least, it is possible to register a handler to the
ResponseAfterTimeOut events. The former is called when a request has not been answered by the DA before the time expires and the latter runs if the DA answers after the timeout expires.
Simple isn’t it?