Tuesday, December 20, 2005

DNS Caching and Windows XP

I recently returned from yet another trip to China (this time with my wife :) and plugged the laptop back into the work network. When I have the laptop in the office I usually use Remote Desktops to connect to it. When I do this I also try to use the wired connection vs. the 802.11b as it is more responsive. What I noticed was that even though the new IP address for the NIC was registered with the DNS server, it was using the old IP Address on my desktop. The effect was that the machine was unreachable.

Using our trusted friend google I found this article to explain how to adjust the DNSClient on the local workstation.

Friday, December 2, 2005

I saw this up on MSDN. Its worth trolling the MSDN Mag site periodically to see what articles are there. I used to, and still do, troll the brick and mortar Barnes and Noble nearby to see the magazines. I no longer see the MSDN Magazine there anymore, so I've switched to reading it on the web from time to time.

Ten Essential Tools: Visual Studio Add-Ins Every Developer Should Download Now -- MSDN Magazine, December 2005

I used the CodeSourceToHtml AddIn to produce the code below. I've been looking for a Source to HTML tool since I started blogging!


public delegate void ProgressEventHandler(object sender, ProgressEventArgs args);
 
public class ProgressEventArgs : EventArgs
{
    private string _probeName;
 
    public ProgressEventArgs(string probeName)
    {
        _probeName = probeName;
    }
 
    public string ProbeName
    {
        get
        {
            return _probeName;
        }
    }
}

Enjoy!

Friday, November 18, 2005

I've pulled out "The Rules" from this article that I found referenced by Stuart:

"The Rules

  1. Calculations and comparisons of DateTime instances are only meaningful when the instances being compared or used are representations of points in time from the same time-zone perspective.
  2. A developer is responsible for keeping track of time-zone information associated with a DateTime value via some external mechanism. Typically this is accomplished by defining another field or variable that you use to record time-zone information when you store a DateTime value type. This approach (storing the time-zone sense alongside the DateTime value) is the most accurate and allows different developers at different points in a program's lifecycle to always have a clear understanding of the meaning of a DateTime value. Another common approach is to make it a "rule" in your design that all time values are stored in a specific time-zone context. This approach does not require additional storage to save a user's view of the time-zone context, but introduces the risk that a time value will be misinterpreted or stored incorrectly down the road by a developer that isn't aware of the rule.
  3. Performing date and time calculations on values that represent machine local time may not always yield the correct result. When performing calculations on time values in time-zone contexts that practice daylight savings time, you should convert values to universal time representations before performing date arithmetic calculations. For a specific list of operations and proper time-zone contexts, see the table in the Sorting out DateTime Methods section.
  4. A calculation on an instance of a DateTime value does not modify the value of the instance, thus a call to MyDateTime.ToLocalTime() does not modify the value of the instance of the DateTime. The methods associated with the Date (in Visual Basic®) and DateTime (in the .NET CLR) classes return new instances that represent the result of a calculation or operation.
  5. When using the .NET Framework version 1.0 and 1.1, DO NOT send a DateTime value that represents UCT time thru System.XML.Serialization. This goes for Date, Time and DateTime values. For Web services and other forms of serialization to XML involving System.DateTime, always make sure that the value in the DateTime value represents current machine local time. The serializer will properly decode an XML Schema-defined DateTime value that is encoded in GMT (offset value = 0), but it will decode it to the local machine time viewpoint.
  6. In general, if you are dealing with absolute elapsed time, such as measuring a timeout, performing arithmetic, or doing comparisons of different DateTime values, you should try and use a Universal time value if possible so that you get the best possible accuracy without effects of time zone and/or daylight savings having an impact.
  7. When dealing with high-level, user-facing concepts such as scheduling, and you can safely assume that each day has 24 hours from a user's perspective, it may be okay to counter Rule #6 by performing arithmetic, et cetera, on local times."


Stuart references this article from which I took the above quote.

Stuart Celarier : Quickie: it's about time

Saturday, November 5, 2005

NAnt - The solution task and setup/merge module projects in VS.NET 2003

At my current employer we use CruiseControl.net. Before we had CruiseControl.net in place we used NAnt build files. I'm sure that if you done much more than a "hello world" solution using NAnt you would soon find that there are some project types that do not work well with NAnt (at least 0.85 RC2).

I've given up on getting solutions that contain C++ in them to compile correctly with NAnt. It is just much easier to use an exec task. All that being said, I really dislike seeing warning messages of any kind in the build results:

Warnings: (22)

Only C#, VB.NET and C++ projects are supported. Skipping project 'FirstModule\FirstModule\Module.vdproj'.

Only C#, VB.NET and C++ projects are supported. Skipping project 'AnotherModule\AnotherModule.vdproj'.

Only C#, VB.NET and C++ projects are supported. Skipping project 'AnnoyMeModule\AnnoyMeModule.vdproj'.


The above is a sample with the module names changed. I knew from hacking solution files in the past that it would be possible to simply find the references to the MergeModule projects and remove them and then load the solution.

What I needed to do was remove the lines below:

Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "FirstModule", "FirstModule\FirstModule.vdproj", "{675E01F7-2494-4F95-A271-B2F6F6FD02D4}"
ProjectSection(ProjectDependencies) = postProject
EndProjectSection
EndProject


I thought about three possible solutions to removing these lines:

  1. Write an EXE Console App that used Regex's to find the 'offending' lines and remove them from the output (an "old school" Unix filter!)

  2. Take a similar approach but implement a new NAnt task. I've had to do that in the past to override broken CVS tasks, so this wasn't a bad option.

  3. Write a script task and embed the C# code directly into the build file. Not as elegant, but extremely portable and you don't need to load a DLL to get it working.

Here is the script task that I wrote. It’s not pretty, and certainly could be cleaned up a little, but it does the job:

<!-- Utility Scripts -->

<script language="C#" prefix="filter">

    <code>

    <![CDATA[

    [Function("remove-vdproj")]

    public static string RemoveVdprojFilter(string path)

    {

        string beginPattern = @"^Project\(""\{54435603-DBB4-11D2-8724-00A0C9A8B90C\}""\) = "".+"", "".+\.vdproj"", ""\{[A-F0-9\\-]+\}""$";

        string endPattern = @"^EndProject$";

 

        using (StreamReader reader = File.OpenText(path))

        using (StringWriter writer = new StringWriter())

        {

            bool inside = false;

            string line;

            while ((line = reader.ReadLine()) != null)

            {

                if (inside)

                {

                    Match match = Regex.Match(line, endPattern, RegexOptions.IgnoreCase);

                    inside = match.Success == false;

                }

                else

                {

                    Match match = Regex.Match(line, beginPattern, RegexOptions.IgnoreCase);

                    inside = match.Success;

                    if (inside == false)

                    {

                        writer.WriteLine(line);

                    }

                }

            }

 

            return writer.ToString();

        }

    }

    ]]>

    </code>

</script>



The workhorse of this script is the beginPattern. It reads in the solution file line by line until it finds the project definition that matches a Setup/MergeModule project. It then changes the state to 'inside' and reads until it finds the EndProject line. This will continue until it processes the entire file. The filtered solution file is returned as a string.

I decided to use an 'echo' task to write the filtered solution file back to the disk. It is then ready for consumption in a solution task.

NOTE: You might run into problems if you call this NAnt task from another NAnt task. The script code assumes the working directory of the original Nant project, not the currently executing task's project's directory. To work around this, make sure to surround your relative paths to your solution file with a path::get-full-path() function.

See example below:

<target name="ds.build" description="Builds DirectoryServices">

    <echo file="DirectoryServices/DirectoryServices-NoMerge.sln" message="${filter::remove-vdproj(path::get-full-path('DirectoryServices/DirectoryServices.sln'))}" />

    <solution configuration="${configuration}" solutionfile="DirectoryServices/DirectoryServices-NoMerge.sln" />

    <delete file="DirectoryServices/DirectoryServices-NoMerge.sln" />

</target>



You can easily expand on this concept to alter solution and project files prior to compiling them in your favorite default.build!

Scott pointed out to me that I should cross-link to his article, "Building MSI files from NAnt and Updating the VDProj's version information and other sins on Tuesday". Thanks Scott!

-- John

Thursday, October 27, 2005

Singletons and Dynamic Loading Assemblies

Again in the pits of the coding jungle I discovered that we were breaking the Singleton design pattern. Basically what we had were public default constructors. This of course would allow anyone or any dynamic assembly loader to create a new instance of what should be a unique object!

To solve this I decided to augment our dynamic loading class to look for an attribute: [SingletonAttribute] or [Singleton]. If it finds this, then it will look for [SingletonInstance] that should be on a public static getter.

Old code:

public class NotQuiteSingleton

{

    private static NotQuiteSingleton _instance = new NotQuiteSingleton();

 

    public static NotQuiteSingleton Instance

    {

        get

        {

            return _instance;

        }

    }

 

    /// REALLY BAD!

    public NotQuiteSingleton()

    {

    }

}



New code:

[Singleton]

public class AuthenticSingleton

{

    private static AuthenticSingleton _instance = new AuthenticSingleton();

 

    [SingletonInstance]

    public static AuthenticSingleton Instance

    {

        get

        {

            return _instance;

        }

    }

 

    // Ahhhh... much better...

    private AuthenticSingleton()

    {

    }

}



Enjoy,

John

Wednesday, October 26, 2005

In my stumbling through code I often come across code that looks like:

string bar = "this is some&string&to be split";
char[] foo = new char[1];
foo[1] = '&';
string[] splitString = bar.Split(foo);

Digging into the documentation on string.Split() you find that there are two versions:

public string[] Split(char[], int);

and
public string[] Split(params char[]);
The second is of interest to us because of the 'params' keyword. Those of you familiar with the Console.WriteLine() call (or printf() from the old C days) will recognize that 'params' allows us to supply a parameter list of unknown size and pass it into a function. In this case, the Split() call can be made with a single '&' parameter instead of making it into an array. So we get the following:

string bar = "this is some&string&to be split";
string[] splitString = bar.Split('&');

Also note that if we needed to add another character to split on, say a space, we would write the split like this:

string[] splitString = bar.Split('&', ' ');

Simple, elegant and more in touch with your C# self :)