Category Archives: PHP

Clearing up the confusion over session timeouts in PHP and Zend Framework

I’ve recently made a foray into the world of Zend Framework.  If you’ve not come across it, it is one of several popular PHP frameworks that implements the Model View Controller software architecture.

By day I’m mostly devoted to hacking ASP.NET MVC, but having had some experience with PHP I decided to get my teeth stuck into Zend for a new PHP project I’ve been working on.  Somewhere in my head I have another blog post to write about the dyer state that is documentation for Zend, but that’s a topic for another night.

This post details my journey through figuring out how PHP sessions really work and how to fix them for properly bringing down a user’s session after a period of inactivity, in the context of Zend Framework (although most of the post applies to PHP proper).

Get your server’s clock synchronised

The PHP session files are date-stamped when they are created/updated.  The PHP Session cookie that gets sent to the browser has an expiry date-stamp on it.  While you can’t rely on the client’s system clock to be correct, making sure your server’s is makes debugging less of a headache.

On a Windows server this should already be set up – if not check the Date & Time Properties in Control Panel and pay attention to the Internet Time tab.  On a Linux server it’s a case of installing and configuring the ntp package – a bit of Googling should show you the way with whichever distribution you are using.

The options for session timeouts

There are a number of options as to how you can handle session timeouts, as I see it:

  1. Keep the session alive until the user closes their browser
  2. End the session after a fixed period of time (regardless of activity)
  3. End the session after a fixed period of time with no activity
  4. Some combination of the above

I’d put my money on the best user experience being a combination of point 1) and point 3).  This, rather surprisingly, isn’t as straightforward as you would hope.

PHP Session Settings – What they mean

image

There are a handful of PHP settings relating to sessions, and understanding how sessions work and what these settings do is critical to getting the behaviour you desire.

PHP sessions are stored as files (by default) – the files contain a serialised version of the $_SESSION superglobal arrray.  The path to these files is set by session.save_path.

N.B. Security Warning – make sure no other users can get to the session directory.  Files are stored unencrypted and therefore should be treated with the same caution as password files.

An interesting side note: if you inspect the cookie PHPSESSID (the default name for the PHP Session Cookie), you’ll notice a funny looking value.  Now take a look at the contents of the PHP session directory – hey presto, there’s a file called sess_<SESSIONID>. Simple eh.

Each time PHP session_start() is called (which is not just when you first start a session, but every time you want to load session data into your app, i.e. on every request for most apps), PHP works out whether or not it should run the session garbage collector.

Garbage – what garbage?

The session garbage collector runs around and checks all of the timestamps on the session files and compares them to the current time less the session.gc_maxlifetime value, to work out if a file should be deleted (because it has exceeded it’s lifetime).

You might think that the garbage collector would run on every session_start(), to avoid any expired session files being left hanging around.  However there is a performance overhead with this and so PHP uses two settings, session.gc_probability and session.gc_divisor to calculate the probability that it should run the garbage collector (it does this by dividing the two and then rolling it’s own dice to see whether it should run).

N.B. Remember that each time you access the session the timestamp on the session file gets updated, this stops the garbage collector from killing a user’s session while they are still active.

This all sounds very cosy and like it will do just what I want – dump the user’s session after a period of inactivity (as specified by session.gc_maxlifetime).  And on low volume sites you could force the garbage collector to run every time by making the probability 100%.

However, all is not quite so straightforward!

The order of things

I’m sure there’s a good reason for this, but I’m not aware of what it is.  PHP first loads up the session, then works out if it should run the garbage collector.  So even if the garbage collector runs every time and the session file has expired, on the first request the user will appear to still have a session, and then only on the next request they won’t.

Imagine this scenario.  A user has logged in to your site.  They go away for lunch, and during that time their session has expired.  They come back and hit refresh – the screen reloads with their session apparently intact.  All is looking good.  Then they click somewhere else and bam – their session is gone.  Kinda confusing really.

In an ideal world there would be a way to check for dead sessions before the session fires up, and in fact there is, but you have to roll it yourself.

Did I mention Zend Framework?

So far everything I have covered is PHP proper, no mention of Zend.  However the Zend_Session object is based on all of these settings, and therefore it warrants understanding the underlying PHP behaviour first.

The solution to this problem is to roll your own inactivity timeout check.  I needed this to implement inactivity logouts.  I used the following code to do my tracking:

$idleTimeout = 3600; // timeout after 1 hour

if(isset($_SESSION[‘timeout_idle’]) && $_SESSION[‘timeout_idle’] < time()) {
Zend_Session::destroy();
Zend_Session::regenerateId();
header(‘Location: /account/signin’);
exit();
}

$_SESSION[‘timeout_idle’] = time() + $idleTimeout;

N.B. If you’re not using Zend Framework you can exchange the Zend_Session lines for their standard PHP equivalents.

All this does is check if we have an idle timeout set in our session (remember this gets loaded even if the session has technically expired) and if it has fallen past the current time we tear down the session, regenerate the session ID (to avoid picking up the existing session file) and head off to the log in screen.

Specifically to Zend, you can set the following settings in the application.ini file:

resources.session.gc_probability = 1
resources.session.gc_divisor = 1
resources.session.gc_maxlifetime = 3600
resources.session.idle_timeout = 3600

And then place the custom idle time out code inside this function in Bootstrap.php:

protected function _initSession()
{
# set up the session as per the config.
$options = $this->getOptions();
$sessionOptions = array(
‘gc_probability’    =>    $options[‘resources’][‘session’][‘gc_probability’],
‘gc_divisor’        =>    $options[‘resources’][‘session’][‘gc_divisor’],
‘gc_maxlifetime’    =>    $options[‘resources’][‘session’][‘gc_maxlifetime’]
);

$idleTimeout = $options[‘resources’][‘session’][‘idle_timeout’];

Zend_Session::setOptions($sessionOptions);
Zend_Session::start();

# now check for idle timeout.
if(isset($_SESSION[‘timeout_idle’]) && $_SESSION[‘timeout_idle’] < time()) {
Zend_Session::destroy();
Zend_Session::regenerateId();
header(‘Location: /account/signin’);
exit();
}

$_SESSION[‘timeout_idle’] = time() + $idleTimeout;
}

And that’s all there is to it!

If you know the reasoning behind some of these apparently strange PHP session operations then please post them here – I’m left scratching my head in a daze at how complex a matter it has been to implement such a standard bit of user experience.

Advertisements

Synchronising Highrise, Basecamp and Google Shared Contacts–Part 2

Background: In Part 1 of this series I wrote about a recent project that I worked on to synchronise contact data between a number of web applications, and how I had to work around the (current) shortcomings of their APIs.

As Scott Hanselman says, “talk is cheap – show me the code”.  So here we go…

Basecamp & Highrise APIs

Implementing an interface to the Basecamp & Highrise APIs is relatively straightforward – I did it using PHP’s curl().  My implementation doesn’t support all of the API’s functions, but you should be able to easily add any additional calls that you need.

The functions centre around a single function which does the hard work on making the request:

private function _getData($uriExtension) {
$base_url = $this->url.$uriExtension;
$session = curl_init();
curl_setopt($session, CURLOPT_URL, $base_url);
curl_setopt($session, CURLOPT_USERPWD, $this->apiKey.':X');
curl_setopt($session, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($session, CURLOPT_HEADER, false);
curl_setopt($session, CURLOPT_HTTPGET, 1);
curl_setopt($session, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
curl_setopt($session, CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($session, CURLOPT_FRESH_CONNECT, true);
$response = curl_exec($session);
curl_close($session);
$xmlObj = new SimpleXMLElement($response);
return ($xmlObj);
}

Google Shared Contacts

This was simply done using the Zend GData classes which you can grab here.  Here’s some sample code for updating a shared contact in Google:

$client=$this->googleClientAuth('cp');
$client->setHeaders('If-Match: *');
$gdata=new Zend_Gdata($client);
$gdata->setMajorProtocolVersion(3);
// get the existing contact.
$query = new Zend_Gdata_Query($id);
$entry = $gdata->getEntry($query);
$xml = simplexml_load_string($entry->getXML());
// update title.
$xnl->title = $title;
// update the fullname.
$xml->name->fullName = $title;
// update entry.
$entryResult = $gdata->updateEntry($xml->saveXML(), $entry->getEditLink()->href);

Get more details over at the Google Apps Shared Contacts API page.

Basecamp & Highrise – Filling in the API gaps

So the big problem with the Basecamp & Highrise APIs is that they have bits missing – rather significant bits it turns out.  In particular the ability to create and update companies.

To get around this I wrote a couple of classes that use a combination of curl() and the awesome phpQuery to walk through the front-end pages and execute URLs just as a real user would.

Whilst this is not impervious to UI changes by 37Signals, it’s a fairly generic implementation and it’s a best endeavours approach given the current lack in the APIs.

Here’s some sample code:

public function createCompany($companyName) {

$success = true;

// go to the company page first, to get the token.
$resp = $this->_fetchUrl($this->_urls['base'].$this->_urls['companies'], 'GET', array(), false, $this->_ch);

phpQuery::newDocument($resp['data']);
$authToken = pq('input[name=authenticity_token]')->val();

$formFields = array(
'authenticity_token' => $authToken,
'company[name]' => $companyName,
'commit' => 'Create Company'
);

$resp = $this->_fetchUrl($this->_urls['base'].$this->_urls['companies'], 'POST', $formFields, false, $this->_ch);

// check - has this company already been created?
phpQuery::newDocument($resp['data']);
if(pq('div.flash_alert')->text() != '') {
$success = false;
$this->_errMsg = pq('div.flash_alert')->text();
}

return $success;
}

Show me the code!

So, in the hope of helping others to get synchronisation working (and work around the lack in the APIs) you can download the code at GitHub for your own use.

Enjoy!

Synchronising Highrise, Basecamp and Google Shared Contacts–Part 2

Background: In Part 1 of this series I wrote about a recent project that I worked on to synchronise contact data between a number of web applications, and how I had to work around the (current) shortcomings of their APIs.

As Scott Hanselman says, “talk is cheap – show me the code”.  So here we go…

Basecamp & Highrise APIs

Implementing an interface to the Basecamp & Highrise APIs is relatively straightforward – I did it using PHP’s curl().  My implementation doesn’t support all of the API’s functions, but you should be able to easily add any additional calls that you need.

The functions centre around a single function which does the hard work on making the request:

private function _getData($uriExtension) {
  $base_url = $this->url.$uriExtension;
  $session = curl_init();
  curl_setopt($session, CURLOPT_URL, $base_url);
  curl_setopt($session, CURLOPT_USERPWD, $this->apiKey.':X');
  curl_setopt($session, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  curl_setopt($session, CURLOPT_HEADER, false);
  curl_setopt($session, CURLOPT_HTTPGET, 1);
  curl_setopt($session, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
  curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($session, CURLOPT_SSL_VERIFYPEER,false);
  curl_setopt($session, CURLOPT_FRESH_CONNECT, true);
  $response = curl_exec($session);
  curl_close($session);
  $xmlObj = new SimpleXMLElement($response);
  return ($xmlObj);
}

Google Shared Contacts

This was simply done using the Zend GData classes which you can grab here.  Here’s some sample code for updating a shared contact in Google:

$client=$this->googleClientAuth('cp');
$client->setHeaders('If-Match: *');
$gdata=new Zend_Gdata($client);
$gdata->setMajorProtocolVersion(3);
// get the existing contact.
$query = new Zend_Gdata_Query($id);
$entry = $gdata->getEntry($query);
$xml = simplexml_load_string($entry->getXML());
// update title.
$xnl->title = $title;           
// update the fullname.
$xml->name->fullName = $title;
// update entry.
$entryResult = $gdata->updateEntry($xml->saveXML(), $entry->getEditLink()->href);

Get more details over at the Google Apps Shared Contacts API page.

Basecamp & Highrise – Filling in the API gaps

So the big problem with the Basecamp & Highrise APIs is that they have bits missing – rather significant bits it turns out.  In particular the ability to create and update companies.

To get around this I wrote a couple of classes that use a combination of curl() and the awesome phpQuery to walk through the front-end pages and execute URLs just as a real user would.

Whilst this is not impervious to UI changes by 37Signals, it’s a fairly generic implementation and it’s a best endeavours approach given the current lack in the APIs.

Here’s some sample code:

public function createCompany($companyName) {

    $success = true;

    // go to the company page first, to get the token.
    $resp = $this->_fetchUrl($this->_urls['base'].$this->_urls['companies'], 'GET', array(), false, $this->_ch);

    phpQuery::newDocument($resp['data']);
    $authToken = pq('input[name=authenticity_token]')->val();

    $formFields = array(
        'authenticity_token' => $authToken,
        'company[name]' => $companyName,
        'commit' => 'Create Company'
    );

    $resp = $this->_fetchUrl($this->_urls['base'].$this->_urls['companies'], 'POST', $formFields, false, $this->_ch);

    // check - has this company already been created?
    phpQuery::newDocument($resp['data']);
    if(pq('div.flash_alert')->text() != '') {
        $success = false;
        $this->_errMsg = pq('div.flash_alert')->text();
    }

    return $success;
}

Show me the code!

So, in the hope of helping others to get synchronisation working (and work around the lack in the APIs) you can download the code at GitHub for your own use.

Enjoy!

Synchronising Highrise, Basecamp and Google Shared Contacts–Part 1

NOTE: You can read part 2 here or just simply get the code.

I recently worked on a project where we needed to synchronise contact data between Highrise CRM (the source), Basecamp (for project management) and Google Shared Contacts (for email).  This is a common issue for small businesses – how to keep disparate systems in sync.  There are already some proprietary systems out there which help with this but they didn’t fulfil the needs of this client so we opted for a custom tool.

I’ll write up the steps I took to get all of this working in a series of posts, but the overall architecture is thus:

image

Feasibility

At the time of writing I determined the feasibility of synchronising all of these three systems by looking in detail at the APIs provided.  I’ve summarised my findings here:

Google Shared Contacts

It’s worth pointing out that we needed to load up NOT Google’s personal contacts, but the domain shared contacts.  This is a repository of contacts that Google provides on its Google Apps product, and works like a Global Address List, providing a single store of contact data to all domain users.  However, for some puzzling reason Google does not provide any user interface to manage this database.  Instead you must use the Google Shared Contacts API.

IMPORTANT POINT: I got very confused as to which versions of Google Apps supports the Google Shared Contacts API. Even though the documentation pages state that it is only supported by Apps for Business (Premium) and Education, the confusing point is that YOU CAN EXECUTE THE API CALLS SUCCESSFULLY and the data IS PERSISTED.

What I later discovered was that although all of the API calls work, the data is not exposed in Google Apps.  This is very frustrating and I wish Google would make this clearer in their documentation.

Also don’t get confused between the Google Contacts Data API and the Google Shared Contacts API.

Highrise & Basecamp

Both of these products are made by 37Signals, and if you’ve ever read their books, you’ll know that they are keen proponents of open systems.  They have well documented APIs which are very easy to get up and working with.

However, when you start to use them, you discover that some key elements are missing. The APIs they have provided allow you to get all information out, but when it comes to creating data, the calls just aren’t there. Despite numerous forum posts, 37Signals have not yet given good reasons for the lack of these API calls.

To get around this, I wrote a cheeky HTTP GET, Parse and Re-POST class which effectively walks through the front-end of the site programmatically, as if it were a standard user. Whilst this has been very stable so far, if either of the products publish changes to their UI then this code may need modification.

Coding Approach

I wrote all of the code in PHP 5, so all the samples here will be in PHP, but you could easily apply the concepts to different programming languages.

I built singelton classes for each of the products, and then a controlling class to tie them up all to achieve the synchronisation. I used PHP curl() to do the web requests, and for the Google API stuff I used Zend’s GData classes to speed up the development.

Synchronisation Approach

In order to synchronise these systems I took the following approach:

  • Get all Highrise Contacts
  • Add to Basecamp, storing the IDs of the two data objects in a synchronisation file
  • Add to Google Contacts, storing the ID of the Highrise Contact in the Notes field

Next Steps

In the next post I’ll start introducing the code I wrote to hook up to the Basecamp and Highrise APIs.

Synchronising Highrise, Basecamp and Google Shared Contacts–Part 1

NOTE: You can read part 2 here or just simply get the code.

I recently worked on a project where we needed to synchronise contact data between Highrise CRM (the source), Basecamp (for project management) and Google Shared Contacts (for email).  This is a common issue for small businesses – how to keep disparate systems in sync.  There are already some proprietary systems out there which help with this but they didn’t fulfil the needs of this client so we opted for a custom tool.

I’ll write up the steps I took to get all of this working in a series of posts, but the overall architecture is thus:

image

Feasibility

At the time of writing I determined the feasibility of synchronising all of these three systems by looking in detail at the APIs provided.  I’ve summarised my findings here:

Google Shared Contacts

It’s worth pointing out that we needed to load up NOT Google’s personal contacts, but the domain shared contacts.  This is a repository of contacts that Google provides on its Google Apps product, and works like a Global Address List, providing a single store of contact data to all domain users.  However, for some puzzling reason Google does not provide any user interface to manage this database.  Instead you must use the Google Shared Contacts API.

IMPORTANT POINT: I got very confused as to which versions of Google Apps supports the Google Shared Contacts API. Even though the documentation pages state that it is only supported by Apps for Business (Premium) and Education, the confusing point is that YOU CAN EXECUTE THE API CALLS SUCCESSFULLY and the data IS PERSISTED.

What I later discovered was that although all of the API calls work, the data is not exposed in Google Apps.  This is very frustrating and I wish Google would make this clearer in their documentation.

Also don’t get confused between the Google Contacts Data API and the Google Shared Contacts API.

Highrise & Basecamp

Both of these products are made by 37Signals, and if you’ve ever read their books, you’ll know that they are keen proponents of open systems.  They have well documented APIs which are very easy to get up and working with.

However, when you start to use them, you discover that some key elements are missing. The APIs they have provided allow you to get all information out, but when it comes to creating data, the calls just aren’t there. Despite numerous forum posts, 37Signals have not yet given good reasons for the lack of these API calls.

To get around this, I wrote a cheeky HTTP GET, Parse and Re-POST class which effectively walks through the front-end of the site programmatically, as if it were a standard user. Whilst this has been very stable so far, if either of the products publish changes to their UI then this code may need modification.

Coding Approach

I wrote all of the code in PHP 5, so all the samples here will be in PHP, but you could easily apply the concepts to different programming languages.

I built singelton classes for each of the products, and then a controlling class to tie them up all to achieve the synchronisation. I used PHP curl() to do the web requests, and for the Google API stuff I used Zend’s GData classes to speed up the development.

Synchronisation Approach

In order to synchronise these systems I took the following approach:

  • Get all Highrise Contacts
  • Add to Basecamp, storing the IDs of the two data objects in a synchronisation file
  • Add to Google Contacts, storing the ID of the Highrise Contact in the Notes field

Next Steps

In the next post I’ll start introducing the code I wrote to hook up to the Basecamp and Highrise APIs.

Web Development Toolsets – PHP vs. .NET IDEs

I’ve been writing code for the web for 10 years now, and in a (full-time) professional capacity for almost three and a half.  I’ve worked extensively on the Linux Apache MySQL and PHP (LAMP) stack as well as in Microsoft’s ASP.NET.  This post is a helpful summary of my experiences developing in both worlds, and what you need to focus on your job.

Important information on operating systems: I carry out all of my development on Windows, so you’ll not find any suggestions or reviews of software for Macs or Linux here.  If you’re one of these people, why not write up your own review?

Integrated Development Environments (IDEs)

Let’s be honest, we all started out in Notepad, writing angle brackets in long hand and making extensive use of Ctrl+C.  However once we graduated from the single scripted site, managing multiple files and object oriented links between them soon left us lost in a sea of notepad.exe windows.  To curb this problem, and many others, software vendors developed IDEs, to incorporate a number of development tools into one application.

My favourite PHP IDE – Netbeans

If you write any amount of PHP code then you’ll want to get comfortable in one of the many PHP IDEs out there.  In the last two years I’ve spent many hours bashing keys in Netbeans IDE.  Whilst this application is written in Java (which undoubtedly makes it dog slow at times), Netbeans have dramatically improved the performance in the last few releases to the point where most features are finally usable!

Debugging

Remote team debugging does require screwing around with XDebug and Komodo Python Debug Proxy, which I did manage to get working on a Linux development VM.   However it has proved to be a little temperamental, requiring frequent restarts.

Source Control

Netbeans includes integration for SVN Source Control and this works pretty seamlessly – you can commit and revert from within the application, as well as see which files are under source control and their status.

Code Navigation

With the right hints in your code, you can jump between classes with Ctrl+Click pretty quickly.

Summary

I have toyed with other IDEs, notably phpDesigner, however Netbeans has been my favourite (free) IDE, and has served me very well.

My favourite .NET IDE – Visual Studio 2010

Much has been written about Visual Studio 2010, so I’m hesitant to try and add to the endless blog posts detailing every feature of this application.  All I will say is that it is an incredible piece of software.  It runs like grease lightening, it has a database explorer, browser and designer built in, as well as an extension model for a variety of useful add-ons.

Debugging

Even since my days of programming in Visual Basic 6, step-by-step debugging has been the most productive way to debug, rather than the old “Made it to here” debugging output.  You can attach to remote servers, or simply fire up the built in development web server to step through your code, with watch windows and full stack traces to hand.  This debugging experience is head and shoulders above that of Netbeans.

Source Control

Whilst you can get some source control add-ins for Visual Studio, my impression is that use of the Team Foundation Server is quite heavily pushed.  I have yet to find a good HG (Mercurial) add-in, so I hack away on the command line to feel like a real programmer.  On this item, Netbeans is my winner.

Code Navigation

When combined with IntelliSense, it beats Netbeans hands down, purely due to the speed and variety of options.  You get hinting at method parameters, namespaces to include as well as class properties, methods and events.

Summary

No serious .NET developer would use anything else, and as Microsoft make their express editions freely available, there’s really no excuse not to be using Visual Studio if you’re writing .NET code.

The Final Word

My overall development experience is much slicker in Visual Studio 2010; it feels like the right tool for the job, and it is so quick that I never feel like it gets in the way.  Netbeans is more of a necessary evil for development, and it often feels like I’m having to kill time whilst waiting for things to happen, or have those moments of keystroke regret when you see the “scanning for indexes” message in the status bar.  But compared to Notepad, it is so many steps ahead that I’ll just have to keep putting up with the performance issues.

Web Development Toolsets – PHP vs. .NET IDEs

[tweetmeme source=”shuggill” only_single=false]

I’ve been writing code for the web for 10 years now, and in a (full-time) professional capacity for almost three and a half.  I’ve worked extensively on the Linux Apache MySQL and PHP (LAMP) stack as well as in Microsoft’s ASP.NET.  This post is a helpful summary of my experiences developing in both worlds, and what you need to focus on your job.

Important information on operating systems: I carry out all of my development on Windows, so you’ll not find any suggestions or reviews of software for Macs or Linux here.  If you’re one of these people, why not write up your own review?

Integrated Development Environments (IDEs)

Let’s be honest, we all started out in Notepad, writing angle brackets in long hand and making extensive use of Ctrl+C.  However once we graduated from the single scripted site, managing multiple files and object oriented links between them soon left us lost in a sea of notepad.exe windows.  To curb this problem, and many others, software vendors developed IDEs, to incorporate a number of development tools into one application.

My favourite PHP IDE – Netbeans

If you write any amount of PHP code then you’ll want to get comfortable in one of the many PHP IDEs out there.  In the last two years I’ve spent many hours bashing keys in Netbeans IDE.  Whilst this application is written in Java (which undoubtedly makes it dog slow at times), Netbeans have dramatically improved the performance in the last few releases to the point where most features are finally usable!

Debugging

Remote team debugging does require screwing around with XDebug and Komodo Python Debug Proxy, which I did manage to get working on a Linux development VM.   However it has proved to be a little temperamental, requiring frequent restarts.

Source Control

Netbeans includes integration for SVN Source Control and this works pretty seamlessly – you can commit and revert from within the application, as well as see which files are under source control and their status.

Code Navigation

With the right hints in your code, you can jump between classes with Ctrl+Click pretty quickly.

Summary

I have toyed with other IDEs, notably phpDesigner, however Netbeans has been my favourite (free) IDE, and has served me very well.

My favourite .NET IDE – Visual Studio 2010

Much has been written about Visual Studio 2010, so I’m hesitant to try and add to the endless blog posts detailing every feature of this application.  All I will say is that it is an incredible piece of software.  It runs like grease lightening, it has a database explorer, browser and designer built in, as well as an extension model for a variety of useful add-ons.

Debugging

Even since my days of programming in Visual Basic 6, step-by-step debugging has been the most productive way to debug, rather than the old “Made it to here” debugging output.  You can attach to remote servers, or simply fire up the built in development web server to step through your code, with watch windows and full stack traces to hand.  This debugging experience is head and shoulders above that of Netbeans.

Source Control

Whilst you can get some source control add-ins for Visual Studio, my impression is that use of the Team Foundation Server is quite heavily pushed.  I have yet to find a good HG (Mercurial) add-in, so I hack away on the command line to feel like a real programmer.  On this item, Netbeans is my winner.

Code Navigation

When combined with IntelliSense, it beats Netbeans hands down, purely due to the speed and variety of options.  You get hinting at method parameters, namespaces to include as well as class properties, methods and events.

Summary

No serious .NET developer would use anything else, and as Microsoft make their express editions freely available, there’s really no excuse not to be using Visual Studio if you’re writing .NET code.

The Final Word

My overall development experience is much slicker in Visual Studio 2010; it feels like the right tool for the job, and it is so quick that I never feel like it gets in the way.  Netbeans is more of a necessary evil for development, and it often feels like I’m having to kill time whilst waiting for things to happen, or have those moments of keystroke regret when you see the “scanning for indexes” message in the status bar.  But compared to Notepad, it is so many steps ahead that I’ll just have to keep putting up with the performance issues.