Sunday, 30 December 2007

How to rip audio from a DVD to Audio CD

Paul videoed Sevenfold's recent performance and created a really nice DVD for all the band members.

Jane doesn't have a DVD player however, so I used these instructions to burn her an audio CD.

Additional comments 2nd Jan 2008

Things didn't go as smoothly as I'd hoped. My DVD burner came bundled with ULEAD Burn.Now. I wanted to add CD-TEXT track info which Burn.Now supports. However after I burned the CD and tested it, I discovered that some of the tracks had been messed up. The track times were correct, but the content had been messed up. Jumping to track 4 played the content that was actually half-way through track 2. Very weird.

I read that WinAmp supports CD-TEXT, but after trying it out, discovered that it only supports reading CD-TEXT, not writing it.

As a last resort, I grabbed iTunes, and it did the job, though it could only add CD-TEXT for each individual track - I didn't see a way to add info about the album as a whole.

Monday, 17 December 2007

Passed 070-528

This morning I sat the Microsoft .NET Framework 2.0 - Web-based Client Development exam, and am pleased to report I passed with a score of 824. Yippee :-)

There were a few questions that were a bit tricky. One in particular had me wondering if it had an error in it, the possible answers didn't seem to make sense to me.

Not to worry, I'm now a Microsoft Certified Technology Specialist .NET Framework 2.0 Web Applications.

Unlike some previous exams I've taken, I didn't study for this one at all. It just goes to show what Rob says, if you know you're stuff then you can pass these exams just relying on your experience.

Next stop is 70–547 and I'll have have my Microsoft Certified Professional Developer (MCPD): Web Developer.

Friday, 14 December 2007

And a not so great Christmas present..

It was announced last week that the Flexible Learning Centre (the unit I work in at the University of South Australia) is being restructured. (The official memo says "a formal Managing Change process has been initiated").

Being the last University entity left at the old Underdale campus, it was expected that we would get moved to somewhere else eventually (which will probably be part of this process).

Our unit has been through these kinds of things a number of times before, but this is the first time that it has directly affected the team I've been involved with - Online Services.

Online Services is a relatively small team of developers, designers and A/V professionals and we've been responsible for creating the Uni's online teaching and learning platform - "UniSAnet". Whereas many other institutions just went out and bought an off the shelf LMS like Blackboard or WebCT (now the same company), we made the decision early on that we wanted to integrate with other existing systems (student information, courses, Active Directory etc) and the only practical way to do that was to build our own.

The great thing about our team (and I think we've achieved a bit of a reputation for this) is that we can create solutions relatively quickly, in an "agile" kind of way (lots of iterations). What might otherwise take months, we can often achieve in days or weeks.

Our team is regularly looking at new technologies and techniques. For example we're trying to incorporate things like MVP, NHibernate, TDD, CI to help improve both our productivity and the quality of the solutions we produce. Most of our team attend the monthly SQL User Group meetings in the city, and more recently our Tuesday lunchtimes have been spent watching dnrTV screencasts together. There's a genuine interest (encouraged by our managers) in professional development which I really appreciate.

The University also has an IT unit - ISTS, which looks after the systems and network infrastructure of the organisation, as well as manage things like staff and student portals, the corporate web server and various other "big systems". I've collaborated with the ISTS on a number of projects in the past and I think it's fair to characterise their development process as following the waterfall model. ISTS do look after critical systems, so quite reasonably they tend to be pretty conservative in their approach to implementing new things and making changes.

Don't get me wrong - I know lots of people in ISTS, and I'm even going to their Christmas party today (they always do a really good job so I'm looking forward to that!), so I'm not intending to be negative about them. It's just that we don't work on the same kinds of things and we don't work in the same kind of way. If we were to be made part of ISTS (which could be an interpretation of this item from an email sent out today- "UniSAnet and appropriate support functions relocating to ISTS") I don't think is likely that we could continue to provide the services in both the time and fashion that we do now.

So the timing isn't the best that this whole process is happening over the Christmas break. I'm trying not to be cynical, and that just maybe once this has all finished we will end up something better than what we have now, but only time will tell.

So for me:

  • Best case - Online Services continues, most likely in the CBD, and we can continue to develop compelling resources and tools for enabling and enhancing great learning and teaching.
  • Worst case - Time to tidy up my resume.
  • Middle case - ?

Visual Studio Cube

I got a nice Christmas present this morning - my "thanks for testing Visual Studio 2008" cube arrived. It doesn't do much but it is pretty.

Visual Studio crystal cube

Tuesday, 11 December 2007

Not two for free

Just in case you were planning to do what I was - you can't use a "free exam voucher" together with a "second shot" voucher.

I was all set to do two exam next Monday, figuring that even if I didn't pass, I could still retake them in January for free.

Not so.

Fortunately I found this out before I'd used up my second "free exam" voucher.

Wednesday, 5 December 2007

Problems with Visual Studio Conversion Wizard in 2008

Converting projects to Visual Studio 2008 doesn't always go smoothly.

You might see it blow up with a COMException error

If your ASP.NET web application project is configured to use IIS (instead of the built-in web server), then you'll need to run VS as Administrator (thanks to Mikhail posting that workaround) This is because VS needs to look in IIS's metabase and it requires admin privileges to do that.

I'm also getting a very helpful "The operation could not be completed"

Trying to dig deeper, I ran a debug instance of VS (eg. opened up VS and then ran another instance of VS inside that) to see if there were any exceptions that might give a clue as to what was going wrong.

I now get a little bit more info: COMException 0x80020009 "Function evaluation disabled because a previous function evaluation timed out. You must continue execution to reenable function evaluation."

I spent some time trying to figure this out, but in the end gave up and just created a new project in 2008 and copied in the old files.

Monday, 3 December 2007

Visual Studio 2008 Web Deployment Projects - Dec07 CTP

This is one thing that we were missing from our requirements to fully make use of VS 2008.

Web Deployment Projects pre-compile your web application projects, can remove files that aren't needed on the production web server, and as a side-effect of compiling the .aspx pages too, can find some syntax errors that don't get noticed by the normal VS compiler.

Watch out for this bug though.

Friday, 30 November 2007

Tour Down Under payments are not secure

I went to register for the Challenge Tour (the 134km ride on 25 January 2008) today.

I clicked on a link from an email from the South Australian Tourism Commission that goes to this site - http://www.bizgate.sa.gov.au/shop/tdu/site/page.cfm?CONTENT=shop%5Ffront.

Fine.

I pick the correct event to pay for, add it to my cart, enter my details, then go to checkout. I then get a popup window asking for my contact details again (didn't they already know that?), and then the next page asks for my credit card info.

Whoa!

Hang on!

We're still on a HTTP page - no lock icon, no coloured address/location bar, nothing, just plain text sailing off into the Internet for anyone to snoop!

So I rang up the SA Tourism people, and the lady tried to reassure me - "yes, our online payment system is secure".

I was not reassured.

"Funny, we had someone else contact us with a similar query 10 minutes ago" - what a surprise.

I finally got her to find someone else who looks after the Tour down under website - a guy by the name of Darrenn. I spoke to him and explained the problem. I don't think he quite understood my concerns (not being an IT person apparently). Anyway, he gave me his email address and promised to look into it. I fired off an email to him.

Not content with that, I then thought I'd try and find someone who actually runs the BizGate site. Their top-level web page says they're run by the Department for Transport, Energy and Infrastructure. Fine. I rang DTEI and was given another phone number. I rang that and left a message.

Why do I feel a bit pessimistic that anything will get done :-(

The solution by the way, is to use this link instead:

https://www.bizgate.sa.gov.au/shop/tdu/site/page.cfm?content=search_results.cfm&mode=browse

Marvelous what a difference an 's' makes.

Update late Friday afternoon: The link has been fixed now

Thursday, 29 November 2007

Microsoft Office Mobile 6.1

Here's an upgrade for the Office Mobile applications to support Microsoft Office 2007 file formats. I wonder if this will appear on the Windows Update for Windows Mobile?

Cell broadcast name

In Australia, channel 50 is used for the "cell broadcast" information for mobile phone networks. Most often this contains the suburb or locality of the current base station the phone is talking to.

I enabled this on my new phone, and it works ok for GSM.

I've just been speaking to Optus technical support who told me that their 3G network doesnt' support this feature. A bit odd, as you'd think 3G would have more features, not less. The guy didn't know if there was some other way to retrieve the same kind of information.

One problem that I did discover is that every time the phone receives notification that it has changed base stations, it wakes up from standby mode.

That's probably why I was woken up at 2am this morning wondering who had left the light on - turns out it was my phone. Amazing how bright a backlit display can be in the middle of the night :-(

I've contacted HTC in Australia and asked them to find out if this is a bug or a feature. I'm certain my old phone didn't do this, so I consider it the latter.

I've also emailed our local Australian mobile device MVP Nick Randolph to see if he has any suggestions.

Wednesday, 28 November 2007

The old and the new

Here's a side-by-side comparison of my old HP rw6828 and my new HTC TyTN II.

  HP iPAQ rw6828 HTC TyTN II
Processor Intel® PXA270 Processor 416MHz Qualcomm® MSM 7200, 400MHz
Operating System Windows Mobile 5.0 Windows Mobile® 6 Professional
Memory 45MB available for persistent user storage
64MB SDRAM for running applications
ROM: 256MB RAM: 128MB SDRAM
Dimension 102mm (L) x 58mm (W) x 19.5mm (D) 112 mm (L) X 59 mm (W) X 19 mm (D)
Weight 140g 190g with battery
Display 56 x 56mm (2.205 x 2.205 inches) transflective TFT QVGA color, 240 x 240 pixels,0.24mm dot pitch, 64K-color support, portrait and landscape support with touch screen 2.8 inch, 240 X 320 QVGA TFT-LCD display with adjustable angle and backlight
Network GSM, GPRS, EDGE HSDPA/UMTS: Tri-band 850, 1900, 2100 MHz
HSDPA: Up to 384kbps for upload and 3.6Mbps for download
UMTS: Up to 384kbps for upload and download
GSM/GPRS/EDGE: Quad-band 850, 900, 1800, 1900 MHz (The device will operate on frequencies available from the cellular network)
Device Control   Finger scrolling and panning 5-Way navigation control
Keyboard None Slide-out QWERTY keyboard
Connectivity Class II device; up to 4 dBm transmit, typical 10 meter range
(approximately 33 feet) - High-speed, low-power, short-range
wireless communication with other Bluetooth devices Serial IrDA SIR, data transfer up to 115.2 Kb per second, USB
1.1 Client - support via HP standard mini-USB cable 802.11b (WEP and WPA)
Bluetooth® 2.0 Wi-Fi®: IEEE 802.11 b/g (WEP, WPA, WPA2)
HTC ExtUSB™ (11-pin mini-USB and audio jack in one)
GPS antenna connector
Camera Built-in UXVGA 1.3MP with LED light, 1280 x 1024 resolution,
JPEG and 3GP format, support for still image, and video
playback, H.263
Main camera: 3 megapixel CMOS color camera with auto focus Second camera: VGA CMOS color camera
Audio Integrated microphone, receiver, speaker and one 2.5mm
stereo headphone jack, MP3 stereo (through headphone jack)
Handsfree speakerphone
Built-in microphone and speaker
Ringtone formats Simple MIDI Type 0, WMA, WAV, MP3, polyphonic MIDI
  • MP3, AAC, AAC+, WMA, WAV, and AMR-NB
  • 40 polyphonic and standard MIDI format 0 and 1(SMF)/SP MIDI
Headset 2.5mm jack with single earbud style headset (mute and volume control) mini-USB connector
Battery Removable/rechargeable 1200 mAh, 3.7 Volt, Lithium polymer battery 1,350 mAh rechargeable Li-polymer battery Standby time:
  • Up to 350 hours for UMTS
  • Up to 365 hours for GSM
Talk time:
  • Up to 264 minutes for UMTS
  • Up to 420 minutes for GSM
  • Up to 120 minutes for video call
Expansion Slot Supports mini SD memory standard microSD™ memory card (SD 2.0 compatible)

Interesting things I've noticed so far:

  • While the new phone uses the "ExtUSB" port to connect the headset (meaning it should never get stuck), this does mean you can't charge or dock the device and listen on the headset at the same time.
  • The HTC device happily continues to play audio even when it loses the phone signal - my HP had this annoying "feature" that most times when I was travelling on a train through a tunnel, it would pause Media Player.
  • There's no volume control on the headset. Fortunately the headset does seem much more comfortable and clearer than the HP one.
  • The HTC memory card seems a lot faster to access.

Daylight Saving in South Australia

In most states and territories of Australia, we have daylight saving. While there probably aren't as many benefits for those living in the northern areas of the continent, I quite like it.

Next year, South Australia's daylight saving will end on Sunday 6 April at 3am. This is a change from the previous rule of the last Sunday in March, but it brings us into line with NSW, Victoria, ACT and Tasmania, which is probably a good idea.

Microsoft have been busy releasing time zone updates this year, particularly with changes in some US states and also Western Australia.

The latest revision includes updates for Australia, including Central Australian Standard Time, Australia Eastern Standard Time and Tasmania Standard Time. Updates for various operating systems are linked from the KB article.

Tuesday, 27 November 2007

Code analysis, VSLangProj and EnvDTE

If you have a Visual Studio add-in that references the VSLangProj assembly, that assembly implicitly refererences the EnvDTE assembly (version 7.0.3300.0). However that version of EnvDTE doesn't ship with VS2008.

There is a binding redirect in place for Visual Studio (via a devenv.exe.config file), but this doesn't appear to work for the Code Analysis command. I tried modifying the fxcopcmd.exe.config file to no avail.

The is the error I'm seeing:

Running Code Analysis...
C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop\FxCopCmd.exe  /outputCulture:1033 /out:"bin\Debug\Altinoren.ActiveWriter.Dsl.dll.CodeAnalysisLog.xml" /file:"bin\Debug\Altinoren.ActiveWriter.Dsl.dll" /dictionary:"..\CodeAnalysisDictionary.xml" /directory:"C:\Program Files\Common Files\Microsoft Shared\MSEnv\PublicAssemblies" /directory:"C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PublicAssemblies" /directory:"C:\Windows\Microsoft.NET\Framework\v2.0.50727" /directory:"C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies" /directory:"C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Common\Assemblies" /directory:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5" /rule:"C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop\Rules" /searchgac /ignoreinvalidtargets /forceoutput /successfile /ignoregeneratedcode /saveMessagesToReport:Active /timeout:120
MSBUILD : error : CA0001 : The following error was encountered while reading module 'Altinoren.ActiveWriter.Dsl': Assembly reference not resolved: EnvDTE, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.
MSBUILD : error : CA0058 : The referenced assembly 'EnvDTE, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' could not be found. This assembly is required for analysis and was referenced by: 'Altinoren.ActiveWriter.Dsl.dll'.
Code Analysis Complete -- 2 error(s), 0 warning(s)

Update 29th November 2007:

David Kean has responded to my query in the MSDN forums with a workaround to this problem.

Monday, 26 November 2007

ActiveWriter for VS 2008

One of the recent additions to our toolkit has been ActiveWriter - a GUI for generating NHibernate classes and mapping files.

So before we can make the final jump to using 2008 for all our development, ActiveWriter needs to work.

Gökhan is planning an update that will work on 2008 - hopefully in a few weeks.

Wondering if it might not be too hard to do the port myself, I dove in and pointed VS 2008 at the latest source code.

Ouch!

I'm not an expert in the DSL tools stuff (which probably doesn't help), but things do appear have changed a little between 2005 and 2008. I wonder if you're better off creating new projects and copying over the content rather than just doing an upgrade.

Specifically in the ActiveWriter code, it makes use of the IVsDataConnectionsService interface. I'm not sure of the history of this interface, but it does appear that it has disappeared from the 2008 SDK.

Curiously, this interface doesn't even appear documented in the 2005 SDK, and I'm not clear what the appropriate replacement should be.

RikMigrations (database migration)

Roy mentions RikMigrations. I too am quite ignorant about Ruby, though I am certainly aware of how popular it has become.

Something to keep an eye on which might be useful in the future.

Currently we use Red Gate's SQL Compare to do a diff between our test and production databases. SQL Compare then generates the necessary T-SQL to modify the production schema to synchronise it to the same as test.

This works very well for us, so I'm interested as to what benefits a tool like RikMigrations would bring.

Thursday, 22 November 2007

New phone

My new phone was delivered this afternoon.

It is a HTC TyTN II.

It came in a very cool box.

I've unpacked it, and it is now charging.

I'm being very patient.

Visual Studio 2008 RTM - First impressions

I haven't done heaps with 2008 since installing it yesterday.

I did investigate opening one of our existing projects to see if I could create a 2008 solution, while leaving the 2005 solution intact so that other people could still use 2005 to work on the code. I'd read that this is possible.

I struck a problem though - it looks like this strategy doesn't work for web applications projects.

The upgrade wizard modifies the path in the project file to where the web application project msbuild stuff gets included from. This would obviously not work for other team members who didn't also have 2008 installed on their machines.

So it looks like the best plan is to make sure everyone on the team starts using 2008 at the same time.

I also need to confirm that all our various addins and dependencies also continue to work - things like PageMethods and ActiveWriter are good examples.

It is nice to have the IDE start without requiring it to run in Admin mode on Vista though.

.NET Framework Service Packs

Coinciding with the release of Visual Studio 2008, there are new service packs out for .NET 2.0 and 3.0.

If you've installed .NET Framework 3.5, these service packs are already included.

Tuesday, 20 November 2007

Visual Studio 2008 and .NET 3.5

As predicted in the media, they have shipped.

I've managed to get online access to our MSDN Subscription, and am madly downloading bits right now.

Now we can start using all that nice LINQ stuff in VB, not to mention the XML support too - hooray.

Monday, 19 November 2007

Smartphone saga

Did I mention my Smartphone was broken? Oh, yes I did.

Well even though it was still in warranty when I sent it off, unfortunately Optus took a couple of weeks to decide that they don't fix that model anymore. I'm told that the company that does handle repairs wouldn't do it under warranty then...

So finally I rang out our IT department to find out what's going on, and they said that it is fixed. Great. Except that they said what fixed it was upgrading the firmware to the latest version. Hmm, hadn't I already done that a while ago? Skeptical, but not one to argue with the fact that the audio was actually working, I got my phone back last week.

And so it was actually working again..

Until today.

Yep, the same problem again. What I suspect probably happened is that because of the various trips the phone has made, both in our internal mail, and also interstate once (possibly twice), it unstuck the headphone socket. But now there's no audio again, so I'm pretty sure the cause is infact a physical problem, that the audio socket is stuck in the 'plug is still in, so don't allow sound through the external speaker' mode.

Looking ahead, wouldn't one of these be a nice replacement.

One interesting side-note. While my rw6828 was away on its little holiday, I was given a plain old Nokia phone (a 3530?). Nothing remarkable, except that the battery would last for days. We live in a pretty poor reception area, which seems to suck other phone batteries dry in a day, so I was surprised that the Nokia would go for 3-4 days without requiring a recharge.

Sunday, 18 November 2007

Adelaide Geek Dinner

Last night I was honoured to be one of the attendees at the inaugural Adelaide Geek Dinner. A most enjoyable evening was had by all. 

Thanks to Jason Stangroome for organising the evening. The good news is he's planning to do it again in January.

One non .NET thing I did learn - Darren Neimke plays cricket (a bit of a bowler by all accounts)

Friday, 9 November 2007

Using Enum types with NHibernate (implementing IUserType)

NHibernate is a really nice ORM framework, and one of its features is that you can customise the mapping between the business entity's properties and database columns.

For example, you might have a 'GENDER' column in a database table that has the values "M" or "F".

Rather than your entity class have a String or Char property, wouldn't it be nicer to use a custom Enum like this?

EntityObj.Gender = Sex.Male

In order to implement this functionality, there are a few steps that need to be completed.

StringValueAttribute

We need some way of recording the string equivalent for each enum value. One way to do this is to use a custom attribute like this:

Public NotInheritable Class StringValueAttribute
    Inherits System.Attribute
 
    Private _value As String
 
    Public Sub New(ByVal value As String)
        _value = value
    End Sub
 
    Public ReadOnly Property Value() As String
        Get
            Return _value
        End Get
    End Property
End Class

You apply this to each value of the enumerated type:

Public Enum Sex
    <StringValue("M")> Male
    <StringValue("F")> Female
End Enum

TypeConverters

Next, we need a nice way to read those values and convert between the enumerated type and the string value. A custom TypeConverter can do this.

Here's a generic TypeConverter class:

Imports System.ComponentModel
Imports System.Globalization
 
''' <summary>
''' A generic base class for implementing type converters for enumerations. 
''' </summary>
''' <typeparam name="t"></typeparam>
''' <remarks>The Enumerations must use the <see cref="StringValueAttribute">StringValueAttribute</see> attribute 
''' to map the string/character value with each enumerated value.</remarks>
Public Class EnumConverter(Of t As Structure)
    Inherits EnumConverter
 
    Public Sub New()
        MyBase.New(GetType(t))
    End Sub
 
    ''' <summary>
    ''' We can convert from String or Char
    ''' </summary>
    ''' <param name="context"></param>
    ''' <param name="sourceType"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
        If sourceType Is GetType(String) OrElse sourceType Is GetType(Char) Then
            Return True
        Else
            Return False
        End If
        'Return MyBase.CanConvertFrom(context, sourceType)
    End Function
 
    ''' <summary>
    ''' Convert from String and Char
    ''' </summary>
    ''' <param name="context"></param>
    ''' <param name="culture"></param>
    ''' <param name="value"></param>
    ''' <returns></returns>
    ''' <remarks>If it is a comma-separated list, then will combine values</remarks>
    Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
        If TypeOf value Is String OrElse TypeOf value Is Char Then
 
            'Dim x As t
 
            Dim rv As Integer
 
            Dim strValue As String = CStr(value).ToUpper(CultureInfo.CurrentCulture)
 
            For Each ch As String In strValue.Split(","c)
                ch = ch.Trim()
 
                Dim FieldInfos() As System.Reflection.FieldInfo = GetType(t).GetFields(Reflection.BindingFlags.Static Or Reflection.BindingFlags.Public) ' ft.GetType().GetField(value.ToString())
 
                For i As Integer = 0 To FieldInfos.Length - 1
                    Dim fi As Reflection.FieldInfo = FieldInfos(i)
 
                    Dim attributes() As StringValueAttribute = CType(fi.GetCustomAttributes(GetType(StringValueAttribute), False), StringValueAttribute())
 
                    If attributes.Length > 0 AndAlso attributes(0).Value = ch Then
                        Dim newValue As Integer = CInt(System.Enum.Parse(GetType(t), fi.Name))
 
                        rv = rv Or newValue
                    End If
 
                Next
 
            Next
 
            ' a bit messy, but we cast to an object then back to the t type
            Return CType(CType(rv, Object), t)
        Else
            Throw New NotSupportedException()
        End If
    End Function
 
    ''' <summary>
    ''' We can convert to String and Char
    ''' </summary>
    ''' <param name="context"></param>
    ''' <param name="destinationType"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
        If destinationType Is GetType(String) OrElse destinationType Is GetType(Char) Then
            Return True
        Else
            Throw New NotSupportedException()
        End If
 
    End Function
 
    ''' <summary>
    ''' Convert to String or Char
    ''' </summary>
    ''' <param name="context"></param>
    ''' <param name="culture"></param>
    ''' <param name="value"></param>
    ''' <param name="destinationType"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
 
        If value Is Nothing Then
            Throw New ArgumentNullException("value")
        End If
 
        Dim ch As String = String.Empty
        Dim ft As t = CType(value, t)
 
        Dim fi As System.Reflection.FieldInfo = ft.GetType().GetField(value.ToString())
 
        Dim attributes() As StringValueAttribute = CType(fi.GetCustomAttributes(GetType(StringValueAttribute), False), StringValueAttribute())
 
        If attributes.Length > 0 Then
            If destinationType Is GetType(String) Then
                Return attributes(0).Value
            ElseIf destinationType Is GetType(Char) Then
                Return CChar(attributes(0).Value)
            Else
                'Return MyBase.ConvertTo(context, culture, value, destinationType)
                Throw New NotSupportedException()
            End If
 
        Else
            Throw New ArgumentException("StringValue attribute not found for this value")
 
        End If
 
    End Function
 
End Class

You apply this to the enumerated type:

Imports System.ComponentModel
 
<TypeConverter(GetType(EnumConverter(Of Sex)))> _
Public Enum Sex
    <StringValue("M")> Male
    <StringValue("F")> Female
End Enum

Implementing IUserType

You now need a class that implements IUserType. This class is used by NHibernate to do the mapping/conversion to and from the database to the property on the class.

Here's a generic version, that you can then inherit from for your specific type:

Imports NHibernate
Imports NHibernate.UserTypes
 
''' <summary>
''' Generic class for implementing NHibernate's <see cref="IUserType">IUserType</see> for a value type. 
''' </summary>
''' <typeparam name="T">Value type</typeparam>
''' <remarks>The value type's <see cref="System.ComponentModel.TypeConverter">TypeConverter</see> is used for conversion</remarks>
Public MustInherit Class ValueConverterUserType(Of T As Structure)
    Implements IUserType
 
    Protected Converter As ComponentModel.TypeConverter = ComponentModel.TypeDescriptor.GetConverter(GetType(T))
 
    ''' <summary>
    ''' Reconstruct an object from the cacheable representation. 
    ''' </summary>
    ''' <param name="cached"></param>
    ''' <param name="owner"></param>
    ''' <returns></returns>
    ''' <remarks>At the very least this method should perform a deep copy if the type is mutable. (optional operation)</remarks>
    Public Function Assemble(ByVal cached As Object, ByVal owner As Object) As Object Implements NHibernate.UserTypes.IUserType.Assemble
        Return DeepCopy(cached)
    End Function
 
    ''' <summary>
    ''' Return a deep copy of the persistent state, stopping at entities and at collections.
    ''' </summary>
    ''' <param name="value"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function DeepCopy(ByVal value As Object) As Object Implements NHibernate.UserTypes.IUserType.DeepCopy
        Return value
    End Function
 
    ''' <summary>
    ''' Transform the object into its cacheable representation. 
    ''' </summary>
    ''' <param name="value"></param>
    ''' <returns></returns>
    ''' <remarks>At the very least this method should perform a deep copy if the type is mutable. 
    ''' That may not be enough for some implementations, however; for example, associations must be cached as identifier values. (optional operation)</remarks>
    Public Function Disassemble(ByVal value As Object) As Object Implements NHibernate.UserTypes.IUserType.Disassemble
        Return DeepCopy(value)
 
    End Function
 
    ''' <summary>
    ''' Compare two instances of the class mapped by this type for persistent "equality" ie. equality of persistent state
    ''' </summary>
    ''' <param name="x"></param>
    ''' <param name="y"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shadows Function Equals(ByVal x As Object, ByVal y As Object) As Boolean Implements NHibernate.UserTypes.IUserType.Equals
        Return x.Equals(y)
    End Function
 
    ''' <summary>
    ''' Get a hashcode for the instance, consistent with persistence "equality"
    ''' </summary>
    ''' <param name="x"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shadows Function GetHashCode(ByVal x As Object) As Integer Implements NHibernate.UserTypes.IUserType.GetHashCode
        Return x.GetHashCode()
    End Function
 
    ''' <summary>
    ''' Are objects of this type mutable?
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property IsMutable() As Boolean Implements NHibernate.UserTypes.IUserType.IsMutable
        Get
            Return False
        End Get
    End Property
 
    ''' <summary>
    ''' Retrieve an instance of the mapped class from an <see cref="IDataReader">IDataReader</see>.
    ''' </summary>
    ''' <param name="rs">IDataReader</param>
    ''' <param name="names">Column names</param>
    ''' <param name="owner">The containing entity</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function NullSafeGet(ByVal rs As System.Data.IDataReader, ByVal names() As String, ByVal owner As Object) As Object Implements NHibernate.UserTypes.IUserType.NullSafeGet
 
        Dim obj As Object = NHibernateUtil.String.NullSafeGet(rs, names)
 
        Dim s As String = CStr(obj)
 
        Return Converter.ConvertFromString(s)
 
    End Function
 
    ''' <summary>
    ''' Write an instance of the mapped class to a prepared statement.
    ''' </summary>
    ''' <param name="cmd">an IDbCommand</param>
    ''' <param name="value">the object to write</param>
    ''' <param name="index">command parameter index</param>
    ''' <remarks>Implementors should handle possibility of null values.  A multi-column type should be written to parameters starting from index.</remarks>
    Public Sub NullSafeSet(ByVal cmd As System.Data.IDbCommand, ByVal value As Object, ByVal index As Integer) Implements NHibernate.UserTypes.IUserType.NullSafeSet
 
        Dim NativeValue As String = Converter.ConvertToString(value)
 
        NHibernateUtil.String.NullSafeSet(cmd, NativeValue, index)
 
    End Sub
 
    ''' <summary>
    ''' During merge, replace the existing (target) value in the entity we are merging to with a new (original) value from the detached entity we are merging. 
    ''' </summary>
    ''' <param name="original">the value from the detached entity being merged</param>
    ''' <param name="target">the value in the managed entity</param>
    ''' <param name="owner">the managed entity</param>
    ''' <returns>the value to be merged</returns>
    ''' <remarks>For immutable objects, or null values, it is safe to simply return the first parameter. 
    ''' For mutable objects, it is safe to return a copy of the first parameter. 
    ''' For objects with component values, it might make sense to recursively replace component values.</remarks>
    Public Function Replace(ByVal original As Object, ByVal target As Object, ByVal owner As Object) As Object Implements NHibernate.UserTypes.IUserType.Replace
        Return original
 
    End Function
 
    ''' <summary>
    ''' The type returned by NullSafeGet()
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ReturnedType() As System.Type Implements NHibernate.UserTypes.IUserType.ReturnedType
        Get
            Return GetType(T)
        End Get
    End Property
 
    ''' <summary>
    ''' The SQL types for the columns mapped by this type.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public MustOverride ReadOnly Property SqlTypes() As NHibernate.SqlTypes.SqlType() Implements NHibernate.UserTypes.IUserType.SqlTypes
 
End Class

Note that you'll need to override the SqlTypes() property. This tells NHibernate the type of the database column. So in the following example, as the table column is probably a char(1), we use GetString(1).

Imports NHibernate.SqlTypes
 
Public Class SexTypeUserType
    Inherits ValueConverterUserType(Of Sex)
 
    Public Overrides ReadOnly Property SqlTypes() As NHibernate.SqlTypes.SqlType()
        Get
            Dim a() As SqlType = {SqlTypeFactory.GetString(1)}
 
            Return a
        End Get
    End Property
End Class

Mapping file

Now in the hbm.xml mapping file, you tell NHibernate to use your IUserType class:

    <property name="Gender" access="property" type="MyNameSpace.SexTypeUserType,MyAssemblyName">
      <column name="PERSON_GENDER" not-null="true" sql-type="char(1)" />
    </property>

ActiveWriter users.

If you use ActiveWriter to generate your classes and hbm.xml mapping files, please be aware that it is currently unable to resolve your custom user type. Hopefully this will be resolved in future releases.

The workaround for now is to generate with ActiveWriter, then hand-edit the appropriate hbm and code files. Just remember if you regenerate, you'll have to reapply your edits!

Thursday, 8 November 2007

Windows Live

There's a new version of Windows Live Messenger - v8.5.1302.1018 and the RTM of Windows Live Writer.

Get them both from the one installer.

I'm using Live Writer to create most of my blog posts now - it's a really nice editor, and you can tell it to generate XHTML (go to your weblog settings, look in the Advanced section and click XHTML).

Monday, 5 November 2007

Microsoft Visual Basic Compiler has stopped working

It would be me that happens to make the Visual Basic compiler crash!

As I've mentioned in the Rhino Mocks group, the crash appears to be caused by an interesting combination of a generic interface, another interface that inherits from the generic one, and a mock object that implements that interface. When you then try to use the new void method handling code (a new feature of Rhino Mocks 3.3) the compiler barfs (that's a compiler technical term).

I've attached a simple project to the MS bug report that reproduces the crash. Please contact me if you are interested in the code.

The workaround is to revert to the pre-3.3 way of handing void method expectations:

eg. instead of this:

Using Mocks.Record()
 
 Expect.Call(MockedObject.Subroutine)
 
End Using

Do this:

Using Mocks.Record()
 
 MockedObject.Subroutine
 
End Using

Fix it Pat (part 2)

Photo of David from Sunday Mail As we arrived at Church on Sunday, I was greeted by a number of people saying, "We saw you in the paper today!"

So while I didn't get a mention from Thursday's interview on the day, apparently my mug is in the paper edition, along with along with some of my comments (down under the Belair sub-heading).

To be fair to the Sunday Mail, they are being pretty open about the fact that they were the ones handing out the badges, and I think I've been quoted accurately, which is reassuring.

(Photo from Sunday Mail, 4th November 2007)

Friday, 2 November 2007

Fix it Pat

Today at Blackwood Train station, I was interviewed by a Sunday Mail journalist.

Apparently they are running a campaign to encourage more investment in public transport, particularly after the problems the trains had yesterday with the computer system failing completely.

I didn't make the copy in the final article - oh well.

Owen Vinall, 56, of Coromandel Valley and Susan Lewis, 49, of Blackwood ride the Belair train this morning. Picture: JENNIE GROOM

Looks like I will need to get Owen's autograph after all (he's the guy on the left). I was sitting just out of shot behind the guy standing up near the door on the right :-)

Sunday, 28 October 2007

Building Rhino Tools from the trunk

I read about the release of Binsor 2.0, and thought that might be really handy for a current project. The problem is that you need to compile it yourself.

This was causing me a few headaches and one late night after a countless false starts I gave up and posted to the Rhino Tools Dev group.

Prerequisites

You will need the command-line SVN tools (even if you already have TortoiseSVN).

Directory layout

I've used the following directory layout (with SVN repository paths):

I then saved RhinoTools-Trunk\BuildFromTrunk-Config.build.sample to BuildFromTrunk-Config.build and edited it like this:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Update-All;Build-All;Copy-To-Artifact-Dir" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <!-- Configuration -->
 <PropertyGroup>
 <Configuration>debug</Configuration>
 <Enable-Tests>false</Enable-Tests>
 
 <!-- User Settings -->
 <Dest-Dir></Dest-Dir>
 <Dest-Lib-Dir>$(Dest-Dir)\lib\$(Configuration)</Dest-Lib-Dir>
 <Dest-Tool-Dir>$(Dest-Dir)\tools</Dest-Tool-Dir>
 
 <Tools-Dir>$(MSBuildProjectDirectory)\SharedLibs\Tools</Tools-Dir>
 <NH-Dir>$(MSBuildProjectDirectory)\..\NHibernate-Trunk\nhibernate</NH-Dir>
 <Castle-Dir>$(MSBuildProjectDirectory)\..\Castle-Trunk</Castle-Dir>
 <Rhino-Dir>$(MSBuildProjectDirectory)</Rhino-Dir>
 <Artifact-Dir>$(MSBuildProjectDirectory)\Trunk-Artifacts</Artifact-Dir>
 
 <MSBuildCommunityTasksPath>$(Tools-Dir)</MSBuildCommunityTasksPath>
 <Svn-Dir>$(Tools-Dir)\Subversion</Svn-Dir>
 </PropertyGroup>
</Project>

The final thing that caused me grief was some missing edits to RhinoTools-Trunk\rhino-commons\Rhino.Commons\Rhino.Commons.csproj. Hopefully this will be patched soon, but I had to make the following hand-edits to the csproj file:

Index: Rhino.Commons.csproj =================================================================== --- Rhino.Commons.csproj (revision 908) +++ Rhino.Commons.csproj (working copy) @@ -158,7 +158,9 @@ <Compile Include="Binsor\Extensions\StartableExtension.cs" /> <Compile Include="Binsor\Macros\AbstractBinsorMacro.cs" /> <Compile Include="Binsor\Macros\BaseBinsorExtensionMacro.cs" /> - <Compile Include="Binsor\Macros\BaseNamedBinsorMacro.cs" /> + <Compile Include="Binsor\Macros\BaseBinsorToplevelMacro.cs" /> + <Compile Include="Binsor\Macros\BaseConfigurationMacro.cs" /> + <Compile Include="Binsor\Macros\ParametersMacro.cs" /> <Compile Include="Binsor\Macros\ComponentMacro.cs" /> <Compile Include="Binsor\Macros\ComponentMethodVisitor.cs" /> <Compile Include="Binsor\Macros\ConfigurationMacro.cs" />

I then opened a Visual Studio 2005 Command Prompt ("Run as Administrator" on Vista), and entered:

msbuild BuildFromTrunk.build /t:Build-All

3 minutes and 44 odd seconds later, Rhino.Commons.dll appeared.

One thing to note, this is built of the trunks of NHibernate and Castle Project code, so those bits may or may not be as stable as the most recent public releases.

Friday, 19 October 2007

G3 on the way

It is with great excitement that I can now announce that we're expecting our third child, due in May next year.

Not specifically "one for the country", but at least with our extension almost finished we should have enough room for everyone!

Strongly-typed primary key identifiers

Often the primary keys for tables are integer types. One problem can occur when you accidentally use the OrderID when you really meant to use CustomerID. This bug can happen inside the database and it can also arise in your code data layer.

One solution for this at the code level is to create custom types (eg. OrderIdentifier and CustomerIdentifier) so that the compiler will throw an error if you try and assign or compare different types.

The down-side to this is that because Integers are value types, you can't just inherit from the Integer class. Instead you need to store the actual integer value inside the class and expose it through a property.

eg.

Public Class OrderIdentifier
 
 Private _value As Integer
 Public Property Value() As Integer
 Get
 Return _value
 End Get
 Set(ByVal Value As Integer)
 _value = Value
 End Set
 End Property
 
 Public Sub New()
 End Sub
 
 Public Sub New(ByVal value As Integer)
 Me.Value = value
 End Sub
 
End Class

I've also noticed that NHibernate doesn't like having custom types for primary keys, as it tries to use the System.Type.IsAssignableFrom method to see if it can convert an Integer to the OrderIdentifier object (which fails).

I'm not sure that there's a workaround for that, as I think it would require a class to inherit from the Int32 structure, which isn't possible.

Wednesday, 17 October 2007

ActiveWriter for NHibernate

Like NHibernateAddin, ActiveWriter is an add-in for Visual Studio 2005.

It leverages the DSL functionality to allow you to model classes and relationships and it generates the NHibernate mapping and class files (in VB or C#).

I think this has the potential to meet all my requirements for doing the hard work of class and mapping generation.

Gökhan (the main developer) has done a great job, though his focus is more on C#, so I've submitted some bugs (and worked on some patches) to improve the VB side of things.

One thing that is missing at the moment, is that if you drop tables onto the design surface one at a time, they don't automatically add relations with existing classes. I did discover yesterday that if you drop multiple tables at the same time, it does figure out the relations and add them in.

One of my patches adds support to clean up the class property names. If your database naming strategy means that your column names have some kind of common prefix, you probably don't want that in the property name.

For example, given the following database table:

CREATE TABLE [dbo].[TEST_CONTENT]
( 
 [TC_ID] [int] IDENTITY(1,1) NOT NULL, 
 [TC_TEST] [int] NOT NULL, 
 [TC_CONTENT] [int] NOT NULL
) 
If you set the model's "Property Name Filter Expression" property to the regular expression ^\w+?_ it will then remove the text up to the first underscore for each property name, so that you end up with a class like this:

TestContent Class Diagram

Future improvements

  • Add relations when dropping additional tables
  • Support for custom types for properties
  • Auto-arrange model layout

Wednesday, 3 October 2007

bit columns

I was looking at some T-SQL that RJ had written and noticed he was using 'True' and 'False' when dealing with bit data types.

I've always used 1 and 0, but I think 'True' and 'False' is more obvious, and I think I'll start using it from now on. 

Sure enough, the SQL 2005 Books Online entry for bit (Transact-SQL) mentions this:

The string values TRUE and FALSE can be converted to bit values: TRUE is converted to 1 and FALSE is converted to 0.

Tuesday, 2 October 2007

Monday, 1 October 2007

Mounting ISO files from the command-line

With some recent birthdays, we've acquired some computer games for the kids to play.

The titles include:

  • Arthur's Wilderness Rescue
  • Arthur's Sand Castle Content
  • Arthur's Pet Chase
  • Discovering Dinosaurs
  • Phonics 1 for beginners

Even though these games are quite engaging and the kids really seem to like them, it's amazing at how clumsy some of these applications are to get installed in a 'safe' way.

I install them myself, and then set up shortcuts so that the kids can log in using their username. I am an admin on our home pc, but the other accounts are just regular users.

The games expect to run as Administrator (eg. the "Arthur" titles try and write the saved games back to the Program File installed directory (instead of in the user's profile). As a consequence, I had to fire up cacls.exe to grant Everyone permission to write to this folder.

They also seem to assume you have a VGA monitor - when they try and change the resolution it doesn't always look that good on a LCD display.

They also often require the CD to be in the CD-ROM drive. I find this particularly annoying. The installation should allow me to install everything from the drive onto the hard disk so you don't need to be swapping CD's.

I'm trying to work around this by generating an ISO image of the CD using ISO Recorder, and then mounting and unmounting the image when you want to run the game.

I found FileDisk - a Windows driver and command-line tool to mount and unmount ISO files. I then created a small batch file that I thought should do the job:

@echo off

REM mount cd
"C:\bin\filedisk.exe" /mount 1 "c:\Install\ISO\ArthurWildernessRescue.iso" /cd e:

cd "C:\Program Files\The Learning Company\Arthur's Wilderness Rescue\Launcher"

REM run
"C:\Program Files\The Learning Company\Arthur's Wilderness Rescue\Launcher\TLCLauncher.exe"

REM unmount
"C:\bin\filedisk.exe" /umount e:

I then tried this out with one of the non-admin logins, but sadly it fails with an "access denied" error. It appears that filedisk is using one of the Win32 API calls assuming the current user is an Administrator too.

Filedisk does include source code, so it might be possible to fix this, but I'm guessing it might take a bit of time to figure it out.

Taking "Arthur's Wilderness Rescue" as an example, I looked closer at what files it had left on the CD, and it didn't appear to be many at all!

I located a file "salstartup.xml" and noticed that it was pointing to a file on the CD, so I copied this file onto the hard disk and updated the XML file and it appears that it all works now without the CD being in the drive.

Tuesday, 25 September 2007

DisplayFusion

If you like the multi-monitor desktop wallpaper support provided by UltraMon but can't afford USD39.95, you could either write your own, or you could check out Binary Fortress Software's DisplayFusion.

I trialled UltraMon for 30 days, and I really like the extended task bar that they provide too.

Monday, 24 September 2007

Marking deleted rows in a database table

In one of our applications, there is a requirement that data not be permanently deleted (or at least not initially).

The original way I implemented this was to add a DELETED bit column to each table, and if that column was set to 1 (true), then I would treat that row as if it didn't exist.

This is achieved through ensuring access is via a View that excluded those rows, stored procedures, and also adding an INSTEAD OF DELETE trigger that intercepted regular DELETEs and converted them into UPDATEs to flip the column bit.

That works pretty well, but I'm trying to weigh it up against the other approach I can think of, which is to have a separate table that only holds the deleted rows.

eg. if the original table was FRED, then the other table could be named DELETED_FRED (just to be clear what it was for!).

I'd guess you could still use a trigger to catch any deletes to table FRED, and use them to copy the deleted row over to DELETED_FRED.

I suspect that the latter approach probably makes it easier to use some kind of ORM framework, as you don't have to be careful about avoiding the deleted rows.

The advantage of the former though is that you maintain your referential integrity for all of your data. Because of the relationships of other tables besides FRED, I don't think I could do this with the DELETED_* tables.

I'm curious as to what other pros and cons are of these approaches, and whether there is a formal name for this kind of thing?

There are some resources I have found:

Thursday, 20 September 2007

Installing Roxio Creator Plus on Vista

Up until yesterday, I've resisted installing the OEM version of Roxio that came bundled with my Dell Optiplex GX620 workstation, as I found the combination of the inbuilt CD/DVD burning along with ISO Recorder handled all my requirements.

I was putting together a backup of my Documents and latest PST file, and there must be something funny about the files, but both the latter solutions croaked on them.

The problem with installing Roxio is that the installer blows up with an error saying you must be an administrator to install the product.

In desperation, I followed these steps to enable the builtin Administrator account (it is disabled by default on a clean install of Vista). I did have to update the local policy too.

I then logged in as the Administrator account (using runas didn't work), and the install proceeded happily.

The Roxio media has a date of 2005, and the "About this Software" dialog lists the following versions:

Module Name: Roxio Creator Plus - Dell Edition
Build: 2.4.32a
Serial Number: SC-204B28D
Copyright: Sonic Solutions 2005

Module Name: PX Engine
Build: 2.2.54a, 68
Copyright: Sonic Solutions 2004

Module Name: Roxio RecordNow Audio
Version: 2.0.4
Build: 204B28C, RCP
Serial Number: CQDKR2PD6YT5KBYHS
Copyright: Sonic Solutions 2004

Module Name: Home
Copyright: Sonic Solutions 2004

Module Name: Roxio RecordNow Data
Version: 2.0.4
Build: 204B37C, RCP
Serial Number: CEEWK9329SNNFA84H
Copyright: Sonic Solutions 2004

Module Name: Launch Backup
Build: 204B37C, RCP
Copyright: Sonic Solutions 2004

Module Name: Roxio RecordNow Copy
Version: 2.0.4
Build: 204B28D, RCP
Serial Number: CUY8FVJQ32XQJBRJB
Copyright: Sonic Solutions 2004

Even though it is a few years old, the good news is that it was able to burn the files successfully.

I also tried running the "Check for Updates.." option, but either there are no updates or the updater can't find it's way through our proxy here at work.

Replacement iPAQ

As I mentioned previously, my iPAQ has been sent off to get its audio fixed.

P9200060I thought I might borrow this from my daughter as a temporary replacement.

It doesn't have any telephony functionality, but you can write notes on it, and most importantly play a version of "Pop the Balloons" (aka the Balloon Breaker game).

Battery life is very good too :-)

Wednesday, 19 September 2007

CSSVista - Edit your CSS code live on Internet Explorer and Firefox

Mike mentions CSSVista in today's Daily Grind.

It lets you edit CSS and see the results live in embedded IE and Firefox browser windows.