donderdag 14 april 2011

Extracting the URI of a page in silverlight

This blogpost that I’m writing is a prequel to another blogpost about best practices in navigation in MVVM.  
I came upon the idea the other day by extracting the URI of a page by its location in the assembly.  This seemed easier than I thought and if anyone knows of a simpeler solution then I implore you to post it.

I searched for any quick fixes like some magic property on the page that would tell me where the XAML is stored inside the assembly, but no luck.  I even decompiled a few pages to see what’s in the InitialiseComponent  and I was deeply surprised that MS has opted for such a weak implementation.  This is what I found:

  Application.LoadComponent(this, new Uri("/MyAssembly;component/Views/MyPage.xaml", UriKind.Relative));

So by judging at how the XAML page is loaded, there is no property or wrapper class that will tell me where the page is stored.

Then while I was browsing the assembly, I noticed that in the assembly.g.resources file is a listing of all the resources in the assembly (hence the name…).   I saw that not only embedded resources are there but also the XAML of your pages.  It is structured by a dictionary that contains a key/value pair of string / memorystream.  So I thought, this should be easy then, I can just read the resourcemanifest and deserialize it …   Microsoft wouldn’t be Microsoft if they didn’t make all the classes you need to do this internal.  So I had to manually get out the pages from the binary data.  Enough gibberish, here is the code:

public class PageUriLocator
{
    public static string GetUri<T>() where T : Page
    {
        var typeName = typeof(T).Name;

        var allXamlFiles = ReadAssemblyResource();

        var fileName = new Regex(@"[[\w]*/]?" + typeName.ToLower() + @"\.xaml");
        return (allXamlFiles.Where(xamlFile => fileName.IsMatch(xamlFile.ToLower()))
                            .Select(xamlFile => xamlFile))
                            .FirstOrDefault();
    }

    private static List<string> ReadAssemblyResource()
    {
        byte[] binaryData = GetResourceInByteArray();
        var fileNames = new List<string>();

        // the first x bytes of the resource stream we are not interested in
        bool start = false;
        for (int i = 0; i < binaryData.Length; i++)
        {
            if (i < binaryData.Length - 255 && i > 0)
            {
                // the first file in the resource stream is app.xaml => it is 00 00 10 in size
                start = start | (binaryData[i] == 0x00 && binaryData[i + 1] == 0x00 && binaryData[i + 2] == 0x10);
                if (start)
                {
                    int length = binaryData[i + 1] + binaryData[i + 2]; // the length is described in the two latter bytes of the 5 stopbytes
                    if (length > 255) return fileNames; // name cannot be longer then 255 characters

                    // assemble the filename
                    string fileName = "";

                    for (int j = 0; j < length; j += 2)
                    {
                        fileName += Convert.ToChar(binaryData[j + i + 3]);
                    }

                    fileNames.Add(fileName);
                    i += length + 4;
                }
            }
        }

        return fileNames;
    }

    private static byte[] GetResourceInByteArray()
    {
        // get the assembly name
        var assemblyName = Assembly.GetExecutingAssembly().FullName.Split(',')[0];

        // a list of resources is saved in an embedded resource file called [AssemblyName].g.resources
        var resourceName = assemblyName + ".g.resources";

        // read binary data from the stream
        var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
        var streamReader = new BinaryReader(resourceStream);
        return streamReader.ReadBytes((int)resourceStream.Length);
    }
}

And the usage:


string whereIsThatDamnPage = PageUriLocator.GetUri<MainPage>();

Have fun with it and till next time.

1 opmerking:

  1. We've patched this a little bit, because in some Files, after the first 0x00,0x00,0x10 there is not not app.xaml, no there is other data. So we search for app.xaml!

    here is our modified Version:

    https://gist.github.com/jogibear9988/5994727

    BeantwoordenVerwijderen