Windows Store Apps–Sharing HTML with Images

One of the really powerful features of Windows Store apps is the system-provided ability to share data from one app to the other while keeping those 2 apps loosely coupled.

Basics

You can read up on it on the dev center ( under “Sharing and exchanging data” ) but the essence is;

  1. User asks to share from current app.
  2. Current app is asked to share data in as many formats as it can and/or as many as make sense. Formats include text, URI, HTML, RTF, files, etc.
  3. Other apps on the system are interrogated to see if they support the formats of data shared.
  4. User chooses which app to share to.
  5. Receiving app is activated, picks up the shared data and does something with it.

Step (3) is achieved by the application manifest for an app having a list of the formats of data that the app can receive.

My colleague Martin came up with a really great demo of this where he draws a picture in the FreshPaint app ( http://www.microsoft.com/en-us/freshpaint/default.html ) and then shares it over into the PuzzleTouch app which creates jigsaws. That is;

image

and then into PuzzleTouch;

image

and the data is passed from one app to the other and the PuzzleTouch does a nice job and creates a jigsaw from a picture it’s never seen before;

image

which then shows up inside the PuzzleTouch app (I did it a few times as I kept missing the screenshot button!);

image

Sharing Sample Apps

Like most (all?) Windows Store developer features, there are great samples around sharing of data. There’s a sample app that provides the source end of the sharing;

http://code.msdn.microsoft.com/windowsapps/Sharing-Content-Source-App-d9bffd84

and there’s the sample app that provides the target end of the sharing;

http://code.msdn.microsoft.com/windowsapps/Sharing-Content-Target-App-e2689782

At our UK developer camps ( http://www.microsoft.com/uk/msdn/windows8/appclinic.aspx ) we’ve always recommended having at least the share target installed while you are developing an app because it’s very open to lots of different data formats being shared into it which makes it a really useful tool for diagnosing what you’re sharing from your own app.

However…I got bitten by it a little today and that’s the purpose of this post.

Simple HTML Sharing

If you’ve watched video 3 of my flickR demo in .NET you’d have seen a simple implementation of sharing where I used code something like this to share some HTML;

      DataTransferManager transferManager = DataTransferManager.GetForCurrentView();

      transferManager.DataRequested += (s, e) =>
        {
          e.Request.Data.Properties.Title = "My Html";
          e.Request.Data.Properties.Description = "My Html";

          string html = "<div><p>this is some html</p><img src='https://mtaulty.com/blog/images/mtlogo.png'/></div>";

          e.Request.Data.SetHtmlFormat(
            HtmlFormatHelper.CreateHtmlFormat(html));

        };

and if I have this code somewhere in a simple, blank app then the DataRequested event fires when the user asks to share and the HTML is shared across to the target app. Here’s the C# share target sample receiving that HTML;

image

That’s all fine but then my image is on the web so it makes it relatively easy. What if my image wasn’t on the web? What if the image is coming from a file that the app has access to? This might be;

  1. A file in the app’s own package.
  2. A file in the app’s own folders (i.e. local/roaming/temp)
  3. A file that the app has access to because it prompted the user for it using a dialog ( possibly in a previous session and kept access by using the ‘access cache’ functionality )
  4. A file that the app has access to in the user’s libraries ( based on capabilities in the manifest )

and so on. To replicate that kind of idea, I decided to make my image come from a file in the application’s own local folder.

HTML with Images Sharing

In order to simulate this, I took that button that you can see on the screen above and put some code behind it which would prompt the user (me) for a file and then copy that file into my app’s local folder;

    async void OnChooseFile(object sender, RoutedEventArgs e)
    {
      FileOpenPicker p = new FileOpenPicker();

      p.FileTypeFilter.Add("*");

      var file = await p.PickSingleFileAsync();

      var keepFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("foo.png",
        CreationCollisionOption.ReplaceExisting);

      await file.CopyAndReplaceAsync(keepFile);

      this.localFilePath = keepFile.Path;
    }
    string localFilePath;

Note that localFilePath is a member variable which I use to keep hold of that file path. Note, that file path is likely to look like “c:\blah\blah\blah” which becomes important.

Now, when it comes to sharing HTML which has additional ‘content’ reference from inside of it (like images in my case) you have to apply a new technique. That technique is clearly documented but I must admit that I spent 3-4 hours trying to get this to work and hence my sharing it in this post.

Here’s the documentation that tells you how to go about this;

How to Share HTML

and the docs are good, nothing wrong with them and they lay out this idea of;

  1. You share your HTML.
  2. You identify any embedded additional content like images.
  3. You populate the DataPackage 
    1. This has a ResourceMap which is a dictionary of String to RandomAccessStreamReference which provides the receiving side of the operation with the additional content that it needs

Now, as far as I know this is not 100% automatic. You might share an HTML file with 3 images (img1, img2, img3) and the receiving side of the share has to look into the ResourceMap, pull out the 3 shared streams representing the files and then put them somewhere such that they will once again ‘make sense’ from the point of view of the HTML that it has received.

First Attempt – Not All Samples are Created Equal

My first attempt at this took me a long time. I wrote some code that looked something like this (it’s not going to work);

      DataTransferManager transferManager = DataTransferManager.GetForCurrentView();

      transferManager.DataRequested += (s, e) =>
        {
          e.Request.Data.Properties.Title = "My Html";
          e.Request.Data.Properties.Description = "My Html";

          // This code doesn't work.
          string myImageName = "anyName.png";

          string html = string.Format("<div><p>this is some html</p><img src='{0}'/></div>",
            myImageName);

          // localFilePath needs to be set by now by the user hitting the button and picking
          // a file to copy to localFilePath.
          e.Request.Data.ResourceMap[myImageName] = RandomAccessStreamReference.CreateFromUri(
            new Uri(this.localFilePath));

          e.Request.Data.SetHtmlFormat(
            HtmlFormatHelper.CreateHtmlFormat(html));

        };

and I spent a long time staring at this output from the C# Sharing Target Sample;

image

and while I can see that the resource called anyName.png is present in the resource map being sent across to the target, the picture doesn’t show up.

I spent a long time before I finally thought to unpick the sample where I learned that the C# Sharing Target Sample doesn’t have code which will ever display these kinds of images in the HTML passed to it this way – it’s not part of the sample.

That was lesson number 1. Lesson number 2 was to learn that the JavaScript version of that sample is different. Here it is below;

image

Ok, now it still isn’t working but any developer worth their salt knows that progress can often be achieved by moving from one type of failure to another type of failure Smile

Second Attempt – Not Every RandomAccessStreamReference is Equal

In trying to move beyond this error, I re-worked my code such that it got hold of the RandomAccessStreamReference in a different way;

      DataTransferManager transferManager = DataTransferManager.GetForCurrentView();

      transferManager.DataRequested += async (s, e) =>
        {
          DataRequestDeferral deferral = e.Request.GetDeferral();

          e.Request.Data.Properties.Title = "My Html";
          e.Request.Data.Properties.Description = "My Html";

          // This code doesn't work.
          string myImageName = "anyName.png";

          string html = string.Format("<div><p>this is some html</p><img src='{0}'/></div>",
            myImageName);

          // localFilePath needs to be set by now by the user hitting the button and picking
          // a file to copy to localFilePath.
          StorageFile file = await StorageFile.GetFileFromPathAsync(this.localFilePath);

          e.Request.Data.ResourceMap[myImageName] = RandomAccessStreamReference.CreateFromFile(
            file);

          e.Request.Data.SetHtmlFormat(
            HtmlFormatHelper.CreateHtmlFormat(html));

          deferral.Complete();

        };

To be honest, this was me poking around in an area known as “desperate – let’s try anything different to what I’m doing right now” and, as sometimes is the case, it worked. Here’s what shows up in the JS Sharing Target app;

image

You might have noticed that I had to go async to get this done and hence the call to GetDeferral() to get a deferral while I do async work.

Regardless – it worked!

Now, in my original scenario I have file paths kicking around and I don’t really have the actual files kicking around so I really wanted to use RandomAccessStreamReference.CreateFromUri() and to figure out why that didn’t work.

Third Attempt – Use the Right URIs

By this point, I’d asked some sanity-checking questions and got some help from some internal folks which led me to understand that the way that I had been using CreateFromUri() was not going to work.

Here’s my 3rd attempt which works fine;

      DataTransferManager transferManager = DataTransferManager.GetForCurrentView();

      transferManager.DataRequested += (s, e) =>
        {
          e.Request.Data.Properties.Title = "My Html";
          e.Request.Data.Properties.Description = "My Html";

          string myImageName = "anyName.png";

          string html = string.Format("<div><p>this is some html</p><img src='{0}'/></div>",
            myImageName);

          e.Request.Data.ResourceMap[myImageName] = RandomAccessStreamReference.CreateFromUri(
            new Uri("ms-appdata:///local/foo.png"));

          e.Request.Data.SetHtmlFormat(
            HtmlFormatHelper.CreateHtmlFormat(html));
        };

and what’s the big difference? My original code used URIs of the form c:\blah\blah to refer to file paths. This code uses URIs of the form ms-appdata:/// and that’s the difference here because the URI formats that can be used here include http, https, ms-appx and ms-appdata but do not include a file path like C:\ and that seems to be what was causing me the problem.

So…if I have a StorageFile then I can get a reference to it and share that way and if I can build up a path like ms-appx or ms-appdata then I can share that way but I can’t share using a ‘real’ file path like c:\.

Hope that helps you out if you landed on this page as a result of a web search in trying to diagnose your sharing of HTML and images Smile