mike-obrien.net Curriculum Vitae Blog Labs
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 3:54:38 AM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  | 
Thursday, September 04, 2008

Recently I had the need to setup multiple SSL enabled sites on my local machine for development. These sites all had the same root domain but differed by sub domain. Traditionally you need to have a certificate and an IP address per SSL binding because of a "chicken or the egg" problem resolving the host headers in an encrypted HTTP conversation. If you have multiple sites with a common root domain that require SSL you can get around this limitation by using a wildcard certificate for all those sites.

So first I setup mappings in my hosts file (<SystemRoot>\System32\drivers\etc\hosts) as follows:

# SomeSite Dev Mappings
127.0.0.1          www.dev.somesite.net
127.0.0.1     services.dev.somesite.net
127.0.0.1        admin.dev.somesite.net


Next I need to create a self signed wildcard certificate. If you tried the self signed cert "feature" in IIS7 you probably quickly discovered that it is pretty much worthless since you cannot define the common name (CN), it's automatically set to the host name (Why does MS have a habit of giving you a powerful, feature rich car that can only make right turns?). One way to get around this is to generate your self signed cert with a tool and add it to the local machine store. IIS6 ships with a util called selfssl but this requires you to install the IIS6 ResKit (See more about that here). While this works, it bothers me to install tools from a previous version of IIS to accomplish this. Shouldn't the newer version of IIS do more than it's predecessor? One other alternative I found on the internets is to use the certificate creation tool that ships with the .NET 2.0 SDK. For some reason this "feels" better than using a tool from IIS6, probably just a mental thing... Plus you probably already have the SDK installed and are using it.

First create the self signed issuer certificate which will be set as a root cert authority (Fill in the red items):

"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\makecert.exe" -n "CN=My Company Development Root CA,O=My Company,OU=Development,L=Wallkill,S=NY,C=US" -pe -ss Root -sr LocalMachine -sky exchange -m 120 -a sha1 -len 2048 -r

Next create a cert for your sites that is issued from this authority. You must specify the common name (CN=<IssuerName>) you entered above in the issuer name field below (-in <IssuerName>). Also I'm creating a wildcard certificate that will serve all sites with the dev.somesite.net root domain as this is a requirement to use host headers. If I add other sites in the future with a different subdomain I can choose this certificate and all is good. Specifying an asterisk as the subdomain will signify this (Fill in the red items):

"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\makecert.exe" -n "CN=*.dev.somesite.net" -pe -ss My -sr LocalMachine -sky exchange -m 120 -in "My Company Development Root CA" -is Root -ir LocalMachine -a sha1 -eku 1.3.6.1.5.5.7.3.1

You should now see this cert show up in the IIS manager on the "Server Certificates" page:

image

Now again, MS gets you part of the way there in the UI but not all the way. As in IIS6 (SP1+) you cannot specify a host header for SSL bindings in the IIS7 UI because of, as mentioned above, issues with resolving the host headers in an encrypted HTTP request. But since we are using a wildcard certificate these issues are moot and IIS can do it but we have to configure it through the command line with the new appcmd util. The following command must be executed on each site that requires SSL. This command will create the SSL binding and set the host header. Make sure you specify the correct site name and host header for each site (In red):

C:\Windows\System32\inetsrv\appcmd set site /site.name:MySite /+bindings.[protocol='https',bindingInformation='*:443:www.dev.somesite.net']

Next go to the site bindings and you'll now see an SSL binding with a host header defined (Before this field would be disabled for SSL). You will need to select the the wildcard certificate you created earlier in the cert drop down and save your changes.

image

.NET 2.0 | IIS 7 | SSL
Thursday, September 04, 2008 7:47:54 PM (GMT Standard Time, UTC+00:00)  #   |  Comments [0]  |