Rockford Lhotka

 Wednesday, November 2, 2005

Quite a few people have asked me about the effort that will be required to move an application from CSLA .NET 1.0 to CSLA .NET 2.0. The list of changes in the change log can look daunting after all…

 

Fortunately most of the changes in the change log are internal – they don’t directly impact your code in a business class all that much, at least for code written using more recent versions of the framework (1.3 or higher).

 

In fact, the closer your code is to version 1.51 (the current version) the easier the port will be. The closer to the original 1.0 version in the books the harder.

 

The three big change areas are generics, BrokenRules and the DataPortal.

 

Generics mean there are new and different base classes from which you inherit. In most cases changing to the new base class means removing some now unneeded code from your business class. The following table lists the base classes:

 

Old

New

BusinessBase

BusinessBase(Of T)

BusinessBase<T>

T = your business object type

BusinessCollectionBase

BusinessListBase(Of T, C)

BusinessListBase<T, C>

T = your collection type

C = child object type

NameValueCollectionBase

NameValueListBase(Of K, V)

NameValueListBase<K, V>

K = type of the key or name

V = type of the value

ReadOnlyBase

ReadOnlyBase(Of T)

ReadOnlyBase<T>

T = your business object type

ReadOnlyCollectionBase

ReadOnlyListBase(Of T)

ReadOnlyListBase<T>

T = child object type

 

The only place where you need to add code is BusinessBase(Of T), where there’s a new GetIdValue method you must implement (typically with one line of code). In all cases you will remove code such as the System.Objects overrides region and tons of code in collections.

 

A much bigger changes is that BrokenRules has been replaced by ValidationRules and now only supports the idea of rule methods. The BrokenRules.Assert() concept is gone. This will be the biggest change for most people, as all Assert calls must be converted to rule methods. Fortunately that's not terribly hard, but it is work.

 

DataPortal used to call DataPortal_Update, forcing you to write a nested If..Then statement inside DataPortal_Update. It now calls DataPortal_Insert, DataPortal_Update or DataPortal_DeleteSelf as appropriate. So now you write 3 methods instead of 1, but each method is very focused and requires no conditionals. Also, DataPortal now calls MarkNew and MarkOld automatically so you don't need to make those calls (or forget to make them like I typically did...).

 

The end result is that all properties in a BusinessBase(Of T) business object will now look like this:

 

private string _name = string.Empty;

 

public string Name

{

  get

  {

    if (CanReadProperty())

      return _name;

    else

      throw new System.Security.SecurityException(

        "Property get not allowed");

  }

  set

  {

    if (CanWriteProperty())

      if (_name != value)

      {

        _name = value;

        PropertyHasChanged();

      }

    else

      throw new System.Security.SecurityException(

        "Property set not allowed");

  }

}

 

Or

 

Private mName As String = ""

 

Public Property Name() As String

  Get

    If CanReadProperty() Then

      Return mName

    Else

      Throw New System.Security.SecurityException( _

        "Property get not allowed")

    End If

  End Get

  Set(ByVal value As String)

    If CanWriteProperty() Then

      If mName <> value Then

        mName = value

        PropertyHasChanged()

      End If

    Else

      Throw New System.Security.SecurityException( _

        "Property set not allowed")

    End If

  End Set

End Property

 

 

The new Expert VB/C# 2005 Business Objects books and thus the CSLA .NET 2.0 framework are slated for public release in Mar/Apr 2006.

Wednesday, November 2, 2005 10:08:19 AM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Tuesday, November 1, 2005

When you install SP2 on Windows XP it tightens security to the point that MSDTC doesn't work any more. I'm sure this is good for most users, but renders a developer workstation virtually useless... There are a series of steps you follow to reenable MSDTC but changing both its configuration and the Windows Firewall configuration. I think the same thing is true for Windows Server 2003 actually.

I've done this numerous times, but I keep losing the link to the KB article (873160). So I'm putting it here in my blog so I won't lose it again.

Tuesday, November 1, 2005 1:04:47 PM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Monday, October 31, 2005
I recently gave the keynote address at the Heartland Developer's Conference. While there, I was interviewed by PodcastStudio.net and the interview is now online for listening. At the moment it is here, and I'm sure after that it will move to the archive.
Monday, October 31, 2005 10:14:17 AM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Thursday, October 13, 2005

ASP.NET 2.0 has bi-directional data binding, which is a big step forward. This means you can bind the UI to a data source (DataTable, object, etc.) and not only display the data, but allow the user to do “in-place” editing that updates the data source when the page is posted back.

 

The end result is very cool, since it radically reduces the amount of code required for many common data-oriented web pages.

 

Unfortunately the data binding implementation isn’t very flexible when it comes to objects. The base assumption is that “objects” aren’t intelligent. In fact, the assumption is that you are binding to “data objects” – commonly known as Data Transfer Objects or DTOs. They have properties, but no logic.

 

In my case I’m working with CSLA .NET 2.0 objects – and they do have logic. Lots of it, validation, authorization and so forth. They are “real” objects in that they are behavior-based, not data-based. And this causes a bit of an issue.

 

There are two key constraints that are problematic.

 

First, ASP.NET insists on creating instances of your objects at will – using a default constructor. CSLA .NET follows a factory method model where objects are always created through a factory method, providing for more control, abstraction and flexibility in object design.

 

Second, when updating data (the user has edited existing data and clicked Update), ASP.NET creates an instance of your object, sets its properties and calls an Update method. CSLA .NET objects are aware of whether they are new or old – whether they contain a primary key value that matches a value in the database or not. This means that the object knows whether to do an INSERT or UPDATE automatically. But when a CSLA .NET object is created out of thin air it obviously thinks it is new – yet in the ASP.NET case the object is actually old, but has no way of knowing that.

 

The easiest way to overcome the first problem is to make business objects have a public default constructor. Then they play nicely with ASP.NET data binding. The drawback to this is that anyone can then bypass the factory methods and incorrectly create the objects with the New keyword. That is very sad, since it means your object design can’t count on being created the correct way, and a developer consuming the object might use the New keyword rather than the factory method and really mess things up. Yet at present this is how I’m solving issue number one.

 

I am currently solving issue number two through an overloaded Save method in BusinessBase: Save(forceUpdate) where forceUpdate is a Boolean. Set it to True and the business object forces its IsNew flag to False, thus ensuring that an update operation occurs as desired. This solution works wonderfully for the web scenario, but again opens up the door to abuse in other settings. Yet again a consumer of the object could introduce bugs into their app by calling this overload when it isn’t appropriate.

 

The only way out of this mess that I can see is to create my own ASP.NET data control that understands how CSLA .NET objects work. I haven’t really researched that yet, so I don’t know how complex it is to write such a control. I did try to subclass the current Object control, but they don’t provide extensibility points like I need, so that doesn’t work. The only answer is probably to subclass the base data control class itself…

Thursday, October 13, 2005 6:17:12 PM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Thursday, October 6, 2005

Here's a "basic" CSLA .NET 2.0 editable root class. It pretty much illustrates all the things you can do in a class under the upcoming version of the framework. In particular note the way validation rules are handled and all the transactional options in the DataPortal_XYZ methods (presumably you'd pick one for your app :) ).

A CustomerTypes class is also included at the bottom, illustrating how to implement a name-value list that works with data binding.

Imports CSLA

 

<Serializable()> _

Public Class Customer

  Inherits BusinessBase(Of Customer)

 

#Region " Business Methods "

 

  Private mID As Integer

  Private mLastName As String = ""

  Private mFirstName As String = ""

  Private mLastActivity As SmartDate

  Private mType As Integer

  Private mCity As String = ""

 

  Private Shared mCustomerTypes As CustomerTypes

 

  Public Property City() As String

    Get

      If CanReadProperty() Then

        Return mCity

      Else

        Throw New System.Security.SecurityException( _

          "Property read not allowed")

      End If

    End Get

    Set(ByVal value As String)

      If CanWriteProperty() Then

        If Not mCity.Equals(value) Then

          mCity = value

          PropertyHasChanged()

        End If

      Else

        Throw New System.Security.SecurityException( _

          "Property write not allowed")

      End If

    End Set

  End Property

 

  Public Shared ReadOnly Property CustomerTypes() As CustomerTypes

    Get

      If mCustomerTypes Is Nothing Then

        mCustomerTypes = Library.CustomerTypes.GetCustomerTypes

      End If

      Return mCustomerTypes

    End Get

  End Property

 

  Public ReadOnly Property ID() As Integer

    Get

      Return mID

    End Get

  End Property

 

  Public Property LastName() As String

    Get

      If CanReadProperty() Then

        Return mLastName

      Else

        Throw New System.Security.SecurityException( _

          "Property read not allowed")

      End If

    End Get

    Set(ByVal value As String)

      If CanWriteProperty() Then

        If mLastName <> value Then

          mLastName = value.ToUpper

          PropertyHasChanged()

        End If

      Else

        Throw New System.Security.SecurityException( _

          "Property write not allowed")

      End If

    End Set

  End Property

 

  Public Property FirstName() As String

    Get

      If CanReadProperty() Then

        Return mFirstName

      Else

        Throw New System.Security.SecurityException( _

          "Property read not allowed")

      End If

    End Get

    Set(ByVal value As String)

      If CanWriteProperty() Then

        If mFirstName <> value Then

          mFirstName = value

          PropertyHasChanged()

        End If

      Else

        Throw New System.Security.SecurityException( _

          "Property write not allowed")

      End If

    End Set

  End Property

 

  Public Property LastActivity() As String

    Get

      If CanReadProperty() Then

        Return mLastActivity.Text

      Else

        Throw New System.Security.SecurityException( _

          "Property read not allowed")

      End If

    End Get

    Set(ByVal value As String)

      If CanWriteProperty() Then

        If mLastActivity <> value Then

          mLastActivity.Text = value

          PropertyHasChanged()

        End If

      Else

        Throw New System.Security.SecurityException( _

          "Property write not allowed")

      End If

    End Set

  End Property

 

  Public Property CustomerType() As Integer

    Get

      If CanReadProperty() Then

        Return mType

      Else

        Throw New System.Security.SecurityException( _

          "Property read not allowed")

      End If

    End Get

    Set(ByVal value As Integer)

      If CanWriteProperty() Then

        If mType <> value Then

          mType = value

          PropertyHasChanged()

        End If

      Else

        Throw New System.Security.SecurityException( _

          "Property write not allowed")

      End If

    End Set

  End Property

 

  Public ReadOnly Property CustomerTypeText() As String

    Get

      If CanReadProperty() Then

        Return CustomerTypes.Value(mType)

      Else

        Throw New System.Security.SecurityException( _

          "Property read not allowed")

      End If

    End Get

  End Property

 

#End Region

 

#Region " Business Rules "

 

  Protected Overrides Sub AddBusinessRules()

    AddRule(AddressOf Validation.CommonRules.StringRequired, _

      "FirstName")

    AddRule(AddressOf Validation.CommonRules.StringRequired, _

      "LastName")

  End Sub

 

#End Region

 

#Region " Object ID Value "

 

  Protected Overrides Function GetIdValue() As Object

    Return mID

  End Function

 

#End Region

 

#Region " Constructors "

 

  Private Sub New()

 

    ' don't allow a Guest to see or change city data

    AuthorizationRules.DenyRead("City", "Guest")

    AuthorizationRules.DenyWrite("City", "Guest")

 

  End Sub

 

#End Region

 

#Region " Factory Methods "

 

  Public Shared Function NewCustomer() As Customer

    Return DataPortal.Create(Of Customer)(Nothing)

  End Function

 

  Public Shared Function GetCustomer(ByVal id As Integer) As Customer

    Return DataPortal.Fetch(Of Customer)(New Criteria(id))

  End Function

 

  Public Shared Sub DeleteCustomer(ByVal id As Integer)

    DataPortal.Delete(New Criteria(id))

  End Sub

 

#End Region

 

#Region " Criteria "

 

  <Serializable()> _

  Private Class Criteria

    Public ID As Integer

 

    Public Sub New(ByVal id As Integer)

      Me.ID = id

    End Sub

  End Class

 

#End Region

 

#Region " Data Access "

 

  ' Forced to run on client

  <RunLocal()> _

  Protected Overrides Sub DataPortal_Create(ByVal criteria As Object)

 

    ' get default values from db

    ' we get here via DataPortal.Create()

 

    mID = New System.Random().Next(100, 999)

    mType = CustomerTypes.DefaultKey

    CheckRules()

 

  End Sub

 

  ' Runs on client or server based on DataPortal config

  Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)

 

    ' get data from db

    ' we get here via DataPortal.Fetch()

 

    Dim crit As Criteria = DirectCast(criteria, Criteria)

    mID = crit.ID

    mType = CustomerTypes.Key("Hybrid")

    mLastName = "Lhotka"

    mFirstName = "Rocky"

    CheckRules()

 

  End Sub

 

  ' COM+ transactions

  <Transactional()> _

  Protected Overrides Sub DataPortal_Insert()

 

    ' insert data into db

    ' we get here via obj.Save()

 

  End Sub

 

  ' System.Transactions namespace

  Protected Overrides Sub DataPortal_Update()

 

    ' update data in db

    ' we get here via obj.Save()

 

    Using tr As New System.Transactions.TransactionScope

      ' do updates

      tr.Complete()

    End Using

 

  End Sub

 

  ' ADO.NET transactions

  Protected Overrides Sub DataPortal_DeleteSelf()

 

    ' do deferred delete of self

    ' we get here via obj.Save()

 

    Using cn As New SqlClient.SqlConnection

      Using tr As SqlClient.SqlTransaction = cn.BeginTransaction

        ' do delete

      End Using

    End Using

 

  End Sub

 

  ' No transactions

  Protected Overrides Sub DataPortal_Delete(ByVal criteria As Object)

 

    Dim crit As Criteria = DirectCast(criteria, Criteria)

 

    ' do immediate delete based on criteria

    ' we get here via DataPortal.Delete()

 

  End Sub

 

#End Region

 

End Class

 

======================================================

Imports CSLA

 

Public Class CustomerTypes

  Inherits NameValueListBase(Of Integer, String)

 

  Public Function DefaultKey() As Integer

    Return 1

  End Function

 

  Public Function DefaultValue() As String

    Return Value(DefaultKey)

  End Function

 

  Private Sub New()

 

  End Sub

 

  Public Shared Function GetCustomerTypes() As CustomerTypes

    Return DataPortal.Fetch(Of CustomerTypes) _

      (New Criteria(GetType(CustomerTypes)))

  End Function

 

  Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)

    Me.IsReadOnly = False

    Add(New NameValuePair(1, "Domestic"))

    Add(New NameValuePair(2, "International"))

    Add(New NameValuePair(3, "Hybrid"))

    Me.IsReadOnly = True

  End Sub

 

End Class

 

Thursday, October 6, 2005 2:18:16 PM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Wednesday, October 5, 2005
Bill McCarthy has come up with a nice extender control that wraps the solution to the data binding issue I've been discussing. Thanks Bill!!
Wednesday, October 5, 2005 3:09:52 PM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Wednesday, September 28, 2005

This is the conclusion (for now) of the issue with Windows Forms data binding that I discussed in an earlier entry.

 

To recap, the issue occurs in detail forms built using data binding. The current control’s value isn’t refreshed when the user tabs off the control. If the data source (business object or smart DataTable with a partial class) changed the value due to business logic that change is not reflected in the UI, even though the changed value is in the data source.

 

In talking to the data team at Microsoft, it turns out that this is a bug and will likely be fixed in some future service pack. At this late stage of the game however, it won’t be fixed for release of VS 2005.

 

Fortunately they were able to come up with a decent workaround in the meantime. The workaround is done in the UI and involves hooking an event from each Binding object, then in the event handler forcing the current value to be refreshed from the data source.

 

To set this up, add the following code to your form:

 

  Private Sub HookBindings()

 

    For Each ctl As Control In Me.Controls

      For Each b As Binding In ctl.DataBindings

        AddHandler b.BindingComplete, AddressOf ReadBinding

      Next

    Next

 

  End Sub

 

  Private Sub ReadBinding(ByVal sender As Object, _

    ByVal e As BindingCompleteEventArgs)

 

    e.Binding.ReadValue()

 

  End Sub

 

Then just make sure to call HookBindings in your form’s Load event or constructor. Ideally this is the kind of thing you’d do in a base form class, then make all your normal forms just inherit from that base so there’s no extra code in each actual form.

 

The HookBindings method loops through all controls on the form, and all Binding objects for each control. Every Binding object’s BindingComplete event is hooked to the ReadBinding method – making it the event handler for all Binding objects.

 

The ReadBinding method handles all BindingComplete events for the form. Any time that event is raised this method merely forces data binding to read the current value from the data source and refresh the control.

 

Since BindingComplete is raised after the user has tabbed off a control and the binding is “complete”, this refresh of the display value ensures that the current control does actually contain the value from the data source even if the data source changed the value.

Wednesday, September 28, 2005 11:53:53 AM (Central Standard Time, UTC-06:00)  #    Disclaimer

Here’s a helpful little feature of VB 2005. The following is valid code:

 

    <NotUndoable()> _

    <NonSerialized()> _

    Private mParent As Core.IEditableCollection

 

Notice that both attributes are independent – no need to use the harder syntax from 2003:

 

    <NotUndoable(), NonSerialized()> _

    Private mParent As Core.IEditableCollection

 

Very nice little nugget!

Wednesday, September 28, 2005 11:14:22 AM (Central Standard Time, UTC-06:00)  #    Disclaimer
 Sunday, September 25, 2005

In my last post I discussed a nasty bug with Windows Forms data binding, along with one possible solution (which is most certainly a hack). This led to some discussion – including a couple people observing that this hack should probably be in the UI layer, not the business layer (which is where I’d put it).

 

In concept I agree with the idea that the hack could belong in the UI layer. However, that is a slippery slope for two reasons.

 

First, you could argue that all data binding support only exists for Windows Forms, since neither Web Forms and certainly Web services utilize any of the interfaces or event models required by data binding. Yet it is quite obvious that data binding requires that the data source itself implement these behaviors in order to work properly. The hack is merely overcoming a bug in the way data binding works, so it naturally fits along with all the other interfaces and eventing that already exist in the data source.

 

Second, it is a hack. One would hope that this is a temporary issue that Microsoft will fix. Remember that this will break all .NET 1.x code that uses data binding where the data source actually manipulates data! It would seem likely that it would be a high priority to fix this issue (we can only hope). Thus it is less important where the hack is placed, than it is how transparent it is and how easily it can be removed in the future when (if) Microsoft fixes this bug.

 

In the case of CSLA .NET 2.0 I can entirely embed the hack into the framework, meaning that removing the hack in the future would be a simple framework change. In the case of custom objects or smart DataTables a well-implemented hack could be removed by changing just a few lines (3-6) of code in each object.

 

But let’s consider the idea of solving this in the UI. The obvious solution in the UI is to add a LostFocus handler for every control and just updating the control at that point – thus directly overcoming the issue at hand. I’m sure there are various other possibilities, but the problem is merely that the value isn’t updated automatically at LostFocus, so it is hard to imagine a more direct or simple solution than to just update it directly.

 

That turns out to be brainless code in every form in the system. It is brainless enough that you could use a little reflection and generically wrap it up into a new base form class so when the form loads it hooks LostFocus for all events, and the event handler loops through all bindings on the control and refreshes any and all values associated with those bindings. This is effectively what data binding does anyway, so we’d be simply replicating what they should be doing for the control.

 

Wrapping this up into a new base form class is the way to go obviously – since it avoids writing this code in every form. However, it does require the use of reflection and forces all forms to inherit from this new base class – which is a PITA.

 

I think it would be a bit more invasive to remove the hack from the UI than from CSLA .NET. However, it may be less invasive to remove the hack from the UI than to remove it from custom objects or smart DataTable objects.

 

So I will likely show the UI hack in the smart DataTable book, and the Timer hack in the Business Objects books (barring some better hacks or a real fix from Microsoft).

Sunday, September 25, 2005 8:50:02 PM (Central Standard Time, UTC-06:00)  #    Disclaimer