Wednesday, December 12, 2012

Everything is a List

If you take one of my SharePoint classes, there’s a couple of things that are guaranteed. You’re going to hear corny jokes. You’re going to hear obscure references to 80s movies and pop culture. And you’re going to hear me say the following phrase ad nauseum: “Everything in SharePoint is a list, and everything in a list is a list item.”

While this is a slight oversimplification, there’s a method to my madness.

SharePoint is a very large, very flexible, very configurable product. There is no “one size fits all” implementation of SharePoint. As a result, whenever I teach SharePoint my goal is to talk about the technologies, talk about what each item does, and try to arm my students with the knowledge needed to go back to their desks on Monday morning to make decisions that are right for their environment. And that phrase is one of the biggest tools in my tool belt. That phrase answers many, many questions.

Consider the blog site.

When you create a SharePoint blog site, SharePoint creates a few lists. In particular, the big three are Posts, Comments and Categories. Let’s touch on a few common questions about the blog site and see if the phrase “Everything in SharePoint is a list, and everything in a list is a list item” answers the question.

BlogSite

Q: How do I customize the categories for my blog posts?
A: Everything in SharePoint is a list, and everything in a list is a list item.

Your categories are stored in a list named, conveniently enough, Categories. If you wish to add, remove or otherwise customize the categories that are available simply navigate to the Categories list and modify the items.

Q: How do I control who’s allowed to create posts?
A: Everything in SharePoint is a list, and everything in a list is a list item.

All blog posts become items in the Posts list. If you wish to add or remove blog authors, change the permissions of the Posts list.

Q: How do I control what users are allowed to comment on blog posts?
A: Everything in SharePoint is a list, and everything in a list is a list item.

All comments are list items in the Comments list. To grant or revoke a user’s ability to create comments modify the permissions of the Comments list.

Q: Can I get an alert whenever a new blog post is added?
A: Everything in SharePoint is a list, and everything in a list is a list item.

Create an alert for yourself, or other users, on the Posts list.

Q: Can I enable approval of comments?
A: Everything in SharePoint is a list, and everything in a list is a list item.

To enable approval of comments, open the List Settings page, choose Versioning Settings, and then choose “Yes” for “Require content approval for submitted items?”.

Q: Can I create a workflow for comment approval?
A: Everything in SharePoint is a list, and everything in a list is a list item.

Because comments are stored as items in a SharePoint list, you can create and bind whatever workflow you need to the Comments list.

Notice the common theme here[1]. Every question was answered by simply applying our knowledge of lists to each scenario. To meet the goals, the answers were to modify permissions, work with list items, and modify list options. Everything in SharePoint is a list, and everything in a list is a list item. Once you’ve got that, quite a bit of SharePoint falls into place.

[1] It’s sort of hard to miss. :-)

Friday, December 7, 2012

How do I Connect with Other MCPs?


I will always remember my first TechEd in 2004. It was in San Diego. I’d been an Microsoft Certified Trainer (MCT) at that point for about 5 years, but I’d been rather insulated from the outside world. I knew there was a community of MCTs out there, but I’d never met any of them outside of communication on the newsgroups[1]. I didn’t know what to expect. And then, upon showing up at the preconference day, I realized there was this whole new world of people I could connect with. That experience was one of the events in my life that I can point to that changed the direction of my career, and my life.

I settled into the preconference event for MCTs. Before I knew it I was inundated by people walking up to me, introducing themselves, and wanting to finally “put the face to the name”. I was also mesmerized by the interaction between the MCT there. This was not merely a normal gathering of peers. This was a reunion. These were people that truly enjoyed each others’ company and relished the opportunity to reconnect.

I didn’t quite know what was going on, but I knew I wanted in. So while internally I’m a bit of an introvert I pushed that to the side to throw myself right into the mix. Years later I’m still connected with most everyone there. And I’m extremely fortunate to call a fair number of them friends – and that’s not a word I use very lightly.

The MCT community is rather large and rather active. It may be one of my favorite things about being an MCT.
However, community isn’t the sole providence of MCTs. As a Microsoft Certified Professional (MCP) you’re a member of a community that numbers into the hundreds of thousands. Each year at TechEd, MMS, SharePoint Conference, SQL PASS, ..., hundreds of MCPs descend upon a convention center for a week of learning and fun. Every event features numerous opportunities to visit and connect with other attendees and MCPs.

You never know who it is you’re going to meet at events like that - at mixers, at social functions, etc. - or just in passing. Over the course of many conferences over the last several years I’ve met literally hundreds of people. I’ve met some of the most fascinating, interesting people through conferences. My life is much richer for having made those connections.

While I love meeting people on a personal level, on a professional level it’s critical. I’m an independent contractor. I need to work to find work. I need to market myself. The best way to do that is through in person, face to face contacts. These days all of my business is direct through training centers or other organizations. And it’s all because of people that I’ve met and come in contact with.  I’m always armed with a handful of business cards[2], a firm handshake, a warm smile and a quirky laugh. Those little things allow for quick connections, which leads to the next conversation, which leads to a business conversation, which leads to more work. As I type this I’m struggling to remember the last time I had to fill out an application or hand someone a resume.

Introducing yourself to someone you don’t know can certainly be intimidating if you’re a bit introverted. I know – as I mentioned before, despite my extroverted exterior, inside I’m a bit of an introvert myself. On my personal blog[3] I used to do a feature on Friday called my Friday Five. In that vain, here are 5 things that help me connect with people.

1.  Take a deep breath and remember they’re probably just as nervous as you. It’s true. Everyone has some level of nervousness when it comes to talking with strangers. They’re no different than you.

2.  Get the other person talking about themselves. Everyone loves talking about themselves. Everyone has an inner narcissist. Some of us[4] don’t hide it as well as others. Not only does that allow the person you’re trying to meet to do something they love to do, you’re likely to hear some amazing stories.

3.  Take up a hobby. Hobbies give you a common bond with someone even if you know nothing else about them. Personally, I’m a runner. So meeting another runner gives us an instant kinship. It can be almost anything – cooking, knitting, Settlers of Catan. And, if you’re looking to set up a “geek play date” if you will, a hobby gives you something you can do together.

4.  Compliment someone’s funny t-shirt. As someone with a pretty good collection of geek shirts, I can tell you the biggest reason I wear them is for the reaction. And it can be a great conversation starter.
     Other: Hey – great shirt! Where did you get that?
     Me: Thanks! It’s one of my favorites. I got it at Think Geek.
     Other: Really? That’s awesome! I love that site.
     Me: Me too! I always have to resist the urge to just buy one of everything there.

5.  Maintain “cultural literacy”. Know what’s going on in the world, both real news wise and pop culture wise. Depth isn’t important here, breadth is. Having a wide variety of subjects you can talk about, even at a very shallow level, can be very helpful in keeping a conversation flowing. For example, if you’ve never watched Breaking Bad[5], knowing the basic premise will allow you to talk with someone about the show.

Being a geek doesn’t have to be about living in a silo. There’s a whole community of geeks to connect with out there. And that community is one of my favorite parts of being an MCT and MCP. So next time you’re at a conference, stop by and introduce yourself. I’d love to meet you.

[1] Bring back, oh, nevermind… (Closed circuit joke)
[2] Trading cards, actually. They’re really cool. :-)
[3] When I used to maintain a personal blog
[4] For example, someone who would put together a blog at blog.geektrainer.com, for example
[5] One of the greatest shows on television, BTW

Thursday, May 10, 2012

Anonymous Methods, Lambdas and LINQ

… oh my!

[Editor's note: For whatever reason, the tool that I'm using to inject the code (with line numbers, which helps with the explanations) likes to mess with the formatting. I hope that the extra white space doesn't detract too much from the article. After typing all of this, really don't want to start debugging CSS issues. ;-)]

Lately, one of the most common questions I've received from students is, basically, "What in the world is this syntax and what does it mean?[1]"

dc.Categories.OrderBy(c => c.Name);

Well – it's a lambda statement. But of course if you haven't seen one, you need a bit more information than that.

Quite frequently, a full explanation would simply take too long or send the class down another path far away from the topic at hand. What I want to do with this post is answer that question fully.

I'm going to answer the question using one of my biggest philosophies when it comes to training, which is to explain it in the way that I understood it, in whatever method it was that made it finally click for me. In this case, that means stepping all the way back to the beginnings and showing essentially the progression that got us to where we are today.

That's going to take a little while here, so bear with me.

Trust me, we'll get there.

Let's take a simple class called Customer that's defined below.

public class Customer
{
    public Customer(String firstName, String lastName)
    {
        this.FirstName = firstName;
        this.LastName = LastName;
    }
    
    public String FirstName { get; set; }
    public String LastName { get; set; }
    
    public override String ToString()
    {
        return LastName + ", " + FirstName;
    }
}

Pretty straight forward. Couple of properties, a simple constructor. Looks good. Now let's create a couple, put them into a list, and display them.



   1: public static void Main()
   2: {
   3:     List<Customer> customers = new List<Customer>();
   4:     customers.Add(new Customer("Susan", "Ibach"));
   5:     customers.Add(new Customer("Christopher", "Harrison"));
   6:     customers.Add(new Customer("Dave", "Baxter"));
   7:     
   8:     foreach (Customer customer in customers) {
   9:         Console.WriteLine(customer);
  10:     }
  11: }

Again – pretty straight forward[2]. And the output will of course be exactly what we'd expect:


Ibach, Susan
Harrison, Christopher
Baxter, Dave


Now let's see if we can't sort those customers. Fortunately, List has a Sort method. Let's update line 7 to call sort (code below) and run it and see what happens.


customers.Sort(); // Line 7


Result:


Unhandled Exception: System.InvalidOperationException: Failed to compare two elements in the array. ---> System.ArgumentException: At least one object must implement IComparable.


Well that wasn't ideal… In a nutshell, what the runtime is trying to tell us is that we told it to sort a list of customers, but it has no idea how to sort our customers. Makes sense. How do we tell it to sort our customers? Well – by implementing IComparable.


IComparable is an interface with one method – CompareTo(object). CompareTo returns an integer based on the following criteria:


Current object (this) is less than the other object, return a negative number
Current object (this) is equal to the other object, return 0
Current object (this) is greater than the other object, return a positive number


One thing that you'll notice is that every primitive type (and strings) in .NET already implement IComparable. This means we can take advantage of their implementation. Let's update our Customer class to implement IComparable and sort by LastName[3].



   1: public class Customer : IComparable
   2: {
   3:     // existing code
   4:  
   5:     public int CompareTo(object obj)
   6:     {
   7:         // put all NULL objects at the top
   8:         if(obj == null) return 1;
   9:     
  10:         Customer otherCustomer = obj as Customer;
  11:         if(otherCustomer != null) // obj is a customer
  12:             return this.LastName.CompareTo(otherCustomer.LastName);
  13:         else
  14:             throw new ArgumentException
  15:                 ("Object is not a Customer", "obj");
  16:     }
  17: }

The breakdown looks a bit like this:


Line 8 – see if the object is null. If it is, move it to the top of the list.
Line 10 – convert the object to a customer
Line 11 – if it turns out that obj is a Customer, use the CompareTo method on String.
Line 13 – if it turns out that obj is not a Customer, throw an ArgumentException.


If we run the code again, we now get the result that we were hoping for:


Baxter, Dave
Harrison, Christopher
Ibach, Susan


Cool.


But…. If you look at the CompareTo implementation, we're having to cast the obj parameter to Customer. Why can't we just tell the IComparable interface that we want people to pass in a Customer and be done with it?


Fortunately – we can. The way that we do this is by using generics. In a nutshell, generics allow you to pass a type as you would a variable. So at design time we tell IComparable prepare itself for Customer objects. Let's update our Customer class again.



   1: public class Customer : 
   2:                 IComparable, IComparable<Customer>
   3: {
   4:     // existing code
   5:  
   6:     public int CompareTo(object obj)
   7:     {
   8:         // put all NULL objects at the top
   9:         if(obj == null) return 1;
  10:  
  11:         Customer otherCustomer = obj as Customer;
  12:         if(otherCustomer != null) // obj is a customer
  13:             return this.CompareTo(otherCustomer);
  14:         else
  15:             throw new ArgumentException("Object is not a Customer", "obj");
  16:     }
  17:  
  18:     public int CompareTo(Customer other)
  19:     {
  20:         // put all NULL objects at the top
  21:         if(other == null) return 1;
  22:         return this.LastName.CompareTo(other.LastName);
  23:     }
  24: }

I updated our original CompareTo method to call our new one. The new one is using the same logic as before, and running our code again returns the same result.


Cool – we have a Customer class that can be sorted.


But…. It can only be sorted one way – by LastName. If we wanted to sort by FirstName, well – that'd require updating our class. Having to update our class every time we need a different sort order would be a pain.


Fortunately – we don't have to. The .NET framework also includes an IComparer interface. The difference between IComparable and IComparer is that IComparable is for sorting that specific class (which is why Customer implemented IComparable), while IComparer is for sorting other classes – a utility if you will. Let's create a class that can sort Customer objects by FirstName.



public class CustomerSorter : IComparer<Customer>
{
    public int Compare(Customer lhs, Customer rhs)
    {
        // put nulls at the top of the list
        if(lhs == null) return -1;
        if(rhs == null) return 1;
            
        return lhs.FirstName.CompareTo(rhs.FirstName);
    }
}

The main difference, besides calling CompareTo on FirstName, is that we have two parameters of type Customer and we're comparing one to the other. But the logic is still basically the same.


To use the new sorter, we simply pass a new instance of the object into the Sort method on List.



// all other code identical
customers.Sort(new CustomerSorter());

When we run the code now, we get everything sorted by FirstName.


Harrison, Christopher
Baxter, Dave
Ibach, Susan


Cool.


But… If we have to create a new class every single time we need to change the sort order, well – that's going to stink. There's gotta be a better way.


Fortunately, there is. The .NET Framework gives us the ability to use delegates, which allow us to pass methods like we would objects.


Let's take a look at our new Compare() method that we created on our CustomerSorter class. You'll notice that it takes two parameters, each of type Customer, and returns an integer. That's it. And when we call Sort and pass in the CustomerSorter, it simply calls that method. Why can't we just pass a method into Sort?


This is where that delegate, an object that points to a method, comes into play. Since it can be used like an object and passed in as a parameter, we can just tell Sort to call that method directly. This is really the same thing we were doing before by passing in an instance of CustomerSorter to the Sort method, only this time we're just passing in a method.


Our method just needs to match the same signature – two Customer parameters and return an integer.


Let's add a method to our Program class that will use the same logic as our CustomerSorter.



// added to Program class
private static int SortByFirstName(Customer lhs, Customer rhs)
{
    // put nulls at the top of the list
    if(lhs == null) return -1;
    if(rhs == null) return 1;
    
    return lhs.FirstName.CompareTo(rhs.FirstName);
}

Want to know a secret? I simply copied and pasted from the CustomerSorter class and changed public to private , instance to static, and the name to SortByFirstName.


Now we just need to tell the Sort method to use our new SortByFirstName method.



// identical code except for Sort call
customers.Sort(SortByFirstName);

And as before, the result is the same – sorted by FirstName.


Cool.


But... If we need to sort in several different orders, we don't want to have to create a method each and every time. Wouldn't it be nice if we could just inject the logic right into the call to Sort?


Fortunately, we can. We do this by creating an anonymous method. An anonymous method is a method that has no name. We just create the method signature by using a delegate, and pass it right into the Sort method.



// identical code except for the Sort method
customers.Sort(delegate(Customer lhs, Customer rhs) {
    if (lhs == null) return -1;
    if (rhs == null) return 1;
 
    return lhs.FirstName.CompareTo(rhs.FirstName);
});

Just as before, we have the same logic (yes, I copied and pasted). We're just declaring the method just as we normally would with only a couple of differences. First, we're using the keyword delegate instead of using a method name. Second, we're not specifying a return type. The reason is that Sort already knows what the return type is – an integer – so the compiler doesn't require it.


And, as before, the result is the same – sorted by FirstName.


Cool.


But... Why do we have to specify the types of the parameters? After all, we already told the list right up front that we were using Customer objects.


Fortunately, we don't. This is where lambda expressions come into play. A lambda expression is just like an anonymous method, only with a couple more assumptions. In our case, since we know, and the compiler knows, that we are only dealing with Customer objects, and we need to return an integer, we're just going to declare our variables and move on.



// identical code except for call to Sort
customers.Sort((lhs, rhs) => {
    if (lhs == null) return -1;
    if (rhs == null) return 1;
    return lhs.FirstName.CompareTo(rhs.FirstName);
});

The main difference between this and our anonymous method is the syntax and the fact that we're not declaring data types. Since everyone, including the compiler, knows that lhs and rhs can only be Customer objects we don't have to declare it. The => is just the syntax to indicate the start of the method, or lambda expression.


And, as before, the result is the same – sorted by FirstName. Cool.


But... What about the normal situation where all we want to do is just say, "Hey .NET – sort this by ___ for me."? Do we really have to create a method, even in a lambda expression, every single time?


Fortunately, we don't. This is where Language Integrated Query (LINQ) comes into play.


In each of our method implementations, our code has simply taken advantage of the logic in the String class. LINQ will do the same thing for us. We simply tell LINQ, "Hey – sort by this", and it'll handle the translations for us.


The first step to using LINQ is to leave behind the Sort method that we've come to know and love. Unfortunately, Sort doesn't support LINQ.


When Microsoft introduced LINQ, they also introduced something called extension methods.[4] Extension methods are a way of adding a method to an existing class without having to inherit from that class. The method that was added to the List class, or more specifically IEnumerable, for LINQ was OrderBy.


With OrderBy we can, by using a lambda statement[5], simply tell LINQ the property we want to sort by. So the new code looks like this:



   1: // updating the foreach loop
   2: // all other code remains the same
   3: foreach (Customer customer in 
   4:             customers.OrderBy(c => c.FirstName)) {
   5:     Console.WriteLine(customer);
   6: }

Couple of things to notice here.


First up is that unlike Sort, OrderBy is volatile, meaning that it's going to return the sorted list rather than updating the list like Sort did.


Second is the fact that we're not returning an integer. Again, LINQ will handle the translation for us. As long as we specify a property that implements IComparable it will use that for the sorting.


Third is that LINQ uses deferred execution. In other words, it won't actually do the sort until we use the results. In this case, we're doing this when we use the foreach loop.


And now when we run the code we of course get the same results.


Cool.


And that's what that lambda statement is all about. It's really just letting someone else create the query for us based on a couple of assumptions about our code and the types that we're using.


From here, things actually get cooler. We of course have LINQ syntax, which allows us to write something similar to a SQL query. And, LINQ can also translate the queries for other environments, like SQL.


But for now, we'll leave it here, with a very propeller head view of lambda statements. Hopefully this helped bring together what's happening behind the scenes, and why we're able to take the shortcuts we can take.


And as always, if you want the source code, feel free.



[1] Or some variation of that syntax or that question.
[2] The one thing that might be unfamiliar is the <Customer>, or generics, syntax. We'll actually address that in a little while.
[3] Keep in mind this isn't a full implementation of IComparable. We really should implement IEquatable, and consider overloading the associated operators. But that's a topic for another blog post.
[4] Extension methods are worthy of another blog post.
[5] A lambda statement is just like a lambda expression – just one line instead of multiple lines.

Wednesday, May 2, 2012

Asynchronous SharePoint Web Parts

When it comes to SharePoint, the name of the game is performance. And quite frequently, performance comes down to how well your web parts are written. Unfortunately, there's often only so much you can do to a web part, as typically it's making a database call that you may not have complete control over. While we can't necessarily make the database call any faster, we can at least let it run on a separate thread and let SharePoint do other work while we're waiting.

Threading in ASP.NET poses a special challenge – ASP.NET is impatient and doesn't want to wait for any thread that we create manually. So if we simply add the code to the page to create a new thread, the page will still just exit when it's ready to render the contents rather than waiting for our thread to complete.

To enable threading on an ASP.NET page, Microsoft introduced the PageAsyncTask object. The PageAsyncTask object will have handles for both our begin and end methods, and ensure the end method gets called before it returns.

For our scenario, let's take a look at a basic situation – we need to retrieve data from a database, and the query is going to take a little while. Our initial setup looks like this:

private String sql = @"WAITFOR DELAY '00:00:10';
                        SELECT LastName 
                        FROM Person.Contact 
                        WHERE ContactID = 1;";
private String connectionString = "Data Source=..."
private Label lblOutput;
private Label lblTimes;

Pretty straight forward setup – I'm setting up my connection string[1], my SQL statement[2], and a couple of labels that I'm going to use for output. lblOutput will be used to display the name of the contact that we retrieve, and lblTimes will display when the code executed and on what thread.


One special note about the connection string in this demo – because I'm connecting to a database asynchronously, I need to include the Asynchronous Processing=true option in the connection string.


The key to an asynchronous call is to have a method to kick off the operation, and one that will execute once the operation is complete.



private IAsyncResult BeginDatabaseCall
           (Object sender, EventArgs e, 
            AsyncCallback cb, Object state)
{
    AddTimeMessage("Start BeginDatabaseCall");
    SqlConnection connection =
                 new SqlConnection(connectionString);
    SqlCommand command =
                 new SqlCommand(sql, connection);
    connection.Open();
    IAsyncResult result = 
                 command.BeginExecuteReader(cb, command);
    AddTimeMessage("End BeginDatabaseCall");
    return result;
}

The BeginDatabaseCall method looks pretty normal, if only half complete. We create the SqlConnection and SqlCommand objects, open the connection, and call some form of ExecuteReader – only this time it's BeginExecuteReader.


Note the parameters passed into BeginExecuteReader.


The first parameter is the AsyncCallback object that will be passed into the BeginDatabaseCall method. This is passed in when ASP.NET invokes this method, and must be passed into the BeginExecuteMethod call. That's what will actually do the wire-up and ensure the method is allowed to complete. If you don't pass the AsyncCallback object into BeginExecuteReader the operation will immediately timeout.


The second parameter is a "state" object. In reality, it's just a spot where I can drop any object and have it be retrieved on the other side. Think of it like a message in a bottle, or a Memento-style tattoo. I'm passing the SqlCommand object in as the state object so it can be retrieved in the EndDatabaseCall method.


The last thing worth highlighting is the AddTimeMessage method, which I'm using to simply print out where we are in code, the thread that's being used and the time.[3]



private void EndDatabaseCall(IAsyncResult result)
{
    AddTimeMessage("Begin EndDatabaseCall");
    SqlCommand command = (SqlCommand) result.AsyncState;
    using(SqlDataReader reader = command.EndExecuteReader(result)) {
        reader.Read();
        lblOutput = new Label();
        lblOutput.Text = 
            "<br /><b>Contact Last Name: " 
            + reader[0].ToString() + "</b>";
    }
    command.Connection.Close();
    command.Dispose();
    AddTimeMessage("End EndDatabaseCall");
 
    Controls.Add(lblTimes);
    Controls.Add(lblOutput);
    ChildControlsCreated = true;
}

In the EndDatabaseCall method, the first thing we need to do is grab the SqlCommand object from the AsyncState property of IAsyncResult. That was populated by us handing it in as the state object parameter for BeginExecuteReader. We then call EndExecuteReader, which will give us back the SqlDataReader. To make sure we're getting back the right item, we pass in the IAsyncResult as a parameter.


From there it's pretty normal ADO.NET code. Call Read to get to the next line, Close/Dispose of our objects, and add in our controls. You'll notice the last line where ChildControlsCreated is being set to true. This is because we're going to exit the CreateChildControls method before we finish rendering the contents. After all – we're making the database call on a different thread, so we need to make sure everyone's aware of the fact that we're not quite done yet.


Last but not least is our new CreateChildControls method.



protected override void CreateChildControls()
{
    lblTimes = new Label();
    AddTimeMessage("Begin CreateChildControls");
 
    PageAsyncTask task = new PageAsyncTask(BeginDatabaseCall,
                            EndDatabaseCall, 
                            TimeoutDatabaseCall,
                            null,
                            true);
    Page.AsyncTimeout = TimeSpan.FromSeconds(30);
    Page.RegisterAsyncTask(task);
 
    AddTimeMessage("End CreateChildControls");
    ChildControlsCreated = false;
}

The big thing to note in CreateChildControls is our creation of the PageAsyncTask object. PageAsyncTask needs to know a couple of things – the methods to call for start, complete and timeout[4], a state object (if we're using one), and if we want our code to execute in parallel. Then we simply let ASP.NET know we want it to call the methods for us by passing the object into RegisterAsyncTask, and we're done. And to close the point, you'll notice that we tell ASP.NET that we're not done creating the controls quite yet.


And the output looks a bit like this:


DisplayCustomer


Making your calls asynchronously in web parts certainly does take a bit of work. I certainly wouldn't recommend it for every web part. But if you have a web part that takes a few seconds to render, consider updating it to make its calls asynchronously so SharePoint can do other things while you're waiting for a query to complete or a web service call to return.


And if you'd like the source code, you can download it from below:


[1] Yeah, yeah, yeah – it should be in a config file.
[2] You know – we could probably improve performance by pulling that delay out of the query...
[3] The AddTimeMessage is included in the full source code. It wasn't worth highlighting in the post.
[4] The timeout method is included in the full source code. It wasn't worth highlighting here.