Thursday, August 11, 2005

 

CustomValidator in a TemplateColumn of a DataGrid

I had a DataGrid with a TemplateColumn that had a TextBox in the ItemTemplate (not the EditItemTemplate) so that the user can easily enter text in for all of the rows without having to click an Edit and Update link for each one. The problem I had was with validation of this data. I had to use a CustomValidator, but the challenge was to specify the function on the server side that would do the validation. Because I am using Visual Studio, I am designing these pages with a code-behind page. This made the compiler throw a fit if I added an OnServerValidate attribute to the CustomValidator.

The solution that I found was to add another CustomValidator to the page (not inside the DataGrid). This CustomValidator had a blank ErrorMessage and Text property and its Display was set to None. However, it will still be enabled and cause its ServerValidate method to run. I call this validator my trigger validator.

The trigger validator's ServerValidate method is simple. It just loops through each of the items in the DataGrid, finds the TextBox's validator, adds a handler to the appropriate validate function, and then calls the Validate method for each one. Here's the code for the trigger validator:
Private Sub MyValidatorTrigger_ServerValidate(ByVal source _
As System.Object, ByVal args As _
System.Web.UI.WebControls.ServerValidateEventArgs) _
Handles MyValidatorTrigger.ServerValidate

Dim MyValidator As CustomValidator
For Each dgi As DataGridItem In MyDataGrid.Items
MyValidator = dgi.FindControl("MyValidator")
AddHandler MyValidator.ServerValidate, AddressOf MyValidator_ServerValidate
MyValidator.Validate()
Next
args.IsValid = True
End Sub

Then I just write the MyValidator_ServerValidate to validate the TextBox the way I want. In that method, the TextBox is found by using the source.NamingContainer.FindControl method.

The result of this is that each TextBox with an error gets a * (Text property) put beside it if the validation fails. I like the fact that the ErrorMessage is displayed multiple times, once for each offending TextBox. Though, it may be desired sometimes to just show one error message (from the trigger) and multiple *'s.

Note also that the CustomValidator does not have a ControlToValidate property set, so that the validator will run even if the TextBox is blank.

Friday, August 05, 2005

 

Completely Dynamic Paging in SQL Server 2000

Sadly, SQL Server 2000 has no built in way to page the results of a query. That is, there is no simple way to ask for rows 101 to 125 for a SELECT statement. There are many methods out there for writing a stored procedure that will return specified rows for a specific SELECT statement. I didn't want to bother with writing a stored procedure for each different SELECT statement that I have to page. Thus, I wanted a fully dynamic stored procedure that would handle this. I envisioned a stored procedure that I could pass a SELECT statement and the first and last rows desired and it would return only those rows.

After much trial and error, I came up with the following stored procedure. It brings together many different ideas that I read on other websites, but sadly I have since lost the references.
CREATE PROCEDURE spReturnPage
(
@SelectStatement NVARCHAR(1000), --The select statment for the desired query
--MUST contain the phrase SELECT TOP 100 PERCENT
@FirstRow INT, --The first row to fetch
@LastRow INT --The last row to fetch
)
AS
BEGIN
SET NOCOUNT ON --Speed up by not having it count the number of rows returned

SET ROWCOUNT @LastRow --Set the maximum number of rows returned to be the last one desired

DECLARE @SQL NVARCHAR(1200) --Create a variable to store the SQL statment that does the paging

--Create a temporary table called #TempTable which consists of the query passed in
--plus an additional column containing the row number
SET @SQL = 'SELECT IDENTITY(INT,1,1) AS RowNumber, DummyTable.* INTO #TempTable FROM (' + @SelectStatement + ') AS DummyTable; '
--Select everything from the temporary table, which is returned to the user
--This must be done as part of the dynamic sql because of the scope of #TempTable
SET @SQL = @SQL + 'SELECT * FROM #TempTable WHERE RowNumber >= ' + CONVERT(NVARCHAR(10), @FirstRow)

--Run the SQL statement using sp_executesql so there is some hope of optimizing
EXEC sp_executesql @SQL
END

Basically, this procedure takes the first LastRow records of the SELECT statement and adds a column, which is the record/row number. Then it selects only those records with the RowNumber at least FirstRow.

Note that the SELECT statement that is passed to the procedure must begin with SELECT TOP 100 PERCENT and must not end with a ;. SQL Server will complain if there is a subquery like this that uses ORDER BY without the TOP 100 PERCENT.

This stored procedure can then be easily linked to a DataGrid in ASP.NET using the CustomPaging features.

While this stored procedure works, there are two important notes:

1. This procedure is not guaranteed to work. It is assumed that the SELECT statement provided contains an ORDER BY clause, which will order the results of the subquery in the stored procedure. There is no guarantee, however, that SQL Server will not reorder the results in any way it pleases to "optimize" the overall query. I have used this procedure in a number of places and have never seen it order any differently than intended, but there is still that chance.

2. If you are using this procedure in any natural application, it is likely that this is being called several times with the same SELECT statement, but different FirstRow and LastRow values. Stored procedures and views are created often because they run faster than regular SELECT statements. This is because SQL Server can optimize them and pre-compile them as well as keep usage statistics to tune this optimization over time. There is no such advantage with this procedure because the SELECT statement is not actually part of the procedure, but rather created on the fly. This is a trade-off for getting such a generic (dynamic) procedure.

Wednesday, July 20, 2005

 

Keeping an ASP.NET Password Box From Being Cleared

By default, a TextBox in ASP.NET that has its TextMode set to Password will clear out the stored password during a roundtrip to the server. Occasionally, there is the need to keep the password in the box when the page is delivered to the user after a PostBack. Yes, I realize that this is a security concern because anyone can read the password by viewing the source. (It is still easy to steal the password normally unless the server is using SSL or something.)

To implement the saving of the password, I created my password textbox like this:

<asp:textbox id="Password" runat="server"
TextMode="Password" Value="<%# Password.Text%>">

Then in my Page_Load method, I added the following line:
Password.DataBind()

Then I had it saving the password, rather effortlessly.

To overcome some of the security problems, I set my page to not cache. There are many ways to do this, so to make sure it works, I do them all:
Response.Cache.SetCacheability(HttpCacheability.NoCache)
Response.AddHeader("Pragma", "no-cache")
Response.Expires = -1

Monday, July 11, 2005

 

Symantec Security Center

Running Symantec AntiVirus 8.x Corporate Edition ran into problems trying to manage the settings on client computers using Symantec Security Center. I was unable to connect to most of the Windows XP machines because they were running the built-in firewall. The solution is to go to the client machine and open up port 2967 through the Exceptions tab on the Windows Firewall control panel.

Wednesday, June 22, 2005

 

Reducing [Visible] Spam

By far the best solution for spam that I have found is using SpamBayes along with Outlook. It works as a plug in. I have found that the best settings are 80% for Certain Spam and 30% for Possible Spam. Also, setting the timer delays to 0.5 seconds seems to work well.

The only negative thing is that the mail notification icon and sound still play when all you got was spam. But they are already working on fixing this problem.

A problem with Outlook is that there is no way to view the actual message source code as received by the server (unlike in Outlook Express, ironically). One neat side effect of having SpamBayes installed is that you can click "Show spam clues for current message" and get the actual message source code.
 

Epson PrinterPort

Every time the computer started up, an error message titled "EPSON PrinterPort" gave the message "At least one service or driver failed during system startup. Use Event Viewer to examine the event log for details." Looking in the System event log gave the following message titled "Eplpdx02": "The parameter is incorrect". As descriptive as that is, here is a combination of what I found elsewhere online to fix the problem. This might be too many steps, but it worked for me.
  1. Open the Printers and Faxes folder
  2. Right click on any printer and choose properties
  3. Click the Ports tab and remember which port is checked for this printer
  4. Scroll through the list of ports until you find the ones titled EPSON something or other
  5. If none of these EPSON ports are used (none have a printer listed in the Printer column), then just click each one and choose Delete Port
  6. If one of these EPSON ports is used, then the attached printer needs to be moved to another port, probably LPT or USB, depending on how it is connected to the computer. This is done by opening the properties for the associated printer and checking a different box for the port. (I didn't have this situation)
  7. After deleting all the EPSON ports, recheck the box you memorized in step 3, then click ok
  8. Click Start -> All Programs -> EPSON Printer -> Uninstall EPSON Printer Port (or something similar)
  9. Rebooting will no longer give the error message

 

hpcmpmgr

Every time the computer was shutting down, there was a delay waiting for hpcmpmgr to stop running. Apparently this is a problem with Windows XP Service Pack 2 which somehow breaks this program that just checks for updates from HP periodically. The fix is easy to find by searching HP's website for "hpcmpmgr" and leads you to a download page for a program hpcmpmgrFix.exe that fixes the problem.
 

Installing Windows XP Service Pack 2

Recently, I've had to install Windows XP Service Pack 2 on computers that had errors connecting to Windows Update. Sometimes the only way to fix the Automatic Updates part of Windows XP is to uninstall the service pack and reinstall it. The problem is that after uninstalling, you still cannot get to the Windows Update website (some undocumented error message, for example). Buried deep within Microsoft's website, is a link to download Service Pack 2 in a way that avoids the Windows Update website. Here, is the link: http://www.microsoft.com/downloads/details.aspx?FamilyId=049C9DBE-3B8E-4F30-8245-9E368D3CDB5A&displaylang=en
This page was found by searching the Microsoft site for "service pack 2" and then following links for "IT Professionals" to deploy to multiple computers.

Even though the linked page has a bold uppercase line telling you not to install it on single computers, I've done this and it worked. This line is likely there because the download is large to include every possible file your computer could need, even for features you don't have, just so that it will work for everyone. Microsoft probably just doesn't want everyone using up too much bandwidth from their website. Sometimes this is the only way that works.
 

Windows could not load the installer for Display

Today I fixed up a computer that had a ton of different little problems. Among them was the problem involving an error message that started with:
Windows could not load the installer for Display.
Once, I saw it with the word "Monitor" instead of "Display". This error came about when installing some windows updates, but was also seen when doing other things such as viewing the advanced properties in the Display control panels Settings tab. After looking around at various newsgroups, I found the following solution (though I forget where):
  1. Uninstall Windows XP Service Pack 2
  2. Reinstall the display drivers off the manufacturer's website (this case was Toshiba)
  3. Reinstall Windows XP Service Pack 2
Note that one of the problems seemed to come up when installing the display driver that was located through the Windows Update website. Thus, it is important to install the version from the computer manufacturer's website.

This took a long time, especially because the computer was slow, but also because uninstalling, downloading, and reinstalling the service pack are very time consuming tasks.

Tuesday, June 07, 2005

 

Could not access 'CDO.Message' object.

About 2 years back I wrote a windows (not asp.net) program that is a scheduled task to perform a weekly database check and send some emails. I was told that about 6 months or so ago it stopped working. This is the problem:
  • When running the program under my username (a member of the Administrators group), it worked fine.
  • When running the program under the Administrator username, it crashed with the mysterious "Could not access 'CDO.Message' object." error. I need it to work under the Administrator account for the scheduled tasks, and because I won't be working here in a few months and my account may be disabled in the future.
After some investigation, I found that the InnerException that was being thrown was System.Reflection.TargetInvocationException, which told me "The message could not be sent to the SMTP server. The transport error code was 0x80040217. The server response was not available." I found the solution on another forum which requires adding the following line of code:
msg.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "0")
This line of code sets SMTP authentication for this message to be 0 (no authentication / anonymous). This is what I need since I am using what seems to be an unusual configuration (seems typical to me though) of a different server for email and the server does not require SMTP authentication.

While this fixed the problem, the reason for the problem is not immediate, and I only have a few guesses. Perhaps version 1.1 of .NET created some new security setting or changed the defaults for how to send mail. Perhaps someone changed our email server's settings. Only the former seems reasonable because SMTP authentication was never enabled before. The other odd thing here is the fact that it worked just fine under my username, which happens to match my email address username. There is no Administrator@ourdomain.com email account, which might be why it fails under the Administrator account. While that sounds reasonable, there is no reason that the windows account name should matter when sending email, especially when the email server software is not from Microsoft. Furthermore, my account password on the server does not match my email account password, so it could never authenticate automatically between the two anyway.

I guess that's the problem with installing all updates from Microsoft. It seems most likely that upgrading the .NET Framework to 1.1 broke it. Who knows what 2.0 will bring.

Monday, June 06, 2005

 

Get the N-th Monday of the Month (VB.NET)

I needed a way to find the n-th Monday of a month, so this is the best way that I could find.
Private Function GetNthMondayOfMonth(ByVal GivenDate As Date, ByVal N As Integer) As Date
'This function returns the date of the Nth Monday of the month in the given date
'If there are not N Mondays in the month, the last Monday of the month is returned
Dim SixthDayOfMonth As Date = GivenDate.AddDays(6 - GivenDate.Day)
Dim FirstMondayOfMonth As Date = SixthDayOfMonth.AddDays(1 - SixthDayOfMonth.DayOfWeek)
Dim NthMondayOfMonth As Date = FirstMondayOfMonth.AddDays(7 * (N - 1))
'check if N Mondays went into another month (or wrapped around so far it is a different year)
If GivenDate.Month <> NthMondayOfMonth.Month Or GivenDate.Year <> NthMondayOfMonth.Year Then
'Figure out the last Monday of the month and use that
Dim LastDayOfMonth As Date = GivenDate.AddDays(Date.DaysInMonth(GivenDate.Year, GivenDate.Month) - GivenDate.Day)
Dim LastMondayOfMonth As Date = LastDayOfMonth.AddDays(1 - LastDayOfMonth.DayOfWeek)
NthMondayOfMonth = LastMondayOfMonth
End If
GetNthMondayOfMonth = NthMondayOfMonth
End Function

This page is powered by Blogger. Isn't yours?