In addition to letting you deploy WinForms controls, .NET also lets you use IE to deploy entire WinForms applications. This is a completely new feature for the Windows platform. You can best see its value by trying it:
Application DownloadAs a test of the no-touch deployment model for WinForms applications, I built a simple game, as shown in Figure 15.4. [4]
Figure 15.4. The Game of Wahoo!
On the Web server, deployment of a .NET application is merely a matter of dropping the .exe file into a Web application directory so that the Web server can hand out the bits on demand. The .NET runtime is not required on the server, nor is Microsoft's Internet Information Server nor even Windows itself. On the client side, however, things are a bit more interesting. When you feed Internet Explorer an URL such as http://localhost/wahoo/wahoo.exe, it forms an HTTP request for the wahoo.exe file to be streamed back to the client: GET /wahoo.exe HTTP/1.1 Accept: */* Accept-Language: en-us Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461; .NET CLR 1.0.3705) Host: localhost Connection: Keep-Alive The response from the server is just a stream of bytes: HTTP/1.1 200 OK Server: Microsoft-IIS/5.1 Date: Fri, 01 Feb 2002 02:11:29 GMT Content-Type: application/octet-stream Accept-Ranges: bytes Last-Modified: Fri, 01 Feb 2002 01:41:16 GMT ETag: "50aae089c1aac11:916" Content-Length: 45056 <<stream of bytes from wahoo.exe>> In addition to the bytes themselves , the last modified date and time are cached by IE on the client side. This is used to form a request each subsequent time that the application is launched using the same URL: GET /wahoo.exe HTTP/1.1 Accept: */* Accept-Language: en-us Accept-Encoding: gzip, deflate If-Modified-Since: Fri, 01 Feb 2002 01:41:16 GMT If-None-Match: "50aae089c1aac11:916" User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461; .NET CLR 1.0.3705) Host: localhost Connection: Keep-Alive The If-Modified-Since header is kept in IE's download cache and is sent back with each request. In this way, if the bits on the server haven't changed, the server can respond with a header that indicates that the cache is still good, reducing the payload that needs to be downloaded to the client: HTTP/1.1 304 Not Modified Server: Microsoft-IIS/5.1 Date: Fri, 01 Feb 2002 02:42:03 GMT ETag: "a0fa92bc8aac11:916" Content-Length: 0 The bytes themselves are cached in two places: IE's download cache, managed by IE, and the .NET download cache , which is a cache for .NET assemblies (both .exe and .dll files) downloaded on demand. The contents of the .NET download cache can be examined using gacutil.exe /ldl and cleared using gacutil.exe /cdl. If, during your testing, you'd like to ensure that a download happens, make sure to clear IE's cache using the Internet control panel and clear .NET's cache using gacutil. VersioningWhile we're talking about caching and downloading the "latest version," you may be curious about how actual versions affect things. As you may know, you can tag a .NET assembly with a specific version using the AssemblyVersionAttribute: [5]
// Strong naming is required when versioning [assembly: AssemblyKeyFileAttribute("wahoo.key")] [assembly: AssemblyVersionAttribute("1.2.3.4")] With this in mind, you may wonder whether .NET versioning affects caching and downloading. It doessometimes. When a request is formed for http://localhost/wahoo/wahoo.exe, the runtime doesn't have any idea which version we'd like, so it simply asks the Web server for the latest HTTP version as indicated by the If-Modified-Since header sent along with the HTTP request. If the runtime already has the latest HTTP version, no download needs to happen. However, if the server has a newer binary (based on the date/time stamp on the file)even if that binary is of a lower .NET version (based on the AssemblyVersionAttribute)the latest HTTP version will be downloaded. In other words, the .NET version plays no role in what constitutes the "latest version" of the application launched with an URL. On the other handand this is the "sometimes" partany assembly can reference other assemblies, either implicitly as part of the manifest or explicitly via the Assembly.Load method. For example, wahoo.exe references wahooControl.dll. Whatever the .NET version is of wahooControl.dll that wahoo.exe compiles against will be the version that the assembly resolver (the part of the .NET runtime responsible for finding code) expects to find at run time. If that .NET version of wahooControl.dll is in the cache, the assembly resolver will not send a request to the Web server asking whether it has a newer .NET version. This means that if you'd like the client to have a newer .NET version of a referenced assembly, you must make sure that the Web server is serving up an updated EXE assembly that refers to the referenced assembly. Related FilesAs I just mentioned, assemblies can reference other assemblies. For example, an assembly may already be present in the Global Assembly Cache (GAC), the place where systemwide shared assemblies are kept in .NET. If an assembly is in the GAC, such as System.Windows.Forms.dll, then that's where the assembly will be loaded from. If the assembly is not in the GAC, the .NET download cache is checked. If the download cache doesn't contain the assembly, the assembly resolver goes back to the originating server using the application base (appbase) of the assembly. The appbase is the "directory" from which the initial assembly was loadedfor example, c:\wahoo or http://localhost/wahoo. The appbase is available using the GetData function of the currently executing application domain (appdomain) , which is the context under which all .NET code runs. [6] The appbase can be obtained for the appdomain in this way:
string appbase = AppDomain.CurrentDomain.BaseDirectory; For example, when wahoo.exe needs wahooControls.dll and it's not present in the GAC or the download cache, the assembly resolver will go back to the originating Web server, as you've seen. Referenced assemblies, however, are not the only files that the assembly resolver will look for when an executable is loaded. In fact, on my machine when I surf to the initial version of the signed wahoo.exe after the caches have been cleared, 35 requests are made for files, as shown in Table 15.3. Table 15.3. Files Requested When Surfing to wahoo.exe
The .config FileThe first request in Table 15.3 makes sense because it's the assembly we asked for to begin with. The second request is for a .config file, which you may recall from Chapter 11: Applications and Settings. If you're serving your WinForms application from an IIS installation, you'll need to enable anonymous access to your .exe file's Web application. Also, if ASP.NET is installed on the server, you may need to override the default behavior in a virtual directory to disable any .config files being supplied. If you find that your application's .config is available on the server but is not being downloaded to the client, use the following settings in a web.config file at the base of the virtual directory: <configuration> <system.web> <!-- Remove the *.config handler so that we can serve up *.exe.config files, but make it forbidden to serve up the web.config file itself. --> <httpHandlers> <remove verb="*" path="*.config" /> <add verb="*" path="web.config" type="System.Web.HttpForbiddenHandler"/> </httpHandlers> </system.web> </configuration> These configuration settings remove the restriction on all .config files and reinstate it for only web.config files, which still need protection from download. Some versions of ASP.NET disable all .config files, whereas others don't, so if the <remove> element causes an error, you shouldn't need these extra settings in your web.config at all. ResourcesThe third request in Table 15.3 is for the wahooControl.dll assembly that wahoo.exe references, as we expect. The next request is for a resources assembly called Wahoo.resources. You should recognize this request (and the rest of the requests) as made by the resource manager looking for localized resources, as discussed in Chapter 10: Resources. These requests are being made for the WahooControl object's background image, which is stored in the wahooControl assembly's resources. On the local machine or a LAN, the localization probe performed by the resource manager is chewy goodness, but that's not necessarily so in a WAN environment. When the latest wahoo.exe and wahooControl.dll are already cached on my machine, I've seen these additional requests take more than 75 percent of the load time. These extra round-trips can make for a very poor user experience, especially when culture-neutral resources are the only resources in your application. In this case, you have a couple of options. One option is to avoid using WinForms Designer to set properties that use resources. In this way, the Designer-generated code doesn't create a resource manager. Then, to load resources, you write the code yourself in a culture-neutral way: public MainForm() { // Let the Designer-generated code run InitializeComponent(); // Init culture-neutral properties this.game.BackgroundImage = new Bitmap(typeof(MainForm), "sblogo.gif"); } Loading resources manually is handy for resources that you know are culture-neutral and never want to localize, but it does mean giving up WinForms Designer for resource management. If you share my addiction to WYSIWYG form design, the resource manager supports an optimization that often makes this kind of handwritten code unnecessary. Recall NeutralResourcesLanguageAttribute from Chapter 10: Resources. This attribute marks the resources bundled in your assembly as culture-specific so that they will be found first without the round-trips to the Web server when launched from that culture: [assembly: NeutralResourcesLanguageAttribute("en-US")] This attribute reduces the number of requests from 34 to 2 when the assembly is launched from a machine with the culture of the assembly, thereby improving load times considerably. If you'd like to let the Designer generate the code and reduce round-trips for cultures other than the one in which you're writing the application, you can put zero-length files in the first places that the resource manager looks. For example, zero-length files on the server under the following names will be used for the English language and the US: en-US\assemName.resources.dll en\assemName.resources.dll The presence of zero-length files at the appropriate locations causes the resource manager to find the resource files it's looking for, even if they're empty. When it comes to resolving specific resources, zero-length files will cause an extra lookup by the resource manager for a culture- or language-specific resource, but if it's not found, the culture-neutral resources in the resolving assembly will be used in the normal way. Working OfflineOf course, the ultimate reduction of requests is to use no requests at all. By putting IE into offline mode (via the File menu), surfing to an NTD application will work completely from the cache. A better model is to use the cache automatically if the client machine was not connected to the Internet or the server was unavailable, but, as of .NET 1.1, that feature is not part of the NTD implementation. |