Thursday, January 17, 2008

New in VB9

I'm trying to do more blogging as part of my personal learning process, so courtesy of my investigation of changes in .NET 3.5 and VS2008, here are some great places to find information for VB.NET:Also, InfoQ has a small editorial piece, Selecting a .NET Web Framework which raises the question of whether having ASP.NET MVC around will make it tempting to give up WebForms and hence our ASP.NET server control libraries? Personally, I think the control vendors will see this as an opportunity to expand their product lines. I think we will see ASP.NET MVC being used more for Web 2.0/externally facing applications and WebForms still being the dominant framework for intranet development, or web-enabled line of business apps.

Wednesday, January 16, 2008

2008 has arrived!

Yes, I know that I'm a little late for a Happy New Year post, but actually this is another sort of 2008 I'm talking about:
  • Windows 2008 Server
  • SQL Server 2008
  • CommunityManager.NET compiled under Visual Studio 2008 (targeting .NET 3.5 )
Elcom's Technical Director has just announced that http://www.elcom.com.au is now running using these three!

Coatesy gave us a kind word too.

Big props to Balaji (where's your blog man?) for putting in the hard work to make sure the CM code could compile and run under .NET 3.5. Craig mentions some of the other heroes that helped get this done, but Rajesh, Imad and Yul missed mentions there, so here's thanks to them too!

Saturday, January 12, 2008

VB.NET is the illegal immigrant of Microsoft languages

I currently mostly code in VB.NET, developing ASP.NET web applications. I have in the past developed in Omnis, MS Access 2/95/2000, Lotus Notes/LotusScript, Java, VB6, VBA, PHP, JavaScript, C# and Ruby (on Rails). Some of those I did for fun, but most were because I was paid (and asked) to. I point this out not to boast, but in order to put some background around this particular rant.

[NOTE: Another accidental thing that denigrates VB is that we must put ".NET" at the end if we are talking about 'real' development, because VB as a term covers such a broad range of languages. This alone makes C# seem exclusive and more 'grown-up'. In fact C# is more properly referred to as Visual C#, or C#.NET.]

“Computer languages influence how you think about a problem, and how you think about communicating.”
The Pragmatic Programmer
Microsoft have a publicly declared agnostic view of which language a developer chooses to use within the .NET platform, so long as they use it. For this reason they have researched new languages for the CLR, and are introducing the DLR to enable IronPython, IronRuby etc.

Internally however Microsoft have a culture that prefers people using C#. In practice this means that the 'cheap' option is always to do code samples and demo code in C# and not VB (or some other .NET language), because it is simply more convenient to do so. When asked why a given example is not also in VB, this is the answer given (and the occasional plea for help). There is however a tendency to see VB developers as somehow inherently less capable that C# developers - perhaps this is a view that the 'best' work at Microsoft, or maybe it's a hangover from the "Basic" part of VB's name. Most probably it also has a lot to do identifying VB users with using a persona called 'Mort', whilst C# is Elvis and C++ Einstein (thanks usability guys).

A few months ago Bill McCarthy, a VB MVP, ranted about this issue in his post Is VB the n*gg*r of programming languages?. Leaving aside a controversial choice of words (for which he was smacked down), he had a very good point. My post's title is a reference to his point (with a more PC turn of phrase).

Bill points out that how to videos (for beginners?) are often coded in VB, but the SDKs have C# examples (for real developers?).

A more recent balancing view comes from Scott Hanselman, who asks Is rooting for Visual Basic like rooting for the Red Sox?. Leaving aside the unfortunately humorous connotations of 'rooting' to us Aussies, his point is that VB is making something of a comeback in popularity, but old-timers (like Bill?) still want to feel like underdogs. Reading the rest of Scott's blog shows that he's been exploring beyond the homely environs of C# and hanging out with people who are pragmatic enough to use whatever works.

Personally I think this last point is the one that makes the most sense. In The Pragmatic Programmer Andy Hunt and David Thomas said:
“Computer languages influence how you think about a problem, and how you think about communicating ... Designing a solution with Lisp in mind will produce different results than a solution based on C-style thinking, and vice versa. Conversely, and we think more importantly, the language of the problem domain may also suggest a programming solution.”

All of this is what has attracted me to the ALT.NET movement (see the first use of the term, the conference last year, it just had a leadership summit and a Yahoo! group). Martin Fowler weighed in and said this about ALT.NET:
“The alt.net mind-set is one that is very familiar to me. It has that mix of agile + object-orientation + patterns + TDD + DDD which is very much the school of software development that I favor. (Lacking a proper name for it, I'm inclined to call it the OOPSLA school of software development.) There is certainly a belief that there is a mainstream Microsoft orthodoxy at the moment, one that doesn't fit the OOSPLA school. And there's some frustration with that. But the point here is that it's not that the alt.net community thinks that the perceived mainstream Microsoft route should be erased, but that the Microsoft world is big enough for different approaches.”
To all this I say a big hooray! But ... it seems everyone who wants to do something with ALT.NET is a devoted fan of C# (or at least NOT a fan of VB). So every code sample, explanation or tool is aimed at C# and either requires much grokking to be used in VB, or in many cases, cannot be used with VB at all.

The answer I'm afraid is that I must use more C# if I want to become more agile, practice TDD or play with ASP.NET MVC. The fact that I get paid to work with VB.NET is not a good enough reason to stick with it. VB is the illegal immigrant of .NET languages, it snuck into .NET when it should have been told to stay away (perhaps forcibly deported) and the sooner any of us who use it realise that, the better. Shifting to a true .NET citizen like C# is clearly the only responsible thing to do - and using those Johnny-come-latelys like IronRuby or IronPython (or Boo) is a joke - they are only toy languages, not super-strong doers of great deeds like C# is.

Still, at least there is one hope on the horizon, with Silverlight assuming such importance in Microsoft's plans for the web, perhaps VBx will come to our rescue? With VB moving into the DLR perhaps VB developers can all become the new, new boys on the block, and start looking down on those poor C# types who are unable to duck-type or use Jasper. And my persona? Let's just call him Ben.

Wednesday, January 09, 2008

Cargo Cult Programming

I learnt a new term today, cargo cult programming, I'm afraid I've worked too many places where the standard practice seems to strongly resemble it!
“Cargo cult programming is a style of computer programming that is characterized by the ritual inclusion of code or program structures that serve no real purpose.”

Tuesday, January 08, 2008

Buggy VS2008 debugging

Is it just me that's experiencing weird issues when debugging websites in VS2008? The main issue I get is that it starts up 5 (!) ASP.NET web servers every time I try to attach to my main IIS web server process (when I don't need the dev web server!). Recently the first of these has started to crash, which means I get a crash message straight up ... none of its is too hard to handle, I simply stop the dev web servers before starting my debugging, but it is a weird error that bugs me.

Monday, January 07, 2008

Partial classes and methods in VB.NET

For the current project I'm on we decided to spend a little time upgrading our code generation tool so that we could generate DAL classes more easily from our database tables. Part of this involved adding in extra loops to make the generated code bulletproof so we could rely on what was generated and not need to think twice about re-generating classes when we wanted to fix a bug or add a new piece of functionality.

To be clear, the generator would create seven files:
  1. Basic entity class (e.g. Group.vb)
  2. Collection class (GroupCollection.vb)
  3. A DAL class (e.g. GroupDAL.vb)
  4. Insert stored procedure
  5. Update stored procedure
  6. Delete stored procedure
  7. Select stored procedure to get a single record by primary key(s)
[Disclaimer: This is a legacy system, so field and variable names in sample code below are specified for me, and don't reflect my own thoughts on naming conventions.]

The first step was to add more smarts to the templates so that the generator could handle some difficult situations (multiple primary keys, non-identity single primary keys) and to add in some additional base functionality (collection sorting, object caching, OnValidate checking that the IList interface is not being abused).

For a good article on strongly typed Collection classes check out this one on Builder AU.

Next we looked at the range of customisation we already had in existing data layer classes and ways of separating this out so that we could add ways of getting data that meet specific business requirements without polluting our generated classes. The answer of course was to use partial classes.

“we nearly always need an additional partial DAL class to hold useful extra ways of retrieving data”
By declaring that our generated classes were partial public classes we could complete the rest of the class in another file that is not being auto-generated. Somehow I ended up christening these for the team, and where necessary we now have additional files (e.g. Group_Additional.vb, GroupCollection_Additional.vb, GroupDAL_Additional.vb). It turns out that we nearly always need an additional partial DAL class to hold useful extra ways of retrieving data (e.g. searches, find by foreign key, etc.), with regards to collections and entity classes it was less useful, but more on that later. We now had a way to bring in our legacy customisations, whilst still generating most of the code.

Our DAL classes now start like this:

    1 Imports System

    2 Imports System.Data.SqlClient

    3 Imports System.Data

    4 Imports System.Text

    5 Imports System.Collections

    6 Imports Elcom.Common.Data

    7 

    8 ''' <summary>

    9 ''' Data Access layer (DAL) class for Group entity objects.

   10 ''' </summary>

   11 ''' <remarks>

   12 ''' Auto-generated by Elcom Code Generator at 4/01/2008 4:01:03 PM.

   13 ''' </remarks>

   14 Partial Public Class GroupDAL

   15     Inherits BaseDAL

   16 

   17     ''' <summary>

   18     ''' Constructor method is declared and made private to ensure

   19     ''' class is a singleton.

   20     ''' </summary>

   21     Private  Sub New()

   22     End Sub

   23 

   24     ''' <summary>

   25     ''' Public 'Instance' method allows external objects to access

   26     ''' the current singleton instance.

   27     ''' </summary>

   28     ''' <returns>

   29     ''' Returns a reference to the single GroupDAL object

   30     ''' in memory.

   31     ''' </returns>

   32     Public Shared ReadOnly Property Instance() As GroupDAL

   33         Get

   34             If _instance Is Nothing Then

   35                 SyncLock GetType(GroupDAL)

   36                     If _instance Is Nothing Then

   37                         _instance = new GroupDAL()

   38                     End If

   39                 End SyncLock

   40             End If

   41             Instance = _instance

   42         End Get

   43     End Property


The DAL additional class looks like this:

    1 Imports System

    2 Imports System.Data.SqlClient

    3 Imports System.Data

    4 Imports System.Text

    5 Imports System.Collections

    6 Imports Elcom.Common.Data

    7 

    8 ''' <summary>

    9 ''' Data Access Layer class for Group

   10 ''' </summary>

   11 ''' <remarks>

   12 ''' Old code brought in as Additional partial class by Angus

   13 ''' on 4/01/2008 for CM 5.5.

   14 ''' </remarks>

   15 Partial Public Class GroupDAL

   16 



Unfortunately we were still left with a major issue, some of the customisation we had made involved making changes to the way we saved some data items (e.g. encrypting users' passwords before saving them to the database), or having additional read-only data items that were not instantiated in the database table but properly belonged to the entity. The partial class solution allowed us to add extra properties, but did not enable us to override the basic Save() method, so what were we to do?

One option was to implement an alternative Save() method that differed either via parameter signature or name, but that would leave the incorrect one hanging around. Another trade off would be to customise the generated class file and just live with the necessity to not generate that file (problematic and prone to human error).

The answer turned out to be partial methods. The VB team blogged about these last year:
“Partial methods enable code generators to write extremely flexible code by creating a lot of "hooks", where developers can provide their own custom functionality that integrates into the "boiler plate" code created by the generator. Because the hooks are optimized away if they aren't used, no performance penalty is introduced by defining them. This is really useful if generated code needs to be used in high performance scenarios. For example, partial methods are used by the DLINQ designer for exactly this purpose. Developers wishing to invoke custom code on their data objects when properties are set can do so without requiring all users of DLINQ to suffer performance problems.”
So we ended up adding in calls to partial methods in all of our property setting code:

   14 

   15     Public Property intGroupID() As Int32

   16         Get

   17             Return _intGroupID

   18         End Get

   19         Set (ByVal value As Int32)

   20             ' partial event method to allow insertion of

   21             ' behaviour using partial class

   22             OnSettingintGroupID(value)           

   23             _intGroupID  = value

   24         End Set

   25     End Property

   26 



We also ended up adding pre/post save calls to partial methods to allow us to intervene and tweak either data to be saved or to handle other behaviour post the save operation. An example of a typical save operation is below:

   70 

   71     ''' <summary>

   72     ''' Public method allows external objects to save changes to a Group object.

   73     ''' </summary>

   74     Public Sub Save(ByRef objGroup As [Group], ByVal blnForUpdate As Boolean)

   75         Dim parameters As ArrayListNew ArrayList()

   76         Dim sproc As String

   77 

   78         parameters.Add(New SqlParameter("@strName",objGroup.strName))

   79         parameters.Add(New SqlParameter("@intURLID",objGroup.intURLID))

   80         parameters.Add(New SqlParameter("@blnAllowAddressUpdateInEshop",objGroup.blnAllowAddressUpdateInEshop))

   81         parameters.Add(New SqlParameter("@blnShowAdminInTopMenu",objGroup.blnShowAdminInTopMenu))

   82         parameters.Add(New SqlParameter("@strLDAPGroupName",objGroup.strLDAPGroupName))

   83         parameters.Add(New SqlParameter("@strLDAPIdentifier",objGroup.strLDAPIdentifier))

   84         parameters.Add(New SqlParameter("@blnSystemsAdministrator",objGroup.blnSystemsAdministrator))

   85         parameters.Add(New SqlParameter("@blnRestrictAccessBaseFol",objGroup.blnRestrictAccessBaseFol))

   86         parameters.Add(New SqlParameter("@blnRestrictArticleOptAttr",objGroup.blnRestrictArticleOptAttr))

   87         parameters.Add(New SqlParameter("@blnRestrictFolderOptAttr",objGroup.blnRestrictFolderOptAttr))

   88         parameters.Add(New SqlParameter("@blnForceSelectTemplate",objGroup.blnForceSelectTemplate))

   89         parameters.Add(New SqlParameter("@blnRestrictChangeLayout",objGroup.blnRestrictChangeLayout))

   90         parameters.Add(New SqlParameter("@blnRestrictAddElements",objGroup.blnRestrictAddElements))

   91         parameters.Add(New SqlParameter("@blnRestrictCreateTempFromArt",objGroup.blnRestrictCreateTempFromArt))

   92         parameters.Add(New SqlParameter("@blnAllowEventStatusUpdate",objGroup.blnAllowEventStatusUpdate))

   93         parameters.Add(New SqlParameter("@blnAllowVenueLimitUpdate",objGroup.blnAllowVenueLimitUpdate))

   94         parameters.Add(New SqlParameter("@blnAllowAddAttendee",objGroup.blnAllowAddAttendee))

   95         parameters.Add(New SqlParameter("@blnAllowDeleteAttendee",objGroup.blnAllowDeleteAttendee))

   96         parameters.Add(New SqlParameter("@blnAllowOnbeHalfRegistration",objGroup.blnAllowOnbeHalfRegistration))

   97         parameters.Add(New SqlParameter("@blnAllowMoveEvent",objGroup.blnAllowMoveEvent))

   98 

   99         ' partial event method to allow insertion of behaviour using partial class

  100         PreSave(parameters, objGroup, blnForUpdate)

  101 

  102         If blnForUpdate And  objGroup.intGroupID > 0   Then

  103             parameters.Add(New SqlParameter("@intGroupID",objGroup.intGroupID)) 

  104             sproc = "proc_GroupUpdate"

  105             ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, sproc, parameters)

  106         Else

  107             sproc = "proc_GroupInsert"

  108 

  109             Dim intNewKey as Integer

  110             intNewKey = CType(ExecuteScalar(ConnectionString, CommandType.StoredProcedure, sproc, parameters), Integer)   

  111             objGroup.intGroupID = intNewKey

  112         End If

  113 

  114         ' partial event method to allow insertion of behaviour using partial class

  115         PostSave(objGroup, blnForUpdate)

  116 

  117         ' refresh the cached version of this object

  118         Dim cacheName As StringString.Format("Group:{0}", ""  + objGroup.intGroupID.ToString() + "" )

  119         RemoveFromCache(cacheName)

  120         AddToCache(cacheName, objGroup)       

  121     End Sub

  122 



The end result for us was a much more flexible system of generated code which meant that we can now fix problems with out data classes in the templates directly and then just re-generate the code and replace the files in our project folders and commit to our source code repository (it helps that we use Subversion via TortoiseSVN).



Note: Thanks to Guy Burstein for taking the time to migrate the Copy Source as HTML addin to VS2008, even though it seems to have problems with my my colour scheme. Mazal Tov with that new MS job too!