Cool Silverlight Trick #3

22 Comments October 22, 2008

One of the areas of Silverlight that not much has been written about yet is the factoring of applications. Applications don’t have to be packaged in one big XAP file; Silverlight presents numerous options for dividing code and resources among multiple assemblies, packaging resources inside or outside the XAP file in one file or multiple files, and loading assemblies and resources at load time or on demand. And for one feature in particular, much of what has been written is wrong. It’s time to set the record straight and learn something really cool at the same time.

Suppose you’re writing a Silverlight application that uses classes in a library assembly named HelperLib.dll. You add a reference to HelperLib.dll to your project and HelperLib.dll gets embedded in the application package (the XAP file) and loaded into the AppDomain automatically. You can use types in HelperLib.dll as if they were types in your code. No problem there; it just works.

But now let’s take it one step further. Suppose you have lots of library assemblies and don’t want to swell the size of the XAP file with them because depending on how your users interact with the application, you may not need all those assemblies. To first order, the solution is simple. You deploy the library assemblies alongside the XAP file in your server’s ClientBin folder and use WebClient to download them on demand. To load an assembly after it’s been downloaded, you do this:

AssemblyPart part = new AssemblyPart();

Assembly a = part.Load(e.Result);

But what comes next isn’t very pretty. Because you didn’t add a reference to the assembly to your project, you can’t use the new operator to instantiate the assembly’s types. (Why? Because without the assembly reference, the compiler doesn’t know about the types.) So you resort to reflection—specifically, to Assembly.CreateInstance:

Object obj = (Object)a.CreateInstance("Foo");

This creates an instance of the type named Foo. But now if you want to go deeper and access some of Foo’s properties or methods, you have to perform even more reflection. This gets unwieldy fast, and as a practical matter, it means that you’ll probably give up and go back to embedding all those library assemblies in your XAP file.

Fortunately, there is a better way. The Silverlight docs on MSDN suggest that you can download an assembly, load it into the AppDomain, and still have the benefit of strong typing. The docs even offer a working example. Many people have complained that the example doesn’t work, but it does work if you run it exactly as is and don’t change a thing. The example is extremely fragile, but the docs don’t tell you why. To make matters worse, the example can fail given the right circumstances. As my friend and colleague Jeffrey Richter commented, the fact that it works at all is really more a matter of luck than anything else.

Here’s a recipe for downloading assemblies on demand and using the types in those assemblies as if they were right there in the application assembly:

  1. Add a reference to the library assembly to your Silverlight project, but change the “Copy Local” setting in the reference’s properties from true to false. This prevents Visual Studio from embedding the library assembly in the XAP file, but since the project contains a valid reference to the assembly, the compiler knows all about the types in it and will allow you to use them—even new them up.
  2. At run-time, use WebClient to download the assembly and AssemblyPart.Load to load it.
  3. Here’s the critical part. After loading the assembly, call a separate method to instantiate one of the assembly’s types, and decorate that method with a MethodImpl(MethodImplOptions.NoInlining) attribute. In addition, do not reference any of the types inside the library assembly in the method that loads the assembly. If you do, you’ll shoot yourself in the foot and what you’re trying to do will not work. Period.

Step 3 was a mouthful, and it probably makes no sense at all, so let’s look at an example. Assuming Foo is a type in a dynamically loaded library assembly, you want to be able to do this:

AssemblyPart part = new AssemblyPart();

Assembly a = part.Load(e.Result);

Foo foo = new Foo();

// Use Foo methods and properties

But even if you follow steps 1 and 2 above, you’ll be met with this:

Assembly Load Error

The solution is to restructure the code this way:

AssemblyPart part = new AssemblyPart();

Assembly a = part.Load(e.Result);

 

// Create an instance of Foo (but declare it as Object not, Foo!)

Object obj = CreateFoo();

 

// Now pass the Foo reference to another method, and in that method,

// cast the reference from Object to Foo

UseFoo(obj);

 

[System.Runtime.CompilerServices.MethodImpl

    (System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]

private Object CreateFoo()

{

    return new Foo();

}

 

private void UseFoo(Object obj)

{

    Foo foo = (Foo)obj;

    // Now use Foo methods and properties all you want

}

Now it works as intended, and inside the UseFoo method, you can use a Foo as a Foo and not worry about any more casting or reflecting.

Now the big question: why does new Foo() work when it’s in a separate, specially attributed method, but not when the method that loads the assembly calls it? This is a terrific example of a situation in which knowledge of CLR internals can make you a better Silverlight programmer. Before the JIT compiler compiles a method, it checks all the types in that method and ensures that the assemblies implementing those types have been loaded. When you do a new Foo() inside the method that loads the assembly, the CLR tries to load the assembly before you get a chance to call AssemblyPart.Load. But when you break it out into a separate method, the method gets to execute and gets to load the assembly. What does the MethodImpl attribute do? It ensures that the compiler won’t get in the way by inlining the code in the CreateFoo method. That’s why the sample in the Silverlight docs “gets lucky.” It lacks the attribute, and without the attribute, there’s no guarantee that new Foo() won’t get inlined.

So yes, it is possible to load assemblies on demand and still enjoy the benefit of strong typing. But you need to know what you’re doing. And now, hopefully, you do. :-)

Addendum: In the "big" .NET Framework, you can resolve assembly loading issues using the AppDomain.AssemblyResolve event. That event exists in Silverlight, but unfortunately, it's attributed SecurityCritical, which means user code can't register handlers for it.


22 Comments

  • Gravatar Image
    Silverlight news for October 22, 2008 October 22, 2008 11:17 AM

    PingBack from http://www.silverlightshow.net/news/Silverlight-news-for-October-22-2008.aspx

  • Gravatar Image
    Bart Czernicki October 22, 2008 4:20 PM

    I implemented strongly typed assemblies simply through having the interfaces the classes inherit from on the client. Then through the "fun" of the strategy pattern and expression trees load the assembly type. I have multiple types of this class loaded and I simply want to call the appropriate method to return my data.

    I would also be hesitant to deploy your solution in my business app, as I don't like introducing things in architecture that has that "unknown/guru" feel to it.

  • Gravatar Image
    unruledboy October 23, 2008 12:01 AM

    interesting solution

  • Gravatar Image
    Pierre Lagarde, Blog October 26, 2008 7:50 PM

    Quand une application commence à grossir en Silverlight c’est un seul et même fichier qui grossi. L’idée

  • Gravatar Image
    Steve Strong's Blog November 3, 2008 5:23 AM

    Weekly digest of interesting stuff

  • Gravatar Image
    Jeff Prosise's Blog April 6, 2009 11:03 AM

    One of the overarching goals when designing a Silverlight application is to minimize the size of the

  • Gravatar Image
    Wayne B April 17, 2009 4:17 PM

    Say I call Assembly.Load() when my page or my app loads up, but I new up a Foo later, when the user presses a button or some other event.

    I should not need have to use MethodImplOptions.NoInlining on my event handler, correct?

  • Gravatar Image
    herzmeister_der_welten May 27, 2009 8:20 AM

    I think that "strong typing" hack isn't necessary.

    When loading an assembly dynamically like that, the hosting assembly does not need to know and should not need to know any strong types of that loaded assembly.

    Instead it only knows a bunch of IView interfaces that are provided by a small library. That's of course when the components are properly designed. ;-)

    The components which are implementing those IView interfaces then in turn can have a strong reference to the big fat libraries they need.

  • Gravatar Image
    Jeff Prosise's Blog November 6, 2009 10:51 AM

    Another feature of Silverlight 3 that has flown under the radar since the product's release is application

  • Gravatar Image
    Jan B November 18, 2009 10:27 AM

    I need to create an instance of a type dynamically. I have the type name and the short name of the assembly. I though this was the solution I was looking for but - how disappointing! - AssemblyPart can't be created on a worker thread...

  • Gravatar Image
    jprosise November 18, 2009 6:06 PM

    Correct. You have to marshal back to the UI thread. It's not a big deal...just a little more code.

  • Gravatar Image
    Community Blogs December 14, 2009 3:25 AM

    Introduction My XAP file is 5 Mb size, is that bad? Unfortunately that’s a usual question for lot of

  • Gravatar Image
    Denis Vuyka December 21, 2009 3:33 PM

    This is an interesting hack when searching for a fancy hack rather than solution as for me. The properly designed application will deal with contract libraries rather than compiler tricks. If you need loading assembly dynamically than it is automatically assumed that assemblies may be different (otherwise why all these efforts?). In this case the sample you mentioned might be a very bad practice as it might move some of the readers out of the well-defined architecture ways. I would better provide a tiny contract assembly with public interfaces/infrastructure and use the common AppDomain loading procedures.

    Thanks.

  • Gravatar Image
    Bill December 22, 2009 6:34 AM

    Thanks for the post. Not only informative in its own right but it generated comments like those from Denis Vuyka and herzmeister_der_welten who remind me that a potentially more reliable way to load assemblies dynamically and still have strong typing is to use interfaces. And extracting the interface of an existing type is not difficult in VS2008.

    BTW, are dynamically loaded assemblies cached by the web client?

  • Gravatar Image
    acai reviews February 4, 2010 7:53 PM

    Honestly, except for bragging rights the Awards are pretty small potatoes. And even the bragging rights are only important in a pretty rarified crowd. But limited as they are they’ re a way of rewarding excellence. What’ s“ excellence”? It’ s whatever

  • Gravatar Image
    Dom February 15, 2010 2:18 PM

    No need for all this stuff if you define a common interface in a shared DLL (ie referenced and included in both projects)

  • Gravatar Image
    Silverlight driven blogging June 21, 2010 8:13 PM

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

  • Gravatar Image
    Silverlight driven blogging June 24, 2010 7:09 PM

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

  • Gravatar Image
    KEEN September 4, 2010 8:09 AM

    Great article! I am begginer at Silverlight but it is very helpful.

  • Gravatar Image
    Jim Gale February 28, 2011 2:11 AM

    jeff: thanks for the article.

    I am looking to have one final XAP, but including an engine and data - hopefully without compiling after creating the engine [post-development]. Is there a good way you can envision to either inject data (xml file) into (or next to) a XAP while still preserving the single XAP deliverable after the data injection?

  • Gravatar Image
    Raluca April 13, 2011 5:23 AM

    Hi!
    First of all I read and found really interesting your article.

    Do you have any idea on how to load System.Xml.Linq.dll on demand?
    Actually I'm mostly interested on how to use the assembly after loading it.
    10X

  • Gravatar Image
    Paul July 19, 2011 9:36 AM

    Any trick to load the same DLL with different version. The appdomain trick does not work in SL

Have a Comment?

Archives

Tags