Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C#

Self Contained Assembly

Rate me:
Please Sign up or sign in to vote.
4.95/5 (13 votes)
13 Nov 2011CPOL9 min read 47.6K   1.5K   43   14
Shows how deployment of dynamically loaded assembly can be simplified by reducing the assembly to single self contained file.

What It Does?

To see, please download the demo, extract and run. You will see a form with button, click button, choose the color and click ok. The form color will change to cool gradient. Actually, it's a gradient panel control which is created by GradientPanelFactory plugin present in PlugIns folder. The plugin is loaded dynamically at runtime. The plugin in turn relies on Owf.Controls.A1Panel.dll which contains this cool gradient panel control. But you will not see this DLL anywhere with this application, not even in PlugIns folder. Then how does it work? This missing DLL is actually compressed into a zip file and added as embedded resource to plugin DLL. And this missing DLL is extracted by the application at runtime from zip resource of plugin DLL. So the plugin can be self contained and only single file irrespective of the number of dependency DLLs. Read further to see why and how it's done.

Introduction

Recently, our project ran into one issue which is client server application. We have exposed some extension points for our customer for customization of application. The customers can implements their own logic by implementing specific interfaces or abstract class or applying attributes to the class. The custom logic assemblies need to be uploaded on the server along with all dependency DLLs. And client application downloads these assemblies when it requires the functionality. I need not say that the client application is loading assemblies dynamically via reflection.

As per the current implementation, we do not have much control over where the dependency DLLs get downloaded. Also it is became tedious to figure out their path and ensure that the correct dependency DLLs will be loaded. Loading a wrong DLL could be disastrous depending upon the importance of logic to be executed. In one of my apps, such DLLs were responsible for validation of cell phones manufactured in factory which produces 4 lac cell phones per day. Imagine that the DLL loaded is wrong and it is not able to detect any fault in the product. By the time the product goes into the market and customer comes back with complaints, millions of faulty phones will be manufactured.

Solution

The solution that I found is to combine everything into single package, i.e., the main DLL and its dependency DLLs into one unit. So there will not be any need to upload multiple files and download many files. We can get rid of file path related issues. The possibility of loading wrong DLLs is also very minimal unless the wrong DLL is added as a resource. There will always be one assembly which holds all the required DLLs along with it. Visual Studio allows adding any file as embedded resource into a project. Right click on project in solution explorer, choose Add -> Existing Item -> Select File. Make sure that you select All files option in File Dialog to view DLL files. After adding file, set the Build Action property to Embedded Resource. After compilation, the resource files becomes part of the assembly. All dependency DLLs can be added as embedded resource into the project. When code inside this assembly will be executed, it will require these dependency DLLs. Since runtime will not be able to find these assemblies, we need to handle AppDomain.Resolve event to load the DLLs in AppDomain. The handler will retrieve the DLLs from the assembly resource.

To further improve this idea, we can in fact compress all the DLLs into a zip and then add as embedded resource. This can considerably reduce the size of assembly. This makes more sense if number of files are more and size as well specially when transferred over network. However this also means that we need to find this zip from Assembly resource and then extract the required assembly from zip before loading in Resolve Handler. So I started thinking about possible options for working with zip. A couple of very good libraries are available for this purpose. DotNetZip is very easy to use and SharpZipLib is also a popular option. However I decided to use another option ZipStore which is a C# class available at Codeplex. Since it is just a single class file, I can use it directly in the project without depending on any third party lib.

Everything sounds good as an idea, but while implementing I ran into one issue. When we load any assembly, the runtime does not raise AssemblyResolve event until we try to use the type inside assembly that in turn tries to use the type in dependency assembly. Suppose assembly A depends on B. So B is dependency assembly. When you try to use any type from A that depends on type in B at that time the AssemblyResolve is raised if runtime is not able to find the assembly B. Now when AssemblyResolve handler runs, there is no way to figure out which assembly is requesting the required DLL, i.e., A in above example. But we have to extract the required assembly DLL from the resource of requesting assembly (Assembly A). I was hoping that ResolveEventArgs.RequestingAssembly property will help, but it was always null. Then I was hopping from Assembly.GetCallingAssembly() but the result of this varies depending on whether the calling method is inclined or not by JIT. Inspecting all assemblies at runtime is also a very costly option. So as a workaround while loading assembly, I am inspecting if assembly DLL has any zip file as resource. If it has, then I inspect zip for DLL files. Then I am adding the required information to one dictionary with assembly name as key.

Example

For the purpose of the demo, I will use a winform application which will load the plug-in from \\PlugIns folder in its base directory (i.e. location of EXE file). The valid plug-in needs to extend AbstractPanelFactory class and DLL name should end with PanelFactory.dll. The plug-in needs to implement the abstract GetPanel method which returns the Panel based on color argument. Now assume that this plug-in gets downloaded at application startup from server. And there will be only one DLL for plug-in, the dependency DLLs will be extracted at runtime from zip added as embedded resource from the plug-in DLL.

The solution for this demo contains four projects. I will explain the purpose of each one.

The first one is AssemblyLoader - this project contains a single static class responsible for loading and resolving the assemblies. It has only two public members, one is the Boolean property ResolveAssembly. When set to true, this class hooks to AssemblyResolve event. It has a method to LoadAssembly() which takes file path and loads file using Assembly.LoadFrom(). While loading, it also inspect assembly resource to find zip files and also extract information about files available in zip. This information is added to one dictionary. The implementation makes sure that if two plug-ins have the same assembly in the zip resource, it is added only once from the assembly where it was found first. Also a cross verification is done using crc32 values in zip metadata that two files in different zip resources are same. There should not be a DLL file having the same name but having totally different assembly or different version of same assembly. To ensure that all assemblies get loaded from expected locations, you can hook AppDomian.AssemblyLoad event which fires when assembly is loaded. The handler for AssemblyResolve event will extract the required DLL from zip resource of DLL.

The second project is AbstractPanelFactory, this has only one abstract base class with only one abstract method. This acts as an extension point for customer. By extending this class, they can customize the application. The method should return a Panel.

The third project is GradientPanelFactory. Assume this as a plug-in developed by the customer to customize the application. The class will inherit from AbstractPanelFactory. The client is interested to have gradient panel so she/he uses a third party library Owf.Controls.A1Panel.dll. Now this library is added as an embedded resource in this project. In case of multiple assemblies, they can be added to folder and sub folders, then zipped into a single file. Adding multiple zip files is also possible. I have set the output path of this project to plug-in folder of WinForm application. However I have set copy local property of Owf.Controls.A1Panel.dll to false so that it do not get copied to plug-in folder. Remember we are only downloading one plug-in assembly and not its dependencies.

The fourth project is a Winform application. The application relies on Loader class in the first project to load plug-in DLL and resolve missing assemblies. This application downloads (assume so) and loads plug-in DLL and creates the instance of class inherited from AbstractPanelFactory. Then it calls the GetPanel method and docks this panel in form. Even thought the plug-in (GradientPanelFactory) depends on Owf.Controls.A1Panel.dll which is not present with this application, still the example works as it gets extracted from the embedded zip resource in GradientPanelFactory.dll in AssemblyResolve handler.

I request you to download the solution and try yourself. Run DemoClientApp and click GetPanel Button, it opens the color dialog, select color, click ok. You should see the gradient panel docked in the form. However the interesting code of loading assembly executes at FormLoad and ResolveHandler executes when first time GetPanel button is pressed.

first.pngsecond.png

In server client application, this approach simplifies upload and download and resolving assemblies and risk of loading wrong DLL is also reduced. If there is a need to update individual assembly file in zip, then individual file can be uploaded to server and this can be compared to the file in zip before loading in AssemblyResolve handler and if it differs, sever side version can be loaded.

This can simplify the distribution of desktop application as well and is even easier to implement as zip can be added as resource to EXE file and extracted at runtime. There is no need to figure out requesting assembly as EXE assembly holds all files.

Points of Interest

The only drawback I can see as of now is the little extra work on the developers' part to create the zip file and add it as an embedded resource in project.

Credits

I got the idea from here. I Goggled further and found more information. One good post on CodeProject is this which is for EXE application. I wrote this article as it seems to be a good idea worth sharing along with a working example. Let me know if anyone found this post relevant, interesting or useful.

History

  • 13th November, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Honeywell Automation India Ltd.
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionAnother question about disposing plugin Pin
hzawary26-Nov-11 0:21
hzawary26-Nov-11 0:21 
AnswerRe: Another question about disposing plugin Pin
Prafulla Hunde26-Nov-11 3:00
Prafulla Hunde26-Nov-11 3:00 
GeneralRe: Another question about disposing plugin Pin
hzawary26-Nov-11 3:35
hzawary26-Nov-11 3:35 
GeneralRe: Another question about disposing plugin Pin
Prafulla Hunde26-Nov-11 18:46
Prafulla Hunde26-Nov-11 18:46 
QuestionI want this useful project in wpf for an UserControl Pin
hzawary21-Nov-11 1:18
hzawary21-Nov-11 1:18 
AnswerRe: I want this useful project in wpf for an UserControl Pin
Prafulla Hunde24-Nov-11 18:36
Prafulla Hunde24-Nov-11 18:36 
GeneralRe: I want this useful project in wpf for an UserControl Pin
hzawary25-Nov-11 10:36
hzawary25-Nov-11 10:36 
GeneralRe: I want this useful project in wpf for an UserControl Pin
hzawary25-Nov-11 11:01
hzawary25-Nov-11 11:01 
QuestionFor thanks Pin
hzawary19-Nov-11 4:42
hzawary19-Nov-11 4:42 
SuggestionThis is NETZ Pin
Niel M.Thomas14-Nov-11 7:38
professionalNiel M.Thomas14-Nov-11 7:38 
GeneralRe: This is NETZ Pin
Prafulla Hunde15-Nov-11 0:00
Prafulla Hunde15-Nov-11 0:00 
GeneralMy vote of 5 Pin
sgorozco14-Nov-11 7:19
sgorozco14-Nov-11 7:19 
QuestionMy Vote of 5 Pin
Phil Martin13-Nov-11 10:03
professionalPhil Martin13-Nov-11 10:03 
GeneralMy vote of 5 Pin
Prafulla Hunde13-Nov-11 9:20
Prafulla Hunde13-Nov-11 9:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.