Friday, November 18, 2005

This link was posted to the CSLA .NET discussion forum. It uses Google Maps and superimposes the locations of CSLA .NET users who've entered themselves on the site.

That's just way too cool! :)

Friday, November 18, 2005 12:16:08 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 

ClickOnce is a great technology, but it seems that documentation is a bit scarce... In particular, like all web-based technologies, it requires some obscure configuration. The web may be great in some ways, but it does often require that you have a lot of arcane knowledge to do even the simplest thing...

This forum thread covers the little-documented fact that for IIS 6.0 the web server serving up a ClickOnce application needs to map the .application, .manifest and .deploy file types in IIS as follows:

.application -> application/x-ms-application
.manifest -> application/x-ms-application
.deploy -> application/octet-stream

While this may be documented somewhere, it seems quite hard to find. Thus I'm blogging it. If a few people blog the answers to common questions than at least google finds them for people :)

Friday, November 18, 2005 8:44:21 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, November 15, 2005

The ADO Guy expresses his frustration that the ObjectDataSource doesn't work with typed DataSets in ADO.NET 2.0. In an earlier post I expressed my frustration about using it with business objects.

In the case of CSLA .NET 2.0 I'm writing my own CslaDataSource that understands how to work nicely with CSLA .NET business objects. In conferring with the ASP.NET team it seems like this is the best option. It turns out that writing a DataSource control isn't overly difficult - it is writing the designer support (for VS 2005) that is the hard part...

Tuesday, November 15, 2005 9:55:10 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
Here's an article on MSDN that lists some nice add-ins for Visual Studio, and mentions other useful tools like ndoc as well.
Tuesday, November 15, 2005 8:56:45 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, November 02, 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 02, 2005 10:08:19 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, November 01, 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 01, 2005 1:04:47 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 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  |  Comments [0]  | 
 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  |  Comments [0]  | 
 Thursday, October 06, 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 06, 2005 2:18:16 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, October 05, 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 05, 2005 3:09:52 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |