File Management for No-Touch Deployments
by Jon Wojtowicz

This article looks at the issues that you can encounter using No-touch Deployment of Winforms applications that rely on additional files that are not typically considered part of the application. An example of this is the additional configuration file used by the Microsoft Enterprise Library Configuration Management.


No-Touch Deployments
No-Touch deployment (NTD) is the term given to a Winforms application that is launched by navigating to a URL. This deployment option provides the ability to provide a rich user interface with the ease of a web deployment. NTD typically employ a Winforms user interface with web services providing business and data services. This also allows the user client to be fairly small and lightweight.
To set up a Winforms application using NTD all that is needed is the exe, supporting dlls and the config file deployed within a web site. The user simply navigates to the path specifying the exe and the IEExec process will download and launch the application on the client machine. By default a NTD application has a limited set of permissions for actions it can perform on the client machine.
File Management
Typically in a Winforms application determining file information is a simple operation using the File and Path classes in the System.IO namespace. When using NTD the File and Path classes are not useful since they will not be able to find the files using the http: prefix. The specific items that will be looked at are the following.
  • Determine if a file exists.
  • Determining the last modified time of a file.
  • Retrieving the file data.
Determining if a File Exists
When looking at the local file system, determining if a file exists is simple. The following code sample determines if a file called myConfig.config exists in the same directory as the application.
string filePath = AppDomain.CurrentDomain.BaseDirectory + fileName;
bool exists = File.Exists(filePath);
In this case the BaseDirectory property will be in the format C:\MyApplications\MyProject\Bin\Debug. This format works fine for the File.Exists method. When we use the same application using NTD we see that the BaseDirectory points to the URL on the server similar to this, http://localhost/Myproject/. If this is used in the File.Exists method it will always return false.
In order to solve this dilemma the file path needs to be converted to an Uri. By using the Scheme property of the Uri it can be determined if the BaseDirectory uses http or the file scheme. If the file scheme is used then the File.Exists method can be used. If the http scheme is used then a web request can be used to determine if the file exists. The code for the http scheme is the following.
Uri uri = new Uri(filePath);

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "HEAD";
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
return (response.StatusCode != HttpStatusCode.NotFound);
}
catch(WebException e)
{
response = (HttpWebResponse)e.Response;
if( response.StatusCode == HttpStatusCode.NotFound)
return false;
else
throw;
}
finally
{
if(response != null)
response.Close();
}
While the web request pattern can be used for both file and http schemes it is not efficient. Only the HttpWebRequest allows for setting the method. In this case we use the HEAD method which only returns summary information about the file requested. This prevents the transfer of the entire file which is more efficient especially if the file is large. The HttpWebRequest does not support the file scheme so a case/switch statement needs to be used with the appropriate method. The final combined code is as follows.
Uri uri = new Uri(filePath);
switch(uri.Scheme)
{
case "http":
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "HEAD";
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
return (response.StatusCode != HttpStatusCode.NotFound);
}
catch(WebException e)
{
response = (HttpWebResponse)e.Response;
if( response.StatusCode == HttpStatusCode.NotFound)
return false;
else
throw;
}
finally
{
if(response != null)
response.Close();
}
case "file":
return File.Exists(uri.LocalPath);
default:
throw new ArgumentException("Unsupported scheme for file.");
}
Determining the File Modified Time
Determining the last modified date for a file on the file system is just as simple as determining if it exists. The code is as follows.
DateTime lastWriteTime = File.GetLastWriteTime(filePath);
To perform this same operation for the NTD client the web request is again used. The headers collection returns the last-modified header which maps to the LastModified property of the HttpWebResponse. The code to extract this is as follows.
Uri uri = new Uri(filePath);

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "HEAD";
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
return response.LastModified;
}
finally
{
if(response != null)
response.Close();
}
Putting these to methods together results in the following.
Uri uri = new Uri(filePath);
switch(uri.Scheme)
{
case "http":
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "HEAD";
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
return response.LastModified;
}
finally
{
if(response != null)
response.Close();
}
case "file":
return File.GetLastWriteTime(uri.LocalPath);
default:
throw new ArgumentException("Unsupported scheme for file.");
}
Retrieving the File Data
The last item to cover is getting the actual file data. Getting the file data from a file system file is again a fairly simple process. The code is as follows.
Stream s = new FileStream(uri.LocalPath, FileMode.Open, FileAccess.Read);
StreamReader rdr = new StreamReader(s);
String data = rdr.ReadToEnd();
Reading from a NTD location the code becomes the following.
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();v byte[] data = new byte[response.ContentLength];
response.GetResponseStream().Read(data, 0, data.Length);
return new MemoryStream(data);
}
finally
{
if(response != null)
response.Close();
}
Putting these two sections together results in the following.
Uri uri = new Uri(filePath);
switch(uri.Scheme)
{
case "http":
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
byte[] data = new byte[response.ContentLength];
response.GetResponseStream().Read(data, 0, data.Length);
return new MemoryStream(data);
}
finally
{
if(response != null)
response.Close();
}
case "file":
return new FileStream(uri.LocalPath, FileMode.Open, FileAccess.Read);
default:
throw new ArgumentException("Unsupported scheme for file.");
}
Summary

This type of pattern can be used to allow the Microsoft Enterprise Configuration Application Block to be used with NTD applications. The current version I�m using does not and inserting code similar to the preceding will correct the issues. Hopefully the demonstrations also showed how to use the basics of the HttpWebRequest in communicating with web servers.

Download the solution that accompanies this article

Jon Wojtowicz is a C# MVP and a Systems Analyst at a large insurance company in Chattanooga, TN where he currently provides developer support and internal training. He has worked as a consultant working with Microsoft Technologies. This includes ASP, COM, VB6 and .Net, both C# and VB.Net since Beta 1. He has been an MCSD since 1999 and an MCT since 2000. Prior to getting a degree in Computer science he worked as a process engineer focusing on process automation, programmable controllers and equipment installations. In his spare time he likes woodworking and gardening.
Article Discussion: