Silverlight 2 dynamic assembly loading - Part 1

30 01 2009

In this article:
- Rebuilding the AppManifest.xaml
- (Re)compressing .xap files with 7zip
Coming in part 2 (Click to open):
- Loading the assemblies.

Silverlight 2 by default packages all the referenced assemblies into one big xap file. This is a nice for deployment scenarios, but when you have a lot of Silverlight controls, using the same assemblies over and over again, you waste a lot of bandwidth, and user experience drops because of the extra time it takes to load a page.

imageDo not copy local

Some of it can be achieved by setting the “copy local” property of the referenced assemblies to false. This tells the packager not to add them to the .xap file automatically.

Now when the Silverlight application starts in a browser we will have to resolve the “missing” referenced assemblies. I had hoped to use the AppDomain.AssemblyResolve event, but as it seems, this has been marked with the [SECURITY CRITICAL] attribute, which means it can only be used in the .NET framework itself.

Luckily the assemblies are accessed only when needed, so the assembly is only needed when you first access something from the assembly, like a class or method. This means that if we do all the loading in the App.xaml.cs we should be fine. But for this example I will not use this option, so you can put them back to True.

Because I’m Lazy

I am a lazy programmer ;) And I do not want to manually load every assembly for every Silverlight project. I’d rather do some more work now, and save myself (and hopefully you as well) lots of time in the future. And as an added bonus we get some extra compression (+- 20%) in our .xap files, because the normal .xap packager does almost no compression at all. And if you would use the Copy Local option above you would get no compression at all for your referenced assemblies.

To get an idea of what I’m talking about, take a look at the following diagram:

image

What you are seeing above, is the default packaging on the left, and the ReXapped packaging on the right. I also added the size in Kb. To make the difference a bit more drastic, I put 2 images in MyLibrary, which is packaged twice on the left and only once on the right. This also goes for the System.Xml.Linq.dll assembly. The rest of the reduction in size is due to the fact that 7Zip just does better compression. And if you have more then 2 Applications, and you have lots of shared code and images , which in my experience is usually the case, the reduction will be even greater.

ReXapper

Let met start out by saying that the ReXapper name is one I did not think up myself, I have seen it many times already around the web, but why change a good name, right?

The ReXapper is a Console application, which is run After Build on both MySilverlightApplications. It receives the parameters from the build events, and starts ReXapping the applications.

It should be run with the following parameters:
ReXapper.exe [7ZipLocation] [projectName] [projectTargetDir] [silverlightTargetDir]

When used in the After Build event it would be:
$(SolutionDir)ReXapper\bin\debug\ReXapper.exe
$(SolutionDir)7za.exe
$(ProjectName)
$(TargetDir)
$(SolutionDir)DynamicAssemblyLoading.Web\ClientBin

The code for the Rexapper is a bit too big to completely go over right now, but here are a few higlights. You can download the full source code below.

Saving AppManifest.xaml from an XDocument

The AppManifest does not have a xml starting tag. If added, the Silverlight Plugin will fail to load, and of course <?xml is added by default when saving a XDocument :)

The solution is using the XMLWriter with XmlWritersettings having OmitDeclaration = true. Check the SaveXDocumentWithoutXmlDeclaration method in the code.

Changing and not creating the AppManifest

Because I ran into a few problems, creating a new AppManifest from scratch I decided to reuse the ogirinal one and change it to meet the needs.

Lots of Linq and Lambdas

I have become such a fan of Linq and Lambdas that you will see me using it wherever I can, probably even in places where it’s not necessary, I just love to use them :)

Next

Now we have the new xap files, we can run the application and see that they work. Of course you cannot yet use the Referenced assemblies like the System.Linq.Xml and MyLibrary, but because they are not yet used in the code, Silverlight will not trow any errors.

In Part 2, ready hopefully somewhere next week, we will read our custom created ReferencedAssemblies.xaml from our xap, load all the referenced assemblies from their respective .xap files, and use resources from MyLibrary in our Applications.

Click here to download the source code in it’s current state.

Stay in the light!

Robertjan Tuit

Technorati Tags:

*UPDATE: Click here to view part 2



Silverlight 2 Image Tiling

28 01 2009

Silverlight’s big brother WPF has a lot of functionality that you would like to have in Silverlight 2. One of these features is a Tiling Image Brush.

As many of you have probably already found out, it is impossible to create your own brush in Silverlight, because of internal and sealed base classes.

A couple of months a go I was working on a project which required images being tiled across the screen, so I decided to create a tiling control, which takes an image and puts as many as fit the screen into a wrap panel (from the silverlight control toolkit).

Since then I have seen a lot of people asking for this functionality, I though I would share this very simple (user) control. Below is the code, let me know if you have any questions or remarks.

There are still a few improvements you could make to this code. For example determining the width and height of the image automatically. But to keep it simple I left that code out.

The image used in this example is a random one I took from Google image search:

When finished it should look like something like this:

image

In the application: (local namespace should be added to the top of the xaml)

<local:ImageRepeater

   ImageSource="http://ecx.images-amazon.com/images/I/51sghWRypwL._SL75_SS50_.jpg"

   ImageWidth="50"

   ImageHeight="50"/>

UserControl: ImageRepeater.xaml

<UserControl x:Class="ImageRepeater"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"

    >

    <Grid x:Name="LayoutRoot" Background="White">

        <controls:WrapPanel x:Name="xRepeaterPanel"/>

    </Grid>

</UserControl>

UserControl: ImageRepeater.xaml.cs

public partial class ImageRepeater : UserControl

{

    public ImageRepeater()

    {

        InitializeComponent();

    }

 

    // The brush to paint on the rectangle

    private ImageBrush _BrushToTile = new ImageBrush();

    public ImageSource ImageSource

    {

        get { return _BrushToTile.ImageSource; }

        set {

            _BrushToTile.ImageSource = value;

        }

    }

 

    private double _ImageWidth;

    public double ImageWidth

    {

        get { return _ImageWidth; }

        set { _ImageWidth = value; }

    }

 

 

    private double _ImageHeight;

 

    public double ImageHeight

    {

        get { return _ImageHeight; }

        set { _ImageHeight = value; }

    }

 

    protected override Size ArrangeOverride(Size finalSize)

    {

        // To make sure that we fill the screen horizontally and vertically, we create negative margins

        Margin = new Thickness(0, 0, -ImageHeight, -ImageWidth);

 

        // Determine how many rectangles we need to add to fill the available screen width

        var itemCount = (int)Math.Ceiling(finalSize.Width / ImageWidth);

        itemCount = itemCount * (int)Math.Ceiling(finalSize.Height / ImageHeight);

 

        // It is done incrementally, so when the control is resized, we add more to it so fill the area again.

        var diff = itemCount - xRepeaterPanel.Children.Count();

        for (int i = 0; i < diff; i++)

        {

            // Create and add every rectangle

            var rect = new Rectangle();

            rect.Width = ImageWidth;

            rect.Height = ImageHeight;

            rect.Fill = _BrushToTile;

            xRepeaterPanel.Children.Add(rect);

        }

        return base.ArrangeOverride(finalSize);

    }

}

Stay in the light!

Robertjan Tuit