Scott on Writing

Musings on technical writing...

WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled

WARNING: There is a potentially severe concurrency issue with ASP.NET 2.0 GridViews, DetailsView, or FormViews that support editing and/or deleting and whose view state is disabled....

In the Editing, Inserting, and Deleting Data tutorials of my Working with Data in ASP.NET 2.0 tutorial series, I demonstrate how the GridView, DetailsView, and FormView can all support the built-in editing, deleting, and inserting capabilities with view state disabled. The reason is because the key data Web control properties (such as the DataKeys collection), are stored in control state, which is always persisted to view state regardless of the control's EnableViewState property value.

In my tutorials I don't mention the view state issue, I just silently set the EnableViewState property to false. And this works wonderfully when testing the page. Deletes and edits work exactly as expected, with or without view state enabled. However, a rather insidious behavior can rear its head when there are multiple users visiting the same page. This potential trap was pointed out to me by alert reader Jamie Crutchley, whose observed and shared information about this problem on the ASP.NET Forums in the past. I'll explain the problem (and a potential workaround) in my own words, but you can read Jamie's posts here and here, if you'd rather.

PROBLEM: Two users - Alice and Bob - visit a page (Products.aspx) that uses a GridView whose to list products; the GridView's view state has been disabled. The GridView uses its DataKeys collection to store the primary key values of the three products. Imagine that the products listed are Pens, Books, and Paper, and their respective primary key values are 1, 2, and 3.

  1. Alice clicks on the Delete button for the first product in the grid (Pens).
  2. A postback occurs. Because the GridView's view state has been disabled, on postback the data is re-read from the GridView's data source. This has the side effect of repopulating the DataKeys collection with the newly read data!
  3. Since the first row index was clicked, the GridView grabs the DataKeys value indexed at 0 and issues a delete based on that primary key value (1).
  4. Bob still sees all three products on the page (since he loaded the page before Alice deleted Pens). Sometime after Alice has made her deletion, Bob, too, decides that Pens must be deleted. He clicks on the Delete button for Pens.
  5. A postback occurs. Because the GridView's view state has been disabled, on postback the data is re-read from the GridView's data source. This has the side effect of repopulating the DataKeys collection with the newly read data!
  6. Since the first row index was clicked, the GridView grabs the DataKeys value indexed at 0. However, since the DataKeys collection has been reloaded in Step 5, the first DataKeys value is the primary key of Books (since Pens has since been deleted). The consequence is that Books is deleted, even though Bob wanted to delete Pens!!

More generally, if Alice deletes any product whose index preceeds the index of the record Bob deletes, Bob's delete will actually delete a different record. Similarly, if Alice deletes a preceeding record of the one Bob is editing, the edits will be applied to a preceeding row. Eep.

SHORT AND SIMPLE SOLUTION: Unless you are absolutely, 100%, certifiably, unconditionally certain that there will never, ever, not in a million years be two users concurrently editing/deleting records, then be sure to leave the GridView / DetailsView / FormView's EnableViewState property to True (the default).

MORE INVOLVED SOLUTION: If you really would like to reduce the page size by disabling view state, you can use the following “hack”... When a Delete (or Edit) button is clicked in the GridView the RowCommand event fires before the DataKeys collection is internally repopulated. Therefore, you can create an event handler that includes code that “saves” the DataKeys value(s) for the record being deleted. Then, in the ObjectDataSource's Deleting event handler you can reassign this value back to the primary key parameter(s).

Here's some code to implement this approach. First, in the RowCommand event handler the primary key (ProductID) is saved in a page-level variable:

    1 int recordToDelete = -1;

    2 protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)

    3 {

    4     recordToDelete = Convert.ToInt32(GridView1.DataKeys[Convert.ToInt32(e.CommandArgument)].Value);

    5 }

Then, in the ObjectDataSource's Deleting event, assign the page-level variable to the appropriate parameter:

    1 protected void ObjectDataSource1_Deleting(object sender, ObjectDataSourceMethodEventArgs e)

    2 {

    3     if (recordToDelete > 0)

    4         e.InputParameters["ProductID"] = recordToDelete;

    5 }

 

The tutorials and code at Working with Data in ASP.NET 2.0 will be updated so that they no longer disable view state and will include a similar warning as to this blog entry, although due to the breadth of material online, it may take several days or weeks to get all the changes made and propagated...

posted on Tuesday, October 03, 2006 12:56 PM

Feedback

# Chyba GridView, DetailsView a FormView 10/3/2006 10:32 PM Martin Knotek blog

Kdo náhodou necte blog BorBer ani Scotta Mitchela , mel ny kliknout na jeden z techto odkazu.

# re: WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 10/9/2006 12:05 PM Jay

Yet another example of why the solutions discussed in the tutorial series are not well suited for enterprise level design. This is not a minor "gotcha" but an enormous flaw in the proposed architecture and method of data persistence. The problem with these built in .NET Views is that they promote negligence in implementing proper design patterns, in this case, creating an Optimistic Lock ( http://www.martinfowler.com/eaaCatalog/optimisticOfflineLock.html ). Data persistence and lock handling are better handled in the DAL and not in the Views.
Of further note, the .net ViewState is tied to the server, which in a web farm situation can result in serious side effects (http://www.codinghorror.com/blog/archives/000132.html). Nhibernate is a wonderful framework I would highly recommend as opposed to traveling down the .Net ViewState rabbit hole. http://www.hibernate.org/343.html

# re: WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 10/9/2006 10:29 PM Manuel Abadia

Wow, that's a BIG bug.

Do you know if it will be fixed in SP1?

# re: WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 10/16/2006 2:58 PM Scott Mitchell

Manuel, I don't think it will be fixed in SP1, no (although that's just my guess). My hope is that it's becomes better documented (a warning displayed in the EnableViewState property in the docs, perhaps).

Thanks

# re: WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 12/1/2006 1:28 PM kevin

is there a work around for sqldatasources?

I tried adapting your code to them and found that I couldn't capture the value before postback occurs to repop the datakeys.

# re: WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 12/5/2006 11:23 AM Richard

This workaround doesn't seem to work for a GridView bound to a data-source control.

First, the CommandArgument is not set for the RowCommand event.

Second, by the time the RowCommand event is raised, the DataKeys collection has already been updated.

The only solution I've found is to create a derived control:
* Override the OnLoad method and take a copy of the DataKeys;
* Override the OnBubbleEvent, cast the source parameter to a GridViewRow, and use the RowIndex to find the key from the copied list;
* Finally, override the RowDeleting and RowUpdating methods, and copy the values from the original key to the event arguments.

using System;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public class ExtendedGridView : GridView
{
private DataKey[] _originalDataKeys;
private DataKey _rowCommandDataKey;

public ExtendedGridView()
{
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);

if (null != Page && Page.IsPostBack && !EnableViewState)
{
DataKeyArray keys = this.DataKeys;
if (null != keys && 0 != keys.Count)
{
_originalDataKeys = new DataKey[keys.Count];
keys.CopyTo(_originalDataKeys, 0);
}
}
}

protected override bool OnBubbleEvent(object source, EventArgs e)
{
if (null != _originalDataKeys)
{
GridViewRow row = source as GridViewRow;
GridViewCommandEventArgs args = e as GridViewCommandEventArgs;
if (null != row && null != args)
{
_rowCommandDataKey = _originalDataKeys[row.RowIndex];
}
}

return base.OnBubbleEvent(source, e);
}

protected override void OnRowDeleting(GridViewDeleteEventArgs e)
{
if (null != _rowCommandDataKey)
{
foreach (DictionaryEntry entry in _rowCommandDataKey.Values)
{
e.Keys[entry.Key] = entry.Value;
}

_rowCommandDataKey = null;
_originalDataKeys = null;
}

base.OnRowDeleting(e);
}

protected override void OnRowUpdating(GridViewUpdateEventArgs e)
{
if (null != _rowCommandDataKey)
{
foreach (DictionaryEntry entry in _rowCommandDataKey.Values)
{
e.Keys[entry.Key] = entry.Value;
}

_rowCommandDataKey = null;
_originalDataKeys = null;
}

base.OnRowUpdating(e);
}
}

# re: WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled 12/5/2006 9:05 PM Scott Mitchell

Richard, my workaround works fine for me. But let's continue this conversation over at the ASP.NET Forums:
http://forums.asp.net/thread/1401397.aspx

Thanks

# DataView Error on Update With DropDownList | keyongtech 1/21/2009 9:12 PM Pingback/TrackBack

DataView Error on Update With DropDownList | keyongtech

Title:  
Name:  
Url:
Protected by Clearscreen.SharpHIPEnter the code you see:
Comments   

My Links

Ads Via DevMavens

Archives

Post Categories

 

I am a Microsoft MVP for ASP.NET.
I am an ASPInsider.
<July 2009>
SMTWTFS
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Comment Stats

DayTotal% of Total
Sunday 2046.9%
Monday 42314.3%
Tuesday 50116.9%
Wednesday 54518.4%
Thursday 57219.3%
Friday 53618.1%
Saturday 1856.2%
Total 2966100.0%

Hour1Total% of Total
12:00 AM 752.5%
1:00 AM 802.7%
2:00 AM 672.3%
3:00 AM 812.7%
4:00 AM 642.2%
5:00 AM 1234.1%
6:00 AM 1153.9%
7:00 AM 1755.9%
8:00 AM 1876.3%
9:00 AM 1565.3%
10:00 AM 1866.3%
11:00 AM 1926.5%
12:00 PM 1996.7%
1:00 PM 1846.2%
2:00 PM 1675.6%
3:00 PM 1344.5%
4:00 PM 1153.9%
5:00 PM 1063.6%
6:00 PM 993.3%
7:00 PM 1063.6%
8:00 PM 903.0%
9:00 PM 842.8%
10:00 PM 893.0%
11:00 PM 923.1%
Total 2966100.0%

Comments by Blog Entry Date/Time

Day Entry MadeAvg.Total
Sunday 4.91157
Monday 4.92379
Tuesday 4.21471
Wednesday 7.42668
Thursday 6.53666
Friday 5.17450
Saturday 4.73175
Total 5.522966

Hour1 Entry MadeAvg.Total
12:00 AM 5.2937
1:00 AM 1.002
5:00 AM 0.000
7:00 AM 4.0048
8:00 AM 4.29133
9:00 AM 6.04290
10:00 AM 5.83274
11:00 AM 4.36192
12:00 PM 6.44348
1:00 PM 3.14132
2:00 PM 5.04227
3:00 PM 7.97303
4:00 PM 3.8199
5:00 PM 6.00168
6:00 PM 4.56114
7:00 PM 8.95188
8:00 PM 8.58163
9:00 PM 5.00115
10:00 PM 6.31101
11:00 PM 4.5732
Total 5.522966

Learn More About Comment Stats
1 - All times GMT -8...


Blog Stats

Favorite Web Sites

My Books

My MSDN Articles