The intention of this post is really to bring together what I did in this post;
Windows 10 Anniversary Update Preview and Application Extensions
where I did some ‘hello world’ type stuff with App Extensions on the Anniversary Update with what I did in this post;
Example of an App Providing a Photo Search Service to Another App
last year which was some ‘hello world’ type stuff with App Services.
Being a relatively good citizen, that latter post came with source code which I can download and update such that it moves from showing how I can have;
Client of an App Service calling into an App Service which provides a flickR search service
on to showing how I can now have;
Extensible app loads app extension which provides it with a flickR search service via an App Service
Now, my example is going to be a little ‘fake’ because I’m going to make my ‘extensible app’ such that it doesn’t work until it finds one (and only one) extension which allows it to search for images and that’s a bit fake because I think most people would argue that an app which doesn’t work until it gets an extension isn’t really an app
Moving past that slight ‘problem’, I downloaded the code from my post of last year which was a solution that contained 2 projects. One of which is the ‘client’ in that it consumes a service that searches for photos and the other is the ‘service’ in that it offers such an app service.
That code still runs, works on Anniversary Update (once I’d plugged in an API key for flickR) but it has a UI that I wanted to lose most of. Previously, the client asked the user to enter;
- Package name of the app that provides the App Service
- App Service name
- Search term for which to search for photos for.
I think if I change this code from just ‘raw’ App Services to making use of App Extensions, I can get rid of those first 2 pieces of information leaving just a search box and a button.
I’d also like to enable/disable the UI based on the presence of an available App Extension to do the searching and so the first thing I tried out was sketching that out as a custom StateTrigger…
Step 1 – Enabling/Disabling UI Based on the Presence of an Extension
I made 2 projects – one for the app to be extended and one for the app to do the extending;
For the ExtensibleApp I made sure that its manifest specified the extension that it was prepared to host;
<Extensions> <uap3:Extension xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" Category="windows.appExtensionHost"> <uap3:AppExtensionHost> <uap3:Name>PictureSearchExtension</uap3:Name> </uap3:AppExtensionHost> </uap3:Extension> </Extensions>
and for the PictureSearchExtensionApp I made sure its manifest specified the extension that it exposes;
<Extensions> <uap3:Extension xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" Category="windows.appExtension"> <uap3:AppExtension Name="PictureSearchExtension" Id="PictureSearchId" PublicFolder="PublicFolder" DisplayName="flickR picture search"> <uap3:Properties> <ServiceName>flickRSearchService</ServiceName> </uap3:Properties> </uap3:AppExtension> </uap3:Extension> </Extensions>
I then sketched out a quick StateTrigger with the idea being that it can control visual states depending on whether an App Extension of a given name is present on the system. I’m not sure I have this 100% right and it certainly could be a lot more functional but I didn’t spend too much time on it, largely just moving code into it from my post of yesterday;
public class ExtensionTrigger : StateTriggerBase { public ExtensionTrigger() { this.SetActive(false); } async Task SetActiveBasedOnExtensionStatusAsync() { bool active = false; if (this.IsNamingSpecified) { if (!this.IsCatalogOpen) { this.catalog = AppExtensionCatalog.Open(this.ExtensionName); this.catalog.PackageInstalled += OnPackagedInstalled; this.catalog.PackageUninstalling += OnPackagedUninstalling; } active = await this.FindExtensionInCatalog(); } this.SetActive(active); } async Task<bool> FindExtensionInCatalog() { bool found = false; var extensions = await this.catalog.FindAllAsync(); var myExtension = extensions.FirstOrDefault(); if (myExtension != null) { this.Extension = myExtension; found = true; } return (found); } async void OnPackagedUninstalling(AppExtensionCatalog sender, AppExtensionPackageUninstallingEventArgs args) { await this.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () => { this.SetActive(false); } ); } async void OnPackagedInstalled(AppExtensionCatalog sender, AppExtensionPackageInstalledEventArgs args) { await this.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, async () => { await this.SetActiveBasedOnExtensionStatusAsync(); } ); } public string ExtensionName { get { return (this.extensionName); } set { if (!string.IsNullOrEmpty(this.extensionName)) { throw new InvalidOperationException("Not considered changing this value yet"); } this.extensionName = value; this.SetActiveBasedOnExtensionStatusAsync(); } } string extensionName; public AppExtension Extension { get; set; } bool IsCatalogOpen => this.catalog != null; bool IsNamingSpecified => !string.IsNullOrEmpty(this.ExtensionName); AppExtensionCatalog catalog; }
and then took ‘the UI’ from my previous blog post around app services and made that the UI of my ExtensibleApp’s one and only page as below;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="uiStates"> <VisualState x:Name="_default" /> <VisualState x:Name="extensionPresent"> <VisualState.StateTriggers> <local:ExtensionTrigger ExtensionName="PictureSearchExtension" x:Name="extensionTrigger" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="txtNoExtension.Visibility" Value="Collapsed" /> <Setter Target="stackExtension.Visibility" Value="Visible" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.Resources> <Style TargetType="TextBox"> <Setter Property="FontSize" Value="24" /> <Setter Property="FontFamily" Value="Segoe Semilight" /> <Setter Property="Margin" Value="0,0,0,10" /> </Style> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="24" /> <Setter Property="FontFamily" Value="Segoe Semilight" /> <Setter Property="TextAlignment" Value="Center" /> <Setter Property="Margin" Value="0,0,0,10" /> </Style> </Grid.Resources> <Image Stretch="UniformToFill" x:Name="image" /> <StackPanel x:Name="stackExtension" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20" Visibility="Collapsed"> <TextBox Header="Photo query" x:Name="txtQuery" Text="beach" Margin="0,10,0,0" /> <Button Content="Call Other Application" Click="OnSearch" FontSize="24" HorizontalAlignment="Center" Margin="0,10,0,0" /> </StackPanel> <TextBlock Text="No Search Extension" x:Name="txtNoExtension" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </Grid>
And so the idea here is that the ExtensionTrigger switches the UI between this state for when no App Extension is available to do image searching;
into this state when an App Extension is available;
and that’s dynamic in that it switches as the extension app is installed/removed on the system.
Step 2 – Making a Call to the App Service
I lifted the code from my previous blog post in order to make a call to the ‘extension’ app service which is going to search for photos for me as below;
async void OnSearch(object sender, RoutedEventArgs e) { AppServiceConnection connection = new AppServiceConnection(); // Reach into the properties of the extension. var extensionProperties = await this.extensionTrigger.Extension.GetExtensionPropertiesAsync(); // We (recklessly) assume that there is one in there called ServiceName which should, naturally // be in a constant. connection.AppServiceName = (string)((PropertySet)extensionProperties["ServiceName"])["#text"]; // We also need to specify the package family name connection.PackageFamilyName = this.extensionTrigger.Extension.Package.Id.FamilyName; // Now we can open the connection - code from here is lifted from last year's blog // post. var status = await connection.OpenAsync(); if (status == AppServiceConnectionStatus.Success) { ValueSet parameters = new ValueSet(); parameters["query"] = this.txtQuery.Text; var response = await connection.SendMessageAsync(parameters); if (response.Status == AppServiceResponseStatus.Success) { var token = response.Message["pictureToken"] as string; if (!string.IsNullOrEmpty(token)) { var file = await SharedStorageAccessManager.RedeemTokenForFileAsync((string)token); BitmapImage bitmap = new BitmapImage(); using (var fileStream = await file.OpenReadAsync()) { await bitmap.SetSourceAsync(fileStream); } this.image.Source = bitmap; } } connection.Dispose(); } }
This code differs only from the code I posted last year in that it no longer needs to get hold of a package family name or an app service name from the UI in order to open up an AppServiceConnection.
Instead, both of those details are obtained from the AppExtension that we have loaded – one is from the Package itself and the other is from the metadata Properties that I specified in the manifest for the PictureSearchExtensionApp project.
Step 3 – An App Service in Single Process Execution Mode
In my blog post from last year, I implemented the App Service as a background task so I thought I’d see whether I could move that demo code into an in-process model given that this is something that the Anniversary Update enables.
I made sure that my manifest for my PictureSearchExtensionApp specified that it offered an app service;
and then I overrode the App.OnBackgroundActivated class and took the code from the background task in my previous blog post and dropped it into that override. I’m not sure that this is a great implementation or anything, it was just what I already had. This is what I added to the boilerplate App class generated by Visual Studio’s blank project template;
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) { this.deferral = args.TaskInstance.GetDeferral(); args.TaskInstance.Canceled += OnCancelled; var appService = args.TaskInstance.TriggerDetails as AppServiceTriggerDetails; if (appService != null) { // we should do more validation here if (appService.Name == "flickRSearchService") { appService.AppServiceConnection.RequestReceived += OnAppServiceRequestReceived; } } else { this.deferral.Complete(); this.deferral = null; } } async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { var localDeferral = args.GetDeferral(); var parameters = args.Request.Message; ValueSet vs = new ValueSet(); if (parameters.ContainsKey("query")) { var result = await FlickrSearcher.SearchAsync((string)parameters["query"]); if ((result != null) && (result.Count > 0)) { var first = result[0]; HttpClient client = new HttpClient(); using (var response = await client.GetAsync(new Uri(first.ImageUrl))) { var file = await ApplicationData.Current.TemporaryFolder.CreateFileAsync(first.Id.ToString(), CreationCollisionOption.ReplaceExisting); using (var fileStream = await file.OpenStreamForWriteAsync()) { await response.Content.CopyToAsync(fileStream); } var token = SharedStorageAccessManager.AddFile(file); vs["pictureToken"] = token; } } } await args.Request.SendResponseAsync(vs); localDeferral.Complete(); } void OnCancelled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { this.deferral.Complete(); } BackgroundTaskDeferral deferral;
and the only change here is to make this run in process, otherwise the code is unchanged from the earlier example I posted.
Step 4 – Try it Out
I used two copies of Visual Studio to debug this but I have a problem making it into a screen capture right now so I used static screenshots below.
Initially, there’s no extension deployed so the UI says so…
I deploy the extension application from VS;
The UI updates as it can now search for pictures using the app service offered by the extension;
It brings back a picture based on the search term;
I undeploy the extension app;
The UI reverts back to show that there is no extension available although the colouring of the image here makes that tricky to see;
Step 5 – Share It
The code for this is very rough/ready but if you wanted it then feel free to grab it from here. The only thing I’d say is that you’ll need an API key for flickR to make the code compile.
Other Thoughts
I still worry that there’s quite a bit of ‘shared’ information between the application that is extensible and the application that is providing an extension. For instance, both have to agree on;
- the name of the extension
- the property set that describes the extension
and then if the extension is providing an App Service like mine here then they also have to agree on how the data which flows across that boundary is to be represented.
I guess it’s easy enough to write that sort of information on a website somewhere and publish it but there’s a part of me wishes that it could be described by a strongly typed interface such that there was less room for error but maybe this is just me harking back to my earlier days of WCF, .NET Remoting, COM, DCE, RPC, etc
Regardless, I like this way in which App Extensions can act as a ‘broker’ for extending one application with App Services offered by another.
Hi there. I found your post on Intel RealSense camera SR300 and I just bought one recently. I have exactly the same problem as what you faced in this post (https://mtaulty.com/2016/05/01/windows-10-realsense-sr300-person-tracking/) where the Camera Explorer says the camera malfunctioned and the virtual driver keeps connect and disconnect automatically when the explorer wanted to access it. Have you got a solution for it to work? I am trying to setup Windows Hello with it but unsuccessful. I am currently running the latest Windows 10 anniversary update and 6th Gen Intel processor.
Hi,
No, I’ve kind of given up a little on my SR300 camera. I find it to be unreliable and I find the current SDK for it to be unreliable too.
The most success I’ve had is with it plugged directly into a Surface Pro 3. I struggled with it plugged into other machines and it kept going on/off.
I hope yours works out better than mine (let me know if you find a solution) but, for now, I’m not sure that the SR300 was a big leap forward over the existing F200 camera.
Mike
Hi. I have managed to get the SR300 work with Windows Hello and no random disconnections by connecting the camera to a 4-port USB 3.0 hub with external power supply. I think the problem is with the current from the port on the laptop does not provide enough current to the camera and that is why it disconnects when it starts scanning and then reconnects.