Share via


Structuring a .NET Application For Easy Deployment

 

Stephen Scott
StreamByte Limited

February 2000

Summary: How to plan and design for application deployment options presented in the Microsoft .NET Framework. (11 printed pages)

Objectives

  • Understand how assemblies affect deployment
  • Understand the importance of unique namespaces
  • Recognize the advantages of good use of attributes in classes
  • Recognize the advantages of good use of application domains

Assumptions

  • You understand Microsoft® .NET development fairly well
  • You have a basic understanding of assemblies
  • You have a basic understanding of namespaces

Contents

Deployment Philosophy
Fundamentals of .NET Deployment
Namespaces
Attributes
Application Domains
Deployment Differences Between .NET and Visual Basic 6.0
Summary

Deployment Philosophy

One of the main advantages of the .NET Framework is its new approach to application deployment. Microsoft has developed a system that is simpler yet far more flexible than any previous deployment technique.

With this new method of deployment, there are a number of options that are best dealt with when designing your application. This document discusses deployment methods from an application design perspective.

Fundamentals of .NET Deployment

Every Microsoft .NET application will be deployed in the form of one or more assemblies. An assembly is a logical unit of functionality that is made up of one or more files. One of these files will contain the assemblies' manifest(s). A manifest is the metadata that describes the assemblies' identity, publicly exported types, files, and dependencies.

Assemblies

Assemblies can be deployed as either private or shared and can consist of either a single file or multiple files. Understanding the repercussions of deploying an assembly in any of these ways in a certain manner enables you to make decisions about how an application can be broken down into different assemblies and how to deploy those assemblies.

Note All the deployment options detailed in this document assume that the .NET Framework has been installed on the target machine.

Single File Deployment

Using Microsoft .NET, it is possible to deploy your application as a single assembly made up of a single .exe file. This method makes deploying your application very simple, although not every application is well suited to this design.

If your application uses only the .NET Framework classes, the .exe file method of deployment might be suitable as your application is probably quite small and you are unlikely to want to deploy any individual aspect of the application separately from the whole.

Let's look at the simple program shown below that is suitable for single .exe deployment. This Microsoft Visual Basic® .NET Windows Forms project was created using Microsoft Visual Studio® .NET. It has one form that contains a button, a text box, and a list box. The project compiles to a single .exe file assembly of about 10KB.

Figure 1. A small example program compiles to a single 10KB assembly

The .exe assembly file can be copied to any machine that has the .NET Framework installed and will run without any further configuration. This method of simply copying the required files to the target machine is called XCOPY deployment.

Private Assemblies

The .NET Framework is all about classes and its design encourages you to write applications based on classes. When writing classes, it is sensible practice to place related classes into a class library. When you build these class libraries in Visual Studio .NET, you end up with an assembly in the form of a .dll. Any program that needs to use any of the classes in your library requires access to the .dll assembly. An assembly deployed to the same directory as the application is called a private assembly.

Let's assume that the program ListBoxAdd.exe that you just looked at requires access to a class that lives in a new class library called LBClasses.dll. You will need to deploy two files for your application to work properly, ListBoxAdd.exe and LBCLasses.dll. As before, you can simply copy these two files to a directory on the target machine.

When an application tries to reference an assembly at runtime, by default it looks for that assembly in the same directory as the application. So in this case, as soon as ListBoxAdd.exe tries to reference any class in your new class library, the .NET Framework common language runtime looks for the LBClasses.dll in the same directory as the application.

LBClasses.dll is only available to ListBoxAdd.exe or any other application in the same directory. If you ever needed to use LBClasses.dll in another application located in another directory, you would deploy LBClasses.dll into that application's directory. (We will look at how to share assemblies in the Shared Assemblies section.)

If your application requires a large number of assemblies, you may wish to deploy them in a more organized way than simply placing then in the application directory. The common language runtime allows the deployment of assemblies in well-named subdirectories or in custom named directories referenced by an application configuration file and yet maintains the principles of private assemblies. Let's look at both these techniques.

Using Directories

The process the common language runtime uses to locate required assemblies is known as probing. If the runtime cannot find an assembly in the applications directory, it probes for a subdirectory with the same name as the assembly it is looking for. The file extension is not considered part of an assembly name.

Figure 2. Assemblies will be found if they are put in subdirectories with the same name as the assembly

So you could restructure your application directory structure as in Figure 2, and copy the LBClasses.dll into the LBClasses subdirectory. Your application will run without any further configuration.

Application Configuration Files

It is possible to configure an application by using a configuration file in the same directory as the application. The name of the configuration file is the name of the application with a .config extension. So a configuration file for the ListBoxAdd application is called ListBoxAdd.exe.config.

Figure 3. Application configuration files live in the same directory as the application and have the same name as the main application assembly followed by .config

Application configuration files contain settings specific to an individual application. These settings include the assembly binding policy that defines how assemblies should be probed for.

Should probing fail to find an assembly in either the application directory or a subdirectory with the same name as the assembly, it next looks for probing instructions in the application configuration file (if one exists).

Here is an example application configuration file.

<configuration>
<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <probing privatePath="AssemblyDir1;AssemblyDir2"/>
  </assemblyBinding>
</runtime>
</configuration>

This application configuration file uses a probing tag to set the private path to be probed to include two directories called AssemblyDir1 and AssemblyDir2. These directories are considered subdirectories and exist below your application directory.

Summing Up Private Assemblies

Using private assemblies allows you the benefit of splitting your application into multiple assembles for maintenance purposes: you can replace any assembly at any time and it will only affect your application. There is also the downside that any assembly used by more than one program has to be deployed multiple times. Private assemblies don't provide versioning functionality (which you can read about next in the Shared Assemblies section) so it is up to you to ensure that the assembly deployed is compatible with the other assemblies' requirements.

Shared Assemblies

Often, an assembly is required by more than one application or even a host of unknown applications. When this is the case, it is desirable not to have to deploy the assembly privately with every application, but rather to make it available to any application that needs it. The .NET Framework makes this possible through shared assemblies.

The Global Assembly Cache

Shared assemblies are commonly installed in the Global Assembly Cache (GAC). The GAC is a machine-wide store of assemblies.

Because assemblies stored in the GAC are public, they must have what is called a strong name, which is made up of the assembly actual name, version (discussed in the next section), culture, and public/private key pair. The entire process involved in installing assemblies into the GAC is beyond the scope of this document, but the implications of it are not.

Because you now need to generate keys and run install utilities, you no longer have the straight XCOPY deployment scenario you had with private assemblies. Installing assemblies into the GAC is a lot less messy than COM registration used to be but it still requires action. Also, uninstalling an application is more complex because any shared assemblies installed with the application also need to be uninstalled. Because of these limitations, applications that need XCOPY deployment can still copy the shared assembly into the same directory as the application. Deleting a directory and subdirectories can intentionally or inadvertently uninstall an application that uses only private assemblies.

Generally, when designing an application deployment scenario, you should try to use private assemblies whenever possible. Any assembly should only be shared if it is required by multiple applications and its shared deployment is seen to be simpler than multiple private deployments.

A good example of where shared assemblies are needed is the .NET Framework itself (see Figure 4). Every application you deploy needs to use the Framework's assemblies and it would not be pleasant to have to deploy the entire Framework as a set of private assemblies with every application.

Figure 4. The .NET Framework is deployed as a set of shared assemblies

Assembly Versioning

In traditional Windows applications, shared libraries are deployed as standard Windows .dlls and are often placed in the %System% directory. The problem with this is that two vendors could try and deploy the same library .dll with their applications. Only one copy of a given .dll is able to exist in the directory, so one vendor could overwrite another vendor's installation of a library. If both installations use the same version of the library, this would not cause a problem. But if one vendor installed a newer version of the library, the application using the older version might stop working. This means that vendors had to try to make all copies of their libraries backward compatible (and often failed). This situation can sometimes also occur when an application blindly overwrites the vendor library with an older version. This whole mess of a situation is commonly known as "DLL HELL."

In Microsoft .NET, different versions of the same assembly can not only exist at the same time in the GAC, but also can be loaded at the same time.

In Figure 5, you can see three copies of the LBClasses assembly in the GAC.

Figure 5. The GAC allows multiple versions of the same assembly to be installed and loaded at the same time

If you look at the Version column, you will notice that each copy of LBClasses has a different version number. These four-part version numbers are made up as follows:

MajorVersion.MinorVersion.BuildNumber.Revision

It is generally assumed that changes in revision and build are still compatible, and changes in minor or major versions are incompatible.

A version number was added to the assembly by adding the code in one of the code samples below. The first sample is Visual Basic .NET code and the second is C# code.

Imports System.Reflection

<Assembly: AssemblyVersion("1.0.0.0")> 
<Assembly: AssemblyKeyFile("C:\originator.key")>
using System.Reflection;

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyKeyFile("C:\\orginator.key")]

If you fully qualify the assembly version number (as in the samples above), it is your responsibility to update any part of the version number that you wish to change. It is possible to get Visual Studio .NET to automatically update the revision each time you build the assembly by declaring the version number as 1.0.0.*. If you want both the revision and build number updated automatically, declare 1.0.* as the version number. When building an assembly, Visual Studio .NET updates the version number on every build.

When an assembly is changed and rebuilt, it is then reinstalled into the GAC, which will add this new version and keep the old version as well.

Now that there are two versions of the assembly in the GAC, you need to know which one the ListBoxAdd will use. Fortunately, this is fairly simple.

If it exists, the application uses the version of the assembly with which it was built. If the application tries to access another version, it raises an error unless you specify in the application's configuration file that it should use a different version. An example of using the application configuration file to load a different version of an assembly is shown below.

<configuration>   
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            
          <dependentAssembly>
            <assemblyIdentity name="LBClasses" 
                              
                 publicKeyToken="a9f5e3774993ed86"
                 culture="" />
            <bindingRedirect oldVersion="1.0.599.29986"
                               
                             newVersion="1.0.599.30067"/>
            </dependentAssembly>

        </assemblyBinding>
    </runtime>
</configuration>

Versioning now allows shared libraries to be updated without the need for backward compatibility, which in turn means new versions of applications can be restructured without concern for older versions.

Multifile Assemblies

Multifile assemblies are assemblies made from more than one file. A component can be an .exe file a .dll file or a resource file. The components together make up a logical notion of an assembly. One component contains the manifest that describes all the files in the assembly.

When to Use Multifile Assemblies

Multifile assemblies can have great advantages for multi-developer projects. Often, you have a logical piece of work that is best seen as a single entity, but for development purposes fits better as smaller units of work. Using multifile assemblies allows developers to produce a series of components (e.g., .dlls or .exes) that will be seen logically by the application as a single entity.

Let's look at an example: say ten developers each produce a component. A coordinator could then produce an eleventh component that contains a manifest for the whole assembly (or collection of files). These eleven files are deployed as if they were a single assembly. The application that uses this assembly at first only loads the component that contains the manifest. As different classes or methods are created or invoked by the application, the appropriate components are loaded. This means that the application only ever needs to load the components it actually runs.

To take the example further, if code changes are required, only the changed component and the component containing an updated manifest needs to be redeployed.

Multifile assemblies provide a realistic logical grouping of functionality in small, maintainable parts. They are a great way to optimize code for download, and they are useful for building an assembly consisting of code written in different languages.

Building Multifile Assemblies

Currently, Visual Studio .NET creates single file assemblies by default. You can create multifile assemblies using the /addModule input parameter on the command line compiler. Probably the easiest way to create a multifile assembly is to use a makefile. However, using makefiles and the command line compiler are beyond the scope of this document. For more information about building multifile assemblies, see the Multifile Assembly Example in the .NET Framework Developer's Guide.

Summing Up Assemblies

As you can see, deciding how you break your application into assemblies affects how you deploy those assemblies. Classes and code specific to the application are best deployed as private assemblies. Even assemblies that are shared between applications are sometimes best deployed multiple times as private assemblies to avoid GAC installation and versioning issues. However, assembly versions will still be checked even when they are deployed as private assemblies.

Choosing to use many assemblies or fewer multifile assemblies also affects the way you deploy and maintain an application.

Assemblies that need to be shared can be installed into the GAC. This complicates install (and uninstall) routines but allows assemblies to be generally available and provides other benefits, such as better performance.

All these options allow the application developer the facility of deploying various aspects of an application in the most suitable fashion. Yet for this to be possible the structure of the application has to be carefully considered in light of how it is to be deployed.

Namespaces

When writing your own class libraries, you should take note of the way you name namespaces. Namespaces were invented to help with object separation. It is possible to have two classes called class1 providing that they exist in different Namespaces. This practice only works if you ensure that namespaces are uniquely named.

For example, if you produced a library of graphics classes, it would not be sensible to place all the classes in a namespace just called "graphics", as any other producer of graphics libraries will probably do the same. This could cause namespace clashes if both libraries were placed on the same machine.

Note To the runtime, two types with the same name (including namespace) that are in different assemblies are completely different so you won't have conflicts at runtime.

The generally accepted way of ensuring namespace separation is to include your company or application name (or some similar identifier) in the name of your namespaces. Your graphics library might use the namespace YourCompanyName.Graphics, as shown below.

using System;

namespace YourCompanyName.Graphics
{
   public class WidgitClass1
   {
      public WidgitClass1()
      {
         // Code
      }
   }
}

Attributes

Metadata is one of the most noticeable assets of the Microsoft .NET philosophy. Metadata is the data about the application itself. It can provide you with information about your specific classes, properties, methods, and events. Attributes are often used for adding metadata to your classes, properties, methods, and events.

As you developed classes in Microsoft .NET, you probably used some of the predefined attributes available in the .NET Framework, such as ReadOnly, Public, Private, etc. You can also create your own custom attributes.

Custom Attributes

Custom attributes are implemented as classes. When you develop a custom attribute class, it inherits from System.Attribute. The custom attribute's positional parameters are implemented in the class's constructor and its named parameters are implemented as properties. You must also include an attribute that informs the compiler with which type of elements it can be used.

Detailed information on creating custom attributes is beyond the scope this document.

Using Custom Attributes

Using custom attributes in your classes will mainly be of benefit to other developers who use your classes. Commenting your classes with information such as author, bug fix numbers, and statements saying in which version code was introduced is great for a developer who has access to the source code. But sometimes, it would be great to embed information into your classes that could be seen by users of the class who don't have access to the source code. Custom attributes (like all attributes) are available to developers who use the class through a technique known as reflection. By putting information into your classes in the form of custom attributes, you can make this information that will help in their use of the class available to them.

You can view a class's custom attributes (along with the class's intrinsic attributes) using the ILDasm tool that ships with the .NET Framework. If you wish to supply a better user-friendly interface to viewing the custom attributes of your class, you can develop your own application to examine classes using the System.Reflection assembly. Information on using reflection in Microsoft .NET can be found in the Microsoft .NET Developer's Guide.

Application Domains

An application in Microsoft .NET runs in a process. A process is separated into one or more application domains. Every process has at least one application domain (the default), but can have additional ones as you create them.

Application domains are objects that act like a process within a process. Everything running in an application domain is isolated from any other application domains within the same process; meaning that if one application domain crashes it will not affect the others. Individual application domains within a process can be independently started and stopped.

Using Application Domains

Most applications you write probably only need the default application domain. You may choose to create additional application domains if you wish to isolate some code so that a desire to unload a given piece of code does not stop the whole program. An example of this might be if you have written an application that supports third-party plug-ins. You may wish to load each plug-in into a separate application domain to isolate each from the main program and from each other.

Application domains can also have different security environments, allowing you to run different parts of your program under different security constraints.

Creating Application Domains

An application domain is encapsulated by the AppDomain class. A new domain is created by calling the static CreateDomain method of this class.

AppDomain MyAppDomain = 
   AppDomain.CreateDomain("Some Friendly Name",null,null);

Once you have created an application domain, you can create instances of objects on that domain using the CreateInstance method of your AppDomain object.

A detailed explanation of coding application domains is beyond the scope of this document.

Deployment Differences Between .NET and Visual Basic 6.0

From what has been discussed so far, it should be fairly obvious that deploying a Visual Basic .NET application will be different than deploying an application written in Visual Basic 6.0.

  Visual Basic 6.0 Visual Basic .NET
Runtime Visual Basic 6.0 requires specific Visual Basic 6.0 runtime files to be deployed. Visual Basic .NET applications require the .NET Framework to be deployed regardless of the language used to develop the application.
Components Visual Basic 6.0 applications that use Microsoft ActiveX® Controls other than the standard Visual Basic ones need to deploy the required .dlls.

Each ActiveX .dll or OCX needs to be registered using RegSvr32.exe.

Visual Basic .NET applications using components or objects other than the standard .NET Framework need to deploy the correct assemblies.

These assemblies may not require registration of any form unless they are to be shared, in which case, the assembly should be installed in the GAC.

Summary

The way you design your application can affect the way it will be deployed, updated, and used. Microsoft .NET provides a lot of flexibility in its deployment options but they will only really be of benefit if you plan how to use them when you begin to design your application.

The architecture of your class libraries and assemblies is the key to keeping deployment and upgrading simple. The good practice of well-named namespaces could save you a whole lot of trouble, and the carefully planned use of attributes could be of benefit to both you and other users of your classes.

About the Author

Steve Scott runs StreamByte Limited, based in Gloucestershire, England. Since joining the software industry in 1987, he has worked in many industry sectors, using a multitude of programming languages and databases on more platforms than he cares to remember. In recent years, he has specialized in Web development as a developer, trainer, and consultant, in the British Isles, Europe, and the United States. Steve is the father of lots of small children with whom he spends what little spare time he has.

About Informant Communications Group

Informant Communications Group, Inc. (www.informant.com) is a diversified media company focused on the information technology sector. Specializing in software development publications, conferences, catalog publishing and Web sites, ICG was founded in 1990. With offices in the United States and the United Kingdom, ICG has served as a respected media and marketing content integrator, satisfying the burgeoning appetite of IT professionals for quality technical information.

Copyright © 2002 Informant Communications Group and Microsoft Corporation

Technical editing: PDSA, Inc. and KNG Consulting