How To: Merge assemblies into WPF application

by Vladimir Enchev | Comments 4
As you may know you cannot use ILMerge to merge assemblies with XAML (WPF application) however there is a little known trick you can use to achieve your goal in a bit different way. 

- Add needed assemblies as embedded resources in your application
- Load needed assemblies from embedded resources in runtime

How to do this?

First you need to ensure that all referenced assemblies (except the framework itself) are added properly to the application resources on every application build. To do this you can use this simple MSBuild task (AfterResolveReferences) at the end of your project:

...
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Target Name="AfterResolveReferences">
    <ItemGroup>
      <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
        <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
  </Target>
...

Now you need to write some code to load needed assemblies from resources when requested. To do this you can define your own entry point for the WPF application, preload all assemblies from resources and return needed assembly using AppDomain.CurrentDomain.AssemblyResolve event:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
 
namespace WpfApplication1
{
    public class Program
    {
        [STAThreadAttribute]
        public static void Main()
        {
            var assemblies = new Dictionary<string, Assembly>();
            var executingAssembly = Assembly.GetExecutingAssembly();
            var resources = executingAssembly.GetManifestResourceNames().Where(n => n.EndsWith(".dll"));
 
            foreach (string resource in resources)
            {
                using (var stream = executingAssembly.GetManifestResourceStream(resource))
                {
                    if (stream == null)
                        continue;
 
                    var bytes = new byte[stream.Length];
                    stream.Read(bytes, 0, bytes.Length);
                    try
                    {
                        assemblies.Add(resource, Assembly.Load(bytes));
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.Print(string.Format("Failed to load: {0}, Exception: {1}", resource, ex.Message));
                    }
                }
            }
 
            AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
            {
                var assemblyName = new AssemblyName(e.Name);
 
                var path = string.Format("{0}.dll", assemblyName.Name);
 
                if (assemblies.ContainsKey(path))
                {
                    return assemblies[path];
                }
 
                return null;
            };
 
            App.Main();
        }
    }
}



That’s all! 

Enjoy!
Make your XAML development easier, visit Telerik XAMLFlix for more WPF tutorials and tips!

Download

,
Senior Technical Architect

Posted in: wpf

4 Comments

MikeH
Hey cool! But what about performance?

Did you check the method against different anti-virus products?
Vlad
Hi,

I've not checked the approach with different anti-virus products however I'm running Symantec Endpoint Protection and everything is working normally. 
Paul R
I recommend the Costura extension for VS 2010, great tool that simply adds an additional build command to your project file and works very well.
Fabien B
Hi,

this solution doesn't works with Prism :s

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by Vladimir Enchev - or - read latest articles in Developer Tools


Follow vladimir_enchev on Twitter