There have been customizations I have done for Data Collection (DC), both the 2.x/2009 and 3.x/2012 versions, which have exhibited a behavior such that the form data appears to have been submitted to DC twice. That is, the code to process the form has executed once (“posted”), and then immediately executed again. Sometimes this results in duplicate data being collected, and sometimes, based on how the form is coded, the data is recorded once and then an error is returned saying that the data was rejected for being a duplicate. After investigation, we realized this was a feature of Microsoft’s Internet Information Service — the web server that DC runs on. To prevent this, we have used a coding trick to identify this situation and avoid the duplicate posting.
When scanning data into a form, the software that decodes the barcode and puts the data in the input field is configured to append a Tab to the end of the data. Different barcode hardware does this in different ways, but the intent is to enter the data and then immediately move to the next input field. If you have many fields to scan, you can scan each barcode, and not have to hit a key on the keyboard between them.
In ASP.NET programming, one of the features you can add to some controls is the ability to call a method in the code-behind for the form when a certain event occurs. (The “code-behind” is the code that is attached to a particular form. The form MyForm.aspx has an associated MyForm.aspx.cs C# code-behind class.) Events that are used throughout Data Collection are the OnTextChangedevent for input fields, and the OnClick event for button.
Here’s a sample form to demonstrate:
The purchase order will likely be a barcode, but the vendor packing slip may or may not be. (The vendor might not barcode it on their paperwork), so the user may have to enter it on the keyboard. If it isn’t barcoded, the user would need to type it in on the keyboard and press the OK button. The ASP.NET code for the OK button looks like this:
<asp:Button ID="okButton" runat="server" Text="<%$ Resources:Labels, Ok %>" OnClick="okButton_Click" />
The main thing is that the OnClick event tells the web server (Microsoft’s IIS) what method to call when the button is pressed.
If the vendor packing slip is barcoded, however, then it would be preferable to automatically “press” the OK button after scanning it. It is the last field on the form, and the scanner sends a Tab after the data is placed in the field, so the next thing to do is whatever the OK button does. Here is what the ASP.NET code looks like for the vendor packing slip:
<asp:TextBox ID="vendorDocId" runat="server" AutoPostBack="True" OnTextChanged="okButton_Click"></asp:TextBox>
The AutoPostBack property means that, when this field loses focus (that is, the cursor leaves the field, either by clicking on some other control or by pressing the Tab key), a “postback” occurs; the form data is read and processed by the code-behind. (A button press triggers a postback, so this property is not needed on the OK button.) The OnTextChanged event tells IIS what method to call when the text has changed, and it’s set to the same method that the OK button would call.
So what if the user types in the data and then presses OK? The field text has changed and the OK button has been pressed. Does this execute both the field’s OnTextChanged event and the button’s OnClick event? The answer is, “Yes, but…” Yes, both events are queued up to be executed (which makes sense – they could be calling different methods, though in our case they are not), but if the method called by the event goes to another form, the event queue is cleared out. (If we’re on another form, the event for the previous form doesn’t really apply any more.)
In the standard Data Collection forms, we are always either going to another form, or staying on the same form after an exception occurs (which also clears the event queue), so we don’t have this issue normally. However, there have been customizations that I have done for some customers where they want to streamline a particular process, and thus stay on a particular form until the user presses a button to indicate they’re done. In these cases, the form is double-posted, and we needed a way to prevent processing it a second time.
We handle this by saving the scanned information in memory and, if the currently scanned data matches the saved data, we assume that this is a double-post and ignore it. There are two similar ways we do this.
1. Store the scanned value in the current transaction’s “extra” data area.
If you are on a form that has a current DC transaction, it has a set of methods that allows access to a Dictionary collection. You can put any data you want here. It is not used by the standard DC code; it’s just there if you need to save some information during the life of the transaction.
What we do is:
- Initialize the value in the “extra” data area if it doesn’t already exist.
- Get the last scanned value from the dictionary.
- If the current scanned value matches the last scanned value, just return from the method without error.
- Set the last scanned value (in the “extra” data area) to the current scanned value.
- Process the data normally.
Here is the sample code:
private const string LAST_BARCODE_KEY = "Last_barcode_scanned";
protected void okButton_Click (object sender, EventArgs e)
ScannedTransaction trans = ScannedTransaction.Current;
itemTextBox.Text = itemTextBox.Text.Trim().ToUpper();
string lastScan = (string)trans.GetExtra(LAST_BARCODE_KEY);
if (lastScan.Length != 0 && itemTextBox.Text == lastScan)
// Reset form fields, set focus, etc.
itemTextBox.Text = "";
// Normal processing
One drawback of this is that if the user actually scans the same thing twice, it looks exactly like a double-post to the code, so the field clears out and no error message is displayed. In some situations, this may be confusing.
2. If there is no current transaction for the form, you can cache the value in the session context.
The session context is an IIS feature that, like the DC transaction “extra” data area, is a Dictionary that you can put your own data in. However, instead of using methods to retrieve and set the values (GetExtra and SetExtra above), you access the Dictionary directly, so the syntax is a little different.
if (HttpContext.Current.Session[SiteConstants.LAST_BARCODE_KEY] == null)
HttpContext.Current.Session[SiteConstants.LAST_BARCODE_KEY] = "";
string lastScan = (string)HttpContext.Current.Session[SiteConstants.LAST_BARCODE_KEY];
if (lastScan.Length != 0 && docId.Text == lastScan)
HttpContext.Current.Session[SiteConstants.LAST_BARCODE_KEY] = docId.Text;
This has the same drawback as method 1, with the additional caveat that the cached value is saved between transactions, whereas when the transaction is posted or abandoned, the value in method 1 is removed. Depending on the application, you may try clearing out the cached value before calling the form.