Asp.NET 2.0 CustomValidator and Client script validation

When it’s time to validate the user input on an asp.net page the validators control come to the rescue. If you want to do some custom validation that is not supported by the standard validator controls that ship with asp.net 2.0 you can resort to use asp:CustomValidator. Let’s do a little example on how to build a CustomValidator to check the text length of a textbox, useful I need to be sure that that the user insert a string whose length is between two values. First of all here is the declaration of the control on the page.

<asp:CustomValidator 
    ControlToValidate=”txtBoxData” 
    ID=”valLength” Display=”Dynamic”
    runat=”server” CssClass=”lblError”
    ErrorMessage=”MyValidator”
    OnServerValidate=”ValidateLength” 
    min=”5″ max=”15″ 
    ClientValidationFunction=”CheckLen”></asp:CustomValidator>

As you can see the control simply defines the function that is called during a server validation with OnServerValidate attribute, the function is really simple and is not worth to show here. To enable client side validation I need to specify a javascript function with ClientValidationFunction attribute, and also, as you can see, I added the two attributes min and max, that are signaled as warning in the designer but are useful to specify minimum and maximum length for the string contained in the textbox. Here is the client side javascript function that does the validation.

<script language=”JavaScript”>
//<!–
  function CheckLen(sender, args)
  {
    var min;
    var max;
      min = sender.attributes[‘min’].nodeValue;
      max = sender.attributes[‘max’].nodeValue;
      args.IsValid = args.Value.length >= min && args.Value.length <= max;
  }
// –>
</script>

The function is really simple, just retrieve the min and max value from the attributes of the sender and check against the args.Value.length that contains the length of the string to validate. To tell asp.net infrastructure code the result of the validation, simply set the value of args.IsValid to either true or false. If you need to set the min and max value from server code you can add dynamically attributes to the validator.

valLength.Attributes.Add(“min”, “5”)
valLength.Attributes.Add(
“max”, “15”)

CustomValidator will bring you all the infrastructure code needed to create a validator, leaving to you the task to only create the server side validation function and a client validation functions that is needed only if you want to leverage the client side validation for the page.

Alk.

Importance of good mappings in nhibernate

Suppose you have created some classes to access customer table in northwind database, a good mapping could be the following.

<?xml version=1.0 encoding=utf-8 ?>
<hibernate-mapping xmlns=urn:nhibernate-mapping-2.2
                  namespace=Nablasoft.NhibernateGen.Domain.Entities
                  assembly=Nablasoft.NhibernateGen.Domain>
   <class name=Customer table=Customers lazy=true >
      <id name=Id column=CustomerId>
         <generator class=identity />
      </id>
      <component name=AddressInfo>
         <property name=Address />
         <property name=City />
         <property name=Region />
         <property name=PostalCode />
         <property name=Country />
      </component>
 
      <component name=ContactInfo>
         <property name=ContactName/>
         <property name=ContactTitle/>
      </component>
 
 
      <property name=CompanyName/>
 
      <property name=FaxNumber column=Fax />
      <property name=PhoneNumber column=Phone/>
   </class>
</hibernate-mapping>

Undoubtedly this mapping works well, but what happens when you use SchemaExport class to recreate the schema of the database from the mapping? The answer is that the table that gets created is not so appealing.

create table dbo.Customers (
  CustomerId NVARCHAR(255) IDENTITY NOT NULL,
   Address NVARCHAR(255) null,
   City NVARCHAR(255) null,
   Region NVARCHAR(255) null,
   PostalCode NVARCHAR(255) null,
   Country NVARCHAR(255) null,
   ContactName NVARCHAR(255) null,
   ContactTitle NVARCHAR(255) null,
   CompanyName NVARCHAR(255) null,
   Fax NVARCHAR(255) null,
   Phone NVARCHAR(255) null,
   primary key (CustomerId)
)

As you can see all fields are of type nvarchar(255) a default length chosen by nhibernate, moreover the table has no index. Even if you have a legacy database to work with, it is important to include in the mappings all the information to create a correct database schema from the mappings, this is expecially useful for testing purpose, for example if you want to use SQLLite to do some test. As example here is a more verbose mapping, but that generates a better table.

<?xml version=1.0 encoding=utf-8 ?>
<hibernate-mapping xmlns=urn:nhibernate-mapping-2.2
                  namespace=Nablasoft.NhibernateGen.Domain.Entities
                  assembly=Nablasoft.NhibernateGen.Domain>
   <class name=Customer table=Customers lazy=true >
      <id name=Id column=CustomerId type=Int32 >
         <generator class=identity />
      </id>
      <property name=CompanyName not-null=true length=40 type=String
               index=CompanyName/>
 
      <component name=ContactInfo>
         <property name=ContactName not-null=false length=30 type=String/>
         <property name=ContactTitle not-null=false length=30 type=String />
      </component>
 
      <component name=AddressInfo>
         <property name=Address not-null=false length=60 type=String />
         <property name=City not-null=false length=15 type=String
                  index=City/>
         <property name=Region not-null=false length=15 type=String
                  index=Region/>
         <property name=PostalCode not-null=false length=10 type=String
                  index=PostalCode/>
         <property name=Country not-null=false length=15 type=String/>
      </component>
 
      <property name=PhoneNumber column=Phone not-null=false length=24 type=String/>
      <property name=FaxNumber column=Fax not-null=false length=24 type=String/>
 
   </class>
</hibernate-mapping>

As you can see each field has a length attribute that explicitly tells nhibernate the length of the field in the database, to recreate a table identical to the one in northwind I also include the attribute Index into all the columns that I want to be indexed. In this example we have all 1 column index, but if you want an index that span multiple columns it is sufficient to include an index attribute of the same value in multiple property tags. Note also the use of not-null attribute, used to tell nhibernate that the column CompanyName is mandatory. Now here is how the SchemaExport generate the table in the database

create table dbo.Customers (
  CustomerId INT IDENTITY NOT NULL,
   CompanyName NVARCHAR(40) not null,
   ContactName NVARCHAR(30) null,
   ContactTitle NVARCHAR(30) null,
   Address NVARCHAR(60) null,
   City NVARCHAR(15) null,
   Region NVARCHAR(15) null,
   PostalCode NVARCHAR(10) null,
   Country NVARCHAR(15) null,
   Phone NVARCHAR(24) null,
   Fax NVARCHAR(24) null,
   primary key (CustomerId)
)
create index Region on Customers (Region)
create index PostalCode on Customers (PostalCode)
create index City on Customers (City)
create index CompanyName on Customers (CompanyName)

As you can see create a complete and correct mapping it is a very important thing.

Alk.

About speaking error “The file ‘xxx’ has not been pre-compiled, and cannot be requested”

Today I must update a web application in a pre-production environment. I use asp.net 2.0 and web deployment project, so I set the site to be precompiled, not updatable and all assemblies merged in a unique assembly. I build the site, test in developing machine than I do a xcopy deploy on preproduction server.

When I access the site I saw the IE progress bar that runs…..the login page does not appears…. I ask to another developer to try to access the site, and he told me that nothing appears on the page. The fun thing is that after 2 minutes another developer called me and said “hey, whath is happening to that machine, I received 1600 mail of exception”. I begin to faint…… immediately I stopped the server and began to look at the mail. I have an error handling module that intercepts all unhandled exception, create a log with enterprise library configured to send a mail to a distribution group of the developer of the project, and finally redirect the user on a error page called error.aspx.

All the 1600 mails reported the error “The file ‘error.aspx’ has not been pre-compiled, and cannot be requested“, this error is generated for each page in the site. After a short search I found that this problem gets generated when the asp.net engine fails to load the assembly with precompiled code of the site, since the site is not updatable asp.net cannot build the page and generate this error.

After a short brainstorming I realized that the only reason the assembly can fail to load in pre production machine is some unresolved references, after short time I realized that we use a component that was updated in the last few days and was not updated in pre production machine. I upload the assembly, installed in the GAC and the site begin to work.

One thing that I did not like is the fact that the error message “The file xxx has not been pre-compiled, and cannot be requested” gives not any clue to find the error, maybe it could be better if the asp.net engine will show you a more meaningful error such as “cannot load assembly xxxxxxx version yyyyy”, it would have save me a 45 minutes of work.

Alk.

Sometimes it is worth to take a look at….

At…how you had done some queries into the db J. I’ve a project where I need to check every day expired records in a table. That table has a lastUpdateDate column, I simply check if the time passed from last update is greater than a given amount and for each record I must create a message for the user that owns the record, the relation is one to one, one user for each record. Moreover I do not want the user to receive a message each day, but a message even 30 days if he do not update the record, so I need to check also if a message was generated for each expired record. A couple of month ago I create a query with this structure.

Select recordId, ownerUserId from TableA
where lastUpdateDate < @expiredDate
and NOT EXISTS(Select * from Messages where MessageUserID = ownerUSerId and messageDate < DATEADD(dd, -30, GETDATE())

That query simply return each expired record from TableA only if there is not a message directed to the owner of the record in the last 30 days. The real query is more complex with some other conditions but the general scheme is this. Today I saw that the query returned a timeout in the last execution cycle….I ran the query and found with horror that the query needs 90 seconds to execute. This sounds very strange, because tableA had 5.000 record and message table around 12.000……the server is not under heavy load.

When I look at the execution plan I saw a strange merge join operation with an output of 13.000.000 rows, I had no time to investigate so I decided to try to change the structure of the query because I know that not exists correlated subquery is often not the best solution for performance. The new query has the following structure

Select MessageUserId into #tmpMessages
from Messages where messageDate < DATEADD(dd, -30, GETDATE())

Select recordId, ownerUserId from TableA
where lastUpdateDate < @expiredDate
and NOT ownerUserId IN (Select MessageUserId from #tmpMessages)

I simply store in a temporary table all messages directed to all users in the last 30 days, then I use the NOT IN operator…when I run the query the execution time is less than one second……

Alk.

Asp.Net 2.0 session stored in Sql

Sometimes it is preferable to store the session of asp.net in sql, I prefer to use a distinct database for each application, the command to create such database is

aspnet_regsql.exe -S localhost\isntancename -U sa -P sapwd -d databasename -ssadd -sstype c

Notice the use of –sstype c parameter that forces the script to create a custom database, without that argument the scritp does not permit you to use a custom database name to store the session. In web.config you should also state that you want to use a custom type sql database with allowCustomSqlDatabase=”true”.

   <sessionState 
            timeout=60 
            allowCustomSqlDatabase=true 
            mode=SQLServer 
            sqlConnectionString=Database=EasyCVSessionStore;Server=localhost\SQL2000;…/>

Also avoid to copy session database around, and recreate always with aspnet_regsql.exe, it avoid you some headache.

Alk.