mikeobrien.net Curriculum Vitae Blog Labs
Saturday, October 10, 2009

We have a number of Windows Services in our infrastructure, all of which have a frequency in their configuration. Originally this frequency was in milliseconds but this is a real pain since when you set or read the configuration you are always doing the math between milliseconds and hours/minutes/seconds. Then is occurred that we could probably use a TimeSpan in the configuration to represent frequency instead of an integer of milliseconds. The .NET configuration API has a built in converter for the TimeSpan class so all you have to do is set the configuration property type to TimeSpan and your good to go!

Here is the configuration class:

public class Section : ConfigurationSection
{
    private const string FREQUENCY = "frequency";

    [ConfigurationProperty(FREQUENCY)]
    public TimeSpan Frequency
    {
        get { return (TimeSpan)this[FREQUENCY]; }
    }
}

Here is the configuration, much easier to read/set:

<company>
  <services>
    <someService frequency="00:00:30"/>
  </services>
</company>

And the usage:

private Common.Timers.Timer _processTimer =
        new Common.Timers.Timer(
            Configuration.Manager.Current.SomeService.Frequency.TotalMilliseconds,
            Common.Timers.Timer.TimerElapseStartMode.Immediate,
            Common.Timers.Timer.TimerElapseReentranceMode.NonReentrant);
Saturday, October 10, 2009 3:33:27 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Wednesday, October 22, 2008

I have been having XmlTraceListener woes... None of which are showstoppers just major time wasters! The following bug is fixed as of version 4.1! The first of which has to do the message. The message is not escaped and/or CDATA qualified. So if you have an ampersand or greater/less than sign in the message it will throw this cryptic error:

An error occurred while parsing EntityName. Line x, position y.

I ended up manually running the log entry object through the Format() method on the XmlLogFormatter class to get the raw xml and see what exactly it was complaining about. Sure enough there was an ampersand. Adding an extension method to the string class and manually escaping the LogEntry message alleviated this to some degree. Bad thing is though, the message is escaped for all listeners and you probably don't want to see HTML entities in your event log entry or email message. I think the only way around this would be to write your own xml trace listener.

namespace MyApp.Runtime.Extensions
{
    public static class String
    {
        public static string EscapeUnsafeXmlCharacters(this string value)
        {
            return value.Replace("<", "&lt;").Replace(">", "&gt;").Replace("&", "&amp;");
        }
    }
}

Issue #2 has to do with where the log file is saved. The FlatFileTraceListner actually saves the log file relative to the application root (Where the .config file is). Take a look at the RootFileNameAndEnsureTargetFolderExists() method in the FormattedTextWriterTraceListener class (The FlatFileTraceListener's base class). Before it passes the filename to it's base class constructor, it runs it through this method. The XmlTraceListener on the other hand does not follow this pattern which had me going nuts trying to figure out what I was doing wrong (Thinking that it just wasn't saving at all). I wasn't getting any errors from the XmlTraceListener (More on this below) so I started to get the feeling that perhaps it was writing to the log but just not where I expected. So I fired up Process Monitor and didn't see any log writes. Then I switched the app (It was a web application project BTW) to use the built in web server instead of IIS. Ran everything again and voilĂ :

image

So it was saving it somewhere else, but only when running under the built in web server. Looking at the code in reflector you can see that, unlike the FormattedTextWriterTraceListener, the XmlWriterTraceListener does not modify the path to be the application root before it passes it to it's base class constructor. So the directory ends up being that of the entry assembly. Bottom line is you have to supply an explicit path when you're using the XmlTraceListener in a web application.

This leads me to the third oddity; where it would save running under the builtin web server but not IIS. The base class, TextWriterTraceListener, calls the EnsureWriter() method (Shown below) before writing to the file. If it returns true it writes the entry, otherwise it doesn't. Notice that if there is a UnauthorizedAccessException it just returns false and then subsequently, in the calling Write method, silently skips writing. When I was using IIS as my web server (And the code was running as the Network Service account) I didn't see any exceptions and no log writes were showing up in Process Monitor. Which was very confusing! But when I switched over to using the built in web server (Which was obviously running as my interactive account) I saw the log writes. So when it was running under the Network Service account it obviously did not have permissions to save the log file to wherever it was trying to save it and no exception was raised... Very confusing behavior this. Reminds me of the advice in Jeffrey Richter's CLR Via C# book not to ever swallow exceptions! So keep that in mind, if you don't see any errors and log writes with the XmlTraceListner, it may be permissions and/or path related.

Well, hopefully this wasn't too long winded and hopefully it clears up some oddities with the XmlTraceListener.

public class TextWriterTraceListener : TraceListener
{
    public override void Write(string message)
    {
        if (this.EnsureWriter())
        {
            if (base.NeedIndent)
            {
                this.WriteIndent();
            }
            this.writer.Write(message);
        }
    }
    internal bool EnsureWriter()
    {
        bool flag = true;
        if (this.writer == null)
        {
            flag = false;
            if (this.fileName == null)
            {
                return flag;
            }
            Encoding encodingWithFallback = GetEncodingWithFallback(new UTF8Encoding(false));
            string fullPath = Path.GetFullPath(this.fileName);
            string directoryName = Path.GetDirectoryName(fullPath);
            string fileName = Path.GetFileName(fullPath);
            for (int i = 0; i < 2; i++)
            {
                try
                {
                    this.writer = new StreamWriter(fullPath, true, encodingWithFallback, 0x1000);
                    flag = true;
                    break;
                }
                catch (IOException)
                {
                    fileName = Guid.NewGuid().ToString() + fileName;
                    fullPath = Path.Combine(directoryName, fileName);
                }
                catch (UnauthorizedAccessException)
                {
                    break;
                }
                catch (Exception)
                {
                    break;
                }
            }
            if (!flag)
            {
                this.fileName = null;
            }
        }
        return flag;
    }

    // Other members removed for brevity...
}
Wednesday, October 22, 2008 4:54:38 AM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Tuesday, November 27, 2007

NAnt scripts can get cluttered with configuration values. One way to clean this up is to create properties that are used throughout the script. Although, you could take this a step further and store NAnt script configuration values in an Xml file. I like this approach because it creates a clear separation between the script and modifiable parameters. For example lets say we have an Xml configuration file called Build.config that contains paths:

<?xml version="1.0"?>
<buildConfiguration>
    <paths>
        <path name="SubversionPath" value="C:\Program Files\CollabNet Subversion Server\svn.exe" />
    </paths>
</buildConfiguration>

Our NAnt script could then load these paths into parameters in an "init" target. All other targets could then depend on this "init" target.

<?xml version="1.0"?>
<project name="Build" default="default">

    <property name="SubversionPath" value="" />
    <target name="init">
        <xmlpeek

              file="Build.config"
              xpath="/buildConfiguration/paths/path[@name = 'SubversionPath']/@value"
              property="SubversionPath"/>
     </target>

     <target name="default" depends="init">
         <echo message="${SubversionPath}" />

     </target>
</project>

image

Tuesday, November 27, 2007 5:09:38 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Monday, November 26, 2007

I've tried playing with custom configurations (Outside the stock Debug/Release configurations) in the past and found it confusing. So today I tried it again and had much better success. One thing that had me confused is the relationship between Solution level configuration and Project level configuration. What I didn't realize is that they are all completely separate entities... The "Debug" configuration in SolutionA is different from the "Debug" configuration in ProjectA. The "Debug" configuration in ProjectA is different from the "Debug" configuration in ProjectB. They all just happen to have the same name for consistency (Which is where my confusion began; guess I should have read the manual...). Although they are different entities, project level configurations can be mapped to a solution level configuration.

The example below shows a possible configuration. The solution has 2 configurations: "PlanA" and "PlanB". The BLL project have 2 configurations: "Production" and "Staging". The UI project has 2 configurations: "Red" and "Blue". The "PlanA" solution configuration has the BLL project configuration set to "Staging" while the UI project configuration is set to "Red".

image

So basically the "PlanA" and "PlanB" solution configurations represent a specific combination of project configurations. When you build a solution with a specific solution level configuration it will know what project level configuration to use with each of the individual projects in the solution. "PlanA" would use "Staging" for the BLL and "Red" for the UI.

Another thing that got me is how to add/edit/remove the project level configurations. It's pretty simple; the solution level configuration dropdown (Shown in red above) has a "<New...>" and "<Edit..>" item that enables you to modify solution level configurations. Also each individual project has a configuration dropdown (Shown in blue and green above) that give you the same options for project specific configurations.

Another thing to remember is that when you add a new project, the Debug/Release solution configurations will be automatically re-added. So if you removed these earlier they will have to be removed again.

Oh, and one more thing. Website Projects (WSP) show up as having only one option for configuration and platform with no modification options (Shown in red below). This is because configurations do not apply to a WSP (Since it isnt really a project and does not have a project file). It's a little misleading that there is one item in the list as if its assigned to it. It should just be disabled IMO. In any event it can be ignored. If you want to configure build options for a WSP, create a corresponding Web Deployment Project (WDP). You can then create configurations on the WDP that apply to the the WSP.

image

Monday, November 26, 2007 9:31:44 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Tuesday, November 13, 2007

I have gotten the following exception a couple of times:

Service 'Translator' has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.

There are a number of things that could potentially cause this exception but the one that has gotten me a couple of times is making sure the name attribute of the service element in the .config contains the fully qualified class name. So for example if I have the following service:

namespace BabelFish.ServiceHost.Web
{
    public class Translator : BabelFish.Services.Translator
    {
    }
}

The service name should be as follows (in bold):

<configuration>
    <
system.serviceModel>
        <
services>
            <
service behaviorConfiguration="TranslatorBehavior" name="BabelFish.ServiceHost.Web.Translator">
                <
endpoint address="http://localhost/BabelFish.ServiceHost.Web/Translator.svc" binding="wsHttpBinding" name="Translator" contract="BabelFish.Services.Contracts.Service.ITranslator"/>
               <
endpoint address="mex" binding="mexHttpBinding" name="MetaDataExchange" contract="IMetadataExchange"/>
            </
service>
        </
services>
        ...
    </system.serviceModel>
    ...
</configuration>
Tuesday, November 13, 2007 4:36:39 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Monday, November 12, 2007

In order to configure WCF 3.0 to run under ASP.NET you need to run the installer (ServiceModelReg.exe) in the WFC 3.0 Framework folder (<system drive>\<windows folder>\Microsoft.NET\Framework\v3.0\Windows Communication Foundation) with the /i parameter.

image

Monday, November 12, 2007 10:49:55 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Friday, March 23, 2007

I have been working with the new custom configuration classes in .NET 2.0 and they really make creating custom xml configuration a snap. They are a little quirky to use at first but once you get past the initial learning curve they really make the job a lot easier.

One thing I really wanted to figure out is how to use these new classes to read files other than the default app/web.config. For example if you have one config file that might be shared between exes or if a config file is pulled from a URL. Its actually pretty easy to do.

First create your custom configuration file. It will look exactly the same as a web/app.config file except it will only contain your custom section(s). Create a section handler element identifying the custom section handler and the name of the section. Again, nothing different from doing this in the app/web.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <configSections>
  <section name="mySettings" type="CustomConfigHandler.MySettingSection, CustomConfigHandler"/>
 </configSections>
 <mySettings>
  <someSetting someAttribute="This is an attribute value from Settings1.config"/>
 </mySettings>
</configuration>

Next create a Configuration object that points to your file by passing in an ExeConfigurationFileMap with the ExeConfigFilename set.

// Create an exe file map containing the path to your config file
ExeConfigurationFileMap FileMap = new ExeConfigurationFileMap();
FileMap.ExeConfigFilename = "MySystem.config";

// Create a configuration object that is tied to your custom config file.
Configuration Config = ConfigurationManager.OpenMappedExeConfiguration(FileMap, ConfigurationUserLevel.None);

// Create the custom config section handler
MySettingSection MySettings = (MySettingSection)Config.GetSection("mySettings");

Console.WriteLine(MySettings.SomeSetting.SomeAttribute);


The entire source can be downloaded here:

ExternalConfigFile.zip (40.24 KB)
.NET | C# | Configuration | VB.NET
Friday, March 23, 2007 8:30:17 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |  Trackback
Creative Commons License