Historically, attempts to provide a rich client interface over the Web have not been entirely successful. Microsoft's ActiveX controls provided a high level of functionality, but some programmers viewed them as an unacceptable security risk. Java applets were promoted as more secure, but that security came at a price of less utility. It's clear that, although for opposite reasons, neither ActiveX controls nor Java applets have been good at making client-side programming over the Web seamless and feature rich.
The advent of .NET adds a third choice to the list. In addition to writing ActiveX controls (and more in general COM components) and Java classes (including applets), you can now write Windows Forms controls and embed them into HTML pages and ASP.NET generated Web Forms.
Windows Forms Controls
A Windows Forms control is a class that inherits from Control and, once instantiated, occupies a site on the output surface — for example, an HTML page. Just as ActiveX controls and Java applets, the Windows Forms controls require a sort of plug-in module to be installed on the client. The plug-in simply installs the .NET Framework Runtime and is as large as 24 MB. The plug-in works well with Internet Explorer 5.5 and higher and is not needed if you run Windows XP SP1 or higher. The plug-in simply consists of the redistributable version of the .NET Framework.
By using a slightly enhanced version of the <object> tag — the same HTML tag used to import ActiveX controls — you can now embed managed components and have access to and interoperate with all the elements in the page object model. For the whole mechanism to work, a few simple rules have to be enforced. First off, derive the embeddable control from the class Control rather than Form. Second, compile it into an assembly and, finally, deploy the assembly in the same directory as the page or in the codebase directory of the HTML page.
The assembly that contains the component runs as a partially trusted application, so it is not allowed to perform certain operations or access certain protected resources. For example, no direct printing is possible from within the control, no write access to local files or the registry can be performed. In addition, the clipboard is unavailable as well as access to all the classes in the .NET Framework specifically marked for execution only by fully trusted code. (One of these classes is the XSLT processor.)
How to Embed Controls
To embed managed objects in web applications, Internet Explorer 5.5 and newer versions support a special syntax for the <object> tag.
classid="http:[assembly URL]#[full class name]"
The id attribute identifies the instance of the control whereas the width and the height properties determine the dimension of the control's site. The key attribute to consider is classid, however. Normally, classid identifies the CLSID of the COM object or the ActiveX control to embed. Its typical syntax is composed of the keyword clsid followed by a colon and the text representation of the object's CLSID. To support managed components, Internet Explorer accepts an extended format that includes the protocol, the assembly name, and the class name.
The protocol can only be HTTP; moreover, "http:" is a mandatory prefix for the assembly name. If you omit it from the value of the classid attribute, the class won't be loaded at all.
This code snippet instructs the browser to download the DataTreeView assembly from the root of the virtual directory. The class to instantiate within the assembly is called MyCompany.PersonalTreeView. You must reference the class using its fully qualified name. Notice that the assembly containing the class doesn't necessarily have to be a DLL — it can be an EXE file as well.
Pay attention to always explicitly set the size of the object; otherwise, the control will not display in the HTML page. You can specify the size of the control's site in either of the following two ways: using the width and height attributes as shown above or indicating a size in the control class constructor.
Information about the location of the assemblies to download and the configuration file for the control, if any, can be indicated through a special <link> tag with the following syntax:
|<link rel="Configuration" href="[location]">|
The href attribute indicates the URL of the configuration file. By default, IE creates a unique AppDomain to run all the pages of a web site with some managed contents. This setting can be overridden using configuration files. When a configuration file is specified, all pages that point to the same file are created in the same AppDomain.
All needed assemblies must be available in the same directory as the control; that is, the URL indicated through the classid attribute. If needed, though, you can download assemblies from other web sites using the <codebase> setting in a configuration file. The <codebase> setting specifies where the CLR can find a needed assembly. The syntax of the <codebase> setting is shown in Listing 1.
To load assemblies from directories other than the application base directory, you can resort to the <probing> element in the configuration file. In this case, you dictate that the run time searches for assemblies in the listed subdirectories of the application base. The application base is the directory that contains the configuration file or the directory that contains the control, if no configuration file is used. If your control only references assemblies stored in the GAC, you don't need to take any additional measures. Those assemblies are always correctly located.
To successfully test HTML pages that contain managed controls, you should predispose an ad hoc virtual directory and necessarily access the page through IIS. In other words, you can't simply prepare an HTML document and double-click it from Explorer.
Notice that the virtual directory must have the execute permission attribute set to "Scripts Only." The reason is that if you set the execute permission to the "Scripts and Executables" flag, then IIS gets fooled by the assembly's DLL or EXE extension and treats the control's assembly as if it were an ISAPI application. As a result, the control won't be hosted by the browser.
Partially Trusted Code
The managed control should expose properties and methods so that clients can code against it and invoke functions. Script languages are the only tools available to automate the managed control. To identify the currently running instance, you simply use the ID declared in the <object> tag. Internet Explorer creates an instance of the control for you and gives it just that name. Since you'll be driving the control through script code, don't use types that can hardly be managed script wise — for example, arrays and classes — and use only basic types like numbers and strings. In some cases, hidden fields or XML data islands can be effective ways to pack more data and make it easily accessible from the client.
As mentioned earlier, the code running as a managed control through the browser is considered network deployed and, as such, partially trusted. In .NET, partially trusted code suffers from a number of limitations, the most critical of which is the inability to create and edit files. When applications attempt to perform an operation not allowed by the ongoing security policy, the event is signaled by the CLR with an exception of type SecurityException.
If you're going to host managed controls in the Windows shell or in intranet pages, and security is not one of your main concerns, then you can simply work around the partial trust by raising the default trust level of intranet applications and making them full trust altogether. If you don't feel secure changing this setting for the whole intranet, then you can take two possible routes: Use isolated storage or fully trust only the specific managed controls you're using.
Isolated storage is a feature that client applications can take advantage of to create and edit local files in a system-controlled file subtree. Isolated storage represents an assembly-specific virtual filesystem that is managed using the facilities of the local filesystem. Access to the isolated storage is always restricted to the user who created it. The high-level code works with streams and file names as usual. Streams, in turn, read and write files located in a sandboxed portion of the filesystem that the system transparently manages. With isolated storage, you can create and edit files but cannot identify them using absolute names. No drive information is allowed and, with it, no absolute paths. Isolated storage lets partially trusted applications create their own personal filesystems below a well-known and user-specific subtree of directories. The isolated storage API automatically roots relative paths under a physical path, which is different per each user.
The key class to work with is IsolatedStorageFileStream. It inherits from FileStream and represents a stream object you use to actually read and write file contents. To be able to use an isolated storage stream, however, you need an instance of the IsolatedStorageFile class.
iso = IsolatedStorageFile.GetStore(
The isolated storage file represents a distinct storage area containing files and directories. You obtain one specific to the user and/or the assembly by calling the static method GetStore on the IsolatedStorageFile class.
The isolated storage approach elegantly weds the need of security and the need of persisting data on the client. The real problem of persisting data on the client is not the fact that some data is stored. The problem arises from the liberty left to network-deployed code to access in writing any location on the client disk. In this case, at least in theory, the code could delete or modify sensitive data. The isolated storage brilliantly solves the issue by allowing data persistence without introducing security holes.
Trusting Embedded Controls
Isolated storage represents only an implementation technique to work around one particular limitation of partially trusted code. If you're looking for a more general approach, then you should consider enabling a particular assembly to run as fully trusted code.
The .NET Framework provides a Control Panel applet to trust an assembly, as shown in Figure 1. However, this step alone is not sufficient to have an assembly loaded by Internet Explorer as fully trusted code. First off, the assembly must be strong named. Second, you must mark the strong-named assembly with a particular attribute.
In this way, your assembly can be called by the otherwise partially trusted code running in the browser. You only need to add the aforementioned attribute to the assemblyinfo.cs (or .vb) file of the assembly.
To strong name the assembly, you need a pair of keys that the framework can generate for you through the following command line:
The .snk file with the keys must be passed as an argument to another attribute in the assemblyinfo.cs file.
Notice that the AssemblyKeyFile attribute assumes that the location of the .snk file is the project output directory together with the assembly executable. At development time, if you keep the .snk file in the project directory then you must specify a relative path in the AssemblyKeyFile attribute and make it point two (or more) levels up.
Rich Clients Over the Web
Hosting Windows Forms controls in the Internet Explorer browser is another interesting opportunity for building rich client applications. It exploits the capabilities of the client environment much better than ASP.NET, but not as much as a true Windows Forms application would do. It represents a good option in all cases in which you need to deploy rich client software over the Web.
Dino Esposito is Wintellect's ADO.NET and XML expert, and is a trainer and consultant based in Rome, Italy. He is a contributing editor to MSDN Magazine, writing the "Cutting Edge" column, and is the author of several books for Microsoft Press, including Building Web Solutions with ASP.NET and Applied XML Programming for .NET. Contact him at firstname.lastname@example.org.