Recently I was asked to implement a ‘smarter’ application for managing temporary files on our company’s webfarm. We use a number of third-party tools that don’t clean up after themselves really well (or, we may just be using them incorrectly); and as such we frequently run into low diskspace issues due to viewstate and other third party utilies not being cleaned up.
Goal: Build an application that can be configured by the end user to delete directories and files as needed (like viewstate, activePDF temp files) and run as a scheduled task. Any logging should be done to the Windows event log.
There are a number of methods that could be used to enable the user to set up their configuration options, but in keeping with the .NET model for configuration, I picked using XML and the app.config to store the settings using a custom configuration section.
I ran into an issue during the course of development because I had the wrong XML layout. I used the following method:
<folder>
<path>D:MyPathmyFolder</path>
<description>MyDescriptionhere</description>
<fileType>*.txt</fileType>
</folder>
instead of:
<folders>
<folder id=”1″ path=”D:MyPathmyFolder” description=”MyDescriptionhere” fileType=”*.txt” />
</folders>
This creates a problem because of how .NET handles custom sections. It apparently doesn’t like the syntax of the former, and instead of implementing code to serialize it and de-serialize it myself, I chose to stick with implementing the configuration API as written. I had that choice, since I was implementing a new ‘feature’; and if you’re maintaining a legacy project, you may not have that luxury.
Once you know the format your XML will take, it’s a simple matter of creating the custom configuration section C# classes. The first class that is going to be created is a class that is the actual Custom Configuration section. Classes also need to be created for each collection of XML items (the list of folders in the <folders>, and a class needs to be made for each element and its properties (the folder element and the path, description, and fileType properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace DelViewState_CS
{
public class DelViewStateConfigSettings : ConfigurationSection
{
public DelViewStateConfigSettings()
{
}
[ConfigurationProperty(“folders”, IsDefaultCollection = true)]
public FoldersCollection Folders
{
get { return (FoldersCollection)base[“folders”]; }
}
[ConfigurationProperty(“timeout”, IsRequired = true)]
public int Timeout
{
get { return (int)base[“timeout”]; }
set { base[“timeout”] = value; }
}
}
}
The configuration section has two properties, the timeout property (a poorly named legacy property that indicates how long it’s been since the file was accessed), and a folderscollection property that will contain the individual folders and their properties.
Here is the C# code for the folderscollection class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace DelViewState_CS
{
public sealed class FoldersCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new FoldersElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((FoldersElement)element).Id;
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.BasicMap; }
}
protected override string ElementName
{
get
{
return “folder”; }
}
public FoldersElement this[int index]
{
get { return (FoldersElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
new public FoldersElement this[string Path]
{
get { return (FoldersElement)BaseGet(Path); }
}
public bool ContainsKey(string key)
{
bool result = false;
object[] keys = BaseGetAllKeys();
foreach (object obj in keys)
{
if ((string)obj == key)
{
result = true;
break;
}
}
return result;
}
}
}
The final class we have to write is the folder element itself. This contains the necessary properties of the folder element:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
namespace DelViewState_CS
{
public sealed class FoldersElement : ConfigurationElement
{
[ConfigurationProperty(
“id”, IsRequired=
true, IsKey=
true)]
public int Id
{
get {
return (
int)
base[
“id”]; }
set {
base[
“id”] = value; }
}
[ConfigurationProperty(
“path”, IsRequired =
true)]
public string Path
{
get {
return (
string)
base[
“path”]; }
set {
base[
“path”] = value; }
}
[ConfigurationProperty(“description”, IsRequired = false)]
public string Description
{
get { return (string)base[“description”]; }
set { base[“description”] = value; }
}
[ConfigurationProperty(“fileType”, IsRequired = false)]
public string FileType
{
get { return (string)base[“fileType”]; }
set { base[“fileType”] = value; }
}
}
}
Once all of that is written, it’s a simple matter of accessing the custom section and reading these properties (and in my case, deleting the files associated with them. Accessing the custom configuration section is as simple as creating a static factory method in the customconfigurationsection.cs file:
public static DelViewStateConfigSettings GetDelViewStateConfigSettingsSection()
{
DelViewStateConfigSettings cs;
System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None) as System.Configuration.Configuration;
cs = config.GetSection(“delViewStateConfigSettingsGroup/delViewStateConfigSettings”) as DelViewStateConfigSettings;
return cs;
}
There are plenty of methods for writing to the event log, but I followed the guidelines laid out here.
In the end, it took a little over a week from the time that I first learned about the task to the time it was completed (testing, checkin, cleanup). There are a number of ways it could be made better:
- Using delegates and anonymous methods to shorten the written code that deletes the files and writes to the event log
- Pull the custom section into its own XML file that is referenced int he app.config; to allow for drag and drop placement.
- Checking the files for being ‘locked’ (which happens sometimes) and working around that. For the time being, I’m logging those happenings to the event log so I can find out why they’re happening and fix the actual problem
There are more improvements to come, in the next blog post on this topic, I’ll talk about enhancing this utility by getting it to scope out IIS Virtual directories and auto-filling that information in.