microsoft press home   All Products  |   Support  |   Search  |   microsoft.com Home  
 
  Microsoft Press Home  |   Register Books  |   Site Index  |   All Books  |

 

Advanced Search
Hot topics:
Books about:
Books for:
Products for:



Developer Feature Article
Assembling Secure, Stable .NET Applications
Assembling Secure, Stable .NET Applications

By Scott Mauvais, MCSD

As I write this, Microsoft� just announced the release to manufacturing (RTM) of Microsoft Visual Studio� .NET, and the official launch at VSLive in San Francisco is just around the corner. So, I thought it would be a good time to talk about the processes involved in shipping your own .NET applications. Fortunately, streamlining the deployment of applications is a major design goal of the Microsoft .NET Framework, and the process is pretty straightforward. As a result, this article is a bit shorter than usual�so you can get back to building and deploying your new .NET applications.

I'll start off by looking at some of the deployment problems Microsoft sought to solve with the .NET Framework. Next I'll provide a quick introduction to assemblies, and then I'll show you how to use them to prevent version conflicts and ensure the security and integrity of your application. As with most elegant solutions, you'll find that the Microsoft .NET Framework provides a very simple (in a good way) solution to a complex problem.
 
 

My goal here is to give you the basic information you need to decide on your deployment strategy so that you can get your applications in the hands of your end-users right away. No fuss, no muss.

If you want to learn more about the deployment process, including the nitty-gritty of how the common language runtime (CLR) locates and executes your code, you'll want to pick up a copy of Jeffrey Richter's brand new book Applied Microsoft .NET Framework Programming from Microsoft Press�. He has two full chapters devoted to the ins and outs of deployment. I really like this book for two reasons. First off, if you have read either of his earlier books (and if you haven't you should)�Programming Applications for Microsoft Windows� and Programming Server-Side Applications for Microsoft Windows 2000�or his MSDN� (and before that MSJ) columns, you know how useful they are, and this book is just as good. Second, in addition to all the low-level info that you are used to in his books, this new book takes a practical approach by giving you proscriptive advice for common design and implementation challenges. To get a feel for what is in the book and to make sure it is right for you, check out the book's table of contents and read Chapter 6: Common Object Operations.    
 

The Complicated Problem

One of the big advances of Microsoft Windows was the concept of DLLs, which enable you to share code across multiple applications�no longer did each app need its own set of utility functions such as memory management, hardware interfaces, and the like. Back in the late 1980s, when disk storage and memory were very expensive, this led to an immediate and significant cost savings. This approach also held out the promise of increased quality because you had fewer people implementing the same functionality. In other words, everyone could use the Microsoft WinSock components rather than having to recreate them�and potentially introduce bugs�themselves.

The problem, of course, came when you had several applications using the same core set of DLLs, and you updated one of these applications. Oftentimes the update would replace one of these shared DLLs. Theoretically, this should not be a problem because the DLLs were supposed to be backward compatible. However, we all can tell painful stories that demonstrate that this was not always the case. This problem is exacerbated because the OS assumes there will be only one version of a given component on a system at any one time. Microsoft Windows 2000 does help address both of these issues through the use of the Windows File Protection (WPF) and Private DLLs (sometimes called side-by-side DLLs) features. The WPF feature prevents the replacement of files shipped with the operating system. With Private DLLs you can configure the application to instruct the operating system to check the application's private directory for dependent DLLs before looking in the search path.

Contrary to most people's beliefs (or at least their complaints), the root of the problem is not that a given DLL did not maintain backward compatibility. This is inevitable because sometimes you need to break backward compatibility to fix a bug or add some important new functionality. Rather, the problem is that there is no way to ensure that your application will use the same components at runtime that it did when you built, tested, and installed it. For example, I may build, test, and ship my application with Microsoft XML 2.6. After a couple of months of running the application flawlessly, a user decides to download and install the new version of Microsoft Internet Explorer, which just happens to update the XML parser to Microsoft XML 3.0. Will my app continue to work? What about version 3.5? 4.0? Of course, the answer is that it is very likely that the application will run because Microsoft XML has been very solid and has not suffered the regressions that some other components have. But that is not the point; the point is there is no way of guaranteeing this. As you will see later, the Microsoft .NET framework fixes this because you can insure that your application runs only with the specific version of the components that you built and tested it with.

A related problem is that cleaning up after an application is a messy process. When you install a Win32� application, it is almost as if it is a requirement that the installer disperse parts of it throughout every nook and cranny of your system. Besides creating the application's private directory, it almost always requires new Registry settings in your system. Beyond that, many applications place files in %SystemRoot%\System32 and %ProgramFiles%\Common Files and store user-specific data in locations such as %USERPROFILE%\Application Data. When it comes time to uninstall a program, tracking down and removing all its bits and pieces is often difficult.

Worse yet, because so many components are shared, whenever you uninstall an application you run the risk that it will remove a component that some other application is still using. While this should never, ever happen if the application and its installer are well behaved, a poorly written setup or uninstall program can wreak havoc on your shared components.

The end result of this is two-fold. First, over time, if you install and then remove many programs, you may start to experience some stability problems as applications start to get out of synch with the versions of the components they expect. Second, this risk of instability causes people to be reluctant about installing new software, out of fear that it will either overwrite the DLLs of existing applications, or they will never be able to purge all of it from their system. This not only increases the transaction costs associated with distributing your applications, but also often causes end-users to miss out on otherwise good applications.
Okay, enough of the problems with the current system, let's take a look at how the Microsoft .NET Framework addresses these issues.

 

The Simple Solution

The process for sharing and locating Win32 components is fairly complicated and has many moving parts (Registry entries, CLSIDs, AppIDs, ProgIDs, IIDS, Interface Definition Language, the Service Control Manager, DLLHost.Exe, and so on�oyou get the idea). When everything is working fine, the system is pretty elegant. However, when something goes awry it is often hard to track down the problem and undo any damage.

As with most systems that break because of their complexity, the solution is straightforward: simplify the process. If shared components are causing problems, reduce the dependence on shared components. If version conflicts are causing errors, have the runtime enforce versioning. If it's hard to find all the pieces of an application, place them all in one place. Well, this is just what the Microsoft .NET Framework does. Pretty simple, huh?

The key to solving DLL Hell is the .NET assembly. Assemblies are the most basic unit for deployment, versioning, and security enforcement. As such, you can look at them as the building blocks of .NET applications. An assembly is uniquely identified by four pieces of information: the assembly name, its version (in the major.minor.build.revision format ), culture (an RFC 1766-based identifier that specifies language and locality), and optionally a strong name (a guaranteed unique "name" generated by signing the assembly and providing a public key to verify the digital signature).

So what is in an assembly, you may ask? An assembly contains a manifest that both describes the assembly and contains a hash of each of the assembly's elements and the Microsoft Intermediate Language (MSIL) that the common language runtime executes. While it is an oversimplification, you can look at a .NET assembly as an improved version of the DLL (or EXE). The main improvement comes in the form of the manifest or metadata that is compiled into the assembly.

This metadata makes assemblies self-describing, meaning that an assembly has compiled into it all the configuration data the CLR needs to execute it, including the names and locations of any required external components and the runtime permissions required. This embedded configuration information allows for the much-touted XCOPY deployment of .NET applications because an application does not have any external dependencies. By implication, this enables zero-impact installations: if an application does not have any external dependencies, you can completely remove an application from your system simply by removing its directory from your computer. Furthermore, you can install different versions of the same assembly side-by-side on the same computer�you can (with a great deal of care) even use different versions in the same process. For more information on this trick, see About Isolated Applications and Side-By-side Assemblies in the platform SDK.

Besides easy setup and removal of applications, this metadata provides a framework for developers to specify their application's versioning requirements and security policy. Better yet, at runtime, CLR will inspect the metadata and enforce the rules defined by you, the developer. So it is written (in the metadata); so it is done (by the CLR).

To get a better understanding of how versioning works, let's take a simplified look at what happens when an application starts up. For a more detailed description, see Jeffrey Richter's book Applied Microsoft .NET Framework Programming. When an application starts, only the assemblies the application initially calls must be present. If the application needs additional assemblies, the CLR can retrieve these on demand from the code-base specified in the application's configuration file. The benefit here is that if you have portions of your application that are rarely used, you can place them in separate assemblies and reduce the time it takes users to download and install your application and the disk space it requires.

If the assembly includes a strong name, when CLR attempts to locate it the assembly will ensure that it returns the proper version to the calling application. By default, it will load only an assembly that exactly matches the version that the developer used to build the application. You can, of course, override this behavior with the configuration file and instruct the CLR to always load the newest version or potentially some other specific version (for example, an updated version with a bug fix).

  

Make Sure It Is Secure

It is important to remember that the CLR only guarantees version compatibility if you are using strong names. While you may think this is a limitation at first, when you understand the rationale it makes perfect sense. Without the digital signature included in the strong name, there would be nothing to prevent someone from creating their own assembly and giving it the same name, version, and culture as your original. It would be counter-productive to rely on the CLR to ��guarantee�� something that it has no way of verifying.

The strong name prevents this sort of forged-assembly Trojan Horse. Remember that the assembly's manifest contains a hash of each of the elements that make up the assembly, and the strong name adds your digital signature to the assembly. Therefore, when you use strong names, the CLR can guarantee not only that it loads the exact version you specify but also that by comparing the digital signature and your public key it can verify that no one has modified the assembly since you built your application. A subtle but important corollary is that the CLR can also ensure that no one forges an updated version of your component but you. Were someone to attempt this sort of attack, the CLR would prevent the forged assembly from loading because the digital signature would be invalid.

This concludes my overview of assemblies. I started off by looking at some of the design goals for deploying applications based on the Microsoft .NET framework. Next I walked you through the features of .NET assemblies and showed you how they ease deployment and prevent versioning conflicts. I concluded with a discussion of how the CLR uses strongly named assemblies to ensure that you are loading the exact assembly you specify with the assurance that no one has modified it.

 

Microsoft Press Solutions

Now that you have an overview of .NET assemblies and have seen how easy they are to work with, you should be ready to start deploying your own Microsoft .NET applications. As I mentioned in my intro, my goal was to quickly give you the basic information you need to safely and securely deploy your apps. The best way to ramp up on all the features and best practices for deploying your applications is to get a copy of Jeffrey Richter's Applied Microsoft .NET Framework Programming. Microsoft Press� provides in-depth documentation for these and all the other issues related to developing for .NET. For a complete list of .NET titles from Microsoft Press, see the Inside Information About Microsoft .NET page.

For a broader overview of all the Microsoft .NET technologies, you should look at David S. Platt's Introducing Microsoft .NET. It has some great information on COM Interop, Windows Forms, and .NET memory management.

One of my favorites is Jake Sturm's book, Developing XML Solutions, which examines XML-related technologies for data exchange and shows how XML fits in the Microsoft Windows architecture.

For a complete list of all the developer titles, see the Developer Tools section.

For some great links and the latest information on XML Web Services, see the MSDN Web Services home page. For more information on Microsoft Visual Studio .NET, see the Visual Studio Next Generation Home Page.

Top of Page
 
Last Updated: Tuesday, March 5, 2002