Sunday, November 21, 2010

XMLHttpRequest.responseXML returns null

Issue: [JavaScript] Loading XSLT via XMLHttpRequest.responseXML always results in null.

Reason: It is due to incorrect MIME type "application/octet-stream" returned by the HTTP response found in "Content-Type" response header.

   var responseMIMEType = xhttp.getResponseHeader('Content-Type'); // xhttp is XMLHttpRequest object.

According to the XMLHttpRequest specification 3.7.5, XMLHttpRequest.responseXML will return null if the HTTP response header 'content-type' is not XML.

Resolution: If resultant responseXML is null, we could force to override the MIME type or convert XML text to XML DOM.

  • For the latest modern browsers like Mozilla (both SeaMonkey 2.0.10 and Firefox 3.5), Safari 5.0 and Opera 10.51, we could simply instruct the browser to override the MIME Type when it receives the document. Do this before XMLHttpRequest.send.
        // Override MIME type.  Set this before send. 
        if (xhttp.overrideMimeType)   xhttp.overrideMimeType('text/xml');
  • For others except IE, we could set up another defend mechanism by converting XML string to DOM with DOMParser.
        // Create XML DOM from string
        xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml');
  • IE doesn't support MIME overridden. And you may not be able to use DOMParser to parse XML string either. Otherwise, you may step into another error.

    The stylesheet does not contain a document element. The stylesheet may be empty, or it may not be a well-formed XML document.

    If it is the case, for IE, do this:

     xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
     xmlDoc.async = false;
     xmlDoc.loadXML(xhttp.responseText);
    

    The code for loading XML and XSLT may look like the following:

      var xhttp;
      function loadXMLDoc(url) {
        if (window.XMLHttpRequest) {
          xhttp = new XMLHttpRequest();
        }
        else {
          xhttp = new ActiveXObject('Microsoft.XMLHTTP');
        }
        
        xhttp.open('GET', url, false);  
        
        if (xhttp.overrideMimeType) 
          xhttp.overrideMimeType('text/xml');       
    
        xhttp.send(null);
     
    var xmlDoc = xhttp.responseXML; if (!xmlDoc) { if (isIE) { xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); xmlDoc.async = false; xmlDoc.loadXML(xhttp.responseText); } else xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml'); }
    return xmlDoc; }
    One more thing, if you run or test this code under a file-system-based environment such as Visual Studio instead of being served by a Web server, the above stylesheet error on IE may still occur. To resolve this, both XML and XSLT must be loaded by the same method like this:
        var xmlDoc;
        if (isIE) {
          xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
          xmlDoc.async = false;
          xmlDoc.loadXML(xhttp.responseText);
        }
        else {
          xmlDoc = xhttp.responseXML;
          if (!xmlDoc) {
            xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml');
          }
        }

After the XML and the XSLT have successfully been loaded, we can transform the XML using the loaded XSLT.

  // Transform XML using XSLT
  // Here both xml and xslt are xml dom objects
  function getTransformedXML(xml, xslt) {
    if (window.ActiveXObject)  { // for IE
       return xml.transformNode(xslt);      
    }
    // for Mozilla (SeaMonkey and FireFox), Safari, Opera, etc.
    else if (document.implementation && document.implementation.createDocument) {
      var xsltProcessor = new XSLTProcessor();
      xsltProcessor.importStylesheet(xslt);
      return xsltProcessor.transformToFragment(xml, document);
    }
     
    return null;
  }

The complete code

Here the code is revised a bit to locate the MS latest XML parser for use instead of using the default old version, Microsoft.XMLDOM.
  var xhttp;
  function loadXMLDoc(url) {
    if (window.XMLHttpRequest) {
      xhttp = new XMLHttpRequest();
    }
    else {
      xhttp = new ActiveXObject('Microsoft.XMLHTTP');
    }
    
    xhttp.open('GET', url, false);  

    if (xhttp.overrideMimeType) 
      xhttp.overrideMimeType('text/xml');       

    xhttp.send(null);

    if (isIE) {
      xmlDoc = getMSXmlParser();
      xmlDoc.async = false;
      xmlDoc.loadXML(xhttp.responseText);
    }
    else {
      xmlDoc = xhttp.responseXML;
      if (!xmlDoc) {
        xmlDoc = (new DOMParser()).parseFromString(xhttp.responseText, 'text/xml');
      }
    }
    return xmlDoc;
  }

  // Transform XML using XSLT
  // Here both xml and xslt are xml dom objects
  function getTransformedXML(xml, xslt) {
    if (window.ActiveXObject)  { // for IE
       return xml.transformNode(xslt);      
    }
    // for Mozilla (SeaMonkey and FireFox), Safari, Opera, etc.
    else if (document.implementation && document.implementation.createDocument) {
      var xsltProcessor = new XSLTProcessor();
      xsltProcessor.importStylesheet(xslt);
      return xsltProcessor.transformToFragment(xml, document);
    }
     
    return null;
  }
  
function getMSXmlParser() { var parser = ['Msxml2.DOMDocument.6.0', 'Msxml2.DOMDocument.5.0', 'Msxml2.DOMDocument.4.0', 'Msxml2.DOMDocument.3.0', 'MSXML2.DOMDocument', 'Microsoft.XMLDOM']; // the same as MSXML.DOMDocument for (var i in parser) { try { var xParser = new ActiveXObject(parser[i]); if (xParser) { return xParser; } } catch (e) {} } return null; }

Tuesday, September 28, 2010

Free calls to US by gmail is NOT available to anywhere yet!

In Aug, 2010, I was very excited to learn the newly added gmail's phone call feature and wonder I could make use of it when I travel to Asia in September. Unfortunately, this feature is not available when I was out of the USA. Luckily I have my MagicJack around.

Re-visiting the gmail official blog, I found that they have an update on Aug 26 saying this feature is only available in the US. Thus, this feature is not useful to me.

Friday, August 27, 2010

BIOSAgent is no longer available

I have been using BIOSAgent since 2007 when I was working in a project that extensively dealt with system detection. It helped me to fix a lot BIOS issues by providing me every single piece of information stored in the BIOS. Sadly, this wonderful desktop, standalone BIOSAgent is no longer available for download now. The last version is 3.66. I don't have the last version but up to v3.61.

BIOSAgentPlus is the successor or the replacement. The current version I tested is 2.2010.1.8. Unlike BIOSAgent, BIOSAgentPlus requires Internet connection for data interpretation. It doesn't scan your system over the Internet; currently you still have to download an EXE file to do the scanning and then interpret the scanned data at their online server.

Basically, there are two ways to submit the scanned data:

  • Default: Run BIOSAgentPlus while you are connected to the Internet. After the scanning, BIOSAgentPlus will auto-fetch the data.
  • Offline: Set "Work Offline" (see my offline section below to see how) and manually submit your data to DriverAgent (another tool by eSupport).

If the scanned data is auto-fetched by your local copy of BIOSAgentPlus, the result page will display not only the information of the BIOS but also every driver of your system. Any new driver(s) available to your system will also be shown in that page. If you post the data yourself, the BIOS information will not be displayed but you are still able to view the information of your existing and new available drivers online. Thus, the manual method at this point is useless. After all, the data is actually interpreted by DriverAgent, which won't interpret BIOS data.

If you scan your system offline, BIOSAgentPlus will automatically open a text XML file for you. Do a search on <D:smbios> in the file and you should find all the BIOS data. On XP, this file is stored in the folder of %TEMP%\DriverAgent.

Regardless of which method you use to submit and interpret the scanned data, you can always be allowed to choose to "Save Scan Data" for future use. This simply saves the reference data in a HTML file. Next time, you double-click on it and the data will be fetched and viewed from their online server. Again, Internet connection is absolutely required. With this, the BIOS data is always interpreted and displayed in the page.

I personally don't like the BIOSAgentPlus because I am not interested in the driver information or its upgrade. I just want to instantly see what data was written in the BIOS. In addition, when I am doing this, most of time the Internet connection is not available or the system has not completely configured well yet. With BIOSAgentPlus, I have to do more steps to view the data or I interpret data myself. I really miss the nice and simple BIOSAgent.


How to Run BIOSAgentPlus Offline (for version: v2.2010.1.8)

As usual, the downloaded EXE file doesn't require to install; simply download and run it. When BIOSAgentPlus runs and performs the scanning, on XP, the BIOSAgentAgent icon will be displayed in your system tray.

Right click the icon, select "Work Offline" if the Internet connection is not available. If you need scan again, then click "Scan". Note that The Scan button will be grayed out while BIOSAgentPlus is scanning.

As soon as the scanning is done, a text file named DriverAgent.xml.txt will be opened for you. Follow the instruction in that file and submit its XML content to http://driveragent.com/manual.php. As discussed before, the BIOS information will not be interpreted when you manually submit data to DriverAgent.

Wednesday, August 25, 2010

MagicJack: Contacts list is empty and drives inaccessible

MagicJack has done some upgrade within these two weeks that cause some users' contact list wiped forever (see their current announcement or the captured image). In addition, the MagicJack drives are no longer accessible after the softphone has been up and running; both drives occupied by MagicJack become "CD Drive" and "Removable Disk" while the softphone is in service.

Drives while softphone is loading right arrow Drives after softphone is loaded
MJ drives before softphone is loaded MJ drives after softphone is loaded
Accessible Inaccessible

I am lucky still to find my contacts list file in the device. Yours may still exist. See my how-to for details.

From now on, all the profile information including the contacts list and the call history will be stored in the MagicJack server. Thus, your softphone may indicate "Contacts list is empty" after the upgrade regardless of the file existence in your device. Currently there is no way to upload the list to the server but manually input yourself.

How to access the MagicJack device to find your contacts list

  • Unplug the MagicJack if currently connected.
  • Wait a few seconds for your system to clear the device.
  • Hold down the SHIFT key and then plug your MagicJack device in. DON'T RELEASE THE SHIFT KEY WHILE YOU'RE PLUGGING IT IN BEFORE YOU HEAR THE USUAL DEVICE CONNECTION SOUND. The SHIFT key will temporarily disable the softphone auto-run. Therefore, you should see two drives named "magicJack".
  • Go to the drive which contains a folder called "magicJack" as well. Usually it is the 2nd one. Your contact list is stored in AddressBook.xml

Wednesday, August 18, 2010

Will Hong Kong be no longer to call Hong Kong?

Next month I will go Hong Kong for visit. At the same time, I may go Shanghai for a few days if possible. I found that quite a lot of people now go by train instead of flight. Thus I started my research on train. There is a direct train traveling between Hong Kong and Shanghai. Each way operates every other day.

I am surprised that a lot of people emphasized in their blogs to look for the word Jiulong for buying return train ticket in China. Huh? Jiulong? Where is it? Should it be Hong Kong? Some place in Hong Kong? Or the train station in Hong kong?

The train station in Hong Kong to Shanghai is called Hung Hom Train Station and located in Kowloon, Hong Kong. There is no place or a train station in Hong Kong called Jiulong. Then where is Jiulong?

After having done some digging, I finally reach my conclusion. Jiulong is the direct translation from Mandarin pronunciation to English. It is actually referring to Kowloon, the official name in Hong Kong. Why did China make this new name to Kowloon, Hong Kong? Has Kowloon already had a name in English? It is extremely confusing. This makes me wonder that Hong Kong may have a new name in English soon. 50 years unchanged? Certainly something have been changed on their ways. Look! The name is one of the changes. Would Hong Kong be called Xiang Gang in English in the future or whatever name in Mandarin? Note that Xiang Gang is the Mandarin pronunciation in English for "Hong Kong" according to one of the online dictionaries. Please correct me if it is incorrect.

Will Hong Kong people accept this new name? Unfortunately, a lot of people don't even know.


Friday, August 6, 2010

I'm certified - MCTS

keep smiling

Today is my lucky and happy day. I finally got certified with ASP.NET 3.5. I was preparing it for the past few months. Today I finally get an award of it.

My exam time was scheduled at 11:30AM today. I left and headed to the test center an hour ahead of my exam time. It was just about 10 minutes drive but I was almost late and missed the time because of the incorrect Google map. I kept calling the test center but I could not reach anyone. I was panic and even thought that I might be doomed today. The time was ticking and I had only 10 minutes left. Finally, my call got through and I arrived just right on time. Still, my feeling was not so good that I was afraid what happened before was to give me some sort of symbolic or indication that I could fail my exam. However, the result turns out fantastic (a perfect one) and it makes me keep smiling.

The actual exam time lasts 180 minutes (3 hours) for 45 questions. But my confirmation sheet (from Prometric registration) said 4 hours because the additional hour was used for reading instructions, answering questions for survey (compulsorily) and leaving comments to questions after test (voluntarily). When I started taking the exam, the time was set to 180 and then counted down. I spent about 2 hours and read twice. The full score is 1000 and the passing mark is 700.

Monday, June 21, 2010

Double-click and right-click are extremely slow

In the past few months, one of my laptops running with windows XP responded very slowly to both double-click and right-click. Double-click to open a photo less than 1 KB takes more than a minute while the right-click sometimes might freeze the Windows Explorer. Everything works fine if I use the program to open it. This problem is not specific to any type of files.

Unlike others, this laptop didn't install many applications. It doesn't even have Microsoft Office on it. But it carries adobe reader, Nero burning software, AVG anti-virus, Spybot Seach & Destroy, and the default graphic card software by Intel. That's. It was used to be super fast.

Using ShellExView for diagnostics and disabling every single Context Menu Handler, Icon Handler and Property Sheet don't help. Finally the System File Checker (sfc) command fixed the problem.

sfc /scannow

Be sure to have your Windows installation CD ready before you issue the above command.

Now I enabled all the extensions that were disabled via ShellExView. Everything is back to normal now.

In this article, it mentions Context Menu handler for Intel® Graphics could be the possible culprit. But it isn't in my case. The problem I had was not only right-click but also double-click. Thus, if you have the similar trouble, you may have to trial-and-error to rule out the issue. Using ShellExView to find the root cause is extremely time-consuming because every change you make require a reboot in order to see the actual effect. Luckily I finally have my problem solved.

Saturday, June 12, 2010

The LoadDataRow and Delete Methods of DataTable

Oftentimes we encounter problems when we perform update using LoadDataRow or rollback data using RejectChanges after the Delete method. Why do the methods sometimes not do what we want?

Googling a solution for LoadDataRow, we may find that most people resolve the update performed by LoadDataRow must call DataTable.AcceptChanges first. Does this solution work for you 100%? There are still some unresolved posts left unanswered. They follow the solution to call AcceptChanges but the problem still exists. Why?

Deleting a DataRow and then immediately performing RejectAccepts to rollback cause us to receive System.Data.RowNotInTableException. What is it going on? Is the DataRow marked as Deleted? Why can't I rollback?

The above problems are related to either the RowState of a DataRow or if we set up the constraints on DataTable correctly.

continue Assuming there is no constraint problem, the DataRow must be either Unchanged or Modified when we perform an update using the LoadDataRow method on an existing DataRow. The same principle applies to the RejectChanges after calling the Delete method. Apparently, the operations requires an Original version existed for the DataRow. When the RowState of DataRow is Unchanged or Modified, an Original version of the DataRow has been created/existed. Thus, any operations after it won't cause a problem.

continueIf you receive a System.Data.ConstraintException in regard to System.Data.UniqueConstraint.CheckConstraint, please ensure the primary key set up properly. Most likely, setting the Unique property of a DataColumn without primary key to true will cause this problem to occur.

continueIf you receive a System.Data.ConstraintException in regard to System.Data.DataTable.EnableConstraints, please ensure an Original version existed for the DataRow. It means the RowState of the DataRow is still in the state of Added and you need to commit changes first by calling the AcceptChanges method to update the state to Unchanged.

continueSimilarly, if you receive a System.Data.RowNotInTableException when you try to roll back the DataRow after having called the Delete method, a AcceptChanges call is required before the Delete method. After AcceptChanges, an Original version of the DataRow is created and the RowState of the DataRow is set to Unchanged. From this state onwards, any modification including the LoadDataRow method, or calling the Delete method and then the RejectChanges method can be performed.

To sum up what the RowState means is described in the following table.
DetachedThe DataRow is created but it hasn't been added to a DataTable yet. Since it does not exist in a DataTable, no operations can be performed at the DataTable level.
AddedThe DataRow is added to a DataTable but the DataTable has not accepted changes yet. At this stage, it requires AcceptChanges to create Original version for other update operations including rollback.
UnchangedThe DataRow has not changed since the last AcceptChanges call. At this stage, an Original version of the DataRow has already existed. Thus, any operations can be performed without a problem.
ModifiedThe DataRow has been modified since the last AcceptChanges call. Like Unchanged, an Original version of the DataRow has already existed in this stage. A DataRow with Modified row state can continously be modified but the RejectChanges will only roll the data back to the last AcceptChanges call. Any modification made in between is disgarded because the Original version of the DataRow is only pointing the copy when the last AcceptChanges is called.
DeletedThe DataRow was deleted by using the Delete method of the DataRow only when the RowState of the DataRow is Unchanged or Modified.

I also attach some test results I conducted for reference.

Friday, June 11, 2010

About DataRow.Delete, .AcceptChanges and .RejectChanges

See The LoadDataRow and Delete Methods of DataTable for intention.

Purpose

The following 3 tests [ Test 1 | Test 2 | Test 3 ] tell you about in which situation you can roll back your data and when you cannot.

This page is generated by the application developed in C# under Visual 2008 Profession SP + .NET 3.5 SP1 on Windows XP Professional SP3.

Test 1: No AcceptChanges before deletion
  • Set up a DataTable with 2 rows; so the initial RowState of all DataRows is Added.
  • Without AcceptChanges, immediately delete the 2nd DataRow using Delete method.
  • The RowState of the deleted row is not Deleted but Detached; The result is different from what it is said in the documentation [According to MSDN, it is currently supported in: 4, 3.5, 3.0, 2.0, 1.1, 1.0]. Please also see the test foot notes in blue.
  • Then RejectChanges after deletion; unfortunately, the System.Data.RowNotInTableException is received.

EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeAdded
B223456789RexBloomer12.00Bloomer, RexAdded

Initial state: [B223456789, Rex] RowState: Added
Version: Original doesn't not exist.
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

Using Delete method to delete [B223456789, Rex] at RowState: Added...
EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeAdded

The deleted row after deletion: RowState: Detached
Version: Original doesn't not exist.
Version: Current doesn't not exist.
Version: Proposed doesn't not exist.
Version: Default doesn't not exist.

This row has been removed from a table and does not have any data.  BeginEdit() will allow creation of new data in this row.
System.Data.RowNotInTableException
at System.Data.DataRow.GetDefaultRecord() at System.Data.DataColumn.CheckNullable(DataRow row) at System.Data.DataColumn.CheckColumnConstraint(DataRow row, DataRowAction action) at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent) at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Int32 position, Boolean fireEvent, Exception& deferredException) at System.Data.DataTable.SetNewRecord(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Boolean fireEvent) at System.Data.DataTable.RollbackRow(DataRow row) at System.Data.DataRow.RejectChanges() ...

After error: RowState: Detached
Version: Original doesn't not exist.
Version: Current doesn't not exist.
Version: Proposed doesn't not exist.
Version: Default doesn't not exist.

  • By definition of DataRow.Delete method, the row should have been marked as Deleted.
  • According to this, this bug should have been fixed in VS 2008. Apparently, it still exists. Or there may be a typo in the document. Originally it said, "If the RowState of the row is Added, the RowState becomes Detached and the row is removed from the table when you call AcceptChanges." Perhaps, it is meant to say, ""If the RowState of the row is Added, the RowState becomes Detached and the row is removed from the table when you call RejectChanges." Then everything will become and make more sense.
  • Because DataRow.RowState is set to Detached, we cannot roll it back by using RejectChanges.


Test 2: AcceptChanges before deletion
  • Set up a DataTable with 2 rows; so the initial RowState of all DataRows is Added.
  • Then AcceptChanges, which changes the RowState from Added to Unchanged.
  • Delete the 2nd DataRow using Delete method. The RowState of the 2nd DataRow is marked as Deleted now.
  • Immdiately RejectChanges after deletion, which changes the RowState from Deleted back to Unchanged

EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeAdded
B223456789RexBloomer12.00Bloomer, RexAdded

Initial state: [B223456789, Rex] RowState: Added
Version: Original doesn't not exist.
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

AcceptChanges. [B223456789, Rex] RowState: Unchanged
Version: Original Value: Rex
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

Using Delete method to delete [B223456789, Rex] at RowState: Unchanged...
EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeUnchanged
?????Deleted

The deleted row after deletion: RowState: Deleted
Version: Original Value: Rex
Version: Current doesn't not exist.
Version: Proposed doesn't not exist.
Version: Default doesn't not exist.

RejectChanges:
EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeUnchanged
B223456789RexBloomer12.00Bloomer, RexUnchanged

[B223456789, Rex] RowState: Unchanged
Version: Original Value: Rex
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

  • After deletion using Delete method, the RowState is set to Deleted.
  • Thus, RejectChanges can be used to roll back the DataRow to the point where the AcceptChanges method is last called.
  • The RowState of the deleted row is also rolled back to Unchanged. Everything looks exactly like before deletion.

Test 3: AcceptChanges, modification and then deletion
  • Set up a DataTable with 2 rows; so the initial RowState of all DataRows is Added.
  • Then AcceptChanges, which changes the RowState from Added to Unchanged.
  • Start BeginEdit ... EndEdit block to modify the 2nd row. Change the FirstName from Rex to Susan.
  • The expression column LastName and FirstName remains unchanged until the EndEdit method is called while FirstName is modified before calling EndEdit.
  • Then delete the 2nd DataRow using Delete method. The RowState of the 2nd DataRow is marked as Deleted now.
  • Immdiately RejectChanges after deletion, which changes everything back to the state when the AcceptChanges is last called; thus the RowState is rolled back all the way from Deleted to Unchanged

EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeAdded
B223456789RexBloomer12.00Bloomer, RexAdded

Initial state: [B223456789, Rex] RowState: Added
Version: Original doesn't not exist.
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

AcceptChanges. [B223456789, Rex] RowState: Unchanged
Version: Original Value: Rex
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

BeginEdit and modified the 2nd row:
 row.BeginEdit(); row["FirstName"] = "Susan";

EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeUnchanged
B223456789SusanBloomer12.00Bloomer, RexUnchanged
[B223456789, Susan] RowState: Unchanged
Version: Original Value: Rex
Version: Current Value: Rex
Version: Proposed Value: Susan
Version: Default Value: Susan

After EndEdit:
  row.EndEdit();

EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeUnchanged
B223456789SusanBloomer12.00Bloomer, SusanModified
[B223456789, Susan] RowState: Modified
Version: Original Value: Rex
Version: Current Value: Susan
Version: Proposed doesn't not exist.
Version: Default Value: Susan

Using Delete method to delete [B223456789, Susan] at RowState: Modified...
EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeUnchanged
?????Deleted

The deleted row after deletion: RowState: Deleted
Version: Original Value: Rex
Version: Current doesn't not exist.
Version: Proposed doesn't not exist.
Version: Default doesn't not exist.

RejectChanges:
EmpIdFirstNameLastNameSalaryLastName and FirstNameRowState
A123456789BettyeWilliams11.00Williams, BettyeUnchanged
B223456789RexBloomer12.00Bloomer, RexUnchanged

[B223456789, Rex] RowState: Unchanged
Version: Original Value: Rex
Version: Current Value: Rex
Version: Proposed doesn't not exist.
Version: Default Value: Rex

Interesting of this test is that the "LastName and FirstName" column remains unchanged [Bloomer, Rex] after the FirstName has been modified to Susan. The "LastName and FirstName" column is an Expression column set up by expression that contains the values from both FirstName and LastName columns. Changing FirstName will not refect the change immediately in the "FirstName and LastName" column until EndEdit is called.

Wednesday, June 9, 2010

LoadDataRow(object[], bool) and ConstraintException

See The LoadDataRow and Delete Methods of DataTable for intention.


Introduction

In this test, I will use LoadDataRow to perform content update on a DataTable with or without primary key constraint. bool AcceptChanges will be passed as a parameter in LoadDataRow(object[], bool). For result using LoadDataRow(object[], LoadOption), please see this.

When the LoadDataRow method is call, the following statement is used:
   employee.LoadDataRow(
      new object[] { "B223456789", "Denise", "Miller", 13.00 },
      false);  // don't AcceptChanges
When primary key is applied, the following statement is used and it will be executed before the LoadDataRow method:
   employee.PrimaryKey = new DataColumn[] {eid}; // eid is the EmpId column   

The DataTable used for the test has the five (5) columns and initially is populated with 2 sample data rows.
ColumnNameEmpIdFirstNameLastNameSalaryLastName and FirstName **
DataTypestringstringstringdecimalstring
MaxLength1035-1-170
Uniquetruefalsefalsefalsefalse
AllowDBNullfalsefalsefalsetruetrue

** The column of LastName and FirstName is defined as an expression column containing the values from the column of LastName and the column of FirstName.

Every test is based on this table with the initial sample data. The 2nd row is the target to update by using the LoadDataRow method. A grid shown in each test is represented the final result of the DataTable even though the update performed by the LoadDataRow method may fail. Execution messages about the RowState of the 2nd row and its versions are shown afterwards. For clarity, the initial state of the 2nd row is also attached to each test for reference.

The rest of the page is generated by the application developed in C# under Visual 2008 Profession SP + .NET 3.5 SP1 on Windows XP Professional SP3.


Set up a DataTable with 2 rows
- EmpId is the unique key (eid.Unique = true).
- Primary key is not set up initially.

  • Initial Table:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


No primary key applied
Expect errors [System.Data.UniqueConstraint.CheckConstraint]!
The last row remains unchanged.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    Column 'EmpId' is constrained to be unique.  Value 'B223456789' is already present.
    System.Data.ConstraintException
    at System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action) at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent) at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Int32 position, Boolean fireEvent, Exception& deferredException) at System.Data.DataTable.InsertRow(DataRow row, Int32 proposedID, Int32 pos, Boolean fireEvent) at System.Data.DataRowCollection.Add(Object[] values) at System.Data.DataTable.UpdatingAdd(Object[] values) at System.Data.DataTable.LoadDataRow(Object[] values, Boolean fAcceptChanges) ...

    After error: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, no AcceptChanges before or after LoadDataRow
Expect errors [System.Data.UniqueConstraint.CheckConstraint]!
The last row remains unchanged.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    Column 'EmpId' is constrained to be unique.  Value 'B223456789' is already present.
    System.Data.ConstraintException
    at System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action) at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent) at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Int32 position, Boolean fireEvent, Exception& deferredException) at System.Data.DataTable.InsertRow(DataRow row, Int32 proposedID, Int32 pos, Boolean fireEvent) at System.Data.DataTable.UpdatingAdd(Object[] values) at System.Data.DataTable.LoadDataRow(Object[] values, Boolean fAcceptChanges) ...

    After error: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


No primary key, AcceptChanges before LoadDataRow
Expect errors [System.Data.UniqueConstraint.CheckConstraint]!
The last row remains unchanged.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Apply AcceptChanges(): [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    Column 'EmpId' is constrained to be unique.  Value 'B223456789' is already present.
    System.Data.ConstraintException
    at System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action) at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent) at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Int32 position, Boolean fireEvent, Exception& deferredException) at System.Data.DataTable.InsertRow(DataRow row, Int32 proposedID, Int32 pos, Boolean fireEvent) at System.Data.DataRowCollection.Add(Object[] values) at System.Data.DataTable.UpdatingAdd(Object[] values) at System.Data.DataTable.LoadDataRow(Object[] values, Boolean fAcceptChanges) ...

    After error: [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, AcceptChanges before LoadDataRow
No error! The last row is updated.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Apply AcceptChanges(): [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Modified
    Version: Original Value: Rex
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise


No primary key, LoadDataRow in conjunction with Begin/EndLoadData
Expect errors [System.Data.DataTable.EnableConstraints()]!
Duplicate rows are found.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    BeginLoadData(): turn off notification/constraints/indexing.

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

    EndLoadData(): turn on validation, constraints, etc.

    Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
    System.Data.ConstraintException
    at System.Data.DataTable.EnableConstraints() at System.Data.DataTable.set_EnforceConstraints(Boolean value) at System.Data.DataTable.EndLoadData() ...

    After error: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, LoadDataRow in conjunction with Begin/EndLoadData
Expect errors [System.Data.DataTable.EnableConstraints()]!
Duplicate rows are found.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    BeginLoadData(): turn off notification/constraints/indexing.

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

    EndLoadData(): turn on validation, constraints, etc.

    Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
    System.Data.ConstraintException
    at System.Data.DataTable.EnableConstraints() at System.Data.DataTable.set_EnforceConstraints(Boolean value) at System.Data.DataTable.EndLoadData() ...

    After error: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, LoadDataRow in conjunction with Begin/EndLoadData, ApplyChanges before BeginLoadData
No error! The last row is updated.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Apply AcceptChanges(): [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    BeginLoadData(): turn off notification/constraints/indexing.

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Modified
    Version: Original Value: Rex
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

    EndLoadData(): turn on validation, constraints, etc.

    [B223456789, Denise] RowState: Modified
    Version: Original Value: Rex
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

LoadDataRow(object[], LoadOption) and ConstraintException

See The LoadDataRow and Delete Methods of DataTable for intention.


Introduction

In this test, I will use LoadDataRow to perform content update on a DataTable with or without primary key constraint. LoadOption is passed in a parameter with this call LoadDataRow(object[], LoadOption). For result using LoadDataRow(object[], bool), please see this.

When the LoadDataRow method is call, the following statement is used:
   employee.LoadDataRow(
      new object[] { "B223456789", "Denise", "Miller", 13.00 },
      LoadOption.OverwriteChanges);
When primary key is applied, the following statement is used and it will be executed before the LoadDataRow method:
   employee.PrimaryKey = new DataColumn[] {eid}; // eid is the EmpId column  

Before presenting the test results, we will first set up a DataTable with 2 rows without primary key constraint. Our DataTable will have the following columns:
ColumnNameEmpIdFirstNameLastNameSalaryLastName and FirstName **
DataTypestringstringstringdecimalstring
MaxLength1035-1-170
Uniquetruefalsefalsefalsefalse
AllowDBNullfalsefalsefalsetruetrue

** The column of LastName and FirstName is defined as an expression column containing the values from the column of LastName and the column of FirstName.

Every test is based on this table with the initial sample data. The 2nd row is the target to update by using the LoadDataRow method. A grid shown in each test is represented the final result of the DataTable even though the update performed by the LoadDataRow method may fail. Execution messages about the RowState of the 2nd row and its versions are shown afterwards. For clarity, the initial state of the 2nd row is also attached to each test for reference.

The rest of the page is generated by the application developed in C# under Visual 2008 Profession SP + .NET 3.5 SP1 on Windows XP Professional SP3.


Set up a DataTable with 2 rows
- EmpId is the unique key (eid.Unique = true).
- Primary key is not set up initially.

  • Initial Table:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


No primary key applied
Expect errors [System.Data.UniqueConstraint.CheckConstraint]!
The last row remains unchanged.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    Column 'EmpId' is constrained to be unique.  Value 'B223456789' is already present.
    System.Data.ConstraintException
    at System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action) at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent) at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Int32 position, Boolean fireEvent, Exception& deferredException) at System.Data.DataTable.InsertRow(DataRow row, Int32 proposedID, Int32 pos, Boolean fireEvent) at System.Data.DataTable.LoadRow(Object[] values, LoadOption loadOption, Index searchIndex) at System.Data.DataTable.LoadDataRow(Object[] values, LoadOption loadOption) ...

    After error: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, no AcceptChanges before or after LoadDataRow
No error! The last row is updated.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise


No primary key, AcceptChanges before LoadDataRow
Expect errors [System.Data.UniqueConstraint.CheckConstraint]!
The last row remains unchanged.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Apply AcceptChanges(): [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    Column 'EmpId' is constrained to be unique.  Value 'B223456789' is already present.
    System.Data.ConstraintException
    at System.Data.UniqueConstraint.CheckConstraint(DataRow row, DataRowAction action) at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent) at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Int32 position, Boolean fireEvent, Exception& deferredException) at System.Data.DataTable.InsertRow(DataRow row, Int32 proposedID, Int32 pos, Boolean fireEvent) at System.Data.DataTable.LoadRow(Object[] values, LoadOption loadOption, Index searchIndex) at System.Data.DataTable.LoadDataRow(Object[] values, LoadOption loadOption) ...

    After error: [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, AcceptChanges before LoadDataRow
No error! The last row is updated.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Apply AcceptChanges(): [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise


No primary key, LoadDataRow in conjunction with Begin/EndLoadData
Expect errors [System.Data.DataTable.EnableConstraints()]!
Duplicate rows are found.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789RexBloomer12.00Bloomer, Rex
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    BeginLoadData(): turn off notification/constraints/indexing.

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

    EndLoadData(): turn on validation, constraints, etc.

    Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
    System.Data.ConstraintException
    at System.Data.DataTable.EnableConstraints() at System.Data.DataTable.set_EnforceConstraints(Boolean value) at System.Data.DataTable.EndLoadData() ...

    After error: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex


Primary key applied, LoadDataRow in conjunction with Begin/EndLoadData
No error! The last row is updated.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    BeginLoadData(): turn off notification/constraints/indexing.

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

    EndLoadData(): turn on validation, constraints, etc.

    [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise


Primary key applied, LoadDataRow in conjunction with Begin/EndLoadData, ApplyChanges before BeginLoadData
No error! The last row is updated.

  • Resulted DataTable:
  • EmpIdFirstNameLastNameSalaryLastName and FirstName
    A123456789BettyeWilliams11.00Williams, Bettye
    B223456789DeniseMiller13.00Miller, Denise

  • Status duing execution:
  • Initial state: [B223456789, Rex] RowState: Added
    Version: Original doesn't not exist.
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    Apply AcceptChanges(): [B223456789, Rex] RowState: Unchanged
    Version: Original Value: Rex
    Version: Current Value: Rex
    Version: Proposed doesn't not exist.
    Version: Default Value: Rex

    BeginLoadData(): turn off notification/constraints/indexing.

    Executing LoadDataRow(...)
    After LoadDataRow: [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

    EndLoadData(): turn on validation, constraints, etc.

    [B223456789, Denise] RowState: Unchanged
    Version: Original Value: Denise
    Version: Current Value: Denise
    Version: Proposed doesn't not exist.
    Version: Default Value: Denise

Saturday, May 8, 2010

Extract File Extension and Check Image Format

[ Intention | Requirements | Discussion and Implementation | Final Code Snippets ]

Intention [top]

Once in a while, we all may need to extract the file extension from a file name. In the past, when I was doing it in Java, I used to use the String.lastIndexOf() to search for the last period in the filename and then extract the rest of the substring. We can apply the same idea in .NET using String.LastIndexOf() as well. However, .NET provides a single static method call; it makes everything a lot easier. However, this static method internal implementation is not using regular expression to extract the file extension. Yesterday, I was browsing around my old code and thinking that I would like to use regular expression to capture the file extension. Thus, I pick the portion of my file upload program for conversion. First, I will start with the requirements for this file upload portion and then go into the detail discussion. Finally I will present the complete code snippets. In this exercise, I will also show you how to check the image format.

Requirements [top]

  1. Use regular expression to capture or extract the file extension from the file name but the leading period (.) of the resulted file extension must be trimmed off. For example, .txt becomes txt.
  2. A file extension cannot contain spaces.
  3. Save the uploaded file if only if the file extension is one of the listed extensions (see the list below) and the file must be one of the supported image formats.
  4. Supported file extensions are BMP, GIF, PNG, TIFF, JPG, and JPEG.
  5. Supported image formats are BMP, GIF, PNG, TIFF and JPEG. They are also corresponding to the above file extensions

Discussion and Implementation [top]

There are a few ways to extract a file extension from a file name. One of the simple ways is to use System.IO.Path.GetExtension() method by passing the file name as a parameter, e.g.,

   System.IO.Path.GetExtension("abc.txt")

In this example, .txt is the extracted file extension and it always comes with a leading period (.). In order to fit our 1st requirement, we could remove the leading period from the resulted file extension. Unfortunately, the requirement binds us to use regular expression. Thus, the regular expression should look like the following (non-named capturing):

      \.([^.]+)$

According to the 2nd requirement (the extension cannot contain spaces), the previous regular expression is revised and becomes:

      \.([^(\s|.)]+)$

In C#, we can do the following to fulfill the #1 and the #2 requirements:

      String pattern = @"\.([^(\s|.)]+)$";        
      String fileExt = null;

      Regex r = new Regex(pattern);
      Match m = r.Match(filename);
      if (m.Success) {
        fileExt = m.Groups[1].Value;
      }

As we can see, the captured text is always placed inside the group 1 (at index 1), not 0. What if we have many captured groups? It could be hard to remember which one is what. Luckily, .NET supports name capturing. It greatly helps in coding and back-reference. In this example, the requirement is simple and we don't have to use named capture. But to exercise this named capture feature, we could revise the regular expression a bit and then reference the name in our code. Let's say that the captured text name is called ext. The revised code snippet becomes:

      String pattern = @"\.(?<ext>[^(\s|.)]+)$";        
      String fileExt = null;

      Regex r = new Regex(pattern);
      Match m = r.Match(filename);
      if (m.Success) {
        fileExt = m.Groups["ext"].Value; 
      }

A part of the third requirement is to check the resulted file extension against our supported file extension list. We can continuously get help from regular expression. Let's define our pattern for matching. In C#, we can write this:

      String SupportedImageExtPatterns = "^(BMP|GIF|PNG|TIFF|JPE?G)$";

To check the resulted file extension against the list, in C#, we can implement this way:

      Match m1 = Regex.Match(fileExt, 
                             SupportedImageExtPatterns,
                             RegexOptions.IgnoreCase);
      if (m1.Success) {
        ...
      }
or using one single statement,
      if (Regex.Match(fileExt, 
                      SupportedImageExtPatterns,
                      RegexOptions.IgnoreCase).Success) {  
          ...
      }

How to check the file is one of the supported image format? Since the file is uploaded via HTTP, the file could be anything. For this matter, we first need to confirm if the file is an image by referencing to the file's Stream object and then try to convert it to an image. If we get an ArgumentException, it is not an image. Let's say the file upload object is called FileUpload1. In C#, we can write the following to execute the idea.

      System.Drawing.Image theImage = null;
      try {
        theImage = System.Drawing.Image.FromStream(FileUpload1.PostedFile.InputStream);
      }
      catch (System.ArgumentException) {  // not an image
        ...
      }
After we've confirmed that the file is an image, we can go ahead to check the image format against our list with the help of the Equals() method of System.Drawing.Imaging.ImageFormat. For convenience, I group all the required System.Drawing.Imaging.ImageFormat objects together in an array so that I can iterate through the array for checking.
       System.Drawing.Imaging.ImageFormat[] SupportedImageFormats = { 
             System.Drawing.Imaging.ImageFormat.Bmp,      
             System.Drawing.Imaging.ImageFormat.Gif,      
             System.Drawing.Imaging.ImageFormat.Png,
             System.Drawing.Imaging.ImageFormat.Tiff,
             System.Drawing.Imaging.ImageFormat.Jpeg      
       };

The code snippet for checking image format in C# is illustrated below:
       System.Drawing.Imaging.ImageFormat[] SupportedImageFormats = { 
         System.Drawing.Imaging.ImageFormat.Bmp,      
         System.Drawing.Imaging.ImageFormat.Gif,      
         System.Drawing.Imaging.ImageFormat.Png,
         System.Drawing.Imaging.ImageFormat.Tiff,
         System.Drawing.Imaging.ImageFormat.Jpeg      
       };

       System.Drawing.Image theImage = null;
       try {
         theImage = System.Drawing.Image.FromStream(FileUpload1.PostedFile.InputStream);

         for (int i=0; i < SupportedImageFormats.Length; i++) {        
           if (theImage.RawFormat.Equals(SupportedImageFormats[i])) {
             ...
             break;
           }
         }
         ...

       }
       catch (System.ArgumentException) {  // not an image
         ...
       }

As soon as we confirm that the file is an image and it is also one of our expected formats, we can simply use the FileUpload object SaveAs() method by passing the absolute pathname. Let's say the directory to save is called Uploads. The statement may look something like below:

   FileUpload1.SaveAs(Server.MapPath("~/Uploads/") + FileUpload1.FileName);
Up to here, our mission is accomplished from extracting the file extension, checking image format to saving the file. The complete code snippets are presented in the next section.

Final Code Snippets [top]

The followings are two pieces of code snippets summarized for the above discussions. The difference between these two pieces of code is: one is using non-named capture to extract the file extension while the other is named capture. Other than that, everything is identical.

Using non-named capture to extract the file name [ Final Code Snippets | top ]

Note that
  1. the file upload control object is called FileUpload1, and
  2. the save directory is called Uploads and located at the root directory.
// declare the pattern for Requirement #1 and #2 to capture the file extension.
// the following pattern is using non-named capture.
String pattern = @"\.([^(\s|.)]+)$";        
String fileExt = null;

Regex r = new Regex(pattern);
Match m = r.Match(FileUpload1.PostedFile.FileName); 
if (m.Success) {
  // capture the file extension without the period
  // here we use non-named capture group
  fileExt = m.Groups[1].Value;

  // Requirement #4 - the supported file extensions
  String SupportedImageExtPatterns = "^(BMP|GIF|PNG|TIFF|JPE?G)$";

  if (Regex.Match(fileExt, 
                  SupportedImageExtPatterns,
                  RegexOptions.IgnoreCase).Success) {

    // Requirement #5 - the supported image formats 
    System.Drawing.Imaging.ImageFormat[] SupportedImageFormats = { 
          System.Drawing.Imaging.ImageFormat.Bmp,      
          System.Drawing.Imaging.ImageFormat.Gif,      
          System.Drawing.Imaging.ImageFormat.Png,
          System.Drawing.Imaging.ImageFormat.Tiff,
          System.Drawing.Imaging.ImageFormat.Jpeg      
    };

    System.Drawing.Image theImage = null;
    try {
      theImage = System.Drawing.Image.FromStream(FileUpload1.PostedFile.InputStream);
    
      for (int i=0; i < SupportedImageFormats.Length; i++) {        
        if (theImage.RawFormat.Equals(SupportedImageFormats[i])) {
           // Requirement #3 - save the file only if it is our expected image format.
           FileUpload1.SaveAs(Server.MapPath("~/Uploads/") + FileUpload1.FileName);
           break;
        }
      }      
    }
    catch (System.ArgumentException) {  // not an image
      ...  
    }
  }
}

Using named capture to extract the file name [ Final Code Snippets | top ]

Note that
  1. the file upload object is called FileUpload1, and
  2. the save directory is called Uploads and located at the root directory.
// declare the pattern for Requirement #1 and #2 to capture the file extension.
// the following pattern is using named capture.
String pattern = @"\.(?<ext>[^(\s|.)]+)$";        
String fileExt = null;

Regex r = new Regex(pattern);
Match m = r.Match(FileUpload1.PostedFile.FileName);
if (m.Success) {
  // capture the file extension without the period
  // here we use named capture group
  fileExt = m.Groups["ext"].Value;

  // Requirement #4 - the supported file extensions
  String SupportedImageExtPatterns = "^(BMP|GIF|PNG|TIFF|JPE?G)$";

  if (Regex.Match(fileExt, 
                  SupportedImageExtPatterns,
                  RegexOptions.IgnoreCase).Success) {

    // Requirement #5 - the supported image formats 
    System.Drawing.Imaging.ImageFormat[] SupportedImageFormats = { 
          System.Drawing.Imaging.ImageFormat.Bmp,      
          System.Drawing.Imaging.ImageFormat.Gif,      
          System.Drawing.Imaging.ImageFormat.Png,
          System.Drawing.Imaging.ImageFormat.Tiff,
          System.Drawing.Imaging.ImageFormat.Jpeg      
    };

    System.Drawing.Image theImage = null;
    try {
      theImage = System.Drawing.Image.FromStream(FileUpload1.PostedFile.InputStream);
    
      for (int i=0; i < SupportedImageFormats.Length; i++) {        
        if (theImage.RawFormat.Equals(SupportedImageFormats[i])) {
           // Requirement #3 - save the file only if it is our expected image format.
           FileUpload1.SaveAs(Server.MapPath("~/Uploads/") + FileUpload1.FileName);
           break;
        }
      }      
    }
    catch (System.ArgumentException) {  // not an image
      ...  
    }
  }
}