Home > blogroll, consulting > Moving Files from XAP to Isolated Storage for Local HTML Content on Windows Phone 7

Moving Files from XAP to Isolated Storage for Local HTML Content on Windows Phone 7

For the past few months I’ve been working on porting The Shadow in the Cathedral to Windows Phone 7. Chris Cavanagh has been doing the bulk of the UI framework implementation while I’ve been working on adding all of the extras like help, hints, and other “nice touches”.

Adding documentation to a WP7 app is tricky. You can use PDF’s, but that seems shoddy to me. You can lay out tons of pages of large text for the user to page through in XAML, but this is painful and you don’t get the multi-touch and zoom capabilities by default.

Then you discover the WebBrowser control and you think you’re home free. Just make some web pages for your documentation and plug them into the phone application, right? Wrong. It’s not that simple quite yet. WP7’s SDK is still a work in progress and only a ton of usage will refine its design to a point where developers can do most things easily.

The WebBrowser control is currently meant for reaching the Internet. It has the capability of serving local content, but only from IsolatedStorage, not from content or resources in your XAP file. At least not yet. I’m not sure what the reasoning is behind this, but it is what it is.

So in order to serve your fancy html documentation, you need to move it to IsolatedStorage from your XAP file sometime when your application executes. I spent a little time this past week developing a couple of extension methods to help do exactly this.

    public static class ISExtensions
    {
        public static void CopyTextFile(this IsolatedStorageFile isf, string filename, bool replace = false)
        {
            if (!isf.FileExists(filename) || replace == true)
            {
                StreamReader stream = new StreamReader(TitleContainer.OpenStream(filename));
                IsolatedStorageFileStream outFile = isf.CreateFile(filename);

                string fileAsString = stream.ReadToEnd();
                byte[] fileBytes = System.Text.Encoding.UTF8.GetBytes(fileAsString);

                outFile.Write(fileBytes, 0, fileBytes.Length);

                stream.Close();
                outFile.Close();
            }
        }

        public static void CopyBinaryFile(this IsolatedStorageFile isf, string filename, bool replace = false)
        {
            if (!isf.FileExists(filename) || replace == true)
            {
                BinaryReader fileReader = new BinaryReader(TitleContainer.OpenStream(filename));
                IsolatedStorageFileStream outFile = isf.CreateFile(filename);

                bool eof = false;
                long fileLength = fileReader.BaseStream.Length;
                int writeLength = 512;
                while (!eof)
                {
                    if (fileLength < 512)
                    {
                        writeLength = Convert.ToInt32(fileLength);
                        outFile.Write(fileReader.ReadBytes(writeLength), 0, writeLength);
                    }
                    else
                    {
                        outFile.Write(fileReader.ReadBytes(writeLength), 0, writeLength);
                    }

                    fileLength = fileLength - 512;

                    if (fileLength <= 0) eof = true;
                }
                fileReader.Close();
                outFile.Close();
            }

        }
    }

The next step is to add all of your content (html, images, etc) to your project and set the Build Action on every file to Content.

Then execute steps to create your directories and copy your files.

            storageFile.CreateDirectory("HTDocs\\images");
            storageFile.CopyTextFile("HTDocs\\index.html", true);
            storageFile.CopyTextFile("HTDocs\\about.html", true);
            storageFile.CopyTextFile("HTDocs\\games.html", true);
            storageFile.CopyTextFile("HTDocs\\support.html", true);
            storageFile.CopyBinaryFile("HTDocs\\images\\image1.png", true);
            storageFile.CopyBinaryFile("HTDocs\\images\\image2.png", true);
            storageFile.CopyBinaryFile("HTDocs\\images\\image3.png", true);
            storageFile.CopyBinaryFile("HTDocs\\images\\image4.png", true);
            storageFile.CopyBinaryFile("HTDocs\\images\\image5.jpg", true);

The first command “CreateDirectory” is one of the existing methods in the IsolatedStorageFile object.

The CopyTextFile and CopyBinaryFile are the extension methods I’ve added to simplify moving Content files to IS.

In order to serve your content in a WP7 WebBrowser control, you simply set the Base and call the first page.

        private void webBrowser1_Loaded(object sender, RoutedEventArgs e)
        {
            webBrowser1.Base = "HTDocs";
            webBrowser1.Navigate(new Uri("index.html", UriKind.Relative));
        }

In your html, images and other files are relative to the Base. I have not tried JavaScript or CSS files, but I assume they will work the same as images.

In order to make your HTML compliant with the WP7 browser, make sure you have a DOCTYPE tag.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Also make sure you set the viewport in a meta tag, otherwise your HTML will shrink. I think the default viewport is 1000px wide and it’s just easier to set it to 460px wide and work from there. Make sure you manage your margins too.

    <meta name="viewport" content="width=460, user-scalable=no"/>

Now you have everything you need to develop HTML based local content in your Windows Phone 7 application.

Advertisements
  1. LL
    November 20, 2010 at 5:57 pm

    Thanks for the XAP->Isolated Storage code.

    Can you then delete the files from the phone in the XAP bundle and rely on IsoStore permanently?

    If so, how to delete these files during the first App run? I’ll have several hundred web pages in my XAP, so the space saving is not trivial!

    • November 20, 2010 at 7:38 pm

      I don’t think you can remove files from your xap…what you could do is leave the files on a server and pull them down to IS instead.

  2. vijay
    January 18, 2011 at 5:36 pm

    I am having the problem of showing images in local html help file. I would like to use your code.
    But I do not understand how to add the extensions and refer them.
    What does
    Then execute steps to create your directories and copy your files.
    mean?
    Can you give some help?
    Thanks.

    • January 18, 2011 at 6:01 pm

      As shown in the example…in your main startup code, you need to do the copy work.

      So the first set of code…just copy all of it and put it in a class file within your namespace. This will automatically add the two “extension” methods to the IsolatedStorage object.

      Then the second set of code is what you would put in your mail startup code. First create your directory structure (you don’t have to do each one…if you do the deepest folder, that will create the entire branch). Then use the other methods to copy text or binary files.

      I can see I missed one line of code. Place this line before the folder creation and copying.

      IsolatedStorageFile storageFile = IsolatedStorageFile.GetUserStoreForApplication();
      

      Let me know if you need more help.

  3. vijay
    January 18, 2011 at 6:27 pm

    Thanks for your prompt response.
    Excellent. Worked very well.
    I think this is a very useful thing to know.
    I would like to write an article in Codeproject based on this.
    I will definitely refer your blog in this article.
    Please let me know if it is OK.
    Thanks.

    • January 18, 2011 at 6:54 pm

      Go for it – the more people that know how to code for WP7, the better.

  4. March 3, 2011 at 12:10 am

    Dave,
    I have published the article “Windows Phone 7 Help and About files using HTML and Isolated Storage images” in CodeProject.
    The link is given below.
    http://www.codeproject.com/KB/windows-phone-7/WP7Help.aspx.
    I have referred your blog in the article which was a great inspiration for me.
    Thanks for the good work.

    • March 3, 2011 at 3:01 am

      Excellent! I love the article. Much more detail than my blog entry. Great work!

  5. Jerry
    March 11, 2011 at 2:13 pm

    I am having a problem with the code. It isn’t finding the “TitleContainer” class/variable. Is there some other code missing?

    • March 11, 2011 at 2:45 pm

      TitleContainer is in the XNA framework. You’re missing the using statement (and possibly a reference).

      using Microsoft.Xna.Framework;

      • Jerry
        March 11, 2011 at 2:57 pm

        Thanks for the fast reply.

        Unfortunately, that left me with more questions. A little background is in order, I guess. I am not working on a game, just a silverlight application that is using the web page as a “help file”. I ran across your post while searching for a solution on how to launch the help file when running “out of browser”. So, do you know of an equivilent substitute for the TitleContainer class? (or a different way to copy the file from the xap file to isolated storage?)

        Thanks beforehand!

      • Dan
        May 5, 2011 at 3:54 pm

        Jerry :
        Thanks for the fast reply.
        Unfortunately, that left me with more questions. A little background is in order, I guess. I am not working on a game, just a silverlight application that is using the web page as a “help file”. I ran across your post while searching for a solution on how to launch the help file when running “out of browser”. So, do you know of an equivilent substitute for the TitleContainer class? (or a different way to copy the file from the xap file to isolated storage?)
        Thanks beforehand!

        You want:
        Application.GetResourceStream(new Uri(filename, UriKind.Relative)).stream

        .GetResourceStream can return null which will cause an exception. It is often preferable to do something like:

        StreamResourceInfo sr = Application.GetResourceStream(new Uri(filename, UriKind.Relative));

        if (sr != null)
        {
        using (StreamReader stream = new StreamReader(sr.Stream))
        {
        // Code here
        }
        }

  6. May 6, 2011 at 12:37 am

    I am not working on a game, just a silverlight application

    My app wasn’t a “game” per se either. I do text games, not video or animation type games. Using TitleContainer is the way to go. Don’t worry about crossing streams with the XNA framework. This is the recommended solution from the MS people I’ve talked to.

    David

  7. David Maw
    June 19, 2011 at 11:28 pm

    Why bother with both a CopyTextFile and CopyBinaryFile – isn’t “CopyBinaryFile” sufficient for any file type?

    • June 20, 2011 at 12:45 am

      That may be true. I haven’t investigated which function is more efficient. It’s entirely possible that the CopyTextFile method does something more efficiently than the CopyBinary. When I wrote this, I was thinking that the mostly likely user of the code would be a novice or beginner developer and “thinking” in text and images would be helpful.

      I’m weird. I don’t always believe the “right” solution is what an experienced programmer would think. I try to get into the head of non-programmers so they can be more productive. They may become better programmers, at which point they will decide to do things differently.

      David

      • David Maw
        June 21, 2011 at 5:38 am

        Thanks for explaining, I was mostly just curious as to whether there was some nuance I was missing. As you surmise, I am a professional programmer, though new to the Windows phone platform and I noticed was that a whole text file has to fit into a memory buffer, and maybe get translated to Unicode then back to UTF8, but binary files were broken into 512 byte sectors and copied bit-for-bit.

        (another) David

  8. August 30, 2011 at 10:03 pm

    I’m trying to figure out this code for my app (i’m a webOS dev moving towards WP7) and when I copied Extensions code to my mainpage.cs file, I get errors at the “TitleContainer” part. It’s a underlined in red and says doesn’t exist in current context.

    Also, another error is the storagefile.x, I’m also getting the same error. Am I missing a using or something? Sorry, I’m new to the wp7 game, so I’m not exactly sure.

    • August 30, 2011 at 10:10 pm

      There was another commenter that had the same problem and I should update the code sample to reflect the solution. You need to reference the XNA library. It has the TitleContainer class. Not sure about the storage file.x issue.

    • Michael
      October 14, 2011 at 2:39 am

      I had this problem. Not sure if you were able to fix it, but I solved it by going to the Project Menu at the top of the screen, selecing Add Reference, and choosing the Microsoft.Xna.Framework from there. After that, it actually added the framework to my solution. storagefile.x is still not working for me, so if you have any insights you can share there, it would be helpful.

  9. October 27, 2012 at 8:46 pm

    How to handle update-installations? Keep in mind that when a user has already installed V1 of an app and the files are already in the IS. When updating to V2 then the files should be copied (overwritten) and maybe files have renamed and those should be removed from the IS in order to not mess up the IS.

    Always copying on app launch isn’t very good …

    Exists there an event “InstallingXap” or something than can be used? Since this operation is only needed when the XAP is being installed.

  1. August 10, 2013 at 4:35 pm

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: