Still looking for a sponsor Max Paulousky is looking for a Silverlight/.Net job in the Commonwealth

Share to Facebook Tweet this! Share to MySpace Share to Google Share to Live   Share via AddThis

XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

Abstract

I work at Silverlight applications every day and do releases every week or two. Some of them I publish on my blog other are published on other web resources. As a rule, I use MVVM as a basic pattern for Silverlight applications to divide them on logical parts, pack into XAP files and load on demand.

XAP file is a logical item in any Silverlight application and it contains assemblies and app manifest.

During the work I have noticed majority of XAP files contain duplicated assemblies. For example, if I use Prism implementation of the MVVM pattern, all Prism assemblies will be in every XAP file – it is about plus 300 kb to each XAP module. Usually, an application contains about 4-5 XAP modules and you can calculate how much traffic is wasted. Also, some additional assemblies (UI controls, first of all) could be added to XAP file.

All these facts mean we have to start looking for some way out to reduce size of XAP files and size of Silverlight applications as a result.

The Idea

I was working at the Modularity in Silverlight applications post and bumped into the Jeff Prosise's blog post - Cool Silverlight Trick #3. He mentioned, that an assembly can be added to a project but it may not be embedded into XAP file. Developers should just change the CopyLocal option in assembly properties in Visual Studio from True to False. So, a project had a reference to an assembly but that assembly was not in the XAP file. That was what I needed.

I have started thinking further and found out I can add all duplicated assemblies as references to the main (shell/startup/host) project (if they are not there). Added assemblies would be placed only once within all XAPs. Once the main XAP is starting, it loads all assemblies from the AppManifest.xaml file (just extract files from any XAP to find it) into the Application Domain. So, added assemblies will be in memory earlier then other XAP files will be even downloaded.

It shows how the solution will be updated if I change it according to the XAP minifying algorithm

The picture above demonstrates how the solution will be updated if I change it according to the mentioned algorithm. Let’s imagine, each project is a separate XAP file. Project 1 and Project 2 contains the same pair of assemblies (Assembly 1 and Assembly 2) and I set the CopyLocal property to false for them to exclude them from Project1.xap and Project2.xap (excluded assemblies are grayed). Assembly 1 is in the Main Project but not Assembly 2.  So, I add it (Assembly 1 marked as bold on the right side of the picture).

Project 2 contains Assembly 4 that is in the Main Project already. So, I just exclude it.

Project 3 contains unique assemblies so, I leave it as is.

Let’s summarize number of assemblies in each XAP file.

Project Non-Optimized Optimized
Project 1 4 2
Project 2 4 1
Project 3 3 3
Main Project 3 4
Totals 14 10

 

It is obvious optimized solution will have smaller size then non-optimized. The only disadvantage is the Main Project has been increased for one assembly (Assembly 2).

I think it is boring to update solutions with these changes manually. So, I have decided to implement an add-on for Visual Studio 2010 to automate the process.

Add-on for Visual Studio 2010

First of all, I have to install VS2010 SDK to be able to create VS add-ons, packages etc. The first way to do it is to go to the Microsoft’s web site, download it and install. The other way is to open VS2010, go to main menu-Tools-Extension Manager, click Online Gallery, find Visual Studio 2010 SDK in the list of extensions (usually it is on the first page) and install it.

After installing, I create a new project for add-on (menu File-New-Project, select Installed Template section Visual C#-Extensibility-Visual Studio Package):

New Project dialog with selected Visual Studio Packege project

There are several predefined files in the created project:

  • VsPkg.cs. This file contains entry point to the add-on;
  • %ProjectName%.vsct It contains definition of the menu items, where the add-on can be executed.
  • Key.snk The key file. Need to be able to sign the assembly.
  • source.extension.vsixmanifest. Package manifest. Contains most properties of the package.

I start editing the manifest:

The manifest of the VS Package

I set a package name (how the project will be named in the Online Gallery), names of authors, version and description fields.

The Supported VS Edition allows selecting various edition of Visual Studio, like Ultimate, Premium etc. I select all of them except Express editions.

License Terms allows selecting a text file that contains some licensing information. Icon and Preview Image are used in the Online Gallery. I use 32x32 image as an icon and 202x202 one as a preview image.

I have created a folder Resources and store license and images there. The VS manifest wizard does not support using resources outside of the root folder of the project, so after selecting resources, I open source.extension.vsixmanifest file in a text editor and add a folder name to each resource to get correct reference.

More Info URL is a link to my home page and Getting Started Guide is a link to this article. The Supported Framework Runtime option allows selecting minimum and maximum versions of .Net framework. I have typed 3.5 and 4.0 accordingly.

Implementation

I use Interfaces from VS2010 SDK to get access to all parts of the solution (projects, list of references, properties of a project etc ) and change them.

The entry point (XapsMinifier.Package.cs) implements IServiceProvider interface that can get access to the most of objects within a Visual Studio, e.g.:

IServiceProvider provider = xapsMinifierPackage as IServiceProvider;
IVsSolution solution = provider.GetService(typeof(IVsSolution)) as IVsSolution;

The algorithm for excluding duplicated assemblies is following:

Algorithm to get duplicated assemblies to exclude from XAP files

1. Get all silverlight projects from the solution. Method ProjectTypeGuids returns types of enhancements (Silverlight, C#, VB#, Web application, MVC application etc) for the given project.

public IEnumerable<IVsHierarchy> GetProjects(__VSENUMPROJFLAGS flags = __VSENUMPROJFLAGS.EPF_ALLPROJECTS, Guid onlyThisTypeGuid = new Guid())
{
  IEnumHierarchies enumHierarchies;
 
  ErrorHandler.ThrowOnFailure(solution.GetProjectEnum((uint)flags, ref onlyThisTypeGuid, out enumHierarchies));
  uint fetched = 1;
  IVsHierarchy[] hierarchies = new IVsHierarchy[10];
  while (fetched > 0)
  {
    ErrorHandler.ThrowOnFailure(enumHierarchies.Next(10, hierarchies, out fetched));
    for (int n = 0; n < fetched; ++n)
      yield return hierarchies[n];
  }
  yield break;
}
 
public static bool IsSilverlightProject(Project project, IVsSolution solution)
{
  IEnumerable<string> guids = ProjectTypeGuids(project, solution);
  return guids.Count(g => g.Equals(Defines.ProjectTypeGuid.Silverlight, StringComparison.OrdinalIgnoreCase)) > 0;
}
 
private static IEnumerable<string> ProjectTypeGuids(Project project, IVsSolution solution)
{
  string projectTypeGuids = string.Empty;
  IVsHierarchy hierarchy = null;
  int res = solution.GetProjectOfUniqueName(project.UniqueName, out hierarchy);
  if (res == 0)
  {
    IVsAggregatableProject aggregatableProject = (IVsAggregatableProject)hierarchy;
    ErrorHandler.ThrowOnFailure(aggregatableProject.GetAggregateProjectTypeGuids(out projectTypeGuids));
  }
 
  if (string.IsNullOrWhiteSpace(projectTypeGuids))
    return Enumerable.Empty<string>();
 
  return projectTypeGuids.Split(';').ToList();
}

2. Find out a startup project. I get properties of each project and start looking for the SilverlightProject.AppEntryObject key. It should not be empty.

Startup project must be only on in the solution. Otherwise, the add-on will throw an exception.

private bool IsStartup(VSProject project)
{
  Guard.ArgumentNotNull(project, "project");
 
  foreach (EnvDTE.Properties properties in new EnvDTE.Properties[] { project.Project.Properties, project.Project.ConfigurationManager.ActiveConfiguration.Properties })
  {
    if (properties.Cast<Property>().Any(p => p.Name == "SilverlightProject.AppEntryObject" && !string.IsNullOrWhiteSpace(p.Value)))
      return true;
  }
  return false;
}

3. I get all referenced assemblies. I iterate all projects except main one, and iterate all references within a project except ones with CopyLocal == false (e.g. assemblies from GAC).

private static Dictionary<string, List<Reference>> GetProjectsWithReferences(VSProject mainProject, List<VSProject> projectsToClean)
{
  Dictionary<string, List<Reference>> result = new Dictionary<string, List<Reference>>();
  foreach (VSProject project in projectsToClean)
  {
    if (project == mainProject)
      continue;
 
    foreach (Reference reference in project.References)
    {
      if (!reference.CopyLocal) //library in the GAC
        continue;
 
      string referenceID = reference.Identity;
      if (!result.ContainsKey(referenceID))
        result.Add(referenceID, new List<Reference>());
      result[referenceID].Add(reference);
    }
  }
  return result;
}

4. Iterate all references, check the main project on existence of the reference, add a reference to the main project, set CopyLocal=True. See ProcessReferences method in XapsMinifier.Infrastructure.ReferenceProcessor.cs

Visualization

The add-on does not have graphical UI and a user will not be able to see the progress of the add-on. So, I have decided to use built-in in Visual Studio abilities to display progress. I use three things:

  • Progress bar on the Status Bar;
  • Animation on the Status Bar;
  • Logging into OutputWindow.

During the execution of the add-on, I notify appropriate objects and they update UI elements

There are three event for the progress and animation elements – Started, ProgressChanged, Completed:

public VSProgressbarIndicator(IServiceProvider provider)
{
  Guard.ArgumentNotNull(provider, "provider");
 
  Statusbar = provider.GetService(typeof(IVsStatusbar)) as IVsStatusbar;
 
  using (Stream stream = this.GetType().Assembly.GetManifestResourceStream("XapsMinifier.UI.Resources.SilverlightLogo.bmp"))
  {
    Bitmap result = new Bitmap(stream);
 
    result.MakeTransparent();
    silverlightLogoHdc = result.GetHbitmap();
  }
}
 
public void Started(object sender, EventArgs e)
{
  IProgressOperation progress = sender as IProgressOperation;
  object silverlightLogoObjectHdc = (object)silverlightLogoHdc;
  ErrorHandler.ThrowOnFailure(Statusbar.Animation(1, ref silverlightLogoObjectHdc));
  ErrorHandler.ThrowOnFailure(Statusbar.Progress(ref statusBarId, 1, string.Empty, 0, 0));
}
 
public void ProgressChanged(object sender, EventArgs e)
{
  IProgressOperation progress = sender as IProgressOperation;
 
  ErrorHandler.ThrowOnFailure(Statusbar.Progress(ref statusBarId, 1, progress.Message, (uint)progress.Current, (uint)progress.Total));
}
 
public void Completed(object sender, EventArgs e)
{
  ErrorHandler.ThrowOnFailure(Statusbar.Progress(ref statusBarId, 0, string.Empty, 0, 0));
 
  object silverlightLogoObjectHdc = (object)silverlightLogoHdc;
  ErrorHandler.ThrowOnFailure(Statusbar.Animation(0, ref silverlightLogoObjectHdc));
}

I load bmp file from resources, get GDI of that object and use it in the Statusbar.Animation() method to initialize animation.

The Completed method hides progress bar and animation.

I use a Silverlight logo as an animated image. I have created a file (192x16 px) with 12 logos. Visual Studio displays all images sequentially one by one and it generates animation.

I write some log information to OutputWindow when the add-on is running:

public VSOutputWindowIndicator(IServiceProvider provider)
{
  Guard.ArgumentNotNull(provider, "provider");
 
  IVsOutputWindow output = provider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
 
  Guid outputPaneGuid = new Guid(Defines.VSElement.XapsMinifierOutputPane);
  ErrorHandler.ThrowOnFailure(output.CreatePane(ref outputPaneGuid, "Xaps Minifier", 1, 1));
  ErrorHandler.ThrowOnFailure(output.GetPane(ref outputPaneGuid, out pane));
  ErrorHandler.ThrowOnFailure(pane.Activate());
}
 
public void DetailedMessageChanged(object sender, EventArgs e)
{
  IProgressOperation progress = sender as IProgressOperation;
  ErrorHandler.ThrowOnFailure(pane.OutputString(progress.DetailedMessage));
}

I get OutputWindow reference, create a new pane for this window and make it active. When I get DetailedMessageChanged event, I redirect the message to the output window.

At the end of execution, I display some statistics: how many assemblies were excluded from the XAP files, how many projects were added to the main project, how many projects were updated.

Testing on real projects

I have tested several projects. The add-on shows great result on all of them. The first application is my Silverlight Job Board SEO. It has 5 Silverlight projects and 4 XAP files. This application uses Prism Library, WCF Ria services, Navigation and Interactivity libraries.

Sum of all non-optimized XAP files is about 1.2 Mb. After minifying the size is about 500 Kb. But the Main project is increased from 150 Kb to 400 Kb.

John Papa’s Prism Demo application has 7 Silverlight projects in the solution and 4 XAP files. Sum of XAP files is about 5.7 Mb. After minifying the size is 1.6 Mb!!! The Main project is increased in < 100 Kb.

I think, the advantage of that add-on is clear.

Publishing on Visual Studio Gallery

I have an account on the Live.com service, login and I am ready to contribute. I go to the VS gallery, click Upload link and follow the wizard.

Installing the Add-on

There are two ways to install Xaps Minifier onto your copy of Extension Manager, 2010.

  1. Installing *.vsix package manually;
  2. Installing via Visual Studio Gallery.

If you want to install the add-on manually, download binaries from my website and unpack. Do right click on the file and click Open menu item.

Installing Xaps Minifier manually

It will open an installation dialog.

If you would like using Visual Studio Extension Manager to install the add-on, go to main menu-Tools-Extension Manager, select Online gallery from the left side bar and type “xap” (without quotes) in the Search Online Gallery field. Then, select Xaps Minifier and click Download – it will start downloading and installation process.

Xaps Minifier installing via Visual Studio Extension Manager

Using the Add-on

It is very easy.

Just load a Silverlight solution into Visual Studio, right click the solution item in the Solution Explorer and select the Minify Xaps menu item:

How to run Xaps Minifier

It will start processing each project in the solution and the progress will be displayed on the status bar. After minifying, there will be displayed the finish dialog with number of excluded assemblies, updated projects etc.

Attention! 

The add-on can generate an error during the minifying process, if more than one project in the solution has initialized Startup Object:

The Solution must have only one project with initialized Startup Object

Before starting the add-on, check all Silverlight projects for redundant Startup objects and set them to (not set).

Analogues

I have not seen any direct analogue of that add-on. Some companies, like Component One or Telerik provide tools to optimize/minify size of XAP files. But they use completely different algorithm – they try to delete unused classes/controls from assemblies.

I think, it is a really debatable approach. Here are my suggestions:

  • I can load some classes from assemblies dynamically. To be successful, I have to remember all such classes and leave them in the assembly. Also, the same situation with classes that are used in XAML. This can cause a really huge problem in stability of the application because people cannot remember all classes they use/do not use. My add-on is absolutely safe because it does not change any assembly, just move loading of some assemblies to the main/startup project.
  • According to licenses of some 3rd party libraries, I may have no rights to disassemble/reverse engineering. Have mentioned companies get such rights? I do not think so.
  • I and my colleague Alexey spent just one man-week on that add-on. How much time did mentioned companies spend on their minifiers/optimizers? I think, much more.
  • My add-on does not depend on controls/libraries you use. For example, Telerik’s Assembly Minifier beta release works only with Telerik assemblies.
  • Developers should re-minify assemblies after 3rd party libraries/controls new version release. My add-on does not require it.

And last but not least. Developers can use my add-on together with Component One and Telerik tools. They supplement each other.

Known limitations

The add-on does not work with:

  • Silverlight for Windows Phone 7;
  • XNA for Windows Phone 7.

It is very easy to update the add-on to start working with them but I do not have appropriate environment. If anyone is ready to test add-on on Windows Phone 7 projects, I ready to provide a special build.

Also, it could not exclude assemblies that are added to a XAP file dynamically, without adding a reference (one assembly depends on another and adds last one automatically).

Acknowledgements

I would like to say thank you to Alexey Hladkikh. He implemented the initial version of algorithm and gave a lot of useful advice.

Sources and Binaries

You can download sources here. Binaries you can download from my website.

Share impressions!

Please, feel free to share your opinion about that add-on. It would be really helpful for me and for the community. Also, do not hesitate to ask me any question about that add-on.

Additional links

Read my other Xaps Minifier related post:

That is it!

This work is licensed under a Creative Commons Attribution By license.

Leave a Comment [ RSS ]

  • Xaps Minifier. A Step Forward. New Options and New Features

    Xaps Minifier. A Step Forward. New Options and New Features

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Requesting Gravatar... Rodney says:

    Hi Max,

    This all sounds really cool - just to confirm though - it wont make any difference on a single project? I only have 1 project, although it is getting quite large.

    I was wondering if there is a way of splitting up my project into 2 so that all the unchanging core .dlls are in 1 XAP and my application dll is in the other - this way when I change it they only have to download the regularly updated XAP and not all the core libraries again...

    Thanks

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    HI I just downloaded the minifier restarted vs2010 set all the startup project to "Not set" to ovoid redundant object and after all of that I am getting this
    "The extender provider failed to return an extender for this object"

    Any idea

    Alberto Acosta

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Rodney. My tool could not minify 1-xap project. I think, you can use following solution:
    Open properties for your Silverlight project and check the "Reduce XAP size by using application library caching" option. Rebuild your project and some of assemblies will be excluded from XAP file and added to some zip file (check your 'clientbin' folder). Last one will be cached on the client side and it will reduce efforts on xap file downloading

    Hope, it helps

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Alberto, I'm going to write an email to you to ask details about that error.

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Requesting Gravatar... Rodney says:

    Thanks Max, the problem is I am using OOB and assembly caching does not work for some reason OOB, so I am looking for solutions to splitting up my project.

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Hey Max,

    As you also mention it Telerik "Assembly Minifier" disassembles and minifies only Telerik assemblies, so obviously we have rights to do that :).
    The development time is also obviously not comparable, since Dead-code analysis and removal tool is much harder task than add-on that analyses assembly dependencies and change the "Copy Local" attribute.
    Just wanted to mention that it's not a good idea to compare two/three tools that do Not have something in common (except the names).

    Regards,
    Miro Miroslavov

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Thanks, Miro.

    First. Yes, I kept in mind that Telerik had right to disassemble your own assemblies.

    Second. I think, two tools are comparable when they have the same goal nevertheless "they use completely different algorithm" to minimize size of Silverlight applications.

    Third. Development time is comparable as well because we compare hours spent on comparable tools.

    BTW, I underlined, "Developers can use my add-on together with Component One and Telerik tools" and it will be much more effective then using just one tool. So, I was of a high opinion of Telerik "Assembly Minifier".

    So, I think both tools have their own users and should continue simplifying user's life.

    Thanks again for sharing your thoughts.

  • andrew12

    Requesting Gravatar... andrew12 says:

    excelent info, keep it coming

  • albert

    Requesting Gravatar... albert says:

    excelent post, keep it coming

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Requesting Gravatar... dsoltesz says:

    This tool is great, have been using it for quite some time. Another feature that would be nice is that if it could actually remove references that are not used in any of the xaps and remove from startup application if not used in any external xaps

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    hii,
    It was nice reading your article.
    I must say that your way of explanation and presentation is so good that one can get to know the real concept very easily.

    Thanks for sharing this post.
    God Bless.

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Thank you very much for positive feedbacks!

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Requesting Gravatar... Roboblob says:

    Very cool idea and implementation!

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Requesting Gravatar... Greg Lusk says:

    I am geting an error when I run the minifier on my project:

    "Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.VisualStudio.Shell.Interop.IVsAggregatableProject'.

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Thank you, Greg
    could you please provide me your solution? You can just delete all sources (cs, xaml etc) but please leave all folders, project files and solution file. Please email that archive to max.pau (at) gmail.com
    Thank you!

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Hi Max, I have exactly the same problem as Alberto Acosta : "the extender provider failed to return an extender for this object".

    Any idea as to what could cause that error ?

    Many thanks in advance.
    Nicolas

  • re: XAPs Minifier. An Add-on to Visual Studio 2010 to Optimize and Minimize Size of Xap Files

    Requesting Gravatar... Patrick says:

    Nicolas LAURENT says:
    Такаяже ошибка как у автора счем она связана ?!

Comments have been closed on this topic.