Saturday, March 06, 2010

I was working with XML serialization of objects recently and was using the good ol’ DataContractSerializer again.

One thing that I bumped into almost immediately is that the XML that it spits out isn’t exactly the neatest, tidiest of XML possible, to say the least.

So I set out on a little odyssey to see exactly how nice and clean I could make it.

(EDIT: I’ve added more information about how the Name property of the Field object is being serialized twice, which is another big reason for customizing the serialization here, and for specialized dictionary serialization in general).

First, the objects to serialize. I’ve constructed a very rudimentary object hierarchy that still illustrates the problem well.

In this case, I have a List of Record objects, called a Records list. Each Record object is a dictionary of Field objects. And each Field object contains two properties, Name and Value. The code for these (and a little extra code to make populating them easy) is as follows.

Public Class Records
    Inherits List(Of Record)


    Public Sub New()
        '---- default constructor
    End Sub

End Class


Public Class Record
    Inherits Dictionary(Of String, Field)


    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal ParamArray Fields() As Field)
        For Each f In Fields
            Me.Add(f.Name, f)
        Next
    End Sub
End Class


Public Class Field

    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal Name As String, ByVal Value As String)
        Me.Name = Name
        Me.Value = Value
    End Sub


    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Private _Name As String



    Public Property Value() As String
        Get
            Return _Value
        End Get
        Set(ByVal value As String)
            _Value = value
        End Set
    End Property
    Private _Value As String

End Class

Yes, I realize there are DataTables, KeyValuePair objects, etc that could do this, but that’s not the point, so just bear with me\:\-\) .

To populate a Records object, you might have code that looks like this:

Dim Recs = New Records
Recs.Add(New Record(New Field("Name", "Darin"), New Field("City", "Arlington")))
Recs.Add(New Record(New Field("Name", "Gillian"), New Field("City", "Ft Worth")))
Recs.Add(New Record(New Field("Name", "Laura"), New Field("City", "Dallas")))

Ok, so far so good.

Now, lets serialize that with a simple serialization function using the DataContractSerializer:

    ''' <summary>
    ''' Serializes the data contract to a string (XML)
    ''' </summary>
    Public Function Serialize(Of T As Class)(ByVal SerializeWhat As T) As String
        Dim stream = New System.IO.StringWriter
        Dim writer = System.Xml.XmlWriter.Create(stream)

        Dim serializer = New System.Runtime.Serialization.DataContractSerializer(GetType(T))
        serializer.WriteObject(writer, SerializeWhat)
        writer.Flush()

        Return stream.ToString
    End Function

In the test application, I put together, I dump the resulting XML to a text box. Yikes!

image

So, what’re the problems here? \:\-\)

  1. You’ve got that “http://www.w3.org/2001/XMLSchema-instance” namespace attribute amongst other
  2. lots of random letters
  3. no indenting
  4. You can’t really tell it from this shot, but the Record dictionary is serializing the name property twice, because I’m using it as the Key for the dictionary, but it’s also a property of the objects in the dictionary.

All this noise might be fine for computer to computer communication, but it’s pretty tough on human eyes\:\-\) .

Ok, first thing to do is indent:

    ''' <summary>
    ''' Serializes the data contract to a string (XML)
    ''' </summary>
    Public Function Serialize(Of T As Class)(ByVal SerializeWhat As T) As String
        Dim stream = New System.IO.StringWriter
        Dim xmlsettings = New Xml.XmlWriterSettings
        xmlsettings.Indent = True
        Dim writer = System.Xml.XmlWriter.Create(stream, xmlsettings)

        Dim serializer = New System.Runtime.Serialization.DataContractSerializer(GetType(T))
        serializer.WriteObject(writer, SerializeWhat)
        writer.Flush()

        Return stream.ToString
    End Function

Notice that I added the use of the XMLWriterSettings object. This allows me to set the Indent property, and things are much more readable.

image

But that’s still a far cry from nice, simple, tidy XML. Notice all the “ArrayofArrayOf blah blah” names, and the randomized letter sequences? Plus, it’s much more obvious how the NAME jproperty is being serialized twice now. Yuck! Surely, we can do better than this!

Cleaning Up the Single Entity Field Object

The DataContractSerializer certainly works easily enough to serialize the Field object, but unfortunately, it decorates the serialized elements with a load of really nasty looking and completely unnecessary cruft.

My first thought was to simply decorate the class with <DataContract> attributes:

<DataContract(Name:="Field", Namespace:="")> _
Public Class Field

    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal Name As String, ByVal Value As String)
        Me.Name = Name
        Me.Value = Value
    End Sub

    <DataMember()> _
    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Private _Name As String



    <DataMember()> _
    Public Property Value() As String
        Get
            Return _Value
        End Get
        Set(ByVal value As String)
            _Value = value
        End Set
    End Property
    Private _Value As String

End Class

But this yields:

image

So we have several problems:

  • Each field is rendered into a Value element of the Record’s field collection
  • The Key of the Record collection duplicates the Name of the individual Field objects
  • and we still have a noxious xmlns=”” attribute being rendered.

Unfortunately, this is where the DataContractSerializer’s simplicity is it’s downfall. There’s just no way to customize this any further, using ONLY the DataContractSerializer.

However, we can implement IXMLSerializable on our Field object to customize its serialization. All I need to do is remove the DataContract attribute, and add a simple implementation of IXMLSerializable to the class:

Public Class Field
    Implements System.Xml.Serialization.IXmlSerializable


    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal Name As String, ByVal Value As String)
        Me.Name = Name
        Me.Value = Value
    End Sub

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Private _Name As String


    Public Property Value() As String
        Get
            Return _Value
        End Get
        Set(ByVal value As String)
            _Value = value
        End Set
    End Property
    Private _Value As String


    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function


    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml

    End Sub


    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        writer.WriteElementString("Name", Me.Name)
        writer.WriteElementString("Value", Me.Value)
    End Sub
End Class

And that yields a serialization of:

image

Definitely better, but still not great.

Cleaning up a Generic Dictionary’s Serialization

The problem now is with the Record dictionary.

Public Class Record
    Inherits Dictionary(Of String, Field)
    Implements System.Xml.Serialization.IXmlSerializable


    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal ParamArray Fields() As Field)
        For Each f In Fields
            Me.Add(f.Name, f)
        Next
    End Sub

    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml

    End Sub

    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        For Each f In Me.Values
            DirectCast(f, System.Xml.Serialization.IXmlSerializable).WriteXml(writer)
        Next
    End Sub
End Class

Adding an IXMLSerializable implementation to it as well yields the following XML:

image

Definitely much better! Especially notice that we’ve gotten rid of the duplicated “Name” key. It was duplicated before because we used the Name element of the Field object as the Key for the Record dictionary. This be play an important part in deserializing the Record’s dictionary of Field objects later.

Cleaning up the List of Records

Finally, the only thing really left to do is clean up how the generic list of Record objects is serialized.

But once again, the only way to alter the serialization is to implement IXMLSerializable on the class.

<Xml.Serialization.XmlRoot(Namespace:="")> _
Public Class Records
    Inherits List(Of Record)
    Implements System.Xml.Serialization.IXmlSerializable


    Public Sub New()
        '---- default constructor
    End Sub

    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml

    End Sub

    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        For Each r In Me
            DirectCast(r, System.Xml.Serialization.IXmlSerializable).WriteXml(writer)
        Next
    End Sub
End Class

Notice that I’ve implemented IXMLSerializable, but I also added the XmlRoot attribute with a blank Namespace parameter. This completely clears the Namespace declaration from the resulting output, which now looks like this:

image

And that is just about as clean as your going to get!

But That’s Not all there is To It

Unfortunately, it’s not quite this simple. The thing is, you very well may want to serialize each object independently, not just serialize the Records collection. Doing that as we have things defined right now won’t work. The Start and End elements won’t be generated in the XML properly.

Instead, we need to add XmlRoot attributes to all three classes, and adjust where the WriteStartElement and WriteEndElement calls are made. So we end up with this:

<Xml.Serialization.XmlRoot(Namespace:="")> _ Public Class Records Inherits List(Of Record) Implements System.Xml.Serialization.IXmlSerializable Public Sub New() '---- default constructor End Sub Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema Return Nothing End Function Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml End Sub Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml For Each r In Me writer.WriteStartElement("Record") DirectCast(r, System.Xml.Serialization.IXmlSerializable).WriteXml(writer) writer.WriteEndElement() Next End Sub End Class <Xml.Serialization.XmlRoot(ElementName:="Record", Namespace:="")> _ Public Class Record Inherits Dictionary(Of String, Field) Implements System.Xml.Serialization.IXmlSerializable Public Sub New() '---- default constructor End Sub Public Sub New(ByVal ParamArray Fields() As Field) For Each f In Fields Me.Add(f.Name, f) Next End Sub Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema Return Nothing End Function Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml End Sub Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml For Each f In Me.Values writer.WriteStartElement("Field") DirectCast(f, System.Xml.Serialization.IXmlSerializable).WriteXml(writer) writer.WriteEndElement() Next End Sub End Class <Xml.Serialization.XmlRoot(ElementName:="Field", Namespace:="")> _ Public Class Field Implements System.Xml.Serialization.IXmlSerializable Public Sub New() '---- default constructor End Sub Public Sub New(ByVal Name As String, ByVal Value As String) Me.Name = Name Me.Value = Value End Sub Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Private _Name As String Public Property Value() As String Get Return _Value End Get Set(ByVal value As String) _Value = value End Set End Property Private _Value As String Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema Return Nothing End Function Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml End Sub Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml writer.WriteElementString("Name", Me.Name) writer.WriteElementString("Value", Me.Value) End Sub End Class

 

 

And Finally, Deserialization

Of course, all this would be for nought if we couldn’t actually deserialize the xml we’ve just spent all this effort to clean up.

Turns out that deserialization is pretty straightforward. I just needed to add code to the ReadXml member of the implemented IXMLSerializable interface. The full code for my testing form is below. Be sure to add a reference to System.Runtime.Serialization, though, or you’ll have type not defined errors.

Public Class frmSample

    Private Sub btnTest_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnTest.Click

        '---- populate the objects
        Dim Recs = New Records
        Recs.Add(New Record(New Field("Name", "Darin"), New Field("City", "Arlington")))
        Recs.Add(New Record(New Field("Name", "Gillian"), New Field("City", "Ft Worth")))
        Recs.Add(New Record(New Field("Name", "Laura"), New Field("City", "Dallas")))

        Dim t As String
        t = Serialize(Of Field)(Recs(0).Values(0))
        Dim fld = Deserialize(Of Field)(t)
        Debug.Print(fld.Name)
        Debug.Print(fld.Value)
        Debug.Print("--------------")

        t = Serialize(Of Record)(Recs(0))
        Dim rec = Deserialize(Of Record)(t)
        Debug.Print(rec.Values.Count)
        Debug.Print("--------------")

        t = Serialize(Of Records)(Recs)
        tbxOutput.Text = t

        Dim recs2 = Deserialize(Of Records)(t)
        Debug.Print(recs2.Count)
    End Sub
End Class


<Xml.Serialization.XmlRoot(Namespace:="")> _
Public Class Records
    Inherits List(Of Record)
    Implements System.Xml.Serialization.IXmlSerializable


    Public Sub New()
        '---- default constructor
    End Sub

    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
        reader.MoveToContent()
        reader.ReadStartElement("Records")
        reader.MoveToContent()
        Do While reader.NodeType <> Xml.XmlNodeType.EndElement
            Dim Rec = New Record
            DirectCast(Rec, System.Xml.Serialization.IXmlSerializable).ReadXml(reader)
            Me.Add(Rec)
            reader.MoveToContent()
        Loop
        reader.ReadEndElement()
    End Sub

    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        For Each r In Me
            writer.WriteStartElement("Record")
            DirectCast(r, System.Xml.Serialization.IXmlSerializable).WriteXml(writer)
            writer.WriteEndElement()
        Next
    End Sub
End Class


<Xml.Serialization.XmlRoot(ElementName:="Record", Namespace:="")> _
Public Class Record
    Inherits Dictionary(Of String, Field)
    Implements System.Xml.Serialization.IXmlSerializable


    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal ParamArray Fields() As Field)
        For Each f In Fields
            Me.Add(f.Name, f)
        Next
    End Sub

    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
        reader.MoveToContent()
        reader.ReadStartElement("Record")
        reader.MoveToContent()
        Do While reader.NodeType <> Xml.XmlNodeType.EndElement
            Dim fld = New Field
            DirectCast(fld, System.Xml.Serialization.IXmlSerializable).ReadXml(reader)
            Me.Add(fld.Name, fld)
            reader.MoveToContent()
        Loop
        reader.ReadEndElement()
    End Sub

    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        For Each f In Me.Values
            writer.WriteStartElement("Field")
            DirectCast(f, System.Xml.Serialization.IXmlSerializable).WriteXml(writer)
            writer.WriteEndElement()
        Next
    End Sub
End Class


<Xml.Serialization.XmlRoot(ElementName:="Field", Namespace:="")> _
Public Class Field
    Implements System.Xml.Serialization.IXmlSerializable


    Public Sub New()
        '---- default constructor
    End Sub


    Public Sub New(ByVal Name As String, ByVal Value As String)
        Me.Name = Name
        Me.Value = Value
    End Sub

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Private _Name As String


    Public Property Value() As String
        Get
            Return _Value
        End Get
        Set(ByVal value As String)
            _Value = value
        End Set
    End Property
    Private _Value As String


    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function


    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
        reader.MoveToContent()
        reader.ReadStartElement("Field")
        reader.MoveToContent()
        If reader.Name = "Name" Then Me.Name = reader.ReadElementContentAsString
        reader.MoveToContent()
        If reader.Name = "Value" Then Me.Value = reader.ReadElementContentAsString
        reader.MoveToContent()
        reader.ReadEndElement()
    End Sub


    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        writer.WriteElementString("Name", Me.Name)
        writer.WriteElementString("Value", Me.Value)
    End Sub
End Class



Public Module Serialize
    ''' <summary>
    ''' Serializes the data contract to a string (XML)
    ''' </summary>
    Public Function Serialize(Of T As Class)(ByVal SerializeWhat As T) As String
        Dim stream = New System.IO.StringWriter
        Dim xmlsettings = New Xml.XmlWriterSettings
        xmlsettings.Indent = True
        Dim writer = System.Xml.XmlWriter.Create(stream, xmlsettings)

        Dim serializer = New System.Runtime.Serialization.DataContractSerializer(GetType(T))
        serializer.WriteObject(writer, SerializeWhat)
        writer.Flush()

        Return stream.ToString
    End Function


    ''' <summary>
    ''' Deserializes the data contract from xml.
    ''' </summary>
    Public Function Deserialize(Of T As Class)(ByVal xml As String) As T
        Using stream As New MemoryStream(UnicodeEncoding.Unicode.GetBytes(xml))
            Return DeserializeFromStream(Of T)(stream)
        End Using
    End Function


    ''' <summary>
    ''' Deserializes the data contract from a stream.
    ''' </summary>
    Public Function DeserializeFromStream(Of T As Class)(ByVal stream As Stream) As T
        Dim serializer As New DataContractSerializer(GetType(T))
        Return DirectCast(serializer.ReadObject(stream), T)
    End Function
End Module

Of particular note above is the ReadXML function of the Field object.

It checks the name of the node first and then places the value of the node into the appropriate property of that object. If I didn’t do that, the deserialization process would require the fields in the XML to be in a specific order. This is a minor drawback to the DataContractSerializer that this approach alleviates.

What’s Next?

The one unfortunate aspect of this is that it requires you to implement IXMLSerializable on each object that you want the XML cleaned up for.

Generally speaking, The DataContractSerializer will be perfectly fine for those cases where humans aren’t likely to ever have to see the XML you’re generating. And you get a performance boost for sacrificing that flexibility and “cleanliness”.

But for things like data file imports, custom configuration files, and the like, it may be desirable to  implement custom serialization like this so that your xml files can be almost as easy to read as those old school INI files!

posted on Saturday, March 06, 2010 12:16:15 AM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, November 16, 2007

image I'm guessing that the vast majority of VB apps (VB6, or .NET), have no public interface. After all, VB is the prototyping language, not an actual project development language, right? </sarcasm>

Well, if you do eventually find yourself in need of exposing a public interface from your application, what's the right way to do it?

First, a definition, though.

By public interface, I'm talking about some manner of exposing functionality within your application to the outside world. What you do with your BAS files, and private CLS files, not to mention FRM's, or the all-encompassing  VB.NET *.VB file is your own business. But what the world sees, or, more appropriately, has to deal with, is another matter altogether.

There's a pile o' ways to expose functionality from an app, especially a VB app.

First, a few old school methods....

  • DDE - Yeah, it's still alive. Want proof? Just boot up a copy of Vista, load REGEDIT and search for DDEEXEC in the HKCR hive. But seriously, you don't want to go there.
  • Windows Messages - SendMessage/PostMessage. They're not just for subclassing and peeping toms. DDE and even COM relies on the Window Messaging subsystem to work. Your apps can leverage it too. But really, that's a whole lot of pain for not a lot of gain, so why bother?
  • DLL Entry Points - There are several tools out there that make exposing DLL entry points from a VB application relatively easy to do. In some cases, this is how you have to expose functionality (as in when some other app only makes calls to DLL entry points, and you want that app to talk to yours). But using this technique to expose your own functionality to the world just seems so, well, reagan-omic.

And the more common techniques....

  • COM - The classic VB PUBLIC class. All you gotta do is change your class's interface type to PUBLICNOTCREATABLE, MULTIUSE, or GLOBALMULTIUSE and biff boom pow! Anyone can use your class, right?
  • .NET Assemblies - Same as COM, but shinier

OK, COM (or .NET assemblies) it is (I'm going to consider the two the same for now, what sacrilege!)

So how do you do it?

There's piles of books on the mechanics of the task, so I'm not going there. Just look at Kurata's Doing Objects in Visual Basic 2005 or Dan Appleman's various books on ActiveX techniques for those kinds of details.

What I'm talking about is which interface design is most appropriate.

Kinds of Interfaces

Generally, I've seen these designs gather into 3 camps:

  • The massive wad of hierarchically related objects. This is the Outlook, Word or Excel object models.
  • The single public class containing all the functions you could ever want. Not terribly common; I see it most is 3'rd party utility libraries.
  • And finally, the single class with a single (or at most very limited set of) functions that serve as a "command line" of sorts where you might pass in a parameter that is the "operation" and that additional parameters that are the "arguments for the operation". The GroupWise API is a great example of this.

The Massive Hierarchical Wad

Pros

  • Easiest to program against. Intellisense, separate objects, etc speed working with your model.

Cons

  • Hardest to keep compatible. Even minor changes from one version to the next of your application can render it incompatible with previous versions of your application, causing loads of headaches for your users.

The Single Class

Pros

  • Relatively easy to program against. Intellisense will help. So does consistent naming of all those methods and properties.
  • Works well for relatively minor amounts of exposed functionality.

Cons

  • Generally considered bad practice.
  • Suffers from compatibility headaches much like the Massive Wad.

The Command Line Class

Pros

  • Essentially immune from direct interface compatibility issues, since the lone interface is flexible enough initially to never have to be changed.

Cons

  • More difficult to code against. Intellisense only helps with the command line function itself, not with each of the commands that can be sent.
  • Moves compatibility issues to the user. If new functions are introduced, it's the users responsibility to test for the version of the application and react appropriately.

So, what's the final word?

As usual, there's really no right answer.

Personally, I tend to stay away from the "Single Class", simply because it has all the difficulties associated with the Massive Wad, and none of the immunity of the Command Line Class; the worst of both worlds.

The Command Line Class is most useful when your exposed function points number in the 10-100's. Any more than that, and it starts to become unwieldy, though it's still perfectly workable.

The Massive Wad is the most traditional, most familiar style of public interface to those who've likely used public interfaces of other systems. That's not to say it's the best option, just that's it's the most typical.

How about it? What kinds of public interfaces have you built and put out there? Is there a style I've missed. Maybe something better than any of these?

posted on Friday, November 16, 2007 10:30:08 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Wednesday, November 14, 2007

Here's a little trick I'm not sure many people know about, but that I've used for so long, I'd basically forgotten about it.

I wrote about overriding the ERR object in VB here, and also wrote a far more involved article about it.

But did you know you can also override the MSGBOX function?

Just create a function with the following signature in a BAS module of your choice:

Public Function MsgBox(ByVal Prompt$, Optional ByVal Buttons As VbMsgBoxStyle = vbOKOnly, Optional ByVal Title$ = "", Optional ByVal HelpFile$ = "", Optional ByVal Context As Long = 0) As VbMsgBoxResult

Put the appropriate code in it, and presto, custom msgbox.

Why, you might ask?

  • Personally, I like the option of logging every msgbox that's displayed by my app. Errs are handier, but msgbox's can be useful.
  • In addition, you have the option of calling the MessageBox API call directly yourself, which gives you access to additional flags that you can't use with the VB version alone.
  • You might also decide that the MSGBOX proper just doesn't look good, and craft up a modal, nicely skinned form to stand in its place.
  • And finally, in a particular app I worked on, I needed to call a function BEFORE and a different function AFTER displaying every msgbox. I certainly didn't want to alter every call to MSGBOX and add those extra lines. Overriding it made that a trivial task.

Please let me know if you come up with any good uses for this trick.

posted on Wednesday, November 14, 2007 10:03:47 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Tuesday, August 21, 2007

John Dvorak wrote a very interesting article for PCMag on 8/14, Google Pulls Plug, Everyone Misses Point.

I've been saying web apps were overhyped ever since SalesForce first came out and was getting tons of hype back in the 90's, and that's not because I used to work at a competitor (gotta love the wayback machine).

On the surface, it's a great idea. No installation. No deployment headaches. No massive, multi machine upgrade pains. And with AJAX, and now SilverLight, you can get a user experience approaching a traditional fat client. Plus, with broadband nearing ubiquity, some of the bandwidth issues of the past are no more.

And for many installations, where users can connect to a centralized server within the company's domain, it often is a very smart architecture.

So what's the problem?

Dvorak points to the demise of the Google DTO/DTR program as exactly the problem. Imagine if it was YOUR BUSINESS that was running off the DTO program and Google pulled the plug. "Oh, you won't be able to access your customer records after Aug 8th, but here's a coupon for 2 bucks at Google Checkout you can use for the next 2 months."

I doubt SalesForce is going anywhere anytime soon, and they do have a compelling product. I'm not even picking on them, per se. But the whole idea of putting that kind of trust into an app that's running completely NOT under your control is just a little, eh, bothersome?

In fact, I tend to believe that the absolute BEST thing that could happen for everyone is for a few more biggies, like Google Apps, or SalesForce, or Microsoft MSN Mail, etc, to bite the dust and strand millions of people.

It'd hurt, but people would take a serious look at the implications of relying on ASPs to supply services like that.

There were some big reasons back in the early 80's that the personal computer and the fat client app, as opposed to the mainframe and the dumb terminal, because so popular. Some of them have been forgotten, and unfortunately, it might take a few tough lessons to remind people.

posted on Tuesday, August 21, 2007 4:10:05 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [4] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, August 16, 2007

The web as we know it is in its death throws.

Um, right.

I just read the Guest Opinion in the latest Visual Studio Magazine. It's by Rockford Lhotka, who I have a lot of respect for. But this piece was a little off.

He basically claims that "the web as we know it is finally coming to an end". Why, you may ask?

  • Is it the impending rollout of IPV6?
  • Maybe some new 3d "navigator" (as in the-chess-set-on-the-millenium-falcon 3d, not yet-another-2d-window-into-a-virtual-3d-space 3d)?
  • Or maybe there's a new "brain driven" interface I hadn't heard of?

No.

It's Silverlight. and Atlas.

Sigh.

According to Rockford, AJAX is the "last gasp of a dying technology". He argues that at some point, "either the browser transforms into a full blown programming platform or we find another answer", and that answer, apparently is SilverLight.

Now, don't get me wrong. SilverLight is pretty slick, as long as you don't try to find any examples of actual web apps written in it. You know, the kind that people would use as opposed to just play with for 5 seconds. There's tons of nice animated displays synched to music and pretty graphics of pages flipping, but where's the data driven samples, or the reporting grids, or the company dashboards, etc?

Not only that, but hasn't the web already got a "launch point to load something that is a programming platform". I thought it was called Flash? And flash has certainly not brought the end of the Web nigh. Hell, it's harder and harder to find sites that even bother with it anymore.

And SilverLight is from Microsoft, so you can bet that, even if they initially support a plethora of browsers and systems, eventually, it'll get whittled down to Windows Voyeur (or whatever they'll be calling the new version in 5 years).

Rockford makes a good point about technologies lasting upwards of around 10 years before being replaced by newer, better concepts. Things like COM, Win32, and CORBA. Things like ISA, the serial and parallel port, and ATA IDE. Things like hard drives, mice, qwerty keyboards, text files and relational DBMSs.... Oh wait.

I'd lay money that the next "big thing," the thing that "kills the web as we know it" and ushers in a new era similar to the internet age of the 90's, will not come from a big house like Microsoft, or IBM, or even Xerox. It'll come from some dedicated hacker like Marc Andreeson or Bram Cohen. It'll start off small. Hardly anyone will use it, kinda like Google back in, oh, 1999. And it'll probably have a goofy name, nothing as cleanly futuristic as "SilverLight".

Until then, I'd say the odds are long on HTML, Javascript, Ajax, etc, going poof.

posted on Thursday, August 16, 2007 10:14:23 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, August 10, 2007

I've been delving into ASP.NET programming lately and one thing that immediately struck me as odd is just how many controls end up on an ASP page.

A typical data entry page consists of, generally, lots of labels and textboxes, a few option buttons or check boxes, even fewer combo boxes and list boxes, a grid maybe, and few buttons (buttons, links, whatever they happen to look like).

Granted, depending on the application, you may have a few particular screens that are more complicated (say, a scheduling screen), but by and large, for general data input, that about sums it up.

But when I look at what's involved in ASP.NET to make that happen, it's almost mind boggling.

Of course, you have a label control for a particular field, you have the field itself, but then you have separate validator controls for each kind of validation you might want to perform. Add in tables to get things to line up nicely

To me, it seems much more logical to have one "data input" control, that can handle all (or rather the most common) data entry tasks via properties.

Want a caption above the input box, rather than to the left of it; set a property. Want the text to be validated a alpha numeric, but only including A-F, and it must be > 4 chars, less than 10, blah blah; set a property (or two). What this field to be a choice between True/False; set a property to make it a radio button group, a list, or a drop down combo box.

Ages ago, there was a UI package called SmartUI. Looks like it's now sold by Xceed. No idea whether it's any good now. It was ok back then, although it had it's share of issues. But I always thought the idea was right. One control that could do all sorts of the most common data input type tasks, just by dropping it on a form and setting a few properties.

I completely understand the idea of "modularizing" functionality and the ASP control model definitely does that.

So my question is, is that the best way?

All those controls eventually just render HTML anyway, so is there a penalty for one big control vs lots of little ones rendering that html?

Does it make the page easier for the user to work with?

Do separate little controls make it easier for the developer?

Microsoft used to say that INI files were the absolute best way to store your application settings. Then it became the registry, and INI files were passe. Now, it's back to INI files...albeit with a lot more tag stuff, funny angle brackets and often terrible formatting...oh, right, I mean XML files.

My point is, I'm not one to just accept Microsoft's take on a topic, no matter how much framework they've thrown at it. 

And I'm wondering if their control model might be another one of those reasons to break with the pack.

posted on Friday, August 10, 2007 6:23:08 AM (Central Standard Time, UTC-06:00)   •  # •  Comments [3] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, June 28, 2007

Leave it to MS to come up with the phrase Runtime Agility, but the concept, if I'm understanding it right, is very intriguing.

The post is here, but to summarize, Paul Vick is a lead on the VB team, and is discussing some of the items on the block for VB8.

He off hand mentions this...

"Runtime agility. The ability to compile without a VB runtime, or targeting another VB runtime."

Now, if I'm reading that right, this would seem to be huge. The idea of VB being able to "compile in" the runtime is something I know I've been asking for since VB3. Does it waste a little space? Sure. But it means you can truly have a single EXE "copy and deploy" type scenario.

And does this mean the .NET runtime bits that are necessary will get baked in as well? Too early to tell, I suppose. Still. Something very interesting to watch in the coming months.

posted on Thursday, June 28, 2007 8:14:31 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, June 23, 2007

Here's an interesting read on something coming down the pike with Visual Studio. Basically, it's a customizable shell of VS that would theoretically allow you to build you're own Visual Studio, language and all.

Likely, it'll be on a complexity scale of integrating VBA into your app (anyone remember that 300-armed, greased-up monster squid?), but still, very interesting.

Another interesting read on Extension Methods coming in VB9. I've wondered why something like this hasn't been in VB since the very early days of classes (anyone remember VB4?). Why, oh, why have I never been able to create a method like:

Public Function MyOwnReplace(Self as string, Arg as string) as String Extends String
      Return Blah(Arg)
End Function

and then use it by calling the function method off of any ol' string variable...

Debug.Print MyString.MyOwnReplace(Arg)

Maybe this is a contrived example, but the Dynamic languages concept of extending existing objects is something that just belongs in VB.

 

posted on Saturday, June 23, 2007 9:33:06 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [5] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, June 15, 2007

I've recently read several blogs about how great Windows PowerShell is, so I decided to check it out myself last night.

I didn't get very far before I came across this on the download page:

"The .NET Framework 2.0 is required in order to install Windows PowerShell"

Ok, fair enough.

And then I proceeded to actually check out the download page, where they present no fewer than 20 different download options, including variants for "English" and "localized", x86 and x64, plus Itanium.

First, I know from experience that it would be almost insane to suggest that small development shops try to maintain 20 different downloads of what is essentially the same product. MS can get away with this because they've got lots of money to throw at the problem, but is 20 different downloads of a command shell utility really justified? Ok, some of them are for some MultiLingual UI addon, so the total versions of the utility iself is fewer than that, but not by much.

The bigger issue I have here, though, is that I thought, and MS would surely have everyone believe, that the .NET runtime and the JIT compiler within should automatically be handling the differences between x86, x64 and Itanium. That's the whole point of the JIT, right? The multilanguage versions? I can accept them, with reservations, but the different processor versions? I'm no expert on the PowerShell internals, but if it requires the .NET framework, that would seem to imply that it's managed code, which would imply that the JIT should take care of the differences between platforms. And if it's not managed code, why not? Is it that MS is saying that managed code is good enough for everyone else, but not good enough for them?

Reminds me of the discovery I made ages ago that Outlook communicates with Exchange via RPC, not via DCOM, even though DCOM was being pushed hard by Microsoft at the time. Another case of "good for you, not good enough for us", perhaps?

Oh, and PowerShell looks like a very handy tool for Administrators. I'm sure I'll find some uses for it eventually. But, get-childitems -path env: just to retrieve the current environment vars? There's something to be said for mapping all sorts of various available info like that into a consistent interface, but my stack's too deep as it is right now to pile this syntax on top just yet.

posted on Friday, June 15, 2007 1:07:00 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Wednesday, June 13, 2007

There's an interesting opinion piece by Kathleen Dollard in last month's Visual Studio magazine. The latest issue has another piece by Kathleen that is very similar. There's no link to the latest one, because that issue has just come out (I got mine not 30 minutes ago).

In them, she's essentially saying that the .NET framework, and all the various supporting enterprise frameworks, have become so large that no one can hope to be competent enough in them to be able to intelligently determine what to use, when to use it and when to stay away.

I couldn't agree more. But despite comments in the article that she is "more optimistic today" and that "the revolution has begun", I still come away from the articles sensing a muted dread. Fear that suddenly, skills you possess today and have honed over the years will become worthless. The value of the knowledge you currently have is drying up like last weeks doughnut holes.

And I'm just not down with that.

That fact is, my experiences seem to suggest that the less you know about these high level frameworks, the better off you are. I'm not saying don't get familiar with them. I'm saying that if you spend time to learn the basics of computer functionality, hardware, interfaces, binary, and object oriented principles like encapsulation, polymorphism and inheritance, details of specific frameworks just tend to come out in the wash.

On the other hand, if you spend all your time focusing on a specific framework, when it's no longer de rigeur, you're screwed.

Microsoft has created some great stuff, to be sure, and some of the newest bits out of the pipe certainly seem compelling, WPF probably more so than anything else. But it's also thrown out plenty of red herrings, costing companies untold money before people realized that "the silver bullet of the day" probably wasn't a good idea after all.

Back to the knowledge issue though. When VB1 came out, and only a bit later VB3, everyone exclamed at how great it was because "you didn't have to know the nuts and bolts of the Windows API to get something built for Windows". Which was true, unless you wanted to build an program you could actually do something with.

The fact is, VB was like that first step down into the pool. It lets you get your feet wet and get used to cold water before you dive in headfirst. Plus you get to feel for sure whether there's any water in the pool.

But eventually, you either dive in, or you decide you don't like programming.

VB.Net has always been more like the diving board. You either walk away, or you dive in, and you won't know if there's water in the pool till you feel it or you bash your skull on the gunite.

I think there's a bigger issue here, though. Emphasizing all these layers of Linq, Sharepoint, WPF, WF, WCF, Ajax, and blah blah blah, won't help encourage anyone to dive in, and that's what VB.net needs to be doing. Instead we get, what, the MY classes. Puh..leaze! At least edit and continue finally came back with VS 2005. Where's the genius that decided it'd be a good idea to leave it out in the first VS releases, anyway?

Don't get me wrong. You can create some hardcore stuff with VB.Net, a lot easier than was ever possible in VB. But you get some hardcore baggage coming along for the ride too. And that's unfortunate, because falling CS enrollments may mean a pretty sweet employment future for me, but it bodes some not so sweet prospects for our long term technological advancement as a nation.

And dammit, I want my flying car before I GOTO END !

posted on Wednesday, June 13, 2007 10:46:24 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Monday, June 11, 2007

You are writing all your applications with a Client-Server architecture, right?

Oh, wait, this isn't the late 90's.

You are writing all your applications as 3-Tier assemblies, right?

Oh, wait, this isn't the early 2000's.

You are writing all your applications... Oh, to heck with it.

What's the architecture du jour these days, anyway, n-tier? x-tier? SOA? XYZ-PDQ?

So what's all the hubbub?

I propose something radical. Logically tier your code based on functionality, not locality. If you do that properly, monolithic, Client-Server, 3-tier, n-tier, x-tier, tears for fears, it won't matter.

Why not?

Because if the code is properly organized and logically tiered, you'll be able to easily move it wherever it needs to go to create (and remove) whatever tiers become necessary.

For example, I once worked for a company building a sizable billing package based on SQL Server. We wanted it 3 tier (Presentation, Business Logic, and Data Access), but we also needed it to be:

  1. easy to debug and maintain, and I mean easy, not the "learn how to jump through hoops faster" easy, but really easy. The dev team was small. This was a requirement.
  2. easy to deploy in a demonstration mode. If a prospect couldn't fire up a CD install and in 5 minutes or less be playing with it, it wasn't going to fly.
  3. capable of scale-out deployment to accommodate multiple report generation servers, data analysis servers, etc.

In the end, we designed a server app as the BLL (it actually ran as a service), a database chock full of highly optimized stored procs as the DAL, and a fat client that contained not only the user-facing interface but also the entire server app internally. And the key to all this was that the code for the server was exactly the same code as was in the client. The code was literally shared between projects in our VCS.

When connected to an actual server component, it got all the benefits thereof. But for debugging, it was unbelievably efficient to have both the client and server right there running at one time not only in the same IDE, but actually as part of the same executable. And deployment was a snap because the demo install didn't have to bother with a server installation, it just created the DB and dropped the client on the machine.

If we'd needed to split functionality out farther, it would have been a very straightforward process because internally, everything was already split out.

The bottom line is:

Get the logical tiers right and encapsulate with a vengence.

Note to self: Vengeful Encapsulation. Sweet band name!

posted on Monday, June 11, 2007 5:25:38 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, June 02, 2007

I once wrote an article about error handling in VB6 for Visual Basic Programmer's Journal (Sept 2001, jeez that seems like a long time ago!). It was born out of the need for a comprehensive framework for handling errors in an app I was working on at the time.

Since then, I've expanded the framework several times and it's grown into something quite handy. Once I get it cleaned up a bit for public consumption, I'll make the new and improved version available.

I've since been looking at the .NET error handler framework, and while it's certainly much improved over the VB6 days, it still leaves a lot to be desired.

At the very core of handling errors is the division of error types into two distinct classes:

  • Expected Errors
  • Unexpected Errors

In a perfect world, every possible error is an Expected Error and there are no errors that can occur within your program that the code isn't written to intelligently deal with.

At the other end of the spectrum, is, of course, where most textbook and magazine article sample code lies; where every error is an Unexpected Error that will crash the program.

Unfortunately, real world limitations on time, money, manpower, etc tend to force all code somewhere south of Nirvana.

The .NET fanclub used to decry VB's on error goto as antiquated technology, and trumpeted try catch as the silver bullet of the day. And I suppose:

On Error Goto Catch
Try:
...code to try...
Goto EndTry
Catch:
...code to handle any error in the try block...
EndTry:

is quite different from

Try
...code to try... 
Catch ex as exception 
...code to handle any error in the try block... 
End Try

as long as you're only looking at their checksums\:\-\) .

Sure .NET incorporates a wealth of metadata about the exception. The module, function, line number, etc are all available directly while handling the error. But something's missing.

Oh, yeah, What about those Unexpected Errors?

.NET does have the Exception Handling Application Block and it looks very promising, although it also looks very involved. And do you really want to be able to configure exception handling policies via config files? I mean, beyond indicating the level of logging you want (verbose, medium, low, off), and the email address to send exception report to (unluckysupporttech@mycompany.com), I'm having a hard time coming up with anything else I've ever needed to dynamically configure in my apps for exception handling. But, I won't say it couldn't be useful.

In the end, I suppose the thing that bothers me most about the EHAB, and the whole structured exception movement in general, is that it really does nothing to address the Unexpected Errors in an application. You still have to wrap your entire procedure in a TRY block in order to be sure that you catch any exception raised in that procedure. Otherwise, the exception is simply propagated up the call stack to the first point that is covered by an exception handler. The good news about .NET, though, is that at least, in those cases, you can still retrieve a call stack back to the real source of the exception and log it. With VB6, no such luck without some sort of framework.

My other issue is that Try Catch style exception handling forces the declaration of how you intend on handling errors to the END of the function you're looking at. In this age of declarative programming and metadata-attributed interfaces, that just seems so...yesterday.

Take my above comparison of try catch and on error. What actually happens to the error is stated down at the end of the procedure in both styles. Now. I can understand that approach if you have code dedicated to handling specific errors, but then, those are Expected Errors. For the Unexpected Errors, I'd rather know, declared right up front, how the prodecure intends to handle them.

That was the impetus of my article way back when:

  • Provide a framework that could provide a call stack that VB6 was lacking
  • Provide a means of clearly declaring how the procedure intends on handling Unexpected Errors.

Compare a typical Try Catch structure:

Public Sub Test()
   Try                                          
   .....Lots and lots and lots of code....
   Catch Ex as Exception
   ....how to handle specific exceptions, plus how to handle general exceptions...
   End Try
End Sub

with a prologue style declaration like I use in my error handling framework for VB6:

Public Sub Test()
'############### ERR HANDLING #############
Dim Err As CErr: Set Err = New CErr: On Error GoTo Problem
Problem:
Select Case Err.Handle(EHF_PRESENT or EHF_ALLOWRETRY, MODULE, "Test")
Case EHE_NONE: Case EHE_RETRY: Resume: Case EHE_PASS: Resume Next: Case EHE_RESET: Resume Problem
End Select
'##########################################
   .....Lots and lots and lots of code....
End Sub

The code states right up front how unexpected errors are handled (in this case, EHF_PRESENT or EHF_ALLOWRETRY indicates that the user will be shown a dialog and allowed to retry the operation). There's a bit more verbiage here because of the requirements of VB6, but that's not the point.

Personally, I'd prefer it if every routine in .NET could be (or maybe even had to be) attributed as to how unexpected errors are to be handled. Those that need to show a dialog would do so based on one or more "predefined" dialogs that depended on where in the app the error was thrown.

And for those apps that you never want to show an error dialog, you could instead indicated that any unexpected errors should be logged to the Event Log (or somewhere else) and then press on as best as possible.

Unlike the implications in so many magazine articles, error handling is hard. Proper error handling is like a prostate exam; you know you have to do it, but you'd really rather not bother.

posted on Saturday, June 02, 2007 8:52:21 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, June 01, 2007

I've been hunting down an issue with an InstallShield Installation for almost a week now.

Very strange. I have a new installation that is a BASIC MSI install (meaning it contains no funky InstallScript at all, and is this fully MSI compliant).

It is intended to upgrade several previous installations, one of which is also a BASIC MSI, but the others are all InstallScript MSIs.

When I first started testing the upgrade process, I did the install of the old version, followed by an install of the new version. When I went to ADD REMOVE PROGRAMS, the new was there, but the old version was as well. The upgrade didn't work.

Grrr.

So I start digging. Eventually, I determined that the old version was actually being uninstalled (I paused the install midstream and sure enough, all the old files are one point are completely removed). However, for some reason, the entry in the Add Remove Programs list was not being removed.

More digging.

So I reset my test, installed the old version and at Macrovision's request, I searched the reg and found a key here:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F1C8EEFE-8019-44AB-8AA9-D6F13E3BFAF0}]

that contained all the info about the old installation.

There's a value called NOREMOVE set to 1. Hmm, that sounds suspicious. So I set it to 0 and install my new version.

No joy, the old version is still listed in ARP. However, when I went back to the registry to check that key, it was gone! What the hell!? Ok, the entry in ARP must be coming from somewhere else. A few reg searches later turned up this key:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\InstallShield_{F1C8EEFE-8019-44AB-8AA9-D6F13E3BFAF0}]

Notice that the GUID there is the same as the other GUID, but this one has "InstallShield_" at the front of the key name.

Delete THAT key, and suddenly, the ARP entry is completely gone.

It would appear that upgrading an InstallScript based install with a BASIC MSI install unintentionally leaves behind a key that it shouldn't. Some searching the KBs with more specific keywords turned up exactly that.

An InstallScript based install created two entries in the Uninstall registry key, but one is 'hidden' from the ARP list view.

A Basic MSI install only creates one key. And if you upgrade an InstallScript based installation with a Basic MSI installation, that second key (the visible one) won't get removed properly.

You have to add an entry to the RemoveRegistry table in the MSI file and connect it to a component that will be installed by your new installation.

Didn't table-driven programming constructs die back in '91 with Magic. No, wait. Ack, they're still alive?!

Well, tried it then and hated it, still hate it today. To each his own.

posted on Friday, June 01, 2007 9:19:13 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [7] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

There's an interesting discussion going on at SQL Server Central about applications and database portability.

The question thrown out was "How valuable is portability to your application".

And by portability, they're referring to portability across different database backends. Can your app run on Oracle, SQL Server, MySQL, etc, or do you just lock down to a specific vendor, and sell to your apps strengths and not DB portability?

It's an interesting question and from the responses so far, it would seem that DB portability is a 4 letter word. But I think many of the responses are a little short sighted or limited to the DBA/developer perspective.

There is one comment saying something along the lines that an app this company purchased was DB agnostic and was found to contain no where clauses. Now that may have been an exageration to make a point, but I'd argue that an app like that was poorly architected from the outset. The fact that it performs poorly would seem to have less to do with being DB agnostic and more to do with just poor coding/architecture. My guess is, if you looked past the DB code in that app, you'd find a lot more to dislike as well.

The bottom line in any business is 1) The customer is always right and 2) you have to sell the product you have in order to make the product you want to sell.

Now, as to part 1, I'm not saying you can't educate the customer, but in the end, if they really want Oracle as their backend, there may be some business reasons for that that you can't sell around. And if the IT shop of that customer is centered on Oracle, good luck going in with a SQL Server based app.

I suppose it'd be nice if every shop was an IBM sized house that could hire DBAs specifically to design and support the backends for every reasonable DB, but most shops don't have those kinds of resources.

In small shops, it's all about leveraging code as much as possible.

posted on Friday, June 01, 2007 9:07:57 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, May 25, 2007

Not something that often ends up on a VB developer's plate I suspect, but I had need to do some investigating of Lotus Notes and possible integration/extension development using VB (classic, v6.0, not .NET).

Come to find out, Notes does have COM support as of 5.0.2b, so appearently, you do not have to dive down into ever joyous C++ in order to take a stroll through Notes-ville. Unfortunately, finding actual documentation for that object model online feels like finding your kid's matchbox car that fell out of their pocket in the ball pit at Chuck E Cheese. I'll try to post my results, if any, when I have some.

Now, as to why you'd want to dig into Note, one look here should dissuade all but the foolhardiest of souls. Granted, I know very little of the internals of Notes. From what I understand, internally, it's actually a quite capable system, and has been for years and years. Still, those screenshots speak volumes.

Me? Well, I have a foot and here's a Smith and Wesson. With any luck, the obvious won't happen next.

posted on Friday, May 25, 2007 8:51:09 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Saturday, May 05, 2007

I've played a bit now with the .NET configuration model and while I can certainly see some elements in there to like, it seems to me that it falls short in a number of different ways.

First off, it doesn't really have any concept of "multi-level configuration." In fact, it's basically a single level XML file (granted, you could create a hierarchical organization within that single xml file, but there's other issues with that). .NET does help a bit with the concept of a machine config file, but from what I can tell that's about as far as it goes.

Second, the default behavior is to locate the application config file in the same path as the application itself. That's great if your user is running as administrator, but not so great in more locked-down situations where users do not have full admin rights. In those cases, configuration that's writable needs to be stored somewhere within the user's profile directory structure. This is just going to get worse with Vista, too.

Ok, sounds good but what, you may be asking, is a multi-level configuration scheme, and why would I want one? Almost every app I've ever worked on, short of the trivial utilities I've built, has required this sort of configuration scheme (or at the very least greatly benefited from having it). It almost always follows along this line:

  • Administrator specified User specific configuration (rarely used, the user cannot change this configuration)
  • User specific configuration (that the user can change dynamically)
  • Group specific configuration (typically set by a system administrator or group admin and read only to users)
  • Machine specific configuration (typically set by a system admin, and rarely used, read only to users)
  • Application global configuration (typically set by the system admin, read only to users)
  • OEM defaults (typically set by the app developers to provide default config values, generally readonly by everyone except the developers)

At each level, the system provides for an essentially unlimited number of name value pairs that can be cordoned off into groups (or sections as they used to be called in INI files). Some may argue that a section-name-value system like this is too limiting and you should always accomodate full hierarchical structures, but I've found the extra functionality rarely necessary.

Of course, the type of value (string, int, date, etc) for a particular setting is defined within the application, but value types might also be defined as OEM default settings. Should sysadmins really be able to change setting X from a string to a date? I'd imagine not usually.

The trick and the real beauty of the system is in the way that setting values are resolved. The resolution logic basically says that if the app can find a setting at a higher level in the hierarchy, it uses that value. Otherwise it searches down the hierarchy till it either finds a value, or till it drops to the OEM defaults. If no setting is specified at the OEM level, the system defaults to blanks, or 0's depending on the data type.

So, for instance, the code might ask for the setting "AskOnExit" from the "Prompts" section. The config handler needs to look first at the "level of most significance" which, in the above list of levels, would be the Administrator specified user specific settings (ie those settings that are set on a per user basis, but that the user himself doesn't have the authority to change.

If the setting isn't found there, the code continues down the hierarchy, looking at each level in turn until eventually in run through them all and returns the default or a blank or 0.

And, of course, you also have to provide a means to retrieving and writing a setting at a specific level in the hierarchy, for those settings that are always application level or machine level or what not.

Obviously, such a scheme requires a little more work up front, but the configuration flexibility (especially when you're talking about major, multi user systems with lots of configuration options, typically off-the-shelf, reseller-friendly applications like accounting systems or CRM packages), is a huge selling point to admins and users alike.

Why, then does configuration handling in .NET seem so archaically primitive?

Could you roll all these levels into that singular .config file? Sure, when where would you put it? In the Application path? No, users won't typically have write rights there. Under the user's Application Data folder in the Profile? No, can't put app wide settings somewhere that only a single user can access it. How about that machine config file. Again, not writable by typical users.

It's funny that Windows already has a rights system that is very stable, quite flexible and easy to work with, both manually and from within code and yet it often goes completely unused by larger apps. I'm talking, of course, of the file system.

These days, though, web apps are de rigueur and you can't go assigning file system rights to web users. There's no way that would fly with most web developers (you weren't really thinking of something like that, right?). So, roll it up into a simple table, with a getSetting and a setSetting stored procedure to simplify the coding. With proper indexing, and a little intelligent caching, it'll be every big as fast as using the .NET configuration functions, and whole lot more flexible.

Long story short. If your sideline utility needs to save a few settings, use the .NET configuration. It's fast, relatively easy and convenient. But for just about everything else, take a little time and do it right.

Your users and their sysadmins will thank you.

posted on Saturday, May 05, 2007 7:18:08 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions;