Greg Robinson's Blog

I report it, you decide

Click Once

Custom Authentication in Windows Forms

DataBinding Stuff

Favorite Links

My book contribution

My Book Reviews

My Personal Life

Richmond, VA .NET Users Group

Smart Client Stuff

What I am reading

July 2004 - Posts

Coder to Developer: Book Review

I just finished reading Mike Gunderloy's book "Coder to Developer".

First off, great idea for a book.  Where was this in 1998 when I got started in this industry?

I could not agree more with the premise of the book: yeah, you can code, but what do you need to develop applications. 

The book is full of resources for developing applications.  After first discussing the need for the resource, the book takes a quick\brief look at each resource available.  I picked up a few tips to add to my bag of tips and tricks. 

The book walks you through everything you will need to know from day 1 to release to maintenance of an application.  Best part of the bug: no code!

If you have been developing applications for 5+ years, its a good read.  If you have been developing applications for 0-5 years, its a great read.  Overall, two thumbs up; nice job Mike. 

  

DateTime type and Time Zones

Our WinForms app needs to make adjustments for time zones and dates as the data is accessible via .NET Remoting.  This means a user can be in Texas and the data store could be in Virginia.

The solution?  Convert on the server, convert on the client, 4 times. 

ToLocalTime and ToUniversalTime will be your best friends here.

The trick is how to handle truly modified DataRows.  The above code handles this.

Suppose the user updates a field X in DataRow A.

Suppose also that DataRow A has 5 datetime types.

You do not want to convert these types if they were not modified by the client.  Now, on an Added DataRow you do want to convert all of the datetime types.

Client code to pull and push a DataSet:

Private Enum DateTimeConversion

ToLocalTime

ToUniversalTime

End Enum

PrivateShared Sub ConvertDateTimes(ByVal ds As DataSet, ByVal conv As DateTimeConversion)

If ds Is Nothing Then Return

Dim dateTimeType As System.Type = GetType(DateTime)

Dim indices() As Integer

Select Case conv

Case DateTimeConversion.ToLocalTime

If Not ds.HasChanges() Then Return

For Each dt As DataTable In ds.Tables

ReDim indices(-1)

For i As Integer = 0 To dt.Columns.Count - 1

If dt.Columns(i).DataType Is dateTimeType Then

ReDim Preserve indices(indices.Length)

indices(indices.Length - 1) = i

End If

Next

If indices.Length > 0 Then

For Each dr As DataRow In dt.Rows

Select Case dr.RowState

Case DataRowState.Added

For Each i As Integer In indices

dr(i) = DirectCast(dr(i), DateTime).ToLocalTime()

Next

Case DataRowState.Modified

For Each i As Integer In indices

If Not dr(i, DataRowVersion.Original) = dr(i, DataRowVersion.Current) Then

dr(i) = DirectCast(dr(i), DateTime).ToLocalTime()

End If

Next

End Select

Next

End If

Next

Case DateTimeConversion.ToUniversalTime

For Each dt As DataTable In ds.Tables

ReDim indices(-1)

For i As Integer = 0 To dt.Columns.Count - 1

If dt.Columns(i).DataType Is dateTimeType Then

ReDim Preserve indices(indices.Length)

indices(indices.Length - 1) = i

End If

Next

If indices.Length > 0 Then

For Each dr As DataRow In dt.Rows

For Each i As Integer In indices

If TypeOf dr(i) Is DateTime Then dr(i) = DirectCast(dr(i), DateTime).ToUniversalTime()

Next

Next

End If

Next

ds.AcceptChanges()

End Select

End Sub

 

Server code to push and pull a DataSet:

Private Function ConvertDateTimeToLocalTime(ByVal ds As DataSet)

Dim cols As New ArrayList

For Each tbl As DataTable In ds.Tables

For i As Integer = 0 To tbl.Columns.Count - 1

If tbl.Columns(i).DataType.FullName = "System.DateTime" Then cols.Add(i)

Next

If tbl.Rows.Count > 0 AndAlso cols.Count > 0 Then

For Each row As DataRow In tbl.Rows

For Each i As Integer In cols

If TypeOf row(i) Is DateTime Then row(i) = DirectCast(row(i), DateTime).ToLocalTime

Next

Next

End If

Next

If ds.HasChanges Then ds.AcceptChanges()

End Function ' ConvertDateTimeToLocalTime

Private Function ConvertDateTimeToUniversalTime(ByVal ds As DataSet)

Dim cols As New ArrayList

For Each tbl As DataTable In ds.Tables

For i As Integer = 0 To tbl.Columns.Count - 1

If tbl.Columns(i).DataType.FullName = "System.DateTime" Then cols.Add(i)

Next

If tbl.Rows.Count > 0 AndAlso cols.Count > 0 Then

For Each row As DataRow In tbl.Rows

If row.RowState = DataRowState.Added Then

For Each i As Integer In cols

row(i) = DirectCast(row(i), DateTime).ToUniversalTime

Next

ElseIf row.RowState = DataRowState.Modified Then

Dim curr, orig As String

For Each i As Integer In cols

curr = row(i, DataRowVersion.Current).ToString().Trim()

orig = row(i, DataRowVersion.Original).ToString().Trim()

If (Not curr.Equals(orig) OrElse _

curr <> orig OrElse _

String.CompareOrdinal(curr, orig) <> 0) AndAlso _

Not (curr = "-" AndAlso orig = String.Empty) Then

If TypeOf row(i) Is DateTime Then row(i) = DirectCast(row(i), DateTime).ToUniversalTime

End If

Next

End If

Next

End If

Next

End Function ' ConvertDateTimeToUniversalTime

 

 

.NET Remoting and the customErrors tag

To continue on my remoting rant.....

We were stilling see strange behaviors when remoting over the WAN.  I finally broke down and called Microsoft.

I sent them my client and server config files.  They said they both were correct, EXCEPT on the server I had added the customErrors=on element.  Why? Because the exception told me to to see more details.  Why again? Because all of the documentation on remoting tells you to in order to see more details on the exception.

Per the MS rep, the docs are written incorrectly.  Our remote server was not throwing an exception.  7 days of debugging only to discover the docs are inccorect.  I changed this value from 'on' to 'off' and now all of our remote methods work.  You have got to be kidding me.....  

Chris Taylor discusses something similar to this here.

07/23/04 UPDATE

customErrors is an attribute in the server's config file that tells the server to propagate the exception back or send mback a general remoting exception.  Its set to “off“ by default, which tells the server to send the real exception back.  Setting it to “on“ tells the server to send a general remoting exception.  Now, where the confusion lies is the dialog box that pops up the first time you see a general remoting exception. It tells you to set customErrors “on“ to see the details.  Well, “on“ really means set the attribute to “off”.  

 

.NET Remoting and Multiple Threads...beware

I have spent 5 days debugging our application, which uses .net remoting, only to discover the issue was with multiple threads.

We have a WinForms application that runs on the LAN or WAN.  The app is depolyed over from a web server (No Touch Deploy).

The remote server, hosted by IIS, is for data access: searches and update.

We use a shared implementation when it comes to remoting.  The client assemblies are compiled against the data access asembly.  So, the data access assembly lives with the client and it also lives on the web server.  Whe a user is running the app on the LAN, we use TCP\binary and the data access asembly the client was build with.

When the user is on the WAN, we use HTTP\soap and the data access assembly hosted by IIS.  Same exact assembly.

At runtime we have some client decision logic that decides which assembly to use.

When running the app from the WAN, all of our searches work fine with the remote assembly.  Our search logic uses multiple threads (running in the background) to pull back lookup data when the app first starts.  So, multiple threads where making calls to the remote server.

Now, once the app loaded up and we tried to do an update, we'd get remoting exceptions. I could do another search just fine.  If I tried to do an update though, more remoting exceptions.

After debugging my ^%$# off, I finally noticed that the background threads for our searches were the problem.  Once I 'truend these off' and did all the search work on the main thread, updates worked remotely.

Now, I cannot explan why.  Our client has a shared type that all assemblies call into when the need to do a lookup or an update.  The shared type is responsible for creating a data access object, using the local copy or the remote copy.

The shared type than calls into the data access assembly.  For searches this worked fine.  After doing a search (which used many threads) and then an update (which uses the main thread) the shared type could not see the remote server.

Who knows.  Its working when doing everything on the main thread.  I will figure out why it was broken later.  

  

Lets give authoring a book another try

Well, I have done it again.  I have agreed to co-author a book for Sybex.  After my horrible experience with Wrox,  I did not think I would ever write again. 

I am excited about this one as I get to work with an industry guru and the topic is something I have been studying for 2+ years now.

Dogs and cat, get ready for some long nights in the home office.  Amy (my wife), I will see you in a year or two  ;-)

New boat we just bought, sorry, book take priority.

 

 

More Posts