Logging using the Trace and Debug classes in the .Net framework – level 300

I’ve seen several homemade logging mechanism used in various applications, and they all do their job, but every one of them seems to be very tightly coupled, and the source that does the logging must to changed to add a new logging destination.  I’d like to share a method that my team uses in our code.  When we wanted to get the information to another destination, the code doing the logging doesn’t have to know about it.  First, we need an object to hold our logging information.  I’ll call it TraceEntry:


using System;


using System.Collections;


using System.Diagnostics;


using System.Text;



namespace ConsoleTestHarness


{


    public class TraceEntry


    {


        protected string m_message = string.Empty;


        protected DateTime m_createdOn = DateTime.Now;


        protected TraceLevel m_entryLevel = TraceLevel.Info;


        protected Type m_caller;



        public string Message{


            get { return m_message; }


        }


        public DateTime CreatedOn{


            get { return m_createdOn; }


        }


        public TraceLevel EntryLevel{


            get { return m_entryLevel; }


        }



        public TraceEntry(string message, TraceLevel entryLevel, Type caller){


            m_message = message;


            m_entryLevel = entryLevel;


            m_caller = caller;



            Trace.Write(this);


        }



        // For the default trace listeners, this method will be called implicitly.


        public override string ToString() {


            StringBuilder builder = new StringBuilder();



            builder.Append(m_createdOn.ToString().PadRight(22));


            builder.Append(m_caller.Name.PadRight(15));


            builder.Append(m_entryLevel.ToString().PadRight(9));


            builder.Append(m_message);



            return builder.ToString();


        }



    }


}

 
Next, we need code to use this class to log information:

using System;


using System.Diagnostics;


using System.IO;


using System.Reflection;


using Dell.Titans.Framework.Diagnostics;



namespace ConsoleTestHarness


{


    class TestHarness


    {


        [STAThread]


        static void Main(string[] args)


        {


            Trace.Listeners.Add(new CustomTraceListener());


            new TraceEntry(“This is a message”, TraceLevel.Verbose, typeof(TestHarness));


            Console.ReadLine();


        }


    }


}

Notice that all we do is create the object.   The object, when created, sends itself to Trace.Write.  This is a great feature of the .Net framework that allows “listeners” to be attached to listen for messages.  Here we are sending a message, and our CustomTraceListener “hears” the object being sent to it.  Here’s the source for our listener:

using System;


using System.Diagnostics;



namespace ConsoleTestHarness


{


    public class CustomTraceListener : TraceListener


    {


        public override void Write(object o) {


            // If we know what this object is, we can work with it.


            if(o is TraceEntry){


                TraceEntry entry = o as TraceEntry;



                // Pull out the pieces of the object and log them where you wish.


                Console.WriteLine(entry);


            }


        }


        public override void WriteLine(object o) {this.Write(o);}


        public override void Write(object o, string category) {this.Write(o);}


        public override void WriteLine(object o, string category) {this.Write(o);}



        // abstract method implementations.


        public override void Write(string message) {}


        public override void WriteLine(string message) {}


    }


}

And there you have it.  Our listener receives this object.  It tests it to make sure it is of the type it understands, and then it can  use the different pieces of the object to log to fields in a database, log to a file, or anywhere else.  Here, we spit it out to the console, and notice how the ToString() overridden method was implicitly called.
 
A great way to extend this method is to add a listener to send a warning email to support personnel whenever a message with type “Error” is logged.
 
Happy coding.

Changes from ASP.NET 2.0 Beta 1 to Beta 2 – level 100

Check out the ASP.NET Developer center for some changes from ASP.NET Beta 1 to Beta 2.  I welcome some of these changes because I was in the croud that logged bugs against Beta 1, and these actions resolve the bugs.  The special directory names are changing.   Here are the changes:


/Bin
(formerly Application_Assemblies)


stays the same


/Bin


/Code
(formerly Application_Code)


Becomes


/app_code


/Resources
(formerly Application_GlobalResources)


Becomes


/app_globalresources


/LocalResources
(formerly Application_LocalResources)


Becomes


/app_localresources


/WebReferences
(formerly Application_WebReferences)


becomes


/app_webreferences


/Data
(formerly Application_Data)


becomes


/app_data


/Browsers
(formerly Application_Browsers)


becomes


/app_browsers


/Themes
(formerly Application_Themes)


becomes


/app_themes

Generated event handlers will be marked as protected instead of private going forward.

The .aspx will no longer be a partial class with the code file and a system file.  Instead, the .aspx will inherit from the code file, which will be a partial class with a system file.  This solves the problem of not being able to reference UserControl types at design time. 

Another great thing, though, is that VWD2005 supports intellisense in the .aspx, so for whipping up code samples for newsgroups or my blog, I can just use a single file.  Yeah, I know, I’ve grown lazy and depend on intellisense, but when is the last time you actually got up to change the channel on your TV???  Addicted to the remote control???

Austin .Net UG and Web Services with WSE 2.0 – level 200

Last night, Christoph gave a great talk and demo on Web services and WSE 2.0.  I haven’t used WSE yet, so, of course, it seemed like a mystery, but again Microsoft did a great job of making it work with just a few lines of code.  WSE deal with the security aspects of web services.  Both the client and server have to be using it, though.  I found out that if I’m interopping with an older technology using the Soap Toolkit, then I’m stuck with Web Services 1.0 and my own security scheme.

Simplest way to validate Xml against a Xml Schema – level 200

There are plenty of articles out that explaining how to validate Xml, but they all seem to use namespaces or have other factors in them.  I didn’t find one that used plain Xml with no namespace declaration, so I whipped up a little example.  I included the Xml and Xsd inline and load it using a StringReader, but for your purposes, they would probably be in physical files.


            string xsd;


            string xml;


            xsd = @”<xs:schema xmlns:xs=””http://www.w3.org/2001/XMLSchema””&gt;


                        <xs:element name=””address””>


                        <xs:complexType>


                        <xs:sequence>


                        <xs:element name=””name”” type=””xs:string””/>


                        <xs:element name=””street”” type=””xs:string””/>


                        <xs:element name=””city”” type=””xs:string””/>


                        <xs:element name=””country”” type=””xs:string””/>


                        </xs:sequence>


                        </xs:complexType>


                        </xs:element>


                        </xs:schema>”;


            xml = @”<?xml version=””1.0″”?>


                        <address>


                        <name>John Smith</name>


                        <street>109 Abbey Close</street>


                        <city>Hayes</city>


                        <country> UK</country>


                        </address>”;



            XmlValidatingReader   vr   = null;


            vr = new XmlValidatingReader(new XmlTextReader(new StringReader(xml)));


            vr.Schemas.Add(null, new XmlTextReader(new StringReader(xsd)));


            vr.ValidationType = ValidationType.Schema;


            vr.ValidationEventHandler += new ValidationEventHandler(vr_ValidationEventHandler);



            // Validate XML data



            try{


                while(vr.Read());


            }catch{


                Console.WriteLine(“Xml Document is not well-formed.”);


            }finally{


                vr.Close();


            }

Deploying web service clients and web services without recompiling proxy class – level 200

There are a lot of people having issues when they have to change the location of the web service they are calling.  Some resort to recompiling the proxy class, but that isn’t necessary.  The proxy class that is generated by VS hard-codes the URL of the web service in the class, but with a little foresight, you can avoid problems when the location of the web service changes.  First, let’s create a configuration item to control the URL where the web service resides:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<appSettings>
<add key=”webServiceUrl” value=”http://localhost/service.asmx&#8221; />
</appSettings>
</configuration>

Now, we just add 1 line to the code that calls this web service:


            myWebService.Batch service = new myWebService.Batch();


            service.Url = ConfigurationSettings.AppSettings[“webServiceUrl”];


            Debug.WriteLine(service.Url);


            service.Credentials = System.Net.CredentialCache.DefaultCredentials;


            service.MyWebServiceMethod(textBox1.Text);


            Debug.WriteLine(“Called web service”);

By setting the “Url” property, we can change where the system goes to consume the web service.  Now, you can deploy the web service to any server, and if you change your app.config file, your client code won’t miss a beat.

Solution to my UnauthorizedAccessException problem – level -1000

Yesterday afternoon, my web application broke.  I have a page that takes a user data file and saves it to the server.  I have a web service that takes the contents of a file and does the same thing.  A process consumes these files on our development server.  I am developing part of it on my machine and dropping the file to the DEV machine for consumption, so I’m creating files on a UNC path.  Mysteriously I started getting UnauthorizedAccessException, but my code had been working.  I didn’t change anything.  I double-checked all the important stuff (identity impersonation, security, etc), and everything was in order.  I did the normal research. . . Google groups.  For all of you Googlers out there having problems with this exception, I have a solution.  Reboot!  I checked everything I could think of, and I remembered the rule of thumb that I always tell everyone else:  “When in doubt, reboot.”  Since I’ve been using Win XP and now 2003 Server as my dev OS, I haven’t had to do much rebooting.  My home computer (XP Pro) hasn’t been rebooted in at least two months, and it gets a lot of abuse as well as having umpteen external devices connected to it.

I’m using Windows Server 2003, and it is very stable, and even with the abuse I put it through, this was the first time I’d rebooted this week.  It’s a laptop, so I hibernate it.  I rarely reboot, but remember that when there is no rational explanation for a problem. . . reboot.

ASP.NET configuration gotcha – level 200

For people starting out with ASP.NET, the appSettings node in the web.config file will be all that is necessary for storing configuration items, but when you start creating your own config section handlers and strongly-typing your configuration items, you may run into a small snag that may cost you 1/2 a day to track down.

Configuration section names (since they are xml) are case sensitive.  Xml is case sensitive.

I have a section:

<configSections>

<section name=”SiteMap” type=”WebTestHarness.SiteMapConfig, WebTestHarness” />

</configSections>

but if I call:


ConfigurationSettings.GetConfig(“siteMap”);

I get mysterious errors elsewhere in my code.  My config section didn’t get loaded.  It turns out that the section “siteMap” wasn’t found.  “SiteMap” is the correct name.   Of course, I expected the .GetConfig() method to throw an exception if the section wasn’t found, but it doesn’t.  It just returns nothing and lets my code keep going.

Now, armed with this knowledge, I will just check the return value of the method to determine if the GetConfig was successful.

How to capture page output before it is rendered – level 300

If for some reason you need to capture page output either to log it, analyze it or do a global string replace, then you will need to intercept the rendered Html before it makes it to the wire on the way to the client.  Here is a code snippet that will do just that.

protected override void Render(HtmlTextWriter writer)

{


    StringBuilder sb = new StringBuilder();


    HtmlTextWriter htw = new HtmlTextWriter(new StringWriter(sb));


    base.Render(htw);


    writer.Write(sb.ToString());


    Trace.Warn(sb.ToString());


}

The StringBuilder will get the content, and you can do with it what you please.  Then just make sure to call writer.Write( ) to render the final content to the Response stream.