Xap Reflector – Silverlight 4

I’ve always wanted to write a program that ‘opens-up’ a .xap file and go through its contents, kinda like Red-Gates’ Reflector, only that this one does it for .xap files… this marks the birth of a XapReflector. Here are the features of the application:

  • An OOB Silverlight application running with elevated permissions (can also run as a non-OOB, but will have a few limitations)
  • Reads the contents of a .xap file - either browse to a local .xap file or point to a .xap file located on a server… any server (when running OOB).
  • Grabs the entry point assembly (the main .dll of the silverlight application) from the .xap file
  • Displays the fully qualified name of the assembly
  • Lists out all the ‘types’ (classes, interfaces, enums… etc) contained in the assembly
  • Displays public constructors, properties and methods of these types
  • Shows the runtime version against which the application was built
  • Automatically copies the .xap file to users’ My Documents folder if the .xap file comes from a network location
  • Just click on the ‘Use WebClient’ button (with the textbox being empty) to see the details of the XapReflector.xap itself

Wow, even I’m surprised. My app does ALL this? Ok here’s a screenshot of the application:

screen1

Ok, so you can either choose the .xap file sitting somewhere on your machine (Browse Local button – brings up the OpenFileDialog window) or you can provide the network path of the .xap file (and press the Use WebClient button). Once the processing is complete, the fully qualified assembly name, along with the runtime version that it was built against get displayed. The ‘Types’ combo box has a lists of all the ‘types’ in the assembly. Choose one of them and the public constructors, properties and methods of the type get populated. As mentioned above, when you go the ‘Use WebClient’ way, a copy of the .xap file gets copied to user’s My Documents folder under the same file name.

I believe, you have, at some point or other, had a peek into the .xap file. For those who haven’t here’s a summary. The .xap file is basically a .zip file that you can rename and extract its contents from. You’ll see one or more assemblies along with a AppManifest.xaml file. This is the file that tells the Silverlight runtime how to use the assemblies in it and it also has some config info. Here’s the AppManifest.xaml file of the XapReflector:

   1:  <Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" 
   2:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   3:              EntryPointAssembly="XapReflector" 
   4:              EntryPointType="XapReflector.App" 
   5:              RuntimeVersion="4.0.41108.0">
   6:    <Deployment.OutOfBrowserSettings>
   7:      <OutOfBrowserSettings ShortName="XapReflector" 
   8:                            EnableGPUAcceleration="False" 
   9:                            ShowInstallMenuItem="True">
  10:        <OutOfBrowserSettings.Blurb>
  11:            XapReflector on your desktop; at home, at work or on the go.
  12:        </OutOfBrowserSettings.Blurb>
  13:        <OutOfBrowserSettings.WindowSettings>
  14:          <WindowSettings Title="XapReflector" />
  15:        </OutOfBrowserSettings.WindowSettings>
  16:        <OutOfBrowserSettings.SecuritySettings>
  17:          <SecuritySettings ElevatedPermissions="Required" />
  18:        </OutOfBrowserSettings.SecuritySettings>
  19:        <OutOfBrowserSettings.Icons />
  20:      </OutOfBrowserSettings>
  21:    </Deployment.OutOfBrowserSettings>
  22:    <Deployment.Parts>
  23:      <AssemblyPart x:Name="XapReflector" Source="XapReflector.dll" />
  24:      <AssemblyPart x:Name="System.Windows.Controls" Source="System.Windows.Controls.dll" />
  25:      <AssemblyPart x:Name="System.Xml.Linq" Source="System.Xml.Linq.dll" />
  26:    </Deployment.Parts>
  27:  </Deployment>

The EntryPointAssembly attribute (line 3) in the Deployment tag gets matched to the x:Name attribute in the AssemblyPart tags (line 23)and the Silverlight  application’s dll is retrieved. The Runtime then starts by instantiating the type in the EntryPointType attribute (line 4) and goes about running the application - that’s in a nutshell. The ‘OutOfBrowser’ tags are used for, well, Out-of-Browser settings… duh! Line 5 is where the application reads the runtime version from and displays it on the screen.

Let’s dive into some code now. The heart of the application is the ‘DecipherXapFile’ method.

   1:  private void DecipherXapFile(StreamResourceInfo xapFile)
   2:  {
   3:      try
   4:      {
   5:          // read the AppManifest.xaml file from the stream
   6:          StreamResourceInfo appManifest = Application.GetResourceStream
   7:     (
   8:                                               xapFile, 
   9:     new Uri("AppManifest.xaml", UriKind.Relative)
  10:                                           );
  11:   
  12:          StreamReader reader = new StreamReader(appManifest.Stream);
  13:   
  14:          // parse the entries of the AppManifest.xaml file
  15:          XDocument document = XDocument.Load(reader);
  16:   
  17:          // get the EntryPointAssembly
  18:          string entryPointAssemblyName = document.Root.Attribute("EntryPointAssembly").Value;
  19:   
  20:          // and the runtimeVersion
  21:          string runtimeVersion = document.Root.Attribute("RuntimeVersion").Value;
  22:   
  23:          // get the source assembly of the Silverlight application
  24:          string entryPointAssemblySource = GetEntryPointAssemblySource
  25:                                            (
  26:                                              document, 
  27:                                              entryPointAssemblyName
  28:                                            );
  29:   
  30:          StreamResourceInfo assemblyPartStream = Application.GetResourceStream(xapFile,
  31:                                                  new Uri(entryPointAssemblySource, 
  32:                                                  UriKind.Relative));
  33:          AssemblyPart assemblyPart = new AssemblyPart();
  34:          Assembly assembly = assemblyPart.Load(assemblyPartStream.Stream);
  35:   
  36:          // get assemblies' full name and display it
  37:          AssemblyNameBlock.Text = assembly.FullName;
  38:          RuntimeVersionBlock.Text = runtimeVersion;
  39:   
  40:          // load each type in the ReflectedType list
  41:          foreach (Type type in assembly.GetTypes())
  42:          {
  43:              ReflectedType reflectedType = new ReflectedType
  44:              {
  45:                  Name = type.Name,
  46:                  Methods = type.GetMethods(),
  47:                  Properties = type.GetProperties(),
  48:                  Constructors = type.GetConstructors(),
  49:              };
  50:              reflectedTypeList.Add(reflectedType);
  51:          }
  52:          ClassComboBox.ItemsSource = reflectedTypeList;
  53:          ShowAssemblyNameAndTypesList(true);
  54:      }
  55:      catch (Exception)
  56:      {
  57:          MessageBox.Show("Unable to show the details as an error has occurred");
  58:      }
  59:  }

The ReflectedType class has the following definition:

   1:  public class ReflectedType
   2:  {
   3:      public string Name { get; set; }
   4:      public ConstructorInfo[] Constructors { get; set; }
   5:      public PropertyInfo[] Properties { get; set; }
   6:      public MethodInfo[] Methods { get; set; }
   7:  }
The DecipherXapFile is what I consider to be the core part of the application. Initially, I did not have the ‘auto-file-save’ feature in my mind and then I bumped into Michael Wolf’s page. A small vid on his page made me go ‘Holy cow’. I’ll tell you why in just a min.
 
   1:  // automatically save a copy of the .xap file to user's my documents folder
   2:  // courtesy: Michael Wolf
   3:  try
   4:  {
   5:      if (Application.Current.IsRunningOutOfBrowser)
   6:      {
   7:          string filePath = string.Empty;
   8:          if (XapWebAddressBox.Text.Length == 0)
   9:          {
  10:              // this means the user clicked on the 'Use WebClient' button
  11:              // without entering anything int he textbox
  12:              // by default the WebClient downloads the current application's .xap file
  13:              filePath = string.Format("{0}\\XapReflector.xap",
  14:                                              Environment.GetFolderPath(
  15:                                              Environment.SpecialFolder.MyDocuments));
  16:          }
  17:          else
  18:          {
  19:              filePath = string.Format("{0}\\{1}",
  20:                                              Environment.GetFolderPath(
  21:                                              Environment.SpecialFolder.MyDocuments),
  22:                                              GetFileNameFromAddress());
  23:          }
  24:          // this overwrites even if the file already exists
  25:          Stream st = e.Result;
  26:          Byte[] bytes = new byte[st.Length];
  27:          st.Read(bytes, 0, Convert.ToInt32(st.Length));
  28:          FileStream file = File.Create(filePath);
  29:          file.Write(bytes, 0, Convert.ToInt32(st.Length));
  30:          file.Close();
  31:      }
  32:  }
  33:  catch (Exception)
  34:  {
  35:      MessageBox.Show("Unable to save xap file to My Documents folder");
  36:  }

I’ve modified the code slightly. First, I check if the app is running OOB and then if the user has clicked the ‘Use WebClient’ button without adding a network path, I set the file name to be ‘XapReflector.xap’, else it get read from the file path passed. Michael’s code also overwrites the file even if it already exists.

Just to recall one of the features of Silverlight 4, in Tim Heuer’s own words, is:

One of the more significant changes to cross-domain networking comes when you have a trusted application.  For services that have a closed cross-domain policy file (via clientaccesspolicy.xml or crossdomain.xml), if your application is a trusted application (elevated permissions), then the requirement for a cross-domain policy file is no longer required.

This only applies to trusted applications.

So, since you have this application running as OOB with elevated permissions, you are able to access network resources even in the absence of the clientaccesspolicy.xml and the crossdomain.xml files. This means, your app can literally grab ANY Silverlight .xap from a network location and you can run it inside your application. See how Tim Heuer’s is doing something similar it here. This is the first reason for my ‘holy cow’ expression.

The second reason is the easy with which I can copy something onto some user’s machine using the code above and the user will have absolutely no clue this is going on.

The third reason, is that you can even run the downloaded application as seen in Michael’s vid. Seriously, all this made me go ‘Holy cow’. I’m one of those developers who thinks, it’s gone too far when a ‘silly computer program’ starts doing things ‘automatically’. This is one such thing. All this means is that:

We need to take the install screen SERIOUSLY:

screen2

That’s it from me. Do let me know if you think there’s something that I could do to make this app more useful.

Disclaimer: The app is given without any guarantees. So please go through the article/code before you download/run it.

2 Comments

Comments have been disabled for this content.