Devlico.Us
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @devlicious

The Perpetual n00b

There's always something more to learn
  • Off-topic post

    I apologize for the off-topic post from last night. I had forgotten that I had cross-posting enabled from my personal blog. Please disregard it.
  • Salary negotiation in the interview process

    Eric Wise exhorts us to be honest when presenting our salary requirements when interviewing for a new job. I absolutely couldn't agree more. I mean, think about it, you wouldn't want a company pulling the same kinda deal with you would you? Making you an offer to see if you'll accept it, you accept it, and when you do, they decided to see if you'll go for 10-15% less than they originally offered (if that doesn't make sense, go read Eric's blog entry, it'll make more sense). You'd probably be pretty tweaked, right? I know I would, and would very adamantly refuse any further offers, having no interest whatsoever in working for a company that would try to pull such a shady deal. So why should a company's perspective of us be any different at all?

    However, reading his advice reminded me of the job interview scenario that I always dread so much...

    The question I always dread by far the most when I'm in a job interview is: "How much are you making now?"

    Why do I hate it so much? Because part of me can't help but feel like the answer that I *want* to give is "that's really none of your business, and shouldn't really have any bearing on this conversation." Should it? Unfortunately, so far, I've not figured out a way to communicate that without coming across as sounding either arrogant or antagonistic.

    So, I ask you, what would be the most appropriate way to respond to such a question? Is it really a prospective employers business to know how much money you make at your current job? I mean, what if you work for a non-profit company where there's really no way they could have given you a comparable salary to other for-profit employers, but they're working with the technology that you've wanted to get experience with for years, so you took it for the experience. Now that you've got the experience, and are looking to take a step up in the world with regard to salary, we all know its just not going to work if and when that prospective employer finds out that you're currently making considerably less than you should be. No, they're going to make you an offer that's either at, or maybe slightly above what you're currently making.

    So what's the best way to communicate to a prospective employer that its none of their business when they ask you what you are currently making?

  • Tables vs. CSS for layout of web forms

    This is an interesting post, with a good tip for how to apply field labels to TextBox fields on web forms. I did however find one point that was made interesting and it made me think...

    The problem is how to design this form cause there are no tables by default and also tables are not state of the art today.

    I guess the reason I find this interesting is because I only partially agree with his statement. Tables are not "state of the art" today, but I believe this only applies to your overall page layout, not necessarily to forms on your page. I feel like using a table to layout a form in a tabular format is absolutely appropriate, and really the only way to effectively layout a form with lots of fields that you may not necessarily want to layout strictly with vertical orientation (all fields and labels on top of each other). Am I the only one that feels this way? I still use tables to layout my forms, at least partially because I've not found an effective and easy way to use CSS to layout a form in tabular orientation. Am I still in the dark ages and missing something?

    Currently listening to: The Pot Bellied Goddess by He Is Legend from the album Suck Out The Poison
  • Google Code Search - fun for nerds

    Had some fun today exploring Google's new Code Search engine...

    /** Windows has some of the most ridiculous HTTP_USER_AGENT strings */

    # gad, this is getting ridiculous

    175: Here is a ridiculous function, which uses all of the special
    meta-parameters:

    99: we must resort to a different escape method. Now it's turning
    ridiculous quickly.

    * Ahh, the joys of nearly ridiculous over-engineering.

    # The rest are f'n ridiculous. Adobe either didn't think about this, or are just stoopid

    # thus (re-)instating the "war time" rule from 1942. Can you say
    # ridiculous crack-smoking stupidity?

    58: /* Heh, foo, we want to use utils, but they reference applets, and applets_last,
    * so smoke some more crack and make these available */

    170: as with any other free software, I take NO RESPONSIBILITY for
    creating such a masterpiece that will smoke crack, trash your
    hard disk, and make lasers in your CD device dance to the tune of

    * _G_config.h: Smoke less crack, don't define _G_HAVE_ST_BLKSIZE.

    This class allows count distincts with multiple columns for retarded databases (Oracle and SQLite)

    {Name removed to protect the guilty} was retarded

    BSAFE uses the 'retarded' version...Seems like MS uses the 'retarded' version, too.

    Heh. Take care in the comments that you put in your code (or whether you attach your name to them)...you never know where it might end up.

    Currently listening to: Serpent Sickness by He Is Legend from the album Suck Out The Poison

  • XML log file with Enterprise Library

    The Problem...

    For a (personal) project I'm currently working on, I decided to use the Logging and Instrumentation application block from the Enterprise Library to handle logging. The problem I ran into was that I wanted my log destination to be an XML file. Why is that a problem? Well, there's a couple of reasons: 1) The logging application block doesn't provide an option for an XML file as one of the default trace listener options, and 2) working with large sets of XML, such as one that might be generated as a log file, can lead to some performance problems fairly quickly, which is certainly not something you want for such a common operation as logging. Loading up an XML document just to do an AppendChild() on the root node to add a log entry would become an extremely slow operation after about a week's worth of use at the most. So, I had to try to figure out a way to just append an XML node to an existing document without actually loading up the entire document. Here's what I ended up doing...

    The Solution...

    Configuring the Logging Application Block

    Run the Enterprise Library Configuration tool, and open your application's app.config file, and add a new Logging Application Block.

    Add new Logging Application Block screenshot New Logging Application Block screenshot

    The new logging application block adds a default event log trace listener and text formatter. You can do what you want with those, I didn't need them, so I deleted them.

    The next step is to add our XML file trace listener and formatter. So add a new Flat File Trace Listener, and Text Formatter. I called mine Xml TraceListener and Xml Formatter respectively.

    Add new Xml TraceListener screenshot Add new Xml Formatter screenshot

    New Xml TraceListener screenshot New Xml Formatter screenshot

    Next, we need to edit the template of our new Xml Formatter. To do this, highlight the Xml Formatter node, and highlight the Template property on the right, then click the elipsis button on the far right to open up the Template Editor window. For each piece of information that you wish to record in your log entry, wrap the value in XML tags in the editor, as shown below. Optionally, you can add a {tab} token before each node, so that the children of the log entry node will be indented for easier reading.

    Once the formatter template is setup, we need to set the properties of the Xml TraceListener. The first thing we should do is set the Formatter property to our Xml Formatter (select it from the dropdown list of available formatters). Then we need to set the Header and Footer properties such that they wrap the log entry in XML tags to encapsulate the log entry as a proper XML node in the document. I set the Header to <LogEntry> and the Footer to </LogEntry>. Set the Filename property to whatever makes the most sense to you, I set mine to XmlLogFile.log.

    Finally, we need to add Trace Listener references to each of our Category Sources and Special Sources. For each Category Source and Special Source that you have, right-click on it, and select New Trace Listener Reference, and then select the Xml TraceListener in the list of available Trace Listeners for the ReferencedTraceListener property.

    Xml Trace Listener References screenshot

    Once that's done, the configuration is complete. Seems like a lot to do, but once you've done it, you'll realize its really not that big of a deal. Pretty simple. Now that you've finished configuring the application block, click the Save icon on the toolbar to validate and save the changes you've made to your app.config file.

    Writing Log Entries

    In order to simplify the process of writing log entries to my log file, I just created a single file I call Logging.cs, and wrap everything in it in a namespace I called MyApp.Logging. Within that namespace, I created all of the classes that I needed as static classes, and all of the methods within them as static methods. Having done this, anywhere in my application that I want to be able to write log entries to my log file, I just import that namespace, and I have access to all of my logging functionality. For example, I've created structs with string and integer constants for my Priorities, my EventIDs, Categories, etc. I've also created static methods for writing different types of log entries, such as LogError, LogInformation, LogWarning, etc. LogError accepts a .NET Exception object as an argument and allows me to include the full call stack in the ExtendedProperties of the LogEntry to assist in debugging errors. NOTE: This has the potential of introducing a security risk into your application, so use that functionality at your own risk, or remove it if that is an issue for you. Here's what my Logging.cs file looks like, to give you an idea.

    using System;

    using System.Collections.Generic;

    using System.Diagnostics;

    using System.Text;

     

    using Microsoft.Practices.EnterpriseLibrary.Logging;

     

    namespace MyApp.Logging

    {

        /// <summary>

        /// Contains methods for adding LogEntries to the log file

        /// </summary>

        public static class Log

        {

            /// <summary>

            /// Adds an informational LogEntry with the given properties to the log file

            /// </summary>

            /// <param name="message">The message to write in the log entry</param>

            /// <param name="priority">The Priority of the log entry</param>

            /// <param name="severity">The Severity of the log entry</param>

            /// <param name="category">The Category of the log entry</param>

            /// <param name="eventID">The EventID for the log entry</param>

            public static void AddInformation( string message, int priority, TraceEventType severity, string category, int eventID )

            {

                LogEntry le = new LogEntry();

                le.Message = message;

                le.Priority = priority;

                le.Severity = severity;

                le.Categories.Add( category );

                le.EventId = eventID;

                Logger.Write( le );

            }

     

            /// <summary>

            /// Adds an error LogEntry with the given properties to the log file

            /// </summary>

            /// <param name="message">The message to write to the log entry</param>

            /// <param name="priority">The Priority of the log entry</param>

            /// <param name="severity">The Severity of the log entry</param>

            /// <param name="category">The Category of the log entry</param>

            /// <param name="eventID">The EventID of the log entry</param>

            /// <param name="ex">The exception information to add to the log entry</param>

            public static void AddError( string message, int priority, TraceEventType severity, string category, int eventID, Exception ex )

            {

                LogEntry le = new LogEntry();

                le.Message = message;

                le.Priority = priority;

                le.Severity = severity;

                le.Categories.Add( category );

                le.EventId = eventID;

                le.ExtendedProperties.Add( "ErrorStack", ex.ToString() );

                Logger.Write( le );

            }

        }

     

        /// <summary>

        /// Struct containing integer constants used to identify the Priority level for log messages

        /// </summary>

        public struct Priority

        {

            /// <summary>

            /// Low Priority

            /// </summary>

            public const int Low = 1;

     

            /// <summary>

            /// Medium Priority

            /// </summary>

            public const int Medium = 2;

     

            /// <summary>

            /// High Priority

            /// </summary>

            public const int High = 3;

     

            /// <summary>

            /// Urgent Priority

            /// </summary>

            public const int Urgent = 4;

        }

     

        /// <summary>

        /// Struct containing string constants used to identify Categories for log messages

        /// </summary>

        public struct Category

        {

            /// <summary>

            /// Used for Informational log messages

            /// </summary>

            public const string Information = "Information";

     

            /// <summary>

            /// Used for Warning messages, informing the user of a potentially negative conditions or events

            /// </summary>

            public const string Warning = "Warning";

     

            /// <summary>

            /// Used for Error messages, informing the user of Error conditions of events

            /// </summary>

            public const string Error = "Error";

        }

     

        /// <summary>

        /// Struct containing integer constants used to identify events for log messages

        /// </summary>

        public struct EventID

        {

            /// <summary>

            /// Identifies the service starting event

            /// </summary>

            public const int ServiceStart = 1;

     

            /// <summary>

            /// Identifies the service stopping event

            /// </summary>

            public const int ServiceStop = 2;

     

            /// <summary>

            /// Identifies the Blah event

            /// </summary>

            public const int Blah = 3;

     

            /// <summary>

            /// Identifies the configuration update event

            /// </summary>

            public const int ConfigurationUpdate = 4;

     

            /// <summary>

            /// Identifies AnotherBlah event

            /// </summary>

            public const int AnotherBlah = 5;

        }

    }

    So, with a reference to the MyApp.Logging namespace, writing an entry to our log file in XML format becomes as simple as this:

    Log.AddError( "Error performing operation", Priority.High, TraceEventType.Error, Category.Error, EventID.MyOperation, ex );

    The Result

    So, once you start writing log entries, the resulting XmlLogFile.log ends up looking something like this:

    <LogEntry>

        <Timestamp>9/14/2006 2:19:15 AM</Timestamp>

        <Message>Operation completed successfully</Message>

        <Category>Information</Category>

        <Priority>1</Priority>

        <EventID>3</EventID>

        <Severity>Information</Severity>

        <Title></Title>

        <MachineName>MyMachineName</MachineName>

        <AppDomain>MyApp.Service.vshost.exe</AppDomain>

        <ProcessID>3100</ProcessID>

        <ProcessName>C:\MyDirectory\MyApp.Service.vshost.exe</ProcessName>

        <Win32ThreadID>4092</Win32ThreadID>

        <ThreadName></ThreadName>

        <ExtendedProperties></ExtendedProperties>

    </LogEntry>

    <LogEntry>

        <Timestamp>9/14/2006 2:21:47 AM</Timestamp>

        <Message>Error performing operation</Message>

        <Category>Error</Category>

        <Priority>3</Priority>

        <EventID>3</EventID>

        <Severity>Error</Severity>

        <Title></Title>

        <MachineName>MyMachineName</MachineName>

        <AppDomain>MyApp.Service.vshost.exe</AppDomain>

        <ProcessID>3844</ProcessID>

        <ProcessName>C:\MyDirectory\MyApp.Service.vshost.exe</ProcessName>

        <Win32ThreadID>3944</Win32ThreadID>

        <ThreadName></ThreadName>

        <ExtendedProperties>

            ErrorStack - System.InvalidOperationException: Cross-thread operation not valid: Control 'Toast' accessed from a thread other than the thread it was created on.

            at System.Windows.Forms.Control.get_Handle()

            at System.Windows.Forms.Control.get_InternalHandle()

            at System.Windows.Forms.Control.get_CreateParams()

            at System.Windows.Forms.Label.get_CreateParams()

            at System.Windows.Forms.Control.UpdateBounds(Int32 x, Int32 y, Int32 width, Int32 height)

            at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)

            at System.Windows.Forms.Label.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)

            at System.Windows.Forms.Control.SetBounds(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)

            at System.Windows.Forms.Control.set_Size(Size value)

            at System.Windows.Forms.Label.AdjustSize()

            at System.Windows.Forms.Label.OnTextChanged(EventArgs e)

            at System.Windows.Forms.Control.set_Text(String value)

            at System.Windows.Forms.Label.set_Text(String value)

            at BackBurner.Service.BackupServiceController.SetServiceMessageText(String message) in C:\MyDirectory\MyApp.Service\MyAppServiceController.cs:line 189

            at MyApp.Service.MyAppServiceController.MyOperation(Object sender, MyEventArgs e) in C:\MyDirectory\MyApp.Service\MyAppServiceController.cs:line 175

            at MyApp.Service.MyOtherOperation(Blah archive, String folder) in C:\MyDirectory\MyApp.Service\MyClass.cs:line 194

            at MyApp.Service.YetAnotherOperation(AnotherBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line 159

            at MyApp.Service.WowAnotherOperation(BlahBlahBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line 163

            at MyApp.Service.WowAnotherOperation(BlahBlahBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line:line 163

            at MyApp.Service.WowAnotherOperation(BlahBlahBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line:line 163

            at MyApp.Service.WowAnotherOperation(BlahBlahBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line:line 163

            at MyApp.Service.WowAnotherOperation(BlahBlahBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line:line 163

            at MyApp.Service.WowAnotherOperation(BlahBlahBlah archive, String folder) in C:\MyDirectory\MyApp.Service\AnotherClass.cs:line:line 127

        </ExtendedProperties>

    </LogEntry>

    More problems

    Anybody notice the problem here? This is not a well-formed XML document. We can't really do anything useful with this can we? Most XML parsers out there will only work with well-formed XML, and until we have that, this is pretty much useless to us. We need to somehow wrap it all up in a root node, and maybe even an XML document declaration too (not completely necessary for well-formed XML, but nice-to-have for clarity nonetheless).

    Getting around the problem

    In searching for a way to deal with this issue, I found a great article in the MSDN Developer Center by Dare Obasanjo on efficient techniques for modifying large XML files, and specifically uses log files as an example for using this technique. In the article, he offers two different techniques. The one I chose to use involves the use of XML external entities to "include" a fragment of XML inside a well-formed XML document. See the article for more specifics if you're interested, but for now, I'll just discuss the implementation that I used that has worked very well for me so far.

    In my solution, recall that the log file I specified in the Logging application block configuration is called XmlLogFile.log. The contents of this file is the XML fragment that will be included in the actual XML document that ultimately ends up being the well-formed XML document that is our XML log file. This file, I'll call it LogFile.xml, is very simple, and includes nothing more than the following code:

    <?xml version="1.0" encoding="utf-8" ?>

    <?xml-stylesheet type="text/xsl" href="MyLogTransform.xslt"?>

    <!DOCTYPE MyLog [ <!ENTITY LogEntries SYSTEM "XmlLogFile.log"> ]>

    <MyLog>

        &LogEntries;

    </MyLog>

    When this document is actually parsed by an XML parser (or perhaps even XSLT), it will include all of the LogEntry nodes that were written to the XmlLogFile.log file, as if they were actually part of the that physical document. This technique has worked very well for me so far, without any problems at all. I get all the benefits of structure and flexibility of XML as a log source, and the performance capabilities of appending data to a flat file. Nice.

    Viewing the log file

    Obviously, since we're using XML, we can view/display our log entries just about any possible way we wish. But I thought I'd provide a quick sample of the method I used for displaying the contents of my log file to users.

    The application I'm using this technique on is a Windows Service, and it uses a Windows form as the UI for it's configuration. So, I decided to display my log file to users using XSLT to transform the XML into HTML with some embedded JavaScript and CSS to display the results in a Web Browser control on the configuration screen. Noitice in the above code sample how I specified the XSLT file used for transformation of the log file. Here's my XSLT file:

    <?xml version="1.0" encoding="UTF-8" ?>

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

        <xsl:template match="/">

            <html>

                <head>

                    <Title>My Log File</Title>

                    <script language="JavaScript" type="text/javascript">

                        // Shows/Hides the element with the given ID

                        function ShowHide(panelID)

                        {

                            var panel = document.getElementById(panelID);

                            if (panel != null)

                            {

                                if (panel.style.visibility == "hidden")

                                {

                                    panel.style.display = "block";

                                    panel.style.visibility = "visible";

                                }

                                else

                                {

                                    panel.style.display = "none";

                                    panel.style.visibility = "hidden";                   

                                }

                            }

                        }

                    </script>

                    <style>

                        body

                        {

                            font-family: Tahoma, Arial, Verdana, Helvetica, Sans-Serif;

                            font-size: 8pt;

                            color: #000;

                            margin: 0px 6px;

                            padding: 0px;

                            cursor: default;

                        }

                        .LineItem

                        {

                            cursor: pointer;

                        }

                        .LineItem:hover

                        {

                            color: #339;

                        }

                        .EntryDetails

                        {

                            margin: 3px 20px 10px 20px;

                        }

                        .EntryDetailsLabel

                        {

                            color: #777;

                        }

                        .ErrorStackHeader

                        {

                            cursor: pointer;

                        }

                        .ErrorStack

                        {

                            font-family: Lucida Console;

                            font-size: 8pt;

                            color: #f00;

                            margin: 5px -9px 5px 20px;

                        }

                    </style>

                </head>

                <body>

                    <xsl:for-each select="MyLog/LogEntry">

                        <xsl:sort select="Timestamp" order="descending" />

                            <div class="LineItem" onclick="ShowHide('{generate-id(Message)}')"><span class="EntryDetailsLabel"><xsl:value-of select="Timestamp"/>: </span><xsl:value-of select="Message" /></div>

                            <div class="EntryDetails" style="visibility: hidden; display: none;" id="{generate-id(Message)}">

                                <div><span class="EntryDetailsLabel">Category: </span><xsl:value-of select="Category" /></div>

                                <div><span class="EntryDetailsLabel">Priority: </span>

                                <xsl:choose>

                                    <xsl:when test="Priority = 1">Low</xsl:when>

                                    <xsl:when test="Priority = 2">Medium</xsl:when>

                                    <xsl:when test="Priority = 3">High</xsl:when>

                                    <xsl:when test="Priority = 4">Urgent</xsl:when>

                                </xsl:choose></div>

                                <div><span class="EntryDetailsLabel">Event: </span>

                                <xsl:choose>

                                    <xsl:when test="EventID = 1">Service Start</xsl:when>

                                    <xsl:when test="EventID = 2">Service Stop</xsl:when>

                                    <xsl:when test="EventID = 3">My Operation</xsl:when>

                                    <xsl:when test="EventID = 4">Configuration Update</xsl:when>

                                </xsl:choose></div>

                                <div><span class="EntryDetailsLabel">Severity: </span><xsl:value-of select="Severity" /></div>

                                <div><span class="EntryDetailsLabel">Machine name: </span><xsl:value-of select="MachineName" /></div>

                                <div><span class="EntryDetailsLabel">AppDomain: </span><xsl:value-of select="AppDomain" /></div>

                                <div><span class="EntryDetailsLabel">Process ID: </span><xsl:value-of select="ProcessID" /></div>

                                <div><span class="EntryDetailsLabel">Process name: </span><xsl:value-of select="ProcessName" /></div>

                                <div><span class="EntryDetailsLabel">Win32 Thread ID: </span><xsl:value-of select="Win32ThreadID" /></div>

                                <xsl:if test="ExtendedProperties != ''">

                                <div class="ErrorStackHeader" onclick="ShowHide('{generate-id(Message)}-ErrorStack')">Error Stack (click to show):</div>

                                <div class="ErrorStack" style="visibility: hidden; display: none;" id="{generate-id(Message)}-ErrorStack"><xsl:value-of select="ExtendedProperties" /></div>

                                </xsl:if>

                            </div>

                    </xsl:for-each>

                </body>

            </html>

        </xsl:template>

    </xsl:stylesheet>

    And here's a few screenshots of the UI displaying the log entries:

    Error log entry list display screenshot Error log entry list display with entry details screenshot

    Conclusion

    I know what you're thinking...FINALLY!! Please forgive me for my lack of brevity. I tend to be a bit long-winded at times. However, this has proven to be a pretty successful method for generating and viewing flexible log files for me. Hopefully it can be of use to someone else as well. As always, feedback, thoughts or ideas for improvement are always welcome.

    Cross-posted from my personal weblog at Bob.Yexley.Net.

    Currently listening to: Goldie's Torn Locks by He Is Legend from the album Suck Out The Poison
  • Custom service controller for debugging windows services

    The first time I wrote a .NET windows service, I quickly discovered how tedious and painful it can be to debug it when you want to start testing its functionality. The whole process of installing the service using InstallUtil.exe, then starting the service through the standard windows service control monitor, then going back into Visual Studio, attaching to the process, setting up your breakpoints, then stopping the service and restarting it so you can debug what's going on in the OnStart event handler, etc, etc. What a mess. I kept telling myself there has GOT to be a better way to do this.

    Well, there is...

    A friend of mine that I used to work with (thanks Brian), and another that I still work with (thanks Dave) pointed me in the right direction, and I was able to get it working, and have since come to the point where, if you're developing a windows service, you MUST have something like this. It has literally saved me hours and hours of time. The idea is to add a windows form application to your service project that can act as the service control monitor for debugging. Here's how...

    First thing you need to do is add a Windows Form class to your windows service project. This will be the form that you use to control the functions of the service, including starting and stopping it. Add a couple of buttons for starting and stopping the service (I'll explain what to wire the click events of those buttons up to in a minute), and then any other buttons you might want for calling and executing the various operations performed by your service to test them. Obviously, you can do pretty much whatever you want through this interface. This form basically is your service control monitor now.

    Once your form is in place, you need to expose the OnStart and OnStop methods of your service class so that they can be called from your form. My code to do this looks something like this:

    /// <summary>

    /// Starts the service

    /// </summary>

    public void StartService()

    {

        OnStart( new string[] { } );

    }

     

    /// <summary>

    /// Stops the service

    /// </summary>

    public void StopService()

    {

        OnStop();

    }

    Of course, you can wrap/expose any other functionality you want to be able to call or control from your form as well. Options for that are limited only by your own personal creativity. Those are the methods that I call from the OnClick event handlers for the buttons that I put on my form. Again, you can add buttons and expose any functionality of your service that you wish to manually test through this form in this same way.

    The next step is to create a command line switch that can be passed into the entry point of your service, to tell entry point to launch your form, rather than the service itself. The command line switch can basically be anything you want it to be. The command line arguments will be parsed and responded to appropriately in the entry point. I used the "-interactive" switch myself:

    namespace My.Service

    {

        /// <summary>

        /// The main entry point for the service

        /// </summary>

        static class Program

        {

            /// <summary>

            /// The main entry point for the application.

            /// </summary>

            [STAThread]

            static void Main( string[] args )

            {

                ProcessSwitches( args );

            }

     

            /// <summary>

            /// Loop through the command line arguments passed into the application, and parse each switch

            /// </summary>

            /// <param name="args">String array of command line switches to parse</param>

            private static void ProcessSwitches( string[] args )

            {

                // If command line arguments are specified, then loop through them and parse them

                if( args.Length > 0 )

                {

                    for( int i = 0; i < args.Length; i++ )

                    {

                        ParseSwitch( args[ i ] );

                    }

                }

                // If no command line arguments are passed, parse null to just run the service

                else

                {

                    ParseSwitch( null );

                }

            }

     

            /// <summary>

            /// Parse the command line switch and execute the appropriate method based on the switch

            /// </summary>

            /// <param name="cmdSwitch">The command line switch to parse</param>

            private static void ParseSwitch( string cmdSwitch )

            {

                switch( cmdSwitch.ToLower() )

                {

                    // Run in interactive mode

                    case "-interactive":

                    {

                        RunInteractive();

                        break;

                    }

                    // Just run the service

                    default:

                    {

                        ServiceBase[] ServicesToRun;

     

                        // More than one user Service may run within the same process. To add

                        // another service to this process, change the following line to

                        // create a second service object. For example,

                        //

                        //   ServicesToRun = new ServiceBase[] {new Service1(), new MySecondUserService()};

                        //

                        ServicesToRun = new ServiceBase[] { new BackupService() };

     

                        ServiceBase.Run( ServicesToRun );

                        break;

                    }

                }

            }

     

            private static void RunInteractive()

            {

                Application.EnableVisualStyles();

                Application.Run( new InteractiveDebugger( new BackupService() ) );

            }

        }

    }

    Once you've got the command line switch working, now you just need to setup your project so that when you click "Start" from within Visual Studio, your debugger form gets launched. This can be done by specifying the command line switch to pass into the entry point of your service when you Start the application in debug mode. To do this, from within Visual Studio, right-click on your service project and select "Properties" to display the project properties dialog. In this dialog, select the "Debug" tab. On this tab, there is a sub-section called "Start Options" which contains a text field labeled "Command line arguments:", which is the field you want to set. Enter the value of the command line switch that you specified in your code, in my case it was "-interactive".

    Custom service controller project properties screenshot

    Once you've set that property and saved your changes, now, when you run/debug your service, it will startup with your new custom service controller form, and you will be able to set breakpoints and test the functionality of your service all you want, without ever having to use InstallUtil.exe for installing and testing your service again. 

    Custom service controller form

    Cross-posted from my personal weblog at Bob.Yexley.Net.
  • Simple threading using the BackgroundWorker class

    A while back I was doing some work on my latest personal project at home (more on that later), and encountered a situation where I needed to run a long-running, resource intensive method, while at the same time updating a label control on a windows form. The process wasn't working very well, as the label wasn't getting updated properly, or in a timely manner, because the other method was using up all of the processor time allocated to the thread that they were both running in. So, I needed to figure out how to spin up the long-running method on a separate thread. I've never done any sort of multi-threading before, and didn't even really know where to start, so I just started browsing through all of the classes in the System.Threading namespace to see what my options were.

    So, I started out with the Thread class. That actually seemed to do a pretty good job, and really did what I needed with one exception. It ran the task on a separate thread, and the main class was able to update the label control on the form effectively now, but I had a couple of things that I needed to do once the threaded task was complete. Unfortunately, the Th