Tuesday, June 14, 2005
Moving to new blog
Tuesday, June 07, 2005
DataRow.ClearErrors doesn't quite cut it
public static void ClearDataSetErrors(DataSet data)
{
if (data.HasErrors)
{
foreach (DataTable table in data.Tables)
{
foreach (DataRow row in table.GetErrors())
{
foreach (DataColumn column in row.GetColumnsInError())
row.SetColumnError(column, null);
row.RowError = null;
}
}
}
}
Wednesday, June 01, 2005
Arithmetic Rounding in .Net
decimal test = decimal.Round(12.345m, 2);
12.35, right? No - imagine my surprise to find that it returns 12.34!!
It turns out that .Net employs a type of rounding called "banker's rounding". When the rounding logic encounters a midpoint (like the 5 in my example), rather than always rounding up, it rounds to then nearest even number.
Of course, in my situation, I needed the traditional "arithmetic rounding" which always goes up (or away from zero) when a midpoint is encountered. Finding no such feature in .Net, I wrote my own. If you see any bugs in it, let me know!
public static decimal ArithmeticRound (decimal d, int decimals)
{
decimal power = (decimal)Math.Pow(10, decimals);
// Note: converting to positive number before calculating rounding, so that we don't need separate logic for negative number handling
decimal floored = (decimal.Floor((Math.Abs(d) * power)) / power);
decimal result;
if (Math.Abs(d) >= floored + ((decimal)Math.Pow(10, - (decimals + 1)) * 5))
result = floored + (decimal)Math.Pow(10, -(decimals));
else
result = floored;
return result * Math.Sign(d);
}
BTW, .Net 2 provides an option on the Round function to use either type of rounding. See http://msdn2.microsoft.com/library/wxhaazw6(en-us,vs.80).aspx
Data validation in .Net using ErrorProvider and DataSet errors
Some of the validations were clearly business rules that had to be verified on a case-base-case basis. However, I still wanted to provide the user with an adequate error message, and ensure that keyboard focus was placed on the column in question, so that the user could easily correct it. I really didn't want the forms displaying the data to need to be aware of the specific business rules - there were already several instances in the application where business rules were getting checked in the form, resulting in duplicated code that was next-to-impossible to verify with automated unit tests.
So, how can I perform rules validation outside of the form and yet still provide enough information to the form for an optimal user experience? I needed some way to indicate the error message as well as the dataset table/row/column on which the error was occurring back to the UI layer, so that it could deal with the error display and set focus to the appropriate control on the form.
DataSet errors quickly came to mind. DataTables have a feature that allows you to "set" an error on a DataRow and/or DataColumn, identifying that the row or column is in error. The interface is quite simple: just call the DataTable.SetColumnError function, identifying error text and a column. If you want to mark a row as having an error, but don't want to identify a specific column, set the RowError string property on the DataRow.
OK - so it's easy enough to identify a row or column as having an error; what about making use of that information? Yes, you can programmatically identify the rows/columns in error on a dataset. In my case I needed to locate the errors and set focus to the appropriate cell in a C1TruDBGrid. It's not as straight-forward as I would like, but here's the code that I used ( sorry about the indentation - for some reason Blogger is stripping out html space characters, losing the indentation):
if (data.HasErrors)
{
// Loop through data tables, determine if we have a table bound to this grid
foreach (DataTable table in data.Tables)
{
if (table.HasErrors)
{
if (this.DataMember == table.TableName this.DataMember.EndsWith("." + table.TableName))
{
int i = 0;
foreach (DataRow row in table.Rows)
{
errorProcessed = this.setFocusToErrorOnRow(row, i);
if (errorProcessed)
break;
i++;
}
if (errorProcessed)
break;
}
}
}
}
Ideally the DataSet would provide a method to enumerate all errors, but no such luck. At least it tells us whether there are errors or not, so we can decide whether to go any further. For each DataTable, we check whether it has errors or not, and if so, loop through the rows and process each row. The row processing is hidden in setFocusToErrorOnRow, which loops through the results of DataRow.GetColumnsInError, a method that returns any columns on the row that have errors. Now that we have the rows and columns, we can do some magic to find them on the grid (sigh...), and finally set the focus!
Another feature of DataSet errors is the integration with ErrorProviders. C1TrueDBGrid automatically detects the error and puts a nice little red icon on the column or row in error, making it obvious to the user where the problem is.
One more thing: when revalidating a dataset, it is important to clear any errors previously added to it. You can do that by calling ClearErrors on the row. Would be nice to have that at the dataset level, but oh well; a few lines of code does the trick:
// Clear all existing errors
if (data.HasErrors)
foreach (DataTable table in data.Tables)
foreach (DataRow row in table.GetErrors())
row.ClearErrors();
Problem is, ClearErrors doesn't trigger anything that I can find back to the dataset to tell databound components to remove UI indications of the error. One more problem to figure out...
Friday, March 04, 2005
Web Service calls from behind proxy servers
This worked just great, so long as the user had direct HTTP access to my web server. However, it didn't work so well when they were behind a proxy server. The specific problem was with a Squid proxy server with authentication requirements. In this case, the web service call resulted in the error: "System.Net.WebException: The request failed with HTTP status 407: Proxy Authentication Required."
It turned out that the solution was relatively straight-forward. The web service wrapper provided by Visual Studio creates a class inherited from SoapHttpClientProtocol. WebProxy.GetDefaultProxy will retrieve default IE proxy settings for use in web commands. You can then set the user's default credentials on the proxy instances using CredentialCache.DefaultCredentials. Here is some example code:
CrashWebServices webServices = new CrashWebServices ();
// Set the proxy settings to the default IE settings
webServices.Proxy = System.Net.WebProxy.GetDefaultProxy();
// In case proxy authentication is required,
ensure that we make the call with the user's current credentials
webServices.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
string message = webServices.SubmitCrash(email,
crashData);
Hope this helps!
Wednesday, January 19, 2005
SelectNextControl method
SelectNextControl is a Control instance method, however, it also takes a Control as its first parameter. Initially I attempted to run it on the control that currently had focus, but couldn't figure out what to pass as the first parameter! Finally I realized that the method was intended to be run on the parent control, and the first parameter was the Control after which to select the next one. In my case, I needed to call the method on the Form, and pass it the control that had current focus.
To summarize, SelectNextControl is called on a parent control, such as a form. The first parameter is the control that you want to move away from, such as the currenntly selected control.
Tuesday, December 21, 2004
Excellent article explaining C# structs
- an instance of a struct can be created without specifying constructor parameters
- structs support interfaces, but not inheritance
- the CLR boxes structs as objects when accessed through an interface, which is a potential performance hit
- foreach and using statements implicitely set variables declared in them to readonly
Thursday, December 16, 2004
Tidbits from Microsoft Performance Guide
- you can use a finally block without a catch block - this ensures that code is run regardless of whether an exception is raised, and doesn't carry the performance penalties of using catch
- marking derived class methods as "sealed" can improve performance, since the compiler can inline the method call - virtual methods cannot be inlined
- for performs better than foreach because of the overhead and potential boxing caused by the enumerators used by foreach - this doesn't mean you shouldn't use foreach, but where performance is paramount, for is faster
- to perform a case-insensitive string comparison, the String static member Compare is faster than casting strings to lower or upper case