From the June 2002 issue of MSDN Magazine.
Return of the Rich Client
Code Access Security and Distribution Features in .NET Enhance Client-Side Apps
|This article assumes you're familiar with C#, Visual Basic .NET, and the CLR|
|Level of Difficulty 1 2 3 |
|Download the code for this article: Rich.exe (161KB) |
|SUMMARY Rich clients employ many of the features and conveniences of the operating system they run on, and the list of these features has been growing since the dawn of the PC. But as apps have migrated to the Web, the trend towards increasing client-side functionality has ground to a virtual halt. There are several reasons for this; chief among them are security and deployment problems. But that's all about to change. With the .NET Framework, you can participate in building the distributable rich client of the future.|
In this article, the author enumerates the pertinent features of .NET that will allow you to build safe, easily deployable controls. The features discussed include managed code, code access security, versioning control, Windows Forms classes, and isolation..
|n all of the recent coverage of the Microsoft® .NET Framework, I have yet to see discussed the return of the rich client. A rich client is any software running natively on a client machine. This includes traditional applications like word processors and spreadsheets, and also includes Web-deployed executables such as legacy ActiveX® controls and the new Windows® Forms controls. Examining the rich client requires a look at a number of seemingly unrelated features of the .NET Framework such as Windows Forms and security.|
So what makes the rich client rich? The rich client has access to the windowing and GUI features of the operating system on which it runs. This means drag and drop editing, toolbars and menus, animation, and all of the other bells and whistles. These are the features that are often difficult or impossible to implement using markup languages such as HTML or even DHTML.
The rich client also has access to other local resources such as the file system, printers, and sound cards. Yet more and more software is being written with limited or no access to these features. Why is that?
After I talk about what happened to the rich client, I will discuss trusted code and security as well as deployment of code that runs on the .NET Framework (managed code). These are the two features that play the most important roles in the rich client scenario. I will explore how .NET solves deployment issues and then cover other parts of the .NET Framework that provide a tool chest of features for developing rich client software. I'll also show an example that uses partially trusted code and code access security.
Where Did the Rich Client Go? Obviously, the rich client is not completely gone. I use Microsoft Outlook® and Microsoft Word almost every day, both good examples of rich client software. However, while major software publishers publish software that fits this label, enterprise Web development is moving to Web interfaces handled completely by server-side processing. Of course, there are some good reasons for this trend; difficult deployment, isolation, and security models are a few of the unfortunate reasons for the shift away from the rich client. Fortunately, the .NET Framework goes a long way to alleviate these problems.
Let's start with deployment and installation. You've heard the term DLL Hell applied to the situation in which the installation of one application causes another to fail due to incompatibilities in updated versions of one or more DLLs. The sheer number of installation scenarios that need to be tested and tweaked before deploying applications and the deployment nightmares that can turn up later cost both the software publisher and the customer considerable time and money.
Meanwhile, deploying a new Web page to a user's desktop is unlikely to affect the user's existing OS and application installations. In fact, from the system's point of view the same software is being run: the Web browser. It's no mystery why software publishers have been migrating their products to server-side Web-based tools, often at the cost of some functionality to the user. I will show you how the .NET Framework eradicates the deployment issues that have plagued you since the inception of the DLL.
In an enterprise environment, deployment of rich clients is the primary issue. The Internet has two major considerations: compatibility and security. The .NET Framework is built to alleviate both of these issues to some degree.
One reason not to use ActiveX controls in your Web-deployed software is security. The best-case scenario is that the user is presented with a confusing dialog box asking if they trust the software published by a certain publisher. Each time a user clicks the yes button they could possibly undermine their system. If the user says yes, then that ActiveX control can do anything on the system (it has as much access to the system as the user does). This means that a malicious control can undermine a user's system, and even a well-intentioned (but buggy) control can cause serious security problems. The bottom line is that up until now the rich client hasn't fit well into the world of Internet-deployed software.
.NET Framework to the Rescue Managed code strives to solve these problems. You can deploy a managed application in exactly the same manner that you would deploy a Web site. The Common Language Runtime (CLR) uses a new component-binding functionality for referencing managed assemblies (the managed version of a DLL) that alleviates versioning and DLL concerns.
Managed code can run in a partially trusted state meaning that the code can perform functions such as drawing graphics and windows, creating toolbars, implementing drag and drop, and persisting data to the hard drive. Meanwhile, partially trusted code can be barred from general access to the network and file systems, access to the registry, and other protected resources of the system. All of this comes without ever asking the user if they trust the code contained in the control. This is a huge step forward.
While I'll focus primarily on those ideas, for exciting client software it is necessary to use a variety of features found in the .NET Framework class library. I'll provide code that uses classes from the System.Windows.Forms, the System.Drawing namespaces, and a few others. If you are interested in finding more introductory material on Windows Forms, see Jeff Prosise's "Windows Forms: A Modern-Day Programming Model for Writing GUI Applications" (MSDN Magazine, February 2001).
Partially Trusted Code Before getting too deep into this topic, let's take a look at a simple example of partially trusted code. The code shown in Figure 1 is a very simple managed application written in C# that outputs the contents of a text file to the console window. As a matter of fact, the meat and potatoes of the application are in the second and third lines of the Main function, where an instance of the StreamReader object is used to retrieve the contents of a file, which is then written to the console.
Unspectacular though it may be, this application demonstrates one of the most interesting features of managed code: code access security. To try this out, follow these steps: build this application into an executable file, and run it once from the command line, passing the name of any text file as the argument, just to see it work. Now copy the executable to a shared volume on the network or better yet, map the directory on your local system where the file lives as a network share, using the command-line utility net.exe:
This will map your directory to a drive letter. Now, rerun the executable, and this time do it from the newly mapped drive. You should still reference the same file as the one you used on the previous attempt (using the local rather than the mapped drive letter).
net use * \\[machine name]\c$\[directory]
You should see a message that states that the application failed to open the file due to lack of security privileges. What really happened is that an exception of type SecurityException was thrown when the application attempted to instantiate the instance of StreamReader. The application then caught the SecurityException and displayed the error message to the console.
Code Access Security Code access security is a very powerful feature. It allows systems to be configured to execute partially trusted code without prompting the user. In fact, this is the default setting. Meanwhile, the partially trusted code is only allowed to do things appropriate to its level of trust.
Before moving forward, I would like to clarify a couple of points. The system decides what privileges the application, or more specifically the managed assembly, is allowed based on the location of the assembly file. This is why the assembly file has different abilities when it is launched from an enterprise share as opposed to the local machine. (It is also important for you to note that managed security policy is very configurable.)
Also, the abilities of the FileToConsole.exe file are not entirely based on the logged-on user account. Although restricted by this, code access security further restricts the application depending on where it came from. This ability will remove the user's fear of running code from the Internet. Furthermore, code access security is more flexible and robust than a sandbox, although it can be used to solve similar problems.
Before setting the FileToConsole.exe aside, I would like to mention one other test you can try. If you launch the file from a share, let's say from the x: drive, then you are not allowed access to files on your local system. If you pass a file name relative to the x: drive, for example x:SomeText.txt, then the application will succeed because the file comes from the same location as the application. This further demonstrates the real flexibility of code access security.
Code access security is used throughout the .NET Framework class library. It is the backbone of partially trusted code and is not restricted to file access, but rather affects everything from network access to windowing and more. When you use it in your own component code, users will be able to execute code regardless of origin (such as from an Internet site), and their systems will remain safe. This allows you to write code that is featureful, executes in native machine language, and is distributed across a LAN or even over the Internet. You can take advantage of this feature with only a minimal exposure to the underlying mechanics of code access security.
All managed code, whether it is a standalone application, a control, or a DLL full of reusable types (or some mix of all of these), is packaged in a unit called an assembly (the unit of distribution, versioning, and security for managed code). For now, think of an assembly as a managed DLL or EXE file. When an application references a type or code from another assembly, the CLR must locate the file or files for the assembly and load them into your process. At assembly load time, the CLR associates security permissions with the newly loaded assembly based on evidence including, but not limited to, the location from which the assembly is loaded.
Once the permissions are associated with the assembly, they will affect what the code in the assembly can do for the life of the application. Amazingly, an application can be comprised of multiple assemblies, each of which may be allowed or disallowed the use of different parts of the system based on the differing evidence found for each assembly.
If this is starting to sound a little confusing, perhaps an example will help. Suppose you have written a managed executable named MyApp.exe that is installed locally. MyApp.exe also references another managed assembly named Types.dll that is installed on a network share (see Figure 2). The reason to reference another assembly is simply to make use of a type in that assembly, so let's assume that the SomeTypes.dll assembly includes the implementation for the CoolType class.
Figure 2 Referencing an Assembly
Your main assembly is MyApp.exe and is stored on your local file system. This file is likely to contain the definition for several classes, including one that implements the static Main method that is used as the entry point for your application. Because MyApp.exe is stored on your local file system, all of the methods in all of the types in your assembly have total access to the system.
When a thread crosses the assembly boundary from MyApp.exe to Types.dll, the security will be restricted to the permissions awarded assemblies found on network shares. The thread crosses this boundary simply by calling into a method, constructor, or property in a type (such as CoolType) implemented in Types.dll. When the method eventually returns, the security permissions will be those of the more privileged MyApp.exe (see Figure 3).
Figure 3 Executing Code in Assemblies
Code access security is a stack-based algorithm. The abilities of the code differ depending on the location of the instruction pointer in the managed application. The system is able to detect which method is attempting to perform a protected action and then, depending on the permissions of the assembly in which the method exists, allow the action to be performed or throw an exception of type SecurityException.
There are two significant points about code access security. Code that works great when launched from a local drive may begin to throw security exceptions when launched from a network share or when run as a control embedded in an HTML page. This is because your assembly no longer has the unbridled access that it enjoyed when run from the local drive. Furthermore, code access security eliminates the need for messy dialog boxes asking users whether they trust your network or code downloaded from the Internet. Your managed code will just run (albeit with limited system access). This makes your code more useable and is one reason why the rich client is back in use.
If your software is going to be distributed over any kind of network, then it is likely that it will be partially trusted. This means that certain features of your application, such as file access, are likely to begin causing security exceptions. Your software should be able to recover from the security exceptions that might arise from its normal behavior as well as alter its functionality seamlessly.
A simple example of altered functionality would be to gray out menu options that are no longer available due to security restrictions. A more complex example would be to alter your storage facilities from the general file system to an unrestricted solution such as isolated storage (which I'll discuss later). In general, writing partially trusted applications is something of an art because security restrictions will crop up in a variety of locations and you must decide how your application is to behave when it is not allowed to perform certain functions.
Deployment and the .NET Framework Earlier I discussed some of the deployment-related problems that have plagued software developers. These problems tend to be related to versioning of components and libraries that are not entirely compatible with software that is already installed on a system. Other deployment issues can be troublesome, too. For example, ActiveX controls are COM objects that require individual registration on the system. This is another factor that separates the rich client from the typical server-side application.
Ideally, you want the rich features of code executing on the local system while realizing the deployment flexibility that Web sites and server-side code enjoy. Web developers can tweak and adjust their software as often as they want and not have to concern themselves with installations or the configuration of the client system. The deployment model of the .NET Framework provides a similar flexibility.
The .NET Framework CLR manages component loading using an assembly resolver. The deployment problems collectively known as DLL Hell are largely caused by the relatively primitive fashion with which one software library or executable binds to other components. With DLLs you use the file name of the DLL, and with COM objects you use a GUID. The CLR assembly resolver introduces the idea of a strong name that can be applied to any component DLL (or assembly), which increases the information that distinguishes a component library from other libraries on the system.
A strong name consists of the library's file name, a public key and digital signature (using the matching private key), a version number, and a culture identifier. The introduction of strong names for loading component files makes several things possible. It facilitates the coexistence of multiple versions of the same DLL or assembly on a single system. If code binds to an assembly using its strong name, it will then be possible for the assembly resolver to distinguish between the various versions of the assembly installed on the system.
Strong names also allow for configurable binding to libraries. Imagine that two assemblies have matching strong names, but one is v188.8.131.52 and the other is v184.108.40.206. By default, code that referenced v220.127.116.11 at compile time will always bind to that assembly and will ignore any future patches installed to the system. It is also possible to install a configuration file to the system that will cause assemblies binding to the v18.104.22.168 to automatically upgrade their bind to v22.214.171.124. These configuration settings can be set at the application or systemwide level and can also be easily reverted. This allows for both patching of components as well as the ability to easily enforce the exact component binding that existed originally at compile time.
The inclusion of public and private key information in the strong name enables cryptographically sound binding to libraries. Not only is it possible for the assembly resolver to ensure that your code is binding to the same file referenced at compile time, but it is impossible for even a single bit of the file to be changed without the loader detecting the alteration.
Strong names are not the only improvements brought to the table by the CLR assembly resolver. The resolver also simplifies the search semantics for library files by looking in two places for a component library. If the library has a strong name, then it will first look in a system-wide component store known as the Global Assembly Cache (GAC) for a matching assembly. If no match is found or if the assembly does not have a strong name, then the AppBase directory is searched for the assembly. (The AppBase is the name given to the directory from which a managed executable was launched.)
Assemblies located in the AppBase directory are considered to be privately deployed. The advantages of the AppBase directory are fairly simple. The assembly resolver remembers the URL from which your assembly was loaded, for example http://www.Wintellect.com/MyAssembly.dll. At the time that your application needs to bind to assemblies that it needs, the assembly resolver remembers to check at the same location (the AppBase directory) for additional assemblies. Also, you can include a config file in the AppBase directory named after the assembly, which allows for custom extension to the assemblies considered in the AppBase directory for more flexible storage and binding for library files.
The bottom line of the deployment features of the CLR assembly resolver (and therefore the .NET Framework) is that you can deploy a simple hyperlink or a shortcut (encapsulating a hyperlink) on your users' systems that points to your application code deployed on the network. Your application and component assemblies are deployed on the server and the CLR will load them if and when they are needed.
If one of the strongly named assemblies that your distributed application code binds to is already located on the client system (in the GAC), it is possible for the assembly resolver to ascertain that it is the same library file and therefore does not need to download the assembly from your network server.
Writing Partially Trusted Software The .NET Framework provides simplified deployment semantics similar to those enjoyed by Web developers. The .NET Framework also enforces code access security so that your users can safely run your code on their machines (in native machine language) without fearing that the code will undermine their system or data. But how do you, as a developer, write partially trusted applications that exploit these features?
To write network-deployed rich clients you must be aware of two things: the limitations imposed by code access security and the classes and features of the .NET Framework that promote the development of richly featured applications. First, I'll discuss security restrictions.
Writing partially trusted applications can be challenging, with the biggest challenge coming from handling the security restrictions imposed upon your software by code access security. The last thing you want your application to do is fail to work, or worse, raise some confusing dialog telling the user that a SecurityException was thrown. You must be prepared for security restrictions when writing partially trusted code.
The first step towards writing robust partially trusted applications is to consider the zones from which the software will execute. Zones are a subset of a code access security feature known as evidence. When your assembly is loaded, part of the evidence gathered about the assembly is the zone from which the assembly was loaded. Zone evidence plays an important role in establishing the permissions granted to your code. The zones are shown in Figure 4.
The host uses the location of your assembly to apply zone evidence as your assembly loads. All managed applications should be tested in the MyComputer and Intranet zones, and any browser-deployed application should be tested in the Internet zone.
There are different ways to perform these tests. The simplest is to copy the code to a network location and execute it. I have also included with this article a download file with the sources for a utility called Zoner.exe that allows you to select an arbitrary zone for executing a managed executable.
When writing partially trusted code, you can find security restrictions by testing in the different zones, but you should also write code that handles restrictions when they occur. To do this successfully you must be aware of the types of system access that are likely to cause a security exception, and then you must know how to handle the exception. Let's discuss some of the features that are likely to be restricted due to security.
File system access is the big one. You should expect that access to the local file system will be restricted when writing partially trusted applications. When accessing files and resources that are deployed with your assembly, use the code Assembly.GetExecutingAssembly.Location to obtain the path to your assembly.
Like the file system, registry and network access is likely to be restricted. A good rule of thumb is that for partially trusted applications, network communication is only allowed to the URL that is the source of the assembly and your assembly must use the same protocol with which the assembly was loaded.
Software running in the Internet zone can only print by using a common dialog. Software running in the Intranet zone is restricted to default printer use only unless it uses a common dialog to allow the user to select a printer.
The next area is serialization. Runtime serialization is a great way to persist the state of your objects to a file or some other store. However, the ability to rebuild an object from arbitrary data is restricted to code running with full trust. As for XML serialization, the XmlSerializer type should technically be useable by partially trusted code.
What about clipboard operations? Pasting from the clipboard programmatically is a restricted feature of managed code. This is because the clipboard may contain sensitive information.
Partially trusted code is not allowed to register a handler for the GUI unhandled exception filter represented by the Application.ThreadException event. Although this limitation is understandable (this handler is an AppDomain-wide handler), the inability to register an unhandled filter is an extreme oversight of the class library. Perhaps in a future version a virtual method will exist on the Control class that allows your code to handle unhandled exceptions for a single instance of a control.
The next area is the WndProc virtual function on the Control class. This function cannot be overridden by partially trusted code because this method can potentially be used to manipulate sensitive information in the form of window messages.
Finally, there's reflection. Many of the reflection-related features of the runtime are restricted from use by partially trusted code.
This list should give you an idea of the hot spots to look for in your code. Meanwhile, you should handle the restricted features by including the code inside of a try...catch block. This allows your code to implement the feature when it is permitted and to work around the feature when it is not.
Sometimes you want to know ahead of time if a permission is available to your code. To test for this, you can create an instance of a CodeAccessPermission-derived class such as FileIOPermission. Then you can execute the Demand method on the permission inside of a try...catch block. If the exception is thrown then your assembly does not have the permission. (It is also possible to use the SecurityManager.IsGranted method to test for permissions; however, this method does not necessarily represent the exact permissions that your code is executing under due to the nature of the security stack.)
Windows Forms Now let's discuss the namespaces and features of the .NET Framework class library that contain reusable types helpful in writing rich clients. The classes in the System.Windows.Forms namespace provide an easy way to design managed applications that have a GUI interface. The Form class, for example, is used to implement an overlapped window, and there are many control types such as TextBox, Button, and so on.
The Windows Forms classes also form the foundation for rich client applications. The base class for all window-based GUI elements is Control. The Control class encapsulates an OS window handle and is also an important class for Web-deployed client applications. The following simple markup embeds a Control-derived object into an HTML page. This works on any system where the .NET Framework is installed:
The most important tag used here is <OBJECT>, which can be used to indicate the location and class of a managed GUI element that you want to embed into an HTML document deployed over the network. The embedded object is referred to by the value of its id attribute and is located using its classid attribute.
<OBJECT id="List1" classid="http:Controls.dll#DragListBox">
The content of the classid requires some explanation. The classid indicates two pieces of information to the browser: the network location of an assembly that implements the relevant Control-derived type and the public control in the assembly that should be embedded into the HTML document. The two parts of the classid are separated by the pound (#) character like this:
As you saw in the HTML example, an object of type DragListBox is implemented in an assembly named Controls.dll. The location of the assembly is the same path as that of the host HTML file.
classid="[location of assembly]#[class name]"
You can embed almost any Control-derived object in an HTML document; on the other hand, you cannot embed a Form-derived object in an HTML document. This is true even though the Control class is a base class of Form.
The following rules and tips will help you get started with Windows Forms controls embedded in HTML pages.
Take a look at a Web page that has two embedded managed controls. The source code in Figure 5 can be used to build a library assembly (or DLL) that contains the implementation of a class named DragListBox. The DragListBox class is derived from ListBox and implements a list box control that allows dragging and dropping of its items between instances of the control. The HTML sources in Figure 6 include some simple markup to embed two instances of DragListBox, as well as some client-side JScript to set some list items on the controls.
- The embedded class must be derived from Control or from another Control-derived type.
- The embedded class must be public and contain a public default constructor (with no parameters).
- The object's Size property must be set explicitly. If it is not set, it will not display in the HTML page. This can be done in one of two ways. Set the size in markup using the Width and Height attributes of the <Object> tag, or you can chose to assign a size from within your Control-derived type's constructor.
- Code in your class will almost certainly execute with restricted access to the user's system. Through configuration, it is possible to give a network-deployed assembly greater permissions on a system. Your code, though, should not assume the extra configuration step has been taken by a system administrator.
- JScript in the HTML page can, and often must, be used to interact with the control by calling methods on the object. JScript helper code is often desirable for a number of reasons as you will see in the examples later in this article.
- You must use a Web server to test your browser control. Unlike an ActiveX control, you cannot simply point Microsoft Internet Explorer to the appropriate HTML document in your file system and view the page with the control embedded.
- The CLR assembly resolver caches assemblies that contain controls for embedding in HTML documents. It will not redownload an assembly if it matches the strong name of the document in the cache. This means that at development time you must increment the version of your assembly each time you want to test a code addition. To avoid this, you should manually delete the assembly from the assembly cache using a helper BAT file. The assembly resolver download cache can be found at C:\Documents and Settings\UserName\Local Settings\Application Data\assembly\dl.
- Embedded managed controls on a single page share the same managed AppDomain. This means that they are running in the same "program." Your browser-deployed controls can access one another's public methods and properties as well as register for public event notification, and so on. This is a very cool and quite useful feature of browser controls that I will demonstrate with the following example.
If you build the code in Figure 5 into a DLL assembly named Controls.dll you can deploy the DLL and the HTML file represented in Figure 6 to an IIS virtual root. If you browse to the HTML file you will see something like the Web page shown in Figure 7. If you run this example, you can drag and drop list items from one control to the next. This shows that both managed controls are running in the same application and that they can interact with one another any way you like.
Figure 7 Browser Embedded Controls
Although the code in your browser controls is typically restricted from accessing system resources such as the file system or registry, you will find that the GUI-related features that your code has access to are quite rich. It is easy to create controls that implement drag and drop, animation, and interactive UI elements that respond to the user doing things such as moving and clicking the mouse. If fact, most of the functionality that is part of the Windows Forms classes is available to browser controls.
Common Dialogs At first it may seem surprising to you that the common dialog classes play an important role in writing partially trusted code, but upon reflection, the correlation makes sense. As I mentioned, by default the limitations of network-deployed code include restricted access to resources such as the file system or printers, reducing the rich user experience that you want to provide with your partially trusted code.
Common dialog classes are often the solution to restrictions imposed by code access security. OpenFileDialog, SaveFileDialog, PageSetupDialog, and PrintDialog implement common dialogs for accessing the file system and printers. They also represent a way to prompt the user to give your code permission to access system resources. For example, by default the following code in a browser control would raise a security exception:
The reason for this is simple and appropriate. The FileStream class is used to access files on the system, and yet your browser control should be restricted from such access. If your browser control uses the OpenFileDialog class to prompt the user for a file to open, you can use the read-only Stream class opened by the OpenFileDialog class to access the file selected by the user.
FileStream stream = new FileStream("C:\\test.txt", FileMode.Open);
The common dialog classes know when they are being called by partially trusted code, and they adjust their functionality accordingly. For example, in fully trusted code you can use the SaveFileDialog to prompt the user for a file name that you can use in any fashion in your application. In partially trusted code, an instance of the SaveFileDialog will not allow the calling code to obtain the name or location of the file selected by the user, but the code will have access to a write-only Stream object.
When security restrictions are getting in the way, consider common dialogs as a potential workaround.
Isolated Storage Sometimes your partially trusted code simply needs access to the file system without prompting the user for permission. Perhaps your code needs to cache user settings or a temporary data file but the default security cannot allow network-deployed code to access the general file system. The solution to this is a feature called isolated storage.
Isolated storage represents a sandboxed file system that is unique to your assembly and the user for which your assembly's code is executing. It is also possible to further isolate the storage by the URL from which the assembly is loaded. Here is how it works:
This code gets a store based on the assembly in which the code is executing. Then it creates an instance of the IsolatedStorageFileStream class for doing I/O. The IsolatedStorageFileStream class is derived from Stream, so after it has created the object your code no longer needs to think of it as isolated storage. It can then do anything it wants with this file just as though it were dealing with a file in the general file system.
// Get object representing store
IsolatedStorageFile isoStore =
// Create a file in the store
Stream stream = new IsolatedStorageFileStream(
"CachedPuzzle.pzl", FileMode.Create, isoStore);
Isolated storage is a great feature of the runtime. In fact, you should use isolated storage for storing your user configurations for managed applications regardless of whether your software is partially trusted. If your software is running in a Windows domain that supports roaming profiles, your user's isolated storage share will roam with the user's profile.
Code Access Security Example One good way to get your feet wet with code access security and partially trusted applications is to write an application that exploits code access security. For this article, I wrote a simple game application called PuzzlePix.exe. Although many partially trusted applications will be enterprise and business-related software, a simple game can be a great illustrative tool. PuzzlePix uses graphics and other features that would be difficult or impossible to implement in a server-side Web application, and it also accesses local resources such as the file system, which can be restricted depending on where the software is launched from.
Figure 8 PuzzlePix.exe
The PuzzlePix.exe assembly implements a standalone application that can be launched directly from the user's system. It also exposes a public Control-derived type named PuzzleControl that can be embedded into an HTML page and deployed over the network. Figure 8 shows the application (partially solved) launched as an executable and Figure 9 shows the application embedded in a Web page.
Figure 9 PuzzlePix.html
If you are interested in trying the PuzzlePix.exe application, the complete sources are available for download with this article. You should build the PuzzlePix.cs file into an EXE file and deploy it along with its HTML and JPG files to a virtual root for IIS. Then all you have to do is point your browser at the HTML file and you're finished! Managed code, embedded in a Web page.
The core functionality for PuzzlePix.cs is implemented in a class named PuzzleControl, which is derived from Control. Isolated storage is used to cache and restore the state of the control when the user browses back and forth between pages. This simulates a stateful application on a transient page. JScript® is used to interact with the control embedded in the page. The control exposes public methods to construct its state (WebCtor), to cache its state (CacheState), as well as to reset the puzzle (Reset). These methods were actually written with JScript calls in mind.
The PuzzleControl class uses a nested class named PuzzleState that knows how to convert its state to a string as well as back from a string. It does this serialization by hand because the run-time serializer is not available to partially trusted code.
The application accesses its resource files (JPG) by searching in the directory where the application is deployed. This was an arbitrary decision, and the resources could just as well have been embedded or selected from the file system at the user's discretion.
Troubleshooting Browser Control Code Browser controls are a very cool application model for certain types of software. Before addressing some of the problems that can occur with browser controls, I would like to give you a general word of advice. To the extent that it is possible, write your browser-deployed code so that it can also be executed outside of the browser host. The security permissions will differ, but you can use the Zoner.exe tool to address this. Getting the core functionality of your code up and running is much more doable if you are executing your code from the debugger outside of the browser. That said, you are still likely to run into some snags, so I hope that the following tips will help.
Although it is generally unacceptable to write code that catches the base exception (System.Exception), with browser controls you have no choice. Owing to security restrictions, you cannot register an unhandled exception handler, and therefore all of your virtual method overrides and all of your public methods and constructors must catch System.Exception. Otherwise, the exception is caught by the class library (or worse, the host), and the user will be presented with a dialog box in the best case and a missing control in the worst case. I suggest creating a method that logs exception information and calling that method in the catch block of your exception blocks. This way you can find the unexpected exceptions during the testing phase and address them in some manner more appropriate than a catch for all exception types.
The next tip is about virtual root settings. Surprisingly, if the IIS virtual root is set to allow execute permissions of "Scripts and Executables," your managed browser control will not be hosted by the browser because IIS will treat dll or exe like an ISAPI and try to run it. Setting the execute permissions for the virtual root to either "Scripts" or "None" will allow your control to be hosted.
If your Control-derived type is not marked as public, and if it does not have a public default constructor, then it will not be hosted in the browser.
Although Form is derived from Control, your browser control cannot be derived from Form. If it is, the host will refuse to embed your control in the page.
Some security restrictions do not show themselves as a SecurityException. For example, if your type overrides WndProc (a restricted feature), your type simply won't load in the browser. No exception is thrown because the code never even gets the opportunity to run.
Finally, if the control is not showing up in the browser, it is likely that it is because the object's constructor threw an exception that was not caught. You can use a blend of exception handling blocks and MessageBox.Show calls to find out if, and to what point, your code is being executed. If the constructor for your object is not being called at all, then it is most likely having trouble finding the assembly or finding the control type. In addition, the virtual root settings might also be wrong.
The Rich Client in the Future The possibilities for managed code as it matures are many. The infrastructure that code access security and CLR deployment bring to the table enables some cool possibilities. For example, clients that execute code such as Outlook and even Internet Explorer itself can be modified to allow administrators to enable the execution of managed code only. In systems where this is the case, users can freely execute attachments in mail messages as well as code across the Internet without worry of viruses or Trojans. Meanwhile, managed code executes in native machine language and enjoys rich access to OS features.
As the .NET Framework and managed infrastructure matures so will the rich client application. There are many exciting features yet to come. Meanwhile, the functionality available today allows you to create very compelling applications.
The browser control and partially trusted code open the door to some very interesting possibilities using managed code. Enterprise applications can be installed on a share in the network and no more than a shortcut needs be deployed to the user's system. The code base can be updated on the share as needed without concern for installation and configuration problems.
Meanwhile, browser deployed software can be as rich as any other application. For enterprise software the .NET Framework rich client is a real benefit since enterprise networks tend to enjoy the advantages of relatively homogeneous client machines and reliable bandwidth.
| For background information see:|
Windows Forms: A Modern-Day Programming Model for Writing GUI Applications
Applied Microsoft .NET Framework Programming by Jeffrey Richter (Microsoft Press, 2002)
| Jason Clark works on the .NET Framework team at Microsoft. He also conducts training seminars and software consulting for Wintellect (http://www.Wintellect.com). Jason can be reached at email@example.com.|