Saturday, February 23, 2008

This is not the blog you are looking for....

I have upgraded to SubText! You can find this blog now at http://jamescbender.com

For those of you still getting this via RSS fieed, you're obviously not using the feedburner feed since I'be already switched that over. To switch, point your reader at http://feeds.feedburner.com/BendersBlog

I'm not moving all the old posts (probably just the recent WCF ones), so this blog will continue to operate, but there will be no new content.

See you on the other side.

Friday, February 22, 2008

How "WCF Guy" packs

So, Brian Prince is leaving our company for Microsoft, and he's not the only one packing his stuff! Just so happens this is happening at a time when the whole company is moving to swanky new digs on the other side of the freeway.

Trust me, it's nicer than it sounds.

By way of documenting this, Brian has started a little Meme; how does <insert characteristic of you choice>guy pack.

I got tagged as the WCF guy (naturally) so I started thinking about how a WCF guy would pack. I quickly realized that I would probably want to do as much of it in configuration as possible.

Enjoy!

<?xml version="1.0" encoding="utf-8"?>
<
configuration>
<
system.PackingModel>
<
activities>
<
activity name="packing" behaviorConfiguration="packingBehavior">
<
container address="sittingOnADolly"
binding="box"
bindingConfiguration="providedMovingBoxes"
contract="IHoldsYourCrap"/>
</
activity>
</
activities>
<
bindings>
<
box>
<
binding name="providedMovingBoxes">
<
material plastic="true"/>
<
port mode="flipOpenTop"/>
<
transport mode="onADolly"/>
<
security mode="hopeNobodySeesAnythingTheyWantAndTakesIt"/>
</
binding>
</
box>
</
bindings>
<
behaviors>
<
packingBehaviors>
<
behavior name="packingBehavior">
<
breakableProtection bubbleWrapEnabled="true"/>
<
containerReturn returnContainer="true"
fineIfNotReturned="$45.00orSomething..."/>
<
bubbleWrapReturn returnBubbleWrap="true"
fineIfNotReturned="20Bucks?!"/>
</
behavior>
</
packingBehaviors>
</
behaviors>
</
system.PackingModel>
</
configuration>

I apologize for the weird layout. I'm in the process of making some "major" changes to accommodate stuff like this in the future.

Thursday, February 14, 2008

Is There Something I'm Missing Here...?

So, I guess as much as I hate to admit it, it looks like HD-DVD has lost and Blu-Ray has won. HD-DVD fought hard, but in the end Blu-Ray  had enough money to bribe "create synergies" with all but two of the major movie studios. Add to that the one-two punch from Best Buy and Netflix this week and it looks like it's all over. Frankly, I was hoping the war would just stalemate until the next big thing, probably streaming media, came in and crushed both with one blow.

So what we end up with is admittedly a technically superior format, however one that is controlled by one party: Sony.

Now that the war seems to be over, I'm seeing a lot of people posting on various on-line forums that prices of players will be coming down.

Wait.

As is my understanding Blu-Ray is controlled by Sony. Companies that want to make said hardware pay a licensing fee to Sony to use the Blu-Ray technology. No matter who you are, if you want to make a Blu-Ray disc player you have to pay Sony. This creates a situation where there is one company that controls creation and distribution of a product.

So, what incentive does Sony have to lower the prices? They are the only game in town, and could charge whatever they want. Now, I know that Sony isn't going to do something stupid like price themselves out of the market, and I don't begrudge them the right to make whatever they feel they deserve out of their creation. But seeing as they just spent a ton of money to "synergize" with all these studios, I don't see how the this is going to translate into lower prices for Blu-Ray players.

Am I wrong here? I hope I am because I can't see myself paying $300 for a Blu-Ray player. And yes, I know the Play Station 3 plays Blu-Ray movies and games. I barely have time to use the XBox I have, I don't need (or really want) another gaming console.

On the plus side, the HD-DVD fire sales have already begun. If you're a movie collector now might be the time to pick up an HD-DVD player and some movies on the cheap.

Monday, February 11, 2008

Making WCF "Behave" - Part Two

If you read my last post, you should have a pretty good idea of how Behaviors fit into the WCF stack and know that they have the ability to effect the runtime behavior (clever, huh) of your WCF host and/or client runtime by effecting the communication between dispatchers. If you missed this post, go ahead a read it now, I'll wait.

Done? Good.

In this post we are going to focus on service side behaviors (I'll cover client side behaviors soon).

Before we start slingin' code, it important to understand where the extensibility points for WCF behaviors are. On the service side there are two classes you can attach behaviors to. That are the System.ServiceModel.Dispatcher.DispatchOperation and the System.ServiceModel.Dispatcher.DispatchRuntime. Within each of these are properties that take a specific kind of interface which will be implemented by our custom behavior. Here is a quick guide to which property of which class takes which kind of interface.

DispatchRuntime

  • MessageInspectors takes IDispatchMessageInspector
  • InstanceContextProvider takes IInstanceContextProvider
  • InstanceProvider takes IInstanceProvider
  • OperationSelector takes IDispatchOperationSelector

DispatchOperation

  • ParameterInspector takes IParameterInspector
  • OperationInvoker takes IOperationInvoker
  • Formatter takes IDispatchMessageFormatter

Each property/interface pair performs a specific function in the WCF plumbing. Some are pretty obvious based on their names, others are not. I'll cover these in more detail in my upcoming seven part series "Better Know A Dispatcher Behavior."

If that doesn't make a whole lot of sense yet don't worry. As we work with behaviors just refer back to it and it will start to make sense

OK, enough theory, lets write some code!

Here we have an extremely basic WCF service:

[ServiceContract]
    public interface IHelloWCF
    {
        [OperationContract]
        string Hello(string name);
    }

    public class HelloWCF : IHelloWCF
    {
        public string Hello(string name)
        {
            return String.Format("Hello {0}", name);
        }
    }

This is hosted in a console application:

class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host =
                new ServiceHost(typeof(HelloWCF)))
            {
                host.Open();

                Console.WriteLine("Service is available.");

                Console.ReadLine();

                host.Close();
            }
        }
    }

As you would imagine, you call this service by passing in a name, like "James" and get a warm greeting from the service ("Hello James") as you can see below:

image

We're gonna have a little fun here with a behavior based on the IDispatchMessageInspector. We are going to write a behavior to return a different message. The first step is to create a new class which implements this interface:

public object AfterReceiveRequest(ref Message request,
    IClientChannel channel,
    InstanceContext instanceContext)
{
    return null;
}

public void BeforeSendReply(ref Message reply,
    object correlationState)
{
    return;
}

As you can see, there are two methods where we need to provide implementations for. AfterRecieveRequest gets fired when a message is coming into your service. You can take this opportunity to examine and change the message if need. Today we are more concerned with the BeforeSendReply method. As the name suggests this method gives you an opportunity to change the outgoing message. The above implementation will compile and work, but it doesn't do much. Let's change that:

public void BeforeSendReply(ref Message reply,
    object correlationState)
{
    MemoryStream memoryStream = new MemoryStream();
    XmlDictionaryWriter xmlDictionary =
        XmlDictionaryWriter.CreateBinaryWriter(
        memoryStream);
    xmlDictionary.WriteStartElement("HelloResponse",
        "http://tempuri.org/");
    xmlDictionary.WriteStartElement("HelloResult",
        "http://tempuri.org/");
    xmlDictionary.WriteString
        ("Ha ha! I stole your message!");
    xmlDictionary.WriteEndElement();
    xmlDictionary.WriteEndElement();
    xmlDictionary.Flush();

    memoryStream.Position = 0;

    XmlDictionaryReaderQuotas quotas =
        new XmlDictionaryReaderQuotas();

    XmlDictionaryReader xmlReader =
        XmlDictionaryReader.CreateBinaryReader
        (memoryStream, quotas);

    Message newMessage =
        Message.CreateMessage(reply.Version,
        null, xmlReader);
    newMessage.Headers.CopyHeadersFrom(reply.Headers);
    newMessage.Properties.CopyProperties
        (reply.Properties);
    reply = newMessage;
}

The first section of code uses an XmlDictionaryWriter to create the new message body we are going to be sending back. It creates our message text, which is wrapped in a Result, which in turn is wrapped in a Response. The next section creates an XmlDictionaryReader which is used in the last section to "read" the XML we just created into a new blank WCF message. We copy the headers and properties from the original, then set the "reply" reference parameter to our new message. That's it.

Well, that's not it. He have our behavior, but right now we don't have any way to bind it to our service runtime. To do that we have to have our class implement the System.ServiceModel.Description.IEndpointBehavior. There is an interface for operation behaviors as well, but we'll worry about those at another time.

Here is how we implement the IEndpointBehavior:

class MyCustomMessageFormatter : IDispatchMessageInspector,
    IEndpointBehavior
{

    public void AddBindingParameters(
        ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    {
        //Not implemented
    }

    public void ApplyClientBehavior(
        ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        //Not implemented
    }

    public void ApplyDispatchBehavior(
        ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.
            MessageInspectors.Add(this);
    }

    public void Validate(
        ServiceEndpoint endpoint)
    {
        //Not implemented
    }
}

Of the four methods added the one we are really only concerned with right now is the ApplyDispatchBehavior. This method is called when this endpoint behavior is applied by the runtime and it is being used here to add our message inspector (which just happens to be the same class) to the stack. You could have this endpoint behavior implemented in a separate class and have it add multiple dispatch behaviors all at once. This allows you to aggregate several dispatch behaviors into one endpoint behavior.

We're almost there now, we just need to bind our new behaior to our endpoint. First we'll do it in the hosting logic (below in bold):

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost host =
            new ServiceHost(typeof(HelloWCF)))
        {
            host.Description.Endpoints[0].
                Behaviors.Add(
                new MyCustomMessageFormatter());

            host.Open();

            Console.WriteLine("Service is available.");

            Console.ReadKey();

            host.Close();
        }
    }
}

Now, when we run the service we can see the effects of our new behavior:

image

Pretty easy! In part three I'll show how to add this behavior to the service through configuration instead of the hosting code shown above.

Thursday, February 07, 2008

THANK YOU OMAHA, GOOD NIIIIGHT!

Even though I've hemmed and hawed about it for at least six months, I have yet to purchase either Guitar Hero or Rock Band. The primary reason being that I know if I do it's just going to make me feel guilty about neglecting the six real guitars I already own.

So I was thrilled today when I saw that a company called Game Tank announced a game called "Guitar Rising" Basically, if you ever playing Rock Band or Guitar hero and thought "This would be cooler if I could use a real guitar." than this game is for you.

The way it seems to work,from the video, is that you will have the familiar "piano roll" type of interface you get with guitar hero, except instead of just having you press a specific button it shows you which string and fret combination to hit. You will be plugging your guitar into the computer somehow (sound card? USB interface? MIDI interface?) and the game will let you know how accurate you are.

To me, this is more than a game, it's a powerful teaching tool. It's more effective than sitting in a practice room with a metronome, and easier than getting a whole band together to practice whenever you want (what do you mean sleep, I wanna jam!).

Some things that I think would be great in this game:

  • Amp modeling. Presumably you are going to have to plug your guitar into this thing somehow. Line 6 has already show that PC based amp and effect modeling is possible. It would be cool to be able to not only practice the songs by your favorite band, but to do it with the same "rig" that they are using.
  • Composition mode. Basically, a type of "musical notepad" that would allow you to "dictate" notes into a song WHILE showing you the piano roll of what you have played. A lot of players try to do this with tape, but lack the "ear" to be able to figure out what they played afterward.
  • I have a small mountain of sheet music I have collected over the years. A great feature would be the ability to somehow transcribe the sheet music into songs in the game. This would make practice much more rewarding!

Suffice to say, unless there is some HUGE barrier to this game (huge price tag, need to "mutilate" one of my guitars, requirement that you wear spandex while playing) I am TOTALLY getting this!

Monday, February 04, 2008

Making WCF "Behave" - Part One

Some of the most misunderstood features of WCF are behaviors and channels. While these elements of the WCF stack offer a tremendous opportunity to customize the way WCF works, there is the perception (especially with channels) that it involves a lot of  "down to the metal" coding the requires an in-depth understand of of how communication stacks and network protocols work. While this knowledge would be helpful in any distributed computing paradigm, they are hardly required knowledge for extending WCF.

There is also a tendency to confuse behaviors and channels. While on a conceptual level these two types of objects are similar in that they effect communication  in some manner, they do so in different ways.

If you read my last post, you may remember that channels live in a stack between the binding and the transport. Therefore we can see that they impact how messages are communicated to a client and vice versa.

On the other hand, behaviors control the internal communication of the service by changing the way dispatchers function within the service host. It is fair to say that behaviors change the way the service host functions at runtime, while channels customize the way a particular endpoint communicates with a client.

So, a good follow up question is "What is a dispatcher?"

Dispatchers are basically traffic cops. The take incoming messages and route them to the appropriate service method. There are three types of dispatchers; channel, endpoint and operation.

Channel dispatchers receive messages from the channel stack. The channel dispatcher examines the address the message was sent to and sends it to the appropriate endpoint dispatcher. The endpoint dispatcher examines the action header of the message, and passes the message to the appropriate operation dispatcher. Finally, the operation dispatcher deserializes the message to get a set of parameters, and uses those parameters to call the method for the selected operation.

The use of dispatches in this manner allows us to create custom behaviors to act on endpoint operations by implementing the System.ServiceModel.Description.IEndpointBehavior interface or on an operation by implementing the System.ServiceModel.Description.IOperationBehavior interface. The separation of duties here is important as there are going to be behaviors that we wish to apply to all calls to an operation, in which case we would create an operation behavior, and others that we are only going to want to act on calls made through a specific endpoint, which would necessitate the creation of an endpoint behavior.

So, now we have a basic understanding of what behavior are, what they do, and where they fit in the WCF world. Next time we will create a basic custom behavior of our very own!

Friday, January 25, 2008

ENOUGH WITH THE DAMN CODE COVERAGE ALREADY!

Whew!

Sorry about that, I just need to rant a little...

Here's what's got me so upset; TDD is a great cool thing. Everybody should be doing it. It's just the best thing since sliced bread, fluffy puppies and free Wi-Fi as Starbucks all rolled into one.

Now, I am NOT a TDD guru by any means. I've read some articles, tried rolling it into my approach and have had great results. I learn a little more everyday, and my understanding increases over time. If you really want to geek out on it you should talk to guys like Steve Harman and Jay Wren!

But there is an evil specter lurking that is threatening to ruin it all.

Code coverage.

Well, maybe not code coverage, but the way people with only a passing knowledge of TDD cling to code coverage like it's the last Red Bull in the fridge. The people who don't understand that the point of unit testing is not to increase code coverage at all cost, but to ensure your code does exactly what it is supposed to do; no more and no less.

I come not to bury code coverage, it's a perfect way to see what code is being tested, and more importantly what code is not being tested.

Here's where I get frustrated with peoples attitudes to code coverage (henceforth referred to at CC because I'm a lazy typist); there is a perception that a high CC number will guarantee quality code. That's simply not the case as you can hide a lot of skeletons behind a big enough door.

One "trick" I've seen to increase CC is to simply test EVERY method on the public interface of an object. This sucks.

My philosophy is that I really don't care about the individual gets/sets on a class. And to be honest, I don't like explicit tests of public methods on entities either. My belief is that you test the features. If you then find that there are methods and properties on a class that aren't being covered you have two options; either your tests suck or you don't need that property/method.

So maybe your tests suck. The best way around that is to make sure you write your tests first, THEN write your code. If you approach your tests like you approach your business requirements (user stories drive unit tests maybe?) and your test are meaningful this shouldn't be a problem, right?

Or maybe you don't need the property/method in question. I'm a big fan of pruning classes. If there is a method property on a class that never gets used why keep it? It sits there like a parasite, consuming maintenance costs and provides nothing in return. I say get rid of it!

We as developers/architects/information workers/whatever need to educate ourselves, our co-workers and are clients on this. High CC is NOT a panacea! The quality and intelligence of your tests is what ensures you are not developing crap-ware! Please, write your tests first, make sure all the features you develop are well tested. Review your co-workers tests and have them review yours. And if a high CC number is your goal look at what you are testing and don't be afraid to trim unused code!

OK, I feel better now. Code on!

Monday, January 21, 2008

The basics of the WCF stack

During CodeMash I did two "Ask the Experts" sessions where it was my intention to talk about WCF, which I've really been "geeking-out" on for several months. In the combined two hours that I "served" I only had one person come in with an issue, and it was really more of a complaint about why he couldn't use WCF.

I spoke to him for a few minutes and while his communication needs were not exactly run of the mill, they certainly weren't outside of the scope of WCF, given a few extension to the stack with a custom behavior or a custom channel.

After talking to him and a few other people I came to a realization; while there are many people who are using WCF, the vast majority of them don't really know how powerful it is. Most people seem to be content using the out of the box features of creating the normal endpoints. And granted, for 90% of the situations you face those may be fine.

But WCF offers a lot more. It is a open and extendable architecture, and with a little knowledge of the stack, what piece does what and how to extend those pieces, you can have almost unlimited power over the universe!

OK, I'm exaggerating a little. But you can still do some pretty cool stuff!

Lets review the WCF stack. This will provide the foundation for subsequent posts about extending WCF.

Basic WCF Stack

As you can see from the diagram there are a lot of pieces of functionality between your service code and the client. In actuality the client side of the stack also consists of dispatchers, at least one binding, a channel stack and a transport as well. To keep this diagram simple I've aggregated those down to the proxy object that developers use everyday with WCF but keep in mind that these things exist on the client side and the ability to customize WCF extends to that side as well (There are a few things you have to keep in mind with that, more in another post)

When a client makes a call to your service, it invokes a method on the proxy. A transport carries the message across a network to the transport on the service side. Between the transport  and the binding element is the channel stack. For an incoming message, each channel on the stack has a channel listener that receives the message from the previous channel (or in the case of the first channel in the stack, the transport) and creates your custom channel object. The custom channel then performs some action, which may or may not be based on the message, and passes it on to the next channel listener in the stack (or the binding element if it's the last channel in the stack).

This binding/channel/transport stack is what is know to most WCF developers as and endpoint. When you create an endpoint for your WCF service you are selecting a binding/channel/transport stack to for your service to use to communicate with the world. Your service can support as many endpoints as you wish, each with a unique and independent binding/channel/transport stack.

The binding hands the message off to a series of dispatchers. I'll get into these in more details when I discuss custom behaviors, but the three dispatchers that the message passes through in it's way to your service code are the channel dispatcher, the endpoint dispatcher and finally the operation dispatcher. At each one of these dispatchers, behaviors have an opportunity to be invoked. As I'll demonstrate in a future post, while custom channels are responsible for controlling how your service communicates with external applications, behaviors influence how messages are communicated internally to your service.

The response from your service back the client is essentially the reverse of the path the request just took; the response is passed through the dispatchers (this time operation, then endpoint and then channel) to the binding. The binding passes the message to the channel stack, however this time the channel listeners have been replaced with channel factories. The transport passes the message to the network where the it is returned to the client via the proxy.

With all these steps it's clear to see that there are several opportunities for customization. The ability to create these extensions give WCF it's power to allow unlimited ability to tailor how users consume your service and gives a great amount of flexibility that allows systems on disparate technologies to communicate with each other without having to worry about it at the service layer.

Up next, custom behaviors...

Wednesday, January 16, 2008

WCF Brain Fart (and why you "need" base addresses)

Well, you don't really need them, unless you are using the basicHTTPBinding and want to see the WCF help page.

Or want to create a proxy from metadata.

Hmm... OK, maybe you DO NEED base addresses!

Maybe I should back up.

The other day I was showing someone how to throw together a quick WCF service hosted in a console application. This was strictly a "Hello World" type of service, and I got everything written and wired up in about 5 minutes. I didn't worry about setting up a metadata behavior or endpoint, just wanted to get the service up an running as fast as I could. I started the app and fired up IE to show the person the WCF help page (a sure sign that it works and I'm brilliant) but instead of the help page I got this error:

<faultcode >a:ActionNotSupported</faultcode>

<faultstring xml:lang="en-US">The message with Action '' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).</faultstring>

That really sucked.

In my haste, and my eagerness to show how fast and easy it was to get a service up and running, I forgot one minor but important thing; if you want to see the help page, you need to specify the base address if you are using the basicHttpBinding.

It makes sense; if you don't use a base address, you are sending an HTTP Get to your endpoint. Unless your endpoint knows what to do with it, it throws a fault. The wsHttpBinding is able to handle it, but the basic binding rely's on the Service Host which has some internal functionality that gives you the help page.

But since you need base addresses to use the service metadata behavior, you're just better off making sure you use it.

Friday, January 11, 2008

Codemash Day 1 part 2

After my "Ask the Exports" time, I headed over to see Keith Elder talk about Microsoft Workflow. The meat of his presentation can be found here. It was an bit of an entry level talk, which I think is desperately needed. There are still a lot of misconceptions out in the Enterprise community about what Workflow is, how it works and how it fits in with things like the .NET framework overall and Biztalk. He's doing an more "advanced" talk later today "custom activities" which I'm looking forward to.

After that I went to see Dustin Campbell do some F# stuff. I was playing with F# a bit before my laptop died (Note to Dell, you actually have to plug the fans in for them to cool the laptop down.) I haven't really had time to re-install and get back into it. Dustin's talk was almost SRO; functional programming, F# in particular, seems to be something that has gathered a lot of interest.

After dinner, I got to play a lot of "Rock Band" with Keith Elder and Scott Hanselman. Keith is scary good at the "Guitar Hero" type games. Scott claims he's never played the game before, but he did a great job on the drums! As for me... well, have I even mentioned how much "Guitar Hero"is NOT like really playing the guitar...

... speaking of playing guitar, the "CodeMash jam session" also happened last night. I didn't feel like dragging my rig up (I already got enough shit from Kaufman and Wingfield about my "larger-than-necessary suitcase) and I'm kind of glad I didn't. Most of the group seemed to be older guys with acoustics play country and CSN stuff. I doubt any of them know any "Iron Maiden", "Helloween" or even "Limozeen!" I'll track Dustin down later and make him a deal for next year; if he brings his stuff, I'll bring mine.

OK, drink now, more writing later.

Thursday, January 10, 2008

CodeMash, Day One Part One

Well, day one for me got off to a "bumpy" start. I request a wake up call for 7:00 intending to work out, and get downstairs in plenty of time for a little breakfast and the beginning of Neal Ford's keynote. Well, unfortunately, my wake up call didn't arrive till 8:00, so I didn't get to work out, missed breakfast and missed the first few minutes of the keynote.

One that subject...

I really dig what Neal Ford is saying about dynamic languages. I've been playing with Iron Python for a little while now and I see a lot of benefit in it, although it's still a bit "rough" in my opinion. If I never end up using it in a production project, it has made me re-think the way I do things in C#. He said something in his presentation that got a little chuckle out of me; compilers are basically weak unit tests and spell checkers. I remember being in school and hearing a lot of my fellow students in the computer lab "I can't understand why it didn't work, it compiled." I didn't think I would hear that after I left school, but I hear it at least once a year from co-workers. He also beat the testing drum, which I'm all for. I really wish this philosophy would catch on more widely in software development. I don't know why, but I'm still surprised when I meet with a client and find out that they have NO unit testing practices in place at all! I've been doing it so long myself, that I can't imagine developing without it.

From there I went to see Joe O'Brien present "Ruby:Testing Mandatory." I have to admit, I was definitely handicapped here since I have zero experience with Ruby, but I'm always interested in anything that can enhance my test-driven chops (very helpful when I evangelize this to clients). It did get me more interested then I had been in Ruby. I have been playing with Iron Python for awhile now, but I've decided I need to make some time for Ruby as well.

After that I went to see Jay Wren talk about Castle. I've heard a lot about Castle, but never really used it. It was pretty interesting.

Scott Hanselman's keynote was great! Had a great, funny intro followed by some very cool stuff for IIS 7. I've been playing with IIS 7 for a few months, but clearly haven't even scratched the surface with the HTTP Modules. More stuff to start playing with when I get home.

Served my first tour of duty in the "Ask the Experts" lounge. It was pretty cool; I didn't get a lot of people coming in for "hard-core" stuff; got one person looking for book recommendations (but that's another blog post), one person who had an interesting challenge which I will blog about later, but for the most part it was kind of an hour and hanging out and talking with Catherine Devlin and Darrell Hawley. I've never met Catherine, and if I want to continue learning Python, I should definitely start reading her blog. I hadn't seen Darrell in awhile and it was fun to talk to him again.

Off to the next session. To be continued...