Thursday, October 30, 2008

Create a Picture Album using ListView in ASP.NET 3.5

In this article, we will see how to create a Picture Album using the ListView control. We will explore the GroupTemplate of ListView and explore how we can group multiple images together in the ListView, thereby creating an album effect.
In one of the previous articles Save and Retrieve Images from the Database using ASP.NET 2.0 and ASP.NET 3.5 we had discussed how to use image handlers to save and retrieve images in the database. In this article, we will build on that concept and use the same technique to display images in a ListView.

We will learn how to upload an image and then display the uploaded image on the same page in an ‘album mode’ using the ListView. At the end of this article, you will also learn how to use the different templates of a ListView. If you are unfamiliar with the ListView control, I suggest you to read my article Exploring the ListView control in ASP.NET 3.5 to get an understanding of the same. To keep the article simple and easy to understand, I have not covered any validations associated with image control or other control. The article also does not suggest you best practices. In this article, we will only discuss how to read images from the database and display it in the ListView control, and that would be the focus for this article. I assume you have some knowledge of creating ASP.NET 3.5 websites. We will be using Visual Studio 2008.

Let us start off by first creating a sample database and adding a table to it. We will call the database ‘PictureAlbum’ and the table will be called ‘Album’. This table will contain an image column along with some  other columns. Run the following script in your SQL 2005 Query window (or server explorer) to construct the database and the table.

CREATE DATABASE [PictureAlbum] 
GO 
USE [PictureAlbum] 
GO 
CREATE TABLE Album 
pic_id int IDENTITY NOT NULL, 
picture_tag varchar(50), 
pic image 
)

Step 1: Create a new ASP.NET website. In the code-behind, add the following namespace

C#
using System.Data.SqlClient;

VB.NET
Imports System.Data.SqlClient

Step 2: Drag and drop two label and one textbox control. Also drag drop a FileUpload control and a button control to upload the selected image on button click. As mentioned earlier, there are no validations performed. The source would look similar to the following:

<html xmlns="http://www.w3.org/1999/xhtml"> 
<head runat="server"> 
<title>Untitled Page</title> 
</head><body> 
<form id="form1" runat="server"> 
<div> 
Image 
<asp:Label ID="lblTags" runat="server" Text="Tags"></asp:Label> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
<asp:TextBox ID="txtTags" runat="server"></asp:TextBox> 
<br /> 
<asp:Label ID="lblImage" runat="server" Text="Upload Picture"></asp:Label> 
&nbsp;&nbsp;&nbsp;&nbsp; 
<asp:FileUpload ID="imgUpload" runat="server" /> 
<br />
<br /> 
<asp:Button ID="btnSubmit" runat="server" onclick="btnSubmit_Click" 
Text="Submit" /> 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp<asp:Label ID="lblResult" runat="server" ForeColor="#0066FF"></asp:Label> 
<br /> 
<hr /> 
</div>
</form> 
</body> 
</html>

Also in the web.config, add a <connectionStrings> tag as displayed below:

<connectionStrings> 
<add name="albumConnString" connectionString="Data Source=(local); Initial Catalog = PictureAlbum; Integrated Security=True;"providerName="System.Data.SqlClient" /> 
</connectionStrings>

Step 3: In the button click event, add the following code:

C#

protected void btnSubmit_Click(object sender, EventArgs e) 
SqlConnection connection = null; 
try
FileUpload img = (FileUpload)imgUpload; 
Byte[] imgByte = null; 
if (img.HasFile && img.PostedFile != null) 
{
//To create a PostedFile 
HttpPostedFile File = imgUpload.PostedFile; 
//Create byte Array with file len 
imgByte = new Byte[File.ContentLength]; 
//force the control to load data in array 
File.InputStream.Read(imgByte, 0, File.ContentLength); 
// Insert the picture tag and image into db 
string conn = ConfigurationManager.ConnectionStrings["albumConnString"].ConnectionString; 
connection = new SqlConnection(conn);
connection.Open(); 
string sql = "INSERT INTO Album(picture_tag,pic) VALUES(@tag,@pic) SELECT @@IDENTITY"; 
SqlCommand cmd = new SqlCommand(sql, connection); 
cmd.Parameters.AddWithValue("@tag", txtTags.Text); 
cmd.Parameters.AddWithValue("@pic", imgByte); 
int id = Convert.ToInt32(cmd.ExecuteScalar()); 
lblResult.Text = String.Format("Picture ID is {0}", id); 
catch 
lblResult.Text = "There was an error"; 
finally 
{
connection.Close(); 
}

VB.NET

Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs) 
Dim connection As SqlConnection = Nothing 
Try
Dim img As FileUpload = CType(imgUpload, FileUpload) 
Dim imgByte As Byte() = Nothing 
If img.HasFile AndAlso Not img.PostedFile Is Nothing Then 
'To create a PostedFile 
Dim File As HttpPostedFile = imgUpload.PostedFile 
'Create byte Array with file len 
imgByte = New Byte(File.ContentLength - 1){} 
'force the control to load data in array 
File.InputStream.Read(imgByte, 0, File.ContentLength) 
End If 
' Insert the picture tag and image into db 
Dim conn As String = ConfigurationManager.ConnectionStrings("albumConnString").ConnectionString 
connection = New SqlConnection(conn)
connection.Open() 
Dim sql As String = "INSERT INTO Album(picture_tag,pic) VALUES(@tag,@pic) SELECT @@IDENTITY" 
Dim cmd As SqlCommand = New SqlCommand(sql, connection) 
cmd.Parameters.AddWithValue("@tag", txtTags.Text) 
cmd.Parameters.AddWithValue("@pic", imgByte) 
Dim id As Integer = Convert.ToInt32(cmd.ExecuteScalar()) 
lblResult.Text = String.Format("Picture ID is {0}", id) 
Catch
lblResult.Text = "There was an error" 
Finally
connection.Close() 
End Try 
End Sub

In the code above, we are creating a byte array equal to the length of the file. The byte array will store the image. We then load the image data into the array. The record containing the Picture Tags and Image is then inserted into the database using the ADO.NET code. The ID inserted is returned back using the @@Identity. We will shortly use this ID and pass it as a query string parameter to the ShowImage handler. The image will then be fetched against the Picture ID (pic_id). If you run the application as of now, you should be able to upload the images to the database.

Step 4: In order to display the image on the page, we will create an Http handler. To do so, right click project > Add New Item > Generic Handler > ShowImage.ashx. In the code shown below, we are using the Request.QueryString[“id”] to retrieve the PictureID(pic_id) from the handler url. The ID is then passed to the ‘ShowAlbumImage()’ method where the image is fetched from the database and returned in a MemoryStream object. We then read the stream into a byte array. Using the OutputStream.Write(), we write the sequence of bytes to the current stream and you get to see your image.

C#

<%@ WebHandler Language="C#" Class="ShowImage" %>
using System; 
using System.Configuration;
using System.Web; 
using System.IO;
using System.Data; 
using System.Data.SqlClient;

public class ShowImage : IHttpHandler 
public void ProcessRequest(HttpContext context) 
Int32 picid; 
if (context.Request.QueryString["id"] != null) 
picid = Convert.ToInt32(context.Request.QueryString["id"]); 
else 
throw new ArgumentException("No parameter specified");
context.Response.ContentType = "image/jpeg"; 
Stream strm = ShowAlbumImage(picid); 
byte[] buffer = new byte[4096];
int byteSeq = strm.Read(buffer, 0, 4096);
while (byteSeq > 0) 
context.Response.OutputStream.Write(buffer, 0, byteSeq);
byteSeq = strm.Read(buffer, 0, 4096); 
//context.Response.BinaryWrite(buffer);
}
public Stream ShowAlbumImage(int picid) 
{
string conn = ConfigurationManager.ConnectionStrings["albumConnString"].ConnectionString; 
SqlConnection connection = new SqlConnection(conn);
string sql = "SELECT pic FROM Album WHERE Pic_ID = @ID"; 
SqlCommand cmd = new SqlCommand(sql, connection); 
cmd.CommandType = CommandType.Text; 
cmd.Parameters.AddWithValue("@ID", picid); 
connection.Open(); 
object img = cmd.ExecuteScalar(); 
try 
return new MemoryStream((byte[])img); 
catch 
return null; 
finally 
connection.Close(); 

public bool IsReusable 
get 
return false; 
}
}

VB.NET

<%@ WebHandler Language="vb" Class="ShowImage" %>
Imports System 
Imports System.Configuration
Imports System.Web 
Imports System.IO
Imports System.Data 
Imports System.Data.SqlClient

Public Class ShowImage
Implements IHttpHandler 
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim picid As Int32 
If Not context.Request.QueryString("id") Is Nothing Then
picid = Convert.ToInt32(context.Request.QueryString("id")) 
Else
Throw New ArgumentException("No parameter specified") 
End If
context.Response.ContentType = "image/jpeg" 
Dim strm As Stream = ShowAlbumImage(picid)
Dim buffer As Byte() = New Byte(4095){} 
Dim byteSeq As Integer = strm.Read(buffer, 0, 4096)
Do While byteSeq > 0 
context.Response.OutputStream.Write(buffer, 0, byteSeq)
byteSeq = strm.Read(buffer, 0, 4096) 
Loop
'context.Response.BinaryWrite(buffer); 
End Sub

Public Function ShowAlbumImage(ByVal picid As Integer) As Stream
Dim conn As String = ConfigurationManager.ConnectionStrings("albumConnString").ConnectionString 
Dim connection As SqlConnection = New SqlConnection(conn)
Dim sql As String = "SELECT pic FROM Album WHERE Pic_ID = @ID" 
Dim cmd As SqlCommand = New SqlCommand(sql, connection)
cmd.CommandType = CommandType.Text 
cmd.Parameters.AddWithValue("@ID", picid)
connection.Open() 
Dim img As Object = cmd.ExecuteScalar()
Try 
Return New MemoryStream(CType(img, Byte()))
Catch 
Return Nothing
Finally 
connection.Close()
End Try 
End Function

Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get 
Return False
End Get 
End Property
End Class

Step 5: We will now add the ListView to our page. We will see how to use the ‘GroupTemplate’ to display images in multiple columns (in our case 4 images). Add a SQLDataSource control to the page. After configuring it with the database, the code will look similar to the following

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:albumConnString %>" 
ProviderName="System.Data.SqlClient" 
SelectCommand="SELECT [pic_id],picture_tag, [pic] FROM [Album]"></asp:SqlDataSource>

Now add a ListView Control to the page and set its DataSource property to ‘SqlDataSource1’. After adding the LayoutTemplate, ItemTemplate and GroupTemplate the code will look similar to the following:

<asp:ListView ID="ListView1" GroupItemCount="4" runat="server" DataKeyNames="pic_id" 
DataSourceID="SqlDataSource1"> 
<LayoutTemplate> 
<asp:Placeholder 
id="groupPlaceholder" 
runat="server" /> 
</LayoutTemplate> 
<GroupTemplate> 
<div> 
<asp:Placeholder 
id="itemPlaceholder" 
runat="server" /> 
</div> 
</GroupTemplate> 
<ItemTemplate> 
<asp:Image 
id="picAlbum" runat="server" AlternateText='<% #Eval("picture_tag") %>' 
ImageUrl='<%# "ShowImage.ashx?id=" + Eval("pic_id") %>' /> 
</ItemTemplate>
<EmptyItemTemplate>
</EmptyItemTemplate> 
</asp:ListView>

If you observe, the <LayoutTemplate> contains a placeholder called ‘groupPlaceholder’. When the page is rendered, the groupPlaceholder is replaced with the contents of GroupTemplate. Similarly, the <GroupTemplate> includes the ‘itemPlaceholder’ placeholder. The itemPlaceholder is replaced with the contents of the ItemTemplate, which is an image. As you know, the <ItemTemplate> renders each time there is an item from the source. The ‘GroupItemCount’ attribute decides the number of items displayed in a <GroupTemplate> before a new row is created. Here we have set the ImageUrl to an expression which makes a call to the handler and displays the images from the database. In the code below, we pass the pic_id as a query string parameter to the Http Handler. Similarly the tags entered by you are shown as alternate text on the images. To see the text, hover your mouse over the image, when the application is run.

Note: One last thing. In order to display images as they are added, just add this line of code (ListView1.DataBind()) in the btnSubmit_Click, just before the try block ends.

...

lblResult.Text = String.Format("Picture ID is {0}", id);

ListView1.DataBind();

We are all set. It’s time to you run the application!! Start uploading images and you will observe that as you upload the images, the gallery gets updated and the image is displayed in an album mode using the ListView control as shown below:
























The source code for this article in C# and VB.NET can be downloaded from here. I hope you liked the article and I thank you for viewing it.

Query NonGeneric Types using LINQ in C# and VB.NET

Generics was introduced in .NET Framework 2.0. Before that the System.Collections namespace consisted of collections that could store objects. After Generics became available in .NET 2.0, a lot of collection classes adopted Generics and implemented the IEnumerable interface. However there were other nongeneric types that did not support the generic versions of IEnumerable (however they did support the nongeneric version of IEnumerable).

LINQ can be used to query on objects that are generic collections and implement the IEnumerable interface. However nongeneric collections like the ArrayList do not implement the IEnumerable interface. Let us see what happens when we query nongeneric collections using LINQ.

C#

public class Cars
{
public string CarMake { get;set;}
public string CarModel { get; set; }
public int Year { get; set; }
}
class Program
{
static void Main(string[] args)
{
ArrayList carList = new ArrayList();
carList.Add(new Cars
{
CarMake="BMW", CarModel="BMW Art", Year=1978
});
carList.Add(new Cars
{
CarMake = "BMW", CarModel = "Coupe", Year = 1982
});

carList.Add(new Cars
{
CarMake = "Renault", CarModel = "Alpine", Year = 1972
});
carList.Add(new Cars
{
CarMake = "Porsche", CarModel = "Maisto", Year = 1976
});
var carQuery = from car in carList
where car.CarMake == "BMW"
select car;
}

VB.NET

Public Class Cars
Private privateCarMake As String
Public Property CarMake() As String
Get
Return privateCarMake
End Get
Set(ByVal value As String)
privateCarMake = value
End Set
End Property
Private privateCarModel As String
Public Property CarModel() As String
Get
Return privateCarModel
End Get
Set(ByVal value As String)
privateCarModel = value
End Set
End Property
Private privateYear As Integer
Public Property Year() As Integer
Get
Return privateYear
End Get
Set(ByVal value As Integer)
privateYear = value
End Set
End Property
End Class

Friend Class Program
Shared Sub Main(ByVal args() As String)
Dim carList As New ArrayList()
carList.Add(New Cars With {.CarMake="BMW", .CarModel="BMW Art", .Year=1978})
carList.Add(New Cars With {.CarMake = "BMW", .CarModel = "Coupe", .Year = 1982})
carList.Add(New Cars With {.CarMake = "Renault", .CarModel = "Alpine", .Year = 1972})
carList.Add(New Cars With {.CarMake = "Porsche", .CarModel = "Maisto", .Year = 1976})

Dim carQuery = _
From car In carList _
Where car.CarMake = "BMW" _
Select car
End Sub

In the example shown above, we have declared and populated an ArrayList with the Cars objects. We query the ArrayList using LINQ, just as we would have queried a collection implementing the IEnumerable like the Array. What do you think would be the result?

Well the code shown above will never compile at the first place. The reason being that ArrayList does not inherit from IEnumerable and hence LINQ cannot query such collections. So does that conclude that we cannot use LINQ against nongeneric collections like ArrayList? What about those class libraries that contain methods returning ArrayList? Do we scrap those methods or replace them with methods returning Generic Collections? Well without dramatizing this further, the answer is that there are ‘tricks’ using which we can query nongeneric collections using LINQ. Let us see three different ways to do so:

Method 1: By specifying the type of the variable to reflect the specific type of the objects in the collection:

By explicitly declaring the type of the variable while querying, you can cast each item in the ArrayList to reflect the specific type, in our case Cars.

C#

var cc = from Cars car in carList
where car.CarMake == "BMW"
select car;

VB.NET

Dim cc = _
From car As Cars In carList _
Where car.CarMake = "BMW" _
Select car

Observe how we are casting the ‘car’ variable to ‘Cars’.

Method 2: By using the ‘Cast’ operator.

The ‘Cast’ operator takes a nongeneric collection (that implements IEnumerable) and returns an IEnumerable. Once we get an IEnumerable, we can then query the collection using LINQ.

C#

var cc1 = from car in carList.Cast()
where car.CarMake == "BMW"
select car;

VB.NET

Dim cc1 = _
From car In carList.Cast(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

Note: The approach we followed in Method 1 is equivalent to calling Cast.

Method 3: Instead of Cast, you can also use the OfType operator.

The ‘OfType’ operator filters the elements of an IEnumerable based on a specified type. So if you have different types in your ArrayList, you get back only the type you have queried for.

C#

var cc2 = from car in carList.OfType()
where car.CarMake == "BMW"
select car;

VB.NET

Dim cc2 = _
From car In carList.OfType(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

The entire source code with the 3 different methods would look as follows:

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace QueryArraylistWithLINQ
{
public class Cars
{
public string CarMake { get;set;}
public string CarModel { get; set; }
public int Year { get; set; }
}

class Program
{
static void Main(string[] args)
{
ArrayList carList = new ArrayList();
carList.Add(new Cars
{
CarMake = "BMW",
CarModel = "BMW Art",
Year = 1978
});

carList.Add(new Cars
{
CarMake = "BMW",
CarModel = "Coupe",
Year = 1982
});

carList.Add(new Cars
{
CarMake = "Renault",
CarModel = "Alpine",
Year = 1972
});

carList.Add(new Cars
{
CarMake = "Porsche",
CarModel = "Maisto",
Year = 1976
});

var cc = from Cars car in carList
where car.CarMake == "BMW"
select car;
var cc1 = from car in carList.Cast()
where car.CarMake == "BMW"
select car;
var cc2 = from car in carList.OfType()
where car.CarMake == "BMW"
select car;

foreach (Cars c in cc1)
Console.WriteLine(c.CarMake + "-" + c.CarModel);
Console.ReadLine();
}
}
}

VB.NET

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Collections

Namespace QueryArraylistWithLINQ

Public Class Cars

Private privateCarMake As String
Public Property CarMake() As String
Get
Return privateCarMake
End Get
Set(ByVal value As String)
privateCarMake = value
End Set
End Property
Private privateCarModel As String
Public Property CarModel() As String
Get
Return privateCarModel
End Get
Set(ByVal value As String)
privateCarModel = value
End Set
End Property
Private privateYear As Integer
Public Property Year() As Integer
Get
Return privateYear
End Get
Set(ByVal value As Integer)
privateYear = value
End Set
End Property
End Class

Friend Class Program

Shared Sub Main(ByVal args() As String)
Dim carList As New ArrayList()
carList.Add(New Cars With {.CarMake = "BMW", .CarModel = "BMW Art", .Year = 1978})
carList.Add(New Cars With {.CarMake = "BMW", .CarModel = "Coupe", .Year = 1982})
carList.Add(New Cars With {.CarMake = "Renault", .CarModel = "Alpine", .Year = 1972})
carList.Add(New Cars With {.CarMake = "Porsche", .CarModel = "Maisto", .Year = 1976})

Dim cc = _
From car As Cars In carList _
Where car.CarMake = "BMW" _
Select car

Dim cc1 = _
From car In carList.Cast(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

Dim cc2 = _
From car In carList.OfType(Of Cars)() _
Where car.CarMake = "BMW" _
Select car

For Each c As Cars In cc1
Console.WriteLine(c.CarMake & "-" & c.CarModel)
Next c

Console.ReadLine()
End Sub
End Class
End Namespace


It’s a good decision on your part if you convert your nongeneric collections to generic ones to gain performance and strong type checking. However, meanwhile if you happen to use any nongeneric collections in your code and want to query them using LINQ, you can use the methods as discussed in this article. I hope this article was useful and I thank you for viewing it.