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.
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
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”.
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.
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.