Extending FileSystemWatcher to ASP.NET
2011-12-12 09:44
357 查看
I received a number of responses to my article in the July/August edition of VSJ, Your
fingers on the filesystem, regarding the use of the FileSystemWatcher class in ASP.NET applications. The original article mentioned using FileSystemWatcher to update files on a web server, and the model
I envisaged was similar to the Photo Library application described in the article. This would allow files to be copied up to a file-based web server (e.g. IIS) in a controlled manner. Some of the responses I received were asking about the use of FileSystemWatcher
within ASP.NET applications, for example to update a product list.
In this article I will show how FileSystemWatcher can be used within web applications, and then go on to describe the Cache mechanism that provides an ASP.NET specific alternative.
When you create a project with Visual Studio it automatically creates for you a Global.asax file that contains handlers for the principle events in the lifecycle of an application including Start and End.
This seems like the obvious place to put our FileSystemWatcher object, but although Global.asax defines a class named Global (derived from HttpApplication), the ASP.NET system will not necessarily use the same instance of this object for all of the events
handled there!
Applications in ASP.NET store application-wide information in name/value pairs in the AllKeys collection of the Application object (a member of HttpApplication and thus Global), and ASP.NET does ensure that the Application object is the same for each event
handled. To manage the lifecycle of the FileSystemWatcher it must be stored in this collection.
Event handlers for the FileSystemWatcher events may be added to the Global class, but you must ensure that they only access data in the Application collection, and that they do so in a thread-safe manner (see my previous article). Care should be taken when
applying synchronisation in a server environment to ensure that performance and scalability are not compromised.
The basic code for adding a FileSystemWatcher to an ASP.NET application looks like this:
Note the use of the Server.MapPath function to obtain the full path of the application directory from the relative path provided by IIS.
To be a good citizen we should ensure that we dispose of this object properly at application end. The following function shows how to do this. First we must retrieve a reference to the object from the application collection, then remove it from the collection,
and finally dispose the object:
The Application collection is in some ways similar to the Session collection described in the February 2004 edition of VSJ in an excellent article by Dino Esposito, Getting Serious about ASP.NET Session Management
(www.vsj.co.uk/articles/display.asp?id=286). However, the data in the Application collection is always stored In-Process and is therefore not shareable across multiple machines.
An alternative to using a FileSystemWatcher to monitor the filesystem in ASP.NET is to make use of the built-in features of the Page cache. Unlike the Application and Session collections, the Page.Cache collection
allows you to add expiry times and dependencies to the stored items. The best way to explain how this functionality can be used is with an example.
A web shop typically has a product list that is updated infrequently compared with the frequency of page hits, but much more often than the structure of the site. It is not desirable to write new code (HTML or C#) to add these products, especially since this
would usually require down-time on the server.
One solution is to provide product information in an XML file – a simple example is:
Visual Studio will automatically generate a Schema (XSD) file for this XML file if you select the “Create Schema” option from the “XML” menu that appears when editing an XML file. This schema can be modified
if required (I set the price field to be a decimal rather than a string), and then the “Generate Dataset” option on the Schema menu can be used to automatically generate a C# class. This class provides a neat interface to the XML data via ADO.
In order to ensure that each time the page is hit the product list is available for rendering each time it is loaded, I could add the following to the Page_Load function:
This code reads the product list each time the page is hit, so updates to the product list are reflected on the site immediately. It also means that for each and every page hit the server will read the XML
file, parse its contents, and create the corresponding ADO objects. To avoid this load on the server we could use the Application collection:
This code attempts to load the product list from the Application collection and only reads it from the XML file if this operation fails. This reduces the server load in reading and parsing the XML file significantly,
but as the read only takes place infrequently (e.g. when the ASP or IIS process is restarted) updates could take days or even weeks to be reflected on the site (yes, I know it would be months or years on a Linux server!). You could write your own code to handle
this either using a FileSystemWatcher as shown above, or by storing a “time of last read” and updating after a fixed interval. Fortunately ASP.NET has a built-in mechanism to do this for us.
Rather than storing our data in the Application collection we can store it in the Page.Cache. Its function is conceptually similar – to store a name/value pair on behalf of the application. The differences
become apparent when you look at the parameter lists of the Add functions for Application and Page.Cache however. In addition to the name/key and value parameters, Page.Cache.Add has the following:
dependencies – a CacheDependency object indicating what this cache entry’s data is dependent
on
absoluteExpiration – a DateTime indicating the absolute date and time when the data in this cache
entry should be considered invalid (DateTime.MaxValue disables this functionality)
slidingExpiration – a TimeSpan indicating the elapsed time since last usage that will render
cache entry invalid (TimeSpan.Zero disables this functionality, cannot be used in conjunction with absoluteExpiration)
priority – CacheItemPriority enumeration indicating the priority of this item in the ASP.NET
cache
onRemoveCallback – a callback function enabling the application to take action when the data
is removed from the cache
By selecting the parameters to the Add function carefully, we can get ASP.NET to automatically flush the ADO dataset representing the product list from the cache when the file changes:
The CacheDependency object created and passed as a parameter makes an explicit connection between the XML data file and the cache data. If the XML file changes, the data in the cache will be discarded. The
next time the page is requested, the data will not be found in the cache and therefore the updated XML file will be read in.
It is clear that the Page cache offers more functionality than the Application collection, but with more limited scope. How can we take advantage of this functionality for data that is used by more than one
page? Fortunately this can be achieved by storing the cached data in the Application collection and merely using the Page.Cache to let us know when the dependency changes, using the onRemoveCallback. This can be done from a single class shared by all pages
that need to access the data:
Many web applications store their data in a database such as SQL Server rather than in XML files, but the CacheDependency object provides no obvious mechanism for detecting changes in SQL Server. This is
an irritating omission from ASP.NET, which is set to be corrected in ASP.NET 2.0 (now in Beta, releasing 2005) by a new SqlCacheDependency class that can be used to detect changes in SQL Server tables.
If you can’t wait for ASP.NET 2.0 then the solution is to write an Extended Stored Procedure that can be called from SQL Server to update a file when a table changes (this could be run from a trigger or the stored procedure that performs the update). This file
can then be monitored by the web application using a FileSystemWatcher or CacheDependency. For large-scale applications, the file could exist on a share accessible to multiple web servers and multiple database servers.
If you are interested in this technique, then I recommend the excellent article on the subject by Jeff Prosise which can be found in his Wicked
Code column in the April 2003 edition of MSDN
Magazine. This article contains sample code for the web application, SQL database and the Extended Stored Procedure.
I hope that in this article I have shown the benefits of using filesystem monitoring in ASP.NET applications, and shown how this can be done using either a FileSystemWatcher or the Page.Cache.
Ian Stevenson has been developing Windows software professionally
for almost 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works as a consultant for The
Generics Group () and can be contacted at Ian.Stevenson@cyclops-online.co.uk
http://www.developerfusion.com/article/84362/extending-filesystemwatcher-to-aspnet/
fingers on the filesystem, regarding the use of the FileSystemWatcher class in ASP.NET applications. The original article mentioned using FileSystemWatcher to update files on a web server, and the model
I envisaged was similar to the Photo Library application described in the article. This would allow files to be copied up to a file-based web server (e.g. IIS) in a controlled manner. Some of the responses I received were asking about the use of FileSystemWatcher
within ASP.NET applications, for example to update a product list.
In this article I will show how FileSystemWatcher can be used within web applications, and then go on to describe the Cache mechanism that provides an ASP.NET specific alternative.
Using FileSystemWatcher from ASP.NET
When you create a project with Visual Studio it automatically creates for you a Global.asax file that contains handlers for the principle events in the lifecycle of an application including Start and End.This seems like the obvious place to put our FileSystemWatcher object, but although Global.asax defines a class named Global (derived from HttpApplication), the ASP.NET system will not necessarily use the same instance of this object for all of the events
handled there!
Applications in ASP.NET store application-wide information in name/value pairs in the AllKeys collection of the Application object (a member of HttpApplication and thus Global), and ASP.NET does ensure that the Application object is the same for each event
handled. To manage the lifecycle of the FileSystemWatcher it must be stored in this collection.
Event handlers for the FileSystemWatcher events may be added to the Global class, but you must ensure that they only access data in the Application collection, and that they do so in a thread-safe manner (see my previous article). Care should be taken when
applying synchronisation in a server environment to ensure that performance and scalability are not compromised.
The basic code for adding a FileSystemWatcher to an ASP.NET application looks like this:
protected void Application_Start( Object sender, EventArgs e) { FileSystemWatcher fsw = new FileSystemWatcher( Server.MapPath( “.” ) ); Application.Add( “myfsw” , fsw ); // Add event handlers here fsw.EnableRaisingEvents = true; }
Note the use of the Server.MapPath function to obtain the full path of the application directory from the relative path provided by IIS.
To be a good citizen we should ensure that we dispose of this object properly at application end. The following function shows how to do this. First we must retrieve a reference to the object from the application collection, then remove it from the collection,
and finally dispose the object:
protected void Application_End( Object sender, EventArgs e) { FileSystemWatcher fsw = (FileSystemWatcher )Application[“myfsw”]; Application.Remove( “myfsw” ); fsw.Dispose(); }
The Application collection is in some ways similar to the Session collection described in the February 2004 edition of VSJ in an excellent article by Dino Esposito, Getting Serious about ASP.NET Session Management
(www.vsj.co.uk/articles/display.asp?id=286). However, the data in the Application collection is always stored In-Process and is therefore not shareable across multiple machines.
ASP.NET and Data Files
An alternative to using a FileSystemWatcher to monitor the filesystem in ASP.NET is to make use of the built-in features of the Page cache. Unlike the Application and Session collections, the Page.Cache collectionallows you to add expiry times and dependencies to the stored items. The best way to explain how this functionality can be used is with an example.
A web shop typically has a product list that is updated infrequently compared with the frequency of page hits, but much more often than the structure of the site. It is not desirable to write new code (HTML or C#) to add these products, especially since this
would usually require down-time on the server.
One solution is to provide product information in an XML file – a simple example is:
<?xml version=”1.0” encoding=”utf-8”?> <ProductList xmlns=”http:// cyclops-online.co.uk/ProductList/ ProductList.xsd”> <Product> <Title>Mouse House </Title> <Price>4.99 </Price> </Product> <Product> <Title>Monitor Mirror </Title> <Price>9.99 </Price> </Product> <Product> <Title>Stick-on Panic Button </Title> <Price>2.99 </Price> </Product> </ProductList>
Visual Studio will automatically generate a Schema (XSD) file for this XML file if you select the “Create Schema” option from the “XML” menu that appears when editing an XML file. This schema can be modified
if required (I set the price field to be a decimal rather than a string), and then the “Generate Dataset” option on the Schema menu can be used to automatically generate a C# class. This class provides a neat interface to the XML data via ADO.
In order to ensure that each time the page is hit the product list is available for rendering each time it is loaded, I could add the following to the Page_Load function:
private static readonly string _productListTag = “ProductList”; private static readonly string _productListFile = “ProductList.xml”; private ProductList _productList = null; private void Page_Load(object sender, System.EventArgs e) { productList = new ProductList(); _productList.ReadXml(Server.MapPath(_productListFile)); }
This code reads the product list each time the page is hit, so updates to the product list are reflected on the site immediately. It also means that for each and every page hit the server will read the XML
file, parse its contents, and create the corresponding ADO objects. To avoid this load on the server we could use the Application collection:
private void Page_Load(object sender, System.EventArgs e) { _productList = (ProductList)Application[ _productListTag]; if ( null == _productList ) { _productList = new ProductList(); _productList.ReadXml(Server.MapPath( _productListFile)); Application.Add(_productListTag, _productList); } }
This code attempts to load the product list from the Application collection and only reads it from the XML file if this operation fails. This reduces the server load in reading and parsing the XML file significantly,
but as the read only takes place infrequently (e.g. when the ASP or IIS process is restarted) updates could take days or even weeks to be reflected on the site (yes, I know it would be months or years on a Linux server!). You could write your own code to handle
this either using a FileSystemWatcher as shown above, or by storing a “time of last read” and updating after a fixed interval. Fortunately ASP.NET has a built-in mechanism to do this for us.
Using The ASP.NET Page Cache
Rather than storing our data in the Application collection we can store it in the Page.Cache. Its function is conceptually similar – to store a name/value pair on behalf of the application. The differencesbecome apparent when you look at the parameter lists of the Add functions for Application and Page.Cache however. In addition to the name/key and value parameters, Page.Cache.Add has the following:
dependencies – a CacheDependency object indicating what this cache entry’s data is dependent
on
absoluteExpiration – a DateTime indicating the absolute date and time when the data in this cache
entry should be considered invalid (DateTime.MaxValue disables this functionality)
slidingExpiration – a TimeSpan indicating the elapsed time since last usage that will render
cache entry invalid (TimeSpan.Zero disables this functionality, cannot be used in conjunction with absoluteExpiration)
priority – CacheItemPriority enumeration indicating the priority of this item in the ASP.NET
cache
onRemoveCallback – a callback function enabling the application to take action when the data
is removed from the cache
By selecting the parameters to the Add function carefully, we can get ASP.NET to automatically flush the ADO dataset representing the product list from the cache when the file changes:
Cache.Add( _productListTag, _productList, new CacheDependency(Server.MapPath(_productListFile)), DateTime.MaxValue, TimeSpan.Zero, CacheItemPriority.NotRemovable, null);
The CacheDependency object created and passed as a parameter makes an explicit connection between the XML data file and the cache data. If the XML file changes, the data in the cache will be discarded. The
next time the page is requested, the data will not be found in the cache and therefore the updated XML file will be read in.
Taking the Cache Beyond the Page
It is clear that the Page cache offers more functionality than the Application collection, but with more limited scope. How can we take advantage of this functionality for data that is used by more than onepage? Fortunately this can be achieved by storing the cached data in the Application collection and merely using the Page.Cache to let us know when the dependency changes, using the onRemoveCallback. This can be done from a single class shared by all pages
that need to access the data:
public static ProductList GetProductList( System.Web.UI.Page page) { ProductList productList = (ProductList)page.Application[ _productListTag]; if (null == productList) { productList = new ProductList(); productList.ReadXml( page.Server.MapPath( _productListFile)); page.Cache.Add(_productListTag, page.Application, new CacheDependency( page.Server.MapPath( _productListFile)), DateTime.MaxValue, TimeSpan.Zero, CacheItemPriority. NotRemovable, new CacheItemRemovedCallback( RemoveCallback)); page.Application.Add( _productListTag, productList); } return productList; } private static void RemoveCallback( string tag, object obj, CacheItemRemovedReason reason) { HttpApplicationState Application = (HttpApplicationState)obj; if(CacheItemRemovedReason. DependencyChanged == reason) { Application.Remove(tag); } }
Detecting Updates in SQL Server
Many web applications store their data in a database such as SQL Server rather than in XML files, but the CacheDependency object provides no obvious mechanism for detecting changes in SQL Server. This isan irritating omission from ASP.NET, which is set to be corrected in ASP.NET 2.0 (now in Beta, releasing 2005) by a new SqlCacheDependency class that can be used to detect changes in SQL Server tables.
If you can’t wait for ASP.NET 2.0 then the solution is to write an Extended Stored Procedure that can be called from SQL Server to update a file when a table changes (this could be run from a trigger or the stored procedure that performs the update). This file
can then be monitored by the web application using a FileSystemWatcher or CacheDependency. For large-scale applications, the file could exist on a share accessible to multiple web servers and multiple database servers.
If you are interested in this technique, then I recommend the excellent article on the subject by Jeff Prosise which can be found in his Wicked
Code column in the April 2003 edition of MSDN
Magazine. This article contains sample code for the web application, SQL database and the Extended Stored Procedure.
I hope that in this article I have shown the benefits of using filesystem monitoring in ASP.NET applications, and shown how this can be done using either a FileSystemWatcher or the Page.Cache.
Ian Stevenson has been developing Windows software professionally
for almost 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works as a consultant for The
Generics Group () and can be contacted at Ian.Stevenson@cyclops-online.co.uk
http://www.developerfusion.com/article/84362/extending-filesystemwatcher-to-aspnet/
相关文章推荐
- FileSystemWatcher 导致Mono ASP.NET应用程序CPU使用率比较高
- ASP.net:FileSystemObject
- ASP.NET The system cannot find the file specified解决办法
- asp.net中System.DateTime.Now.ToString()的一些用法
- asp.net上传文件使用fileupload控件,判断文件类型和大小,取得文件路径时报错【System.IO.FileNotFoundException:未能找到文件】的解决办法
- Could not write to output file 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Fi
- asp.net sqlite unable to open database file的解决方案
- ASP.NET makes uploading files from the client to the server a snap(UploadInterface.PostedFile.SaveAs)
- SQL Server Setup failed to obtain system account information for the ASPNET
- asp.net中的System.DateTime.Now.ToString()
- 关于CS0016: Could not write to output file ‘c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files… ‘Access is denied.’ 的解决办法
- Read and Write compressed data to a binary file using ASP.NET
- asp.net中System.DateTime.Now.ToString()的一些用法
- 关于CS0016: Could not write to output file ‘c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files… ‘Access is denied.’ 的解决办法
- Extending the ASP.Net Security model to use rights
- 4 ways to send a PDF file to the IE Client in ASP.NET 2.0
- CS0016: Could not write to output file ‘c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET解决方案
- Compiler Error Message: CS0016: Could not write to output file 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\xxx' -- 'Access is denied. '
- asp.net postedFile.SaveAs和SaveAs,解决了本地查看excel找不到System.IO.Path.GetFileName文件问题
- asp.net中System.DateTime.Now.ToString()的一些用法