Assemblies are the building blocks of .NET Framework applications; they form the fundamental unit of deployment, version control, reuse, activation scoping, and security permissions. An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality. An assembly provides the common language runtime with the information it needs to be aware of type implementations.
In general, a assembly can consist of four elements:
- The assembly manifest, which contains assembly metadata.
- Type metadata.
- Microsoft intermediate language (MSIL) code that implements the types.
- A set of resources.
Types of assemblies
.Net assembly can be classified in four Categories:
- With respect to program access.
- Private assembly– A private assembly is normally used by a single application and is stored in the application’s directory.
- Public/Shared assembly– A shared assembly is normally stored in the global assembly cache (GAC) which is a repository of assemblies maintained by the .NET runtime (%systemroot% /assembly). Assemblies residing in the GAC must adhere to a specific versioning scheme which allows for side-by-side execution of different code versions. Specifically, such assembles must be strongly named (SN.exe).
- With respect to number of resources.
- Static assembly– Static assemblies are stored on disk in portable executable (PE) files created at compiled time.
- Dynamic assembly– These are PE-formatted, in-memory assemblies that you dynamically create at runtime using the classes in the System.Reflection.Emit namespace.
- With respect to deployment.
- Satellite Assembly– An assembly containing resources specific to a given language. Using satellite assemblies, you can place the resources for different languages in different assemblies, and the correct assembly is loaded into memory only if the user elects to view the application in that language.
- Resource-Only assembly – Resource assemblies are useful when you need to frequently update resources in a program without having to recompile the entire solution. By placing all of your resources into a resource-only assembly, you can update the assembly as needed without having to recompile your application. Resources compiled into resource assemblies can be retrieved using the ResourceManager class.
- With respect to number of assemblies.
- Single file assembly– Single file assemblies: A single *.dll or *.exe file which contains the CIL code, type metadata, manifest and optional resources in one binary package.
- Multi file assembly– Multi-file assemblies: A logical collection of files (termed ‘modules’) which are versioned and deployed as a single logical unit.
Assembly manifest contents
The following table shows the information contained in the assembly manifest. The first four items: the assembly name, version number, culture, and strong name information — make up the assembly’s identity.
|Assembly name||A text string specifying the assembly’s name.|
|Version number||A major and minor version number, and a revision and build number. The common language runtime uses these numbers to enforce version policy.|
|Culture||Information on the culture or language the assembly supports. This information should be used only to designate an assembly as a satellite assembly containing culture- or language-specific information. (An assembly with culture information is automatically assumed to be a satellite assembly.)|
|Strong name information||The public key from the publisher if the assembly has been given a strong name.|
|List of all files in the assembly||A hash of each file contained in the assembly and a file name. Note that all files that make up the assembly must be in the same directory as the file containing the assembly manifest.|
|Type reference information||Information used by the runtime to map a type reference to the file that contains its declaration and implementation. This is used for types that are exported from the assembly.|
|Information on referenced assemblies||A list of other assemblies that are statically referenced by the assembly. Each reference includes the dependent assembly’s name, assembly metadata (version, culture, operating system, and so on), and public key, if the assembly is strong named.|
Creating & using a Satellite assembly
When you write a multilingual or multi-cultural application in .NET, and want to distribute the core application separately from the localized modules, the localized assemblies that modify the core application are called Satellite assemblies.
- Create a folder with a specific culture name (for example, en-US) in the application’s bindebug folder.
- Create a .resx file in that folder. Place all translated strings into it.
- Create a .resources file by using the following command from the .NET command prompt. (localizationsample is the name of the application namespace. If your application uses a nested namespace structure like MyApp.YourApp.MyName.YourName as the type of namespace, just use the uppermost namespace for creating resources files—MyApp.)
- resgen Strings.en-US.resx LocalizationSample.
- al /embed:LocalizationSample.Strings.en-US.resources
- /out:LocalizationSample.resources.dll /c:en-US
The above step will create two files, LocalizationSample.Strings.en-US.resources and LocalizationSample.resources.dll. Here, LocalizationSample is the name space of the application.
- In the code, find the user’s language; for example, en-US. This is culture specific.
- Give the assembly name as the name of .resx file. In this case, it is Strings.
Using a Satellite Assembly
Follow these steps:
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(specCult);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(specCult);
ResourceManager resMgr = new ResourceManager(typeof(Form1).Namespace + “.” + asmName, this.GetType().Assembly);
btnTest.Text = resMgr.GetString(“Rajneesh”);
Global Assembly Cache (GAC)
Each computer where the common language runtime is installed has a machine-wide code cache called the global assembly cache ((%systemroot% /assembly). The global assembly cache stores assemblies specifically designated to be shared by several applications on the computer.
There are several ways to deploy an assembly into the global assembly cache:
- Use an installer designed to work with the global assembly cache. This is the preferred option for installing assemblies into the global assembly cache.
- Use a developer tool called the Global Assembly Cache tool (Gacutil.exe).
- Use Windows Explorer to drag assemblies into the cache.
Assemblies deployed in the global assembly cache must have a strong name. When an assembly is added to the global assembly cache, integrity checks are performed on all files that make up the assembly. The cache performs these integrity checks to ensure that an assembly has not been tampered with, for example, when a file has changed but the manifest does not reflect the change.
A strong name consists of the assembly’s identity—its simple text name, version number, and culture information (if provided)—plus a public key and a digital signature. It is generated from an assembly file (the file that contains the assembly manifest, which in turn contains the names and hashes of all the files that make up the assembly), using the corresponding private key.
You can ensure that a name is globally unique by signing an assembly with a strong name. In particular, strong names satisfy the following requirements:
- Strong names guarantee name uniqueness by relying on unique key pairs. No one can generate the same assembly name that you can, because an assembly generated with one private key has a different name than an assembly generated with another private key.
- Strong names protect the version lineage of an assembly. A strong name can ensure that no one can produce a subsequent version of your assembly. Users can be sure that a version of the assembly they are loading comes from the same publisher that created the version the application was built with.
- Strong names provide a strong integrity check. Passing the .NET Framework security checks guarantees that the contents of the assembly have not been changed since it was built. Note, however, that strong names in and of themselves do not imply a level of trust like that provided, for example, by a digital signature and supporting certificate.
When you reference a strong-named assembly, you expect to get certain benefits, such as versioning and naming protection. If the strong-named assembly then references an assembly with a simple name, which does not have these benefits, you lose the benefits you would derive from using a strong-named assembly and revert to DLL conflicts. Therefore, strong-named assemblies can only reference other strong-named assemblies.
Sign an assembly with a Strong Name
The Windows Software Development Kit (SDK) provides several ways to sign an assembly with a strong name:
- Using the Assembly Linker (Al.exe) provided by the Windows SDK.
- al /out:<assembly name> <module name> /keyfile:<file name>
- Using assembly attributes to insert the strong name information in your code. You can use either the AssemblyKeyFileAttribute or the AssemblyKeyNameAttribute, depending on where the key file to be used is located.
- Eg : [assembly:AssemblyKeyFileAttribute(@”mykeys.snk”)]
- You must have a cryptographic key pair to sign an assembly with a strong name. For more information about creating a key pair, see How to: Create a Public/Private Key Pair.
Delay signing an assembly
An organization can have a closely guarded key pair that developers do not have access to on a daily basis. The public key is often available, but access to the private key is restricted to only a few individuals. When developing assemblies with strong names, each assembly that references the strong-named target assembly contains the token of the public key used to give the target assembly a strong name. This requires that the public key be available during the development process.
You can use delayed or partial signing at build time to reserve space in the portable executable (PE) file for the strong name signature, but defer the actual signing until some later stage (typically just before shipping the assembly).
An assembly’s name is stored in metadata and has a significant impact on the assembly’s scope and use by an application. A strong-named assembly has a fully qualified name that includes the assembly’s name, culture, public key, and version number. The runtime uses this information to locate the assembly and differentiate it from other assemblies with the same name. For example, a strong-named assembly called myTypes could have the following fully qualified name:
“myTypes, Version=1.0.1234.0, Culture=”en-US”, PublicKeyToken=b77a5c561934e089c
In this example, the fully qualified name indicates that the myTypes assembly has a strong name with a public key token, has the culture value for US English, and has a version number of 1.0.1234.0.
Code that requests types in an assembly must use a fully qualified assembly name. This is called fully qualified binding. Partial binding, which specifies only an assembly name, is not permitted when referencing assemblies in the .NET Framework.
Note The runtime treats assembly names as case-insensitive when binding to an assembly, but preserves whatever case is used in an assembly name. Several tools in the .NET Framework SDK handle assembly names as case-sensitive. For best results, manage assembly names as though they were case-sensitive.
How CLR locates assemblies
By default, the runtime attempts to bind with the exact version of an assembly that the application was built with. This default behaviour can be overridden by configuration file settings.
The common language runtime performs a number of steps when attempting to locate an assembly and resolve an assembly reference. Each step is explained in the following sections.
The preferred way to reference an assembly is to use a full reference, including the assembly name, version, culture, and public key token (if one exists). The runtime uses this information to locate the assembly (applicable to static or dynamic assembly).
The runtime uses the following steps to resolve an assembly reference:
- Determines the correct assembly version by examining applicable configuration files, including the application configuration file, publisher policy file, and machine configuration file. If the configuration file is located on a remote machine, the runtime must locate and download the application configuration file first.
- Checks whether the assembly name has been bound to before and, if so, uses the previously loaded assembly.
- Checks the global assembly cache. If the assembly is found there, the runtime uses this assembly. – happens only if assembly have strong name.
- Probes for the assembly using the following steps:
- If configuration and publisher policy do not affect the original reference and if the bind request was created using the Assembly.LoadFrom method, the runtime checks for location hints.
- If a codebase is found in the configuration files, the runtime checks only this location. If this probe fails, the runtime determines that the binding request failed and no other probing occurs.
- Probes for the assembly using the heuristics described in the probing section below. If the assembly is not found after probing, the runtime requests the Windows Installer to provide the assembly. This acts as an install-on-demand feature.
Locating the Assembly through Probing
If there is no <codeBase> element in the application configuration file, the runtime probes for the assembly using four criteria:
- Application base, which is the root location where the application is being executed.
- Culture, which is the culture attribute of the assembly being referenced.
- Name, which is the name of the referenced assembly.
- Private binpath, which is the user-defined list of subdirectories under the root location. This location can be specified in the application configuration file and in managed code using the AppendPrivatePath property for an application domain. When specified in managed code, the managed code privatePath is probed first, followed by the path specified in the application configuration file.
Probing the Application Base and Culture Directories
The runtime always begins probing in the application’s base, which can be either a URL or the application’s root directory on a computer. If the referenced assembly is not found in the application base and no culture information is provided, the runtime searches any subdirectories with the assembly name. The directories probed include:
[application base] / [assembly name].dll
[application base] / [assembly name] / [assembly name].dll
If culture information is specified for the referenced assembly, only the following directories are probed:
[application base] / [culture] / [assembly name].dll
[application base] / [culture] / [assembly name] / [assembly name].dll
Dynamic assembly loading
You can use AppDomain.Load method to dynamically load an assembly which is some time required when you are working on plug-in driven framework.
However There’s no way to unload an individual assembly from AppDomain without unloading all of the AppDomains containing it. This can by done by calling AppDomain.Unload() for each AppDomain that has it loaded. But you don’t want to unload your primary AppDomain to just unload a single plugin. Solution is to create dedicated AppDomain for plugins.
Application Domain (AppDomain)
When we launch the Notepad program in Windows, the program executes inside of a container known as a process. We can launch multiple instances of Notepad, and each instance will run in a dedicated process. Using the Task Manager application, we can see a list of all processes currently executing in the system.
A process contains the executable code and data of a program inside memory it has reserved from the operating system. There will be at least one thread executing instructions inside of the process, and in most cases there are multiple threads. If the program opens any files or other resources, those resources will belong to the process.
A process is also boundary. Erroneous code inside of a process cannot corrupt areas outside of the current process. It is easy to communicate inside of a process, but special techniques are required to communicate from one process to another. Each process also runs under a specific security context which can dictate what the process can do on the machine and network.
A process is the smallest unit of isolation available on the Windows operating system. This could pose a problem for an ISP who wants to host hundreds of ASP.NET applications on a single server. The ISP will want to isolate each ASP.NET application to prevent one application from interfering with another company’s application on the same server, but the relative cost of launching and executing a process for hundreds of applications may be prohibitive.
Application domains provide a more secure and versatile unit of processing that the common language runtime can use to provide isolation between applications. Application domains are typically created by runtime hosts, which are responsible for bootstrapping the common language runtime before an application is run. You can run several application domains in a single process with the same level of isolation that would exist in separate processes, but without incurring the additional overhead of making cross-process calls or switching between processes. The ability to run multiple applications within a single process dramatically increases server scalability.
AppDomain class represents an application domain, which is an isolated environment where applications execute.
The isolation provided by application domains has the following benefits:
- Faults in one application cannot affect other applications. Because type-safe code cannot cause memory faults, using application domains ensures that code running in one domain cannot affect other applications in the process.
- Individual applications can be stopped without stopping the entire process. Using application domains enables you to unload the code running in a single application.
- Code running in one application cannot directly access code or resources from another application. The common language runtime enforces this isolation by preventing direct calls between objects in different application domains. Objects that pass between domains are either copied or accessed by proxy. If the object is copied, the call to the object is local. That is, both the caller and the object being referenced are in the same application domain. If the object is accessed through a proxy, the call to the object is remote. In this case, the caller and the object being referenced are in different application domains. Cross-domain calls use the same remote call infrastructure as calls between two processes or between two machines. As such, the metadata for the object being referenced must be available to both application domains to allow the method call to be JIT-compiled properly. If the calling domain does not have access to the metadata for the object being called, the compilation might fail with an exception of type System.IO.FileNotFound. See Accessing Objects in Other Application Domains Using .NET Remoting for more details. The mechanism for determining how objects can be accessed across domains is determined by the object. For more information, see MarshalByRefObject Class.
- The behavior of code is scoped by the application in which it runs. In other words, the application domain provides configuration settings such as application version policies, the location of any remote assemblies it accesses, and information about where to locate assemblies that are loaded into the domain.
- Permissions granted to code can be controlled by the application domain in which the code is running.
You must load an assembly into an application domain before you can run the application. Running a typical application causes several assemblies to be loaded into an application domain. By default, the common language runtime loads an assembly into the application domain containing the code that references it.
If an assembly is used by multiple domains in a process, the assembly’s code (but not its data) can be shared by all domains referencing the assembly. This reduces the amount of memory used at run time. This method of sharing an assembly’s code is similar to the way in which the Microsoft Win32 API LoadLibrary shares code pages among processes that reference the same DLL. An assembly is said to be domain-neutral when its code can be shared by all domains in the process. The runtime host decides whether to load assemblies as domain-neutral when it loads the runtime into a process.
Threads and AppDomain
Threads are the operating system construct used by the common language runtime to execute code. At run time, all managed code is loaded into an application domain and is run by a particular operating system thread.
There is not a one-to-one correlation between application domains and threads. Several threads can be executing in a single application domain at any given time and a particular thread is not confined to a single application domain. That is, threads are free to cross application domain boundaries; a new thread is not created for each application domain.
At any given time, every thread is executing in one application domain. The run time keeps track of which threads are running in which application domains. You can locate the domain in which a thread is executing at any time by calling the Thread.GetDomain method.