In the recent Anniversary Update there was such a wonderful thing as App Extensions. Unfortunately, at the moment there is only one video from the documentation on it and a pair of GitHub repositories. But I was able to collect all the necessary information on the use of this feature, and now I will tell you how to write an expandable application.
And yes, you will need an SDK version no lower than 14393.
We will have one host application to which extensions will connect. Each extension will contain a service (App Service), with which the application will interact with the extension.
In a UWP application, you can’t just grab a dynamic library on the fly. This is done for security purposes. Instead, you can communicate with another application using simple data types (their list is very limited). This application should announce that it has a service with which to communicate, and this announcement is written in the application manifest (Package.appxmanifest). All communication takes place by connecting to the service, sending him messages, and receiving a response. Both messages and responses are transmitted using the ValueSet
(in fact, it’s just a Dictionary<string, object>
), and, as mentioned earlier, only the simplest data types (numbers, strings, arrays) can be used as values.
So, we proceed.
For convenience, all projects will be placed in one solution. Open Visual Studio and create an empty UWP application with a minimum version of 14393. I will call it Host.
Now we need to edit the manifest. Open Package.appxmanifest in code mode, and first we find <Package
, add a new namespace: xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
and add uap3 to IgnorableNamespaces. The result should be something like this:
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3">
Further we look for <Application>
and inside it we add the following:
<Extensions> <uap3:Extension Category="windows.appExtensionHost"> <uap3:AppExtensionHost> <uap3:Name>com.extensions.myhost</uap3:Name> </uap3:AppExtensionHost> </uap3:Extension> </Extensions>
Here we declare a new host for extensions. It is by this name that the extensions will connect, and we will look for them. The studio can start swearing, nothing terrible about it.
<?xml version="1.0" encoding="utf-8"?> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3"> <Identity Name="9df790c4-956b-400b-8710-08a834e39c5a" Publisher="CN=acede" Version="1.0.0.0" /> <mp:PhoneIdentity PhoneProductId="9df790c4-956b-400b-8710-08a834e39c5a" PhonePublisherId="00000000-0000-0000-0000-000000000000"/> <Properties> <DisplayName>Host</DisplayName> <PublisherDisplayName>acede</PublisherDisplayName> <Logo>Assets\StoreLogo.png</Logo> </Properties> <Dependencies> <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" /> </Dependencies> <Resources> <Resource Language="x-generate"/> </Resources> <Applications> <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="Host.App"> <Extensions> <uap3:Extension Category="windows.appExtensionHost"> <uap3:AppExtensionHost> <uap3:Name>com.extensions.myhost</uap3:Name> </uap3:AppExtensionHost> </uap3:Extension> <uap:VisualElements DisplayName="Host" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Host" BackgroundColor="transparent"> <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/> <uap:SplashScreen Image="Assets\SplashScreen.png" /> </uap:VisualElements> </Application> </Applications> <Capabilities> <Capability Name="internetClient" /> </Capabilities> </Package>
Now we will write the code to search for extensions. In this example, I will not do UI, architecture, etc., but just do everything in one class. Of course, you shouldn’t do this in a real application, but for the sake of simplification you can
Go to MainPage.xaml.cs, delete everything and write the following:
using System; using System.Collections.Generic; using Windows.ApplicationModel.AppExtensions; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace Host { public sealed partial class MainPage : Page { public MainPage() { InitializeComponent(); Loaded += OnLoaded; } private async void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { AppExtensionCatalog catalog = AppExtensionCatalog.Open("com.extensions.myhost"); var extensions = new List<AppExtension>(await catalog.FindAllAsync()); } } }
Let's look at what happens in the OnLoaded method. First of all, we need to open the extension directory using AppExtensionCatalog.Open
. As an argument, we give him the name of the host, which was previously indicated in the manifest. After that, we get all the extensions in the directory. Since we do not have a user interface, it makes sense to put the breakpoint method at the end. Already you can run the application, you will see that we do not have extensions (which is logical). So let's write the first!
As an extension, we will create a simple calculator with 4 operations and 2 operands. Again we create an empty UWP application (the application itself), call it Calculator, go to the manifest and add the namespace (as in the host application). Now we’re looking for <Application>
again, but adding the code is different:
<Extensions> <uap3:Extension Category="windows.appExtension"> <uap3:AppExtension Name="com.extensions.myhost" PublicFolder="Public" Id="Calculator" DisplayName="Calculator" Description="This is a calculator"> <uap3:Properties> <Service>com.mycalculator.service</Service> </uap3:Properties> </uap3:AppExtension> </uap3:Extension> </Extensions>
Consider this declaration in more detail. To begin with, we are declaring an application extension. This ad has several options:
Next comes the thing like Properties. Here you can declare your specific parameters. In this case, we announce the name of our service (about it very soon).
The expansion framework is ready, you can test: choose Buld> Deploy Solution, start Host, and see our extension! Magic. Let's get him to do something now, no time to rest!
We will render the service in a separate project, because placing it in the same project as the extension application requires additional code modifications. We create a new project, only this time we need the Windows Runtime Component (the Class Library doesn’t fit to VERY great regret). We remove the Class1 we do not need and create the Service class we need. We will write it step by step.
Add the required using'i:
using System; using Windows.ApplicationModel.AppService; using Windows.ApplicationModel.Background; using Windows.Foundation.Collections;
Implement the IBackgroundTask interface in Service:
We will have an empty Run method like thispublic void Run(IBackgroundTaskInstance taskInstance) { }
Explanation: Any service is a background task. But background tasks apply not only to services. You can read more, for example, on MSDN
Create a field for deferral:private BackgroundTaskDeferral _deferral;
It is needed so that our task does not end suddenly.
Add the following code to Run:
_deferral = taskInstance.GetDeferral(); taskInstance.Canceled += TaskInstanceOnCanceled; var serviceDetails = (AppServiceTriggerDetails) taskInstance.TriggerDetails; AppServiceConnection connection = serviceDetails.AppServiceConnection; connection.ServiceClosed += ConnectionOnServiceClosed; connection.RequestReceived += ConnectionOnRequestReceived;
So first we assign our deferral background task to our deferral. Next, we add an event handler for the cancel task. This is necessary so that we can release our deferral and allow the task to complete. Then, we get information directly related to our service, and register two handlers. ServiceClosed is called when the source of a task call destroys the calling object. In RequestReceived, all the processing of the request will occur.
Create handlers for two events related to deferral release:
private void ConnectionOnServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) { _deferral?.Complete(); }
private void TaskInstanceOnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { _deferral?.Complete(); }
Write a method to perform the calculations.
private double Execute(string op, double left, double right) { switch (op) { case "+": return left + right; case "-": return left - right; case "*": return left*right; case "/": return left/right; default: return left; } }
There is nothing supernatural, the method takes an operator and two operands, returns the result of the calculations.
The most flesh. Writing a handler for RequestReceived:
private async void ConnectionOnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { var deferral = args.GetDeferral(); // defferal, AppServiceRequest request = args.Request; // var op = request.Message["Operator"].ToString(); // var left = (double) request.Message["Left"]; // var right = (double) request.Message["Right"]; // var result = Execute(op, left, right); // await request.SendResponseAsync(new ValueSet // { ["Result"] = result }); deferral.Complete(); // deferral }
We wrote our service, it's time to use it!
First, we need to announce our service. Go to the manifest of our extension, go to Extensions and write the following there:
<uap:Extension Category="windows.appService" EntryPoint="CalculatorService.Service"> <uap:AppService Name="com.mycalculator.service" /> </uap:Extension>
Here we declare the name of the service and the class in which it is implemented.
We add reference on CalculatorService to Calculator
Now we need to connect from the host to our service. Go back to MainPage.xaml.cs and add the code to our super method:
var calculator = extensions[0]; var serviceName = await GetServiceName(calculator); var packageFamilyName = calculator.Package.Id.FamilyName; await UseService(serviceName, packageFamilyName);
Here we get the name of the service and the name of the package family (and this and that will be needed to connect to the service) from the extension data.
GetServiceName method:
private async Task<string> GetServiceName(AppExtension calculator) { IPropertySet properties = await calculator.GetExtensionPropertiesAsync(); PropertySet serviceProperty = (PropertySet) properties["Service"]; return serviceProperty["#text"].ToString(); }
Here we extract the service name we specified earlier in the extension manifest, using some semblance of working with XML.
Now we will write the last method, which will finally start doing something specific:
private async Task UseService(string serviceName, string packageFamilyName) { var connection = new AppServiceConnection { AppServiceName = serviceName, PackageFamilyName = packageFamilyName }; // var message = new ValueSet { ["Operator"] = "+", ["Left"] = 2D, ["Right"] = 2D }; // var status = await connection.OpenAsync(); // using (connection) { if (status != AppServiceConnectionStatus.Success) // { return; } var response = await connection.SendMessageAsync(message); // if (response.Status == AppServiceResponseStatus.Success) { var result = (double) response.Message["Result"]; // } } }
Do not forget about the Build> Deploy solution.
And, if you did everything right, you should have this:
If it works out, congratulations - you wrote your full (relatively) modular application on UWP! (And if not, write in the comments)
AppExtensionCatalog has several events that you can use to monitor the states of extensions.
Here is a list of them:
Source: https://habr.com/ru/post/307116/
All Articles