Archive for asp.net

Locking sessions for multi-threaded access

I recently ran into a situation where I needed to upload some small files from a Flex client application to an ASP.NET web server. I decided to store the uploaded files in the users session while they were in the checkout process. Once the user confirms their order, the images are read from the session and stored to the database.

Here is the original code from the page that accepts each uploaded file, and adds it to a Dictionary in the collection:

if (Session[SESSION_ORDER_FILES] == null)
{
	//Our dictionary hasn’t been created, so we do it now
	files = new Dictionary<string, byte[]>();
	Session[SESSION_ORDER_FILES] = files;
}
else
{
	//The dictionary has already been created, just load it
	files = (Dictionary<string , byte[]>) Session[SESSION_ORDER_FILES];
}

//If we have the "_clearPrevious" flag, that means all
//of the files should be removed from this users session
if (_clearPrevious)
	files.Clear();

//If the file name is the same, replace it
if (files.ContainsKey(_fileName))
	files.Remove(_fileName);

files.Add(_fileName, bytes);

The problem is that we ended up with missing images. The client was sending them, but when the user confirmed their order they were missing images in the session. Since ASP.NET will process page requests in multiple threads, the session can be accessed in multiple threads!

Now, we need to find a way to lock them. I questioned whether ASP.NET would give me the same session object each time, or a new instance representing the same session. I whipped up this code in a test page. It saves the previous session reference to the session. I know it’s a little strange, but since no serialization happens with the session, it gave me a good way to know if the previous session object and the current session object were the same instance.

const string SESS_SESS = "test";
var currSessionObj = Session[SESS_SESS];

if(currSessionObj == null)
	//First page load
	Session[SESS_SESS] = Session;
else
	lblText.Text = (Session[SESS_SESS] == Session).ToString();

The result of this page was false. That means you most certainly do get a new session instance each time. Keep in mind that I’m not saying it’s a different session, the object you’re accessing the session with simply changes.

What does this mean?

This means that you have to be careful when there is a chance that you’re working with session objects in multiple pages, or in a page that could be accessed multiple times simultaneously. Thankfully, there are only a few real-world scenarios where this would be a large concern.

As with any other kind of multi-threaded code, be careful if you’re checking the session, and then performing an action based on the result. In that case, you’ll need to lock a global object that is available to all threads that could access that code. Here is an example:

lock(Global.SessionLock)
{
	if(Session["foo"] == null)
		Session["foo"] = new Bar();
}

In your Global class, you’ll need this field:

static object SessionLock = new object();

Object does not match target type in GridView

I created a shopping cart for a website that can display multiple types of items that implement IShoppingCartItem. When the GridView would display items that were different type, I would get this exception:

Exception Details: System.Reflection.TargetException: Object does not match target type.

I found a lot of solutions that I didn’t really like. For example, every shopping cart item type could implement ITypedList.

What I ended up doing was creating a CartGridItem class that implements IShoppingCartItem:

public class CartGridItem : IShoppingCartItem
{
	private readonly IShoppingCartItem _baseCartItem;
	public CartGridItem(IShoppingCartItem baseCartItem)
	{
		_baseCartItem = baseCartItem;
	}
	#region IShoppingCartItem Members
	public string ProductCode
	{
		get { return _baseCartItem.ProductCode; }
	}
//...end of code sample...

This has worked great, and I don’t have to make any changes when I create a new item that implements that interface!

image

Speeding up your ASP.NET Application

I read a post titled "Improve Web Application Performance". I expected to have a lot of information to add, but it’s actually pretty comprehensive. I recommend reading it if you’re developing a web application. One of my biggest complaints about most websites is that they’re way too slow. In this day and age there is really no reason for that.

Analog Stopwatch

The only other optimization I have used in the past that isn’t mentioned in the post, is removing whitespace. While it can make your life a little harder because of some layout glitches it might cause (which are fixable), it can often have a pretty big effect on your page sizes. On one of my websites, I saw an average of a 10-20% decrease in page sizes, even when gzip compression was turned on.

The easiest way to remove whitespace from your pages is to use an HTTP module. One solution is available here for free. You simply add the binary DLL, and reference it in your web.config file. It couldn’t hurt to give it a try the next time you’re writing an ASP.NET application.

Quick and dirty reporting with the Google Chart API

On a recent project, I need a quick and dirty report that would give a breakdown of statistics for orders that have been placed. I didn’t need any fancy drill down, hover help, or transition effects. This sounds like a perfect use for the Google Chart API! There is no limit on use. All you have to do is construct a URL containing your data, and that is the URL for an image containing your chart. Here is a sample of a chart that we’re going to generate in this post:

Pie Sample

First, we need a quick way to generate the data. I used a SQL Server stored procedure, because I knew it would only take a matter of minutes to create. Here is an example of how I retrieved the data for 2 report sections:

--Paper Breakdown
Select IsNull(p.PaperName + ' (' + Cast(Count(o.PaperId) as VarChar(Max)) + ')', 'Unspecified'),
	count(o.PaperId)
From Orders o
Left Outer Join Papers p on o.PaperId = p.Id
Group By p.PaperName
Having Count(o.PaperId) > 0
--Style Breakdown
Select IsNull(s.Sku + ' (' + Cast(Count(o.StyleId) as VarChar(Max)) + ')', 'Unspecified'),
	count(o.StyleId)
From Orders o
Left Outer Join Styles s on o.StyleId = s.Id
Group By s.Sku
Having Count(o.StyleId) > 0

Then, I simply had to call the stored procedure, and put the data into a DataSet called "_data" (code not shown). Then, I wrote a quick method to get the URL for a Google chart:

private static string getPieChartUrl(DataTable dt)
{
	if (dt.Rows.Count == 0)
		return "";
	StringBuilder sb = new StringBuilder();
	sb.Append("http://chart.apis.google.com/chart?chs=600x200&cht=p"); //Base URL
	//Pie slice data
	StringBuilder sliceData = new StringBuilder();
	foreach (DataRow currRow in dt.Rows)
		sliceData.Append(currRow[1] + ",");
	sliceData.Remove(sliceData.Length - 1, 1); //Remove the trailing comma
	sb.AppendFormat("&chd=t:{0}", sliceData);
	//Pie label data
	StringBuilder labelData = new StringBuilder();
	foreach (DataRow currRow in dt.Rows)
		labelData.Append(currRow[0] + "|");
	labelData.Remove(labelData.Length - 1, 1); //Remove the trailing comma
	sb.AppendFormat("&chl={0}", labelData);
	return sb.ToString();
}

My ASPX page then has 2 image controls:

<h2>Breakdown by Paper Type</h2>
<asp:Image runat="server" ID="imgPiePapers" />
<h2>Breakdown by Style SKU</h2>
<asp:Image runat="server" ID="imgPieStyles" />

Then, simply wire up the data:

imgPiePapers.ImageUrl = getPieChartUrl(_data.Tables[0]);
imgPieStyles.ImageUrl = getPieChartUrl(_data.Tables[1]);

Disable auto id’s when copying ASP.NET controls

Don’t you hate it when you copy an ASP.NET server control without an ID, only to have it add one when you paste it?

For example, look at this code:

<asp:RequiredFieldValidator runat="server" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" />

I took the first line, copied it and pasted it, and got the second line. It’s rare that I need to reference a validator in my CodeBehind.

Here is the option to disable this functionality in the Visual Studio options (Tools->Options):

Disable Auto Ids Visual Studio

Simply uncheck the “Auto ID elements on paste in Source view” option.