Monday, December 31, 2007

SQL Server function to Check Whether All Characters In a String Are in Uppercase or Not

SQL Server function to Check Whether All Characters In a String Are in Uppercase or Not

When I was developing a small application, I had to write a Microsoft SQL Server script to check whether all the characters in a given string are uppercase alphabets or not.

Here is the Microsoft SQL Server function code which performs the check. This code is compatible with Microsoft SQL Server 2000 and Microsoft SQL Server 2005.


/****************************************************************

* Purpose: Check whether all characters

* in a string are capital alphabets or or not

* Parameter: input string

* Output: 0 - on success; 1 on failure

****************************************************************/

CREATE FUNCTION fnChkAllCaps(@P_String VARCHAR(500))

RETURNS BIT

AS

BEGIN

DECLARE @V_RetValue BIT

DECLARE @V_Position INT

SET @V_Position = 1

SET @V_RetValue = 0

–Loop through all the characters

WHILE @V_Position <= DATALENGTH(@P_String)

AND @V_RetValue = 0

BEGIN

–Check if ascii value of the character is between 65 & 90

–Note: Ascii value of A is 65 and Z is 90

IF ASCII(SUBSTRING(@P_String, @V_Position, 1))

BETWEEN 65 AND 90

SELECT @V_RetValue = 0

ELSE

SELECT @V_RetValue = 1

–Move to next character

SET @V_Position = @V_Position + 1

END

–Return the value

RETURN @V_RetValue

END


Sample code to test the function

SELECT dbo.fnChkAllCaps(‘TECHTHOUGHTS’) – Returns 0

GO

SELECT dbo.fnChkAllCaps(‘TechThoughts’) – Returns 1

GO


The above function iterates through all the characters of a given input string and checks whether ASCII value of the characters is between 65 and 90 to verify it is an uppercase alphabet or not.

You might be wondering why is ASCII values check is between 65 and 90. It is because the ASCII value of uppercase A is 65 and uppercase Z is 90.

Soon I’ll rewrite the same code using SQL Server 2005 CLR functions and post it. I believe Microsoft SQL Server 2005 CLR functions perform this check very efficiently.

Group validation in ASP.NET 2.0

Group validation in ASP.NET 2.0

Validation groups represent something new, introduced in ASP.NET 2.0. With this feature you can implement validation on groups of controls. Before ASP.NET 2.0, when submitting a form, all the controls on the page were being validated. So when you press the Submit button, each and every TextBox or other control that has a validator, was checked. However, this caused a problem when you had multiple forms on a page, with two or more submit buttons. Pressing one of the buttons would start to validate all the controls on a page, and that’s not what we wanted.

So let’s see how these validation groups work. Start a new Web Site in Visual Studio 2005:

Add two panels to the form and some TextBoxes in both. For each TextBox add a validator, I added RequiredFieldValidators because they are the most common ones. Also, add a button in each of the panel, so that our WebForm looks something like:

Be sure to set each validator to validate one of the TextBoxes. You can do this by setting the ControlToValidate property in the Properties window.

After each TextBox has its RequiredFieldValidator, compile and run the web application. Type something into the TextBoxes in Panel2 and leave the TextBoxes in Panel1 blank, because we are not interested in that form. Press the submit button (the second one, of course), and watch the result:

Bummer! Even though we pressed the second button, the validators in the first form reacted and the form didn’t get submitted.

Here’s
where the validation groups come in handy. Click the first button and in the Properties window scroll to the ValidationGroup property and give it a name, Form1:

Now do the same thing for the second button, set the ValidationGroup property to Form2.

We’re not done yet. The RequiredFieldValidators also have a ValidationGroup property, we need to set this property of the RequiredFieldValidators from Panel1 to the same name we gave the submit button in Panel1: Form1. Same thing needs to be done for the validators in Panel2, set their ValidationGroup property to the same name as the second button: Form2. This way each button is linked to the validators it should fire when it is pressed.

You can compile and run the web application now.

As you can see in the screenshot above, clicking the first button will now fire only the validators that are grouped with that button.

The Fastest Way To Compare Two Strings Equality

The Fastest Way To Compare Two Strings Equality

We usually use “==” for string comparing operations. But What if the code will work 10 million times. You must use the best comparing way for minimum time consuming.

We often works with strings when writing codes. Sometimes the case requires to control if two strings are equal or not. And then we usually use the “==” operator to control equality.
If (s1 == s2)
But What if the code will work 10 million times. You must use the best comparing way for minimum time consuming. Run the code below and see which one the best.
The “==” operator is the slowest, and the “s1.Equals(s2)” is the fastest.
Stopwatch sw = new Stopwatch();
string
s1 = “Some text for testing”;
string
s2 = “Some text for testing.”;
sw.Start();
for
(int i = 0; (i <= 10000000); i++) {
if (s1 == s2) {
// Do something
}
}
sw.Stop()
;
Console.WriteLine(“s1=s2 : ” + sw.Elapsed.TotalMilliseconds.ToString());

sw.Reset();
sw.Start();
for
(int i = 0; (i <= 10000000); i++) {
if (String.Equals(s1, s2)) {
// Do something
}
}
sw.Stop()
;
Console.WriteLine(String.Equals(s1, s2) : ” + sw.Elapsed.TotalMilliseconds.ToString());
sw.Reset();
sw.Start();
for
(int i = 0; (i <= 10000000); i++) {
if (s1.Equals(s2)){
// Do something
}
}
sw.Stop()
;
Console.WriteLine(s1.Equals(s2) : ” + sw.Elapsed.TotalMilliseconds.ToString());

All about IIS:

Ten things to do with IIS

Tip 10: Customize Your Error Pages
Although this is quite simple to do, few people seem to take advantage of it. Just select the “Custom Errors” tab in MMC and map each error, such as 404, to the appropriate HTML or ASP template. Full details can be found here. If you want an even easier solution - or if you want to let developers handle the mapping without giving them access to the MMC - use a product like CustomError.

Tip 9: Dive into the MetaBase
If you think Apache is powerful because it has a config file, then take a look at the MetaBase. You can do just about anything you want with IIS by editing the MetaBase. For example, you can create virtual directories and servers; stop, start and pause Web sites; and create, delete, enable and disable applications.

Microsoft provides a GUI utility called MetaEdit, somewhat similar to RegEdit, to help you read from and write to the MetaBase. Download the latest version here. But to really impress those UNIX admins - and to take full advantage of the MetaBase by learning how to manipulate it programmatically - you’ll want to try out the command-line interface, officially called the IIS Administration Script Utility. Its short name is adsutil.vbs and you’ll find it in C:\inetpub\adminscripts, or else in %SystemRoot%\system32\inetsrv\adminsamples, together with a host of other useful administrative scripts.

A word of caution though: Just like Apache conf files, the MetaBase is pretty crucial to the functioning of your Web server, so don’t ruin it. Back it up first.

Tip 8: Add spell checking to your URLs
Apache folks always brag about cool little tricks that Apache is capable of - especially because of the wealth of modules that can extend the server’s basic functionality. One of the coolest of these is the ability to fix URL typos using a module called mod_speling. Well, thanks to the folks at Port80 Software, it now appears that IIS admins can do this trick too, using an ISAPI filter called URLSpellCheck. You can check it out right on their site, by trying URLs like www.urlspellcheck.com/fak.htm, www.urlspellcheck.com/faq1.htm - or any other simple typo you care to make.

Tip 7: Rewrite your URLs
Cleaning your URLs has all sorts of benefits - it can improve the security of your site, ease migration woes, and provide an extra layer of abstraction to your Web applications. Moving from a ColdFusion to an ASP based site, for example, is no big deal if you can remap the URLs. Apache users have long bragged about the huge power of mod_rewrite - the standard Apache module for URL rewriting. Well, there are now literally a dozen versions of this type of product for IIS - many of them quite a bit easier to use than mod_rewrite, which tends to presume familiarity with regular _expression arcana. Check out, for example, IIS ReWrite or ISAPI ReWrite. So brag no more, Apache partisans.

Tip 6: Add browser detection
There are a lot of ways to build Web sites, but assuming everybody has a certain browser or screen size is just plain stupid. Simple _JavaScript sniff-scripts exist for client-side browser detection, but if you are an IIS user you can do better with a product called BrowserHawk from CyScape. The Apache world doesn’t really have something comparable to this popular, mature and well-supported product. Speaking of CyScape, they’ve recently added an interesting-looking related product called CountryHawk that helps with location detection, but so far I haven’t had the language- or location-sensitive content to warrant trying it out.

Tip 5: Gzip site content
Browsers can handle Gzipped and deflated content and decompress it on the fly. While IIS 5 had a gzip feature built-in, it is pretty much broken. Enter products like Pipeboost to give us better functionality - similar to what Apache users have enjoyed with mod_gzip. Don’t waste your bandwidth - even Google encodes its content, and their pages are tiny.

Tip 4: Cache your content
While I’m on the topic of improving performance, remember to make your site cache friendly. You can set expiration headers for different files or directories right from the MMC. Just right click on an item via the IIS MMC, flip to the “HTTP Headers” tab, and away you go. If you want to set cache control headers programmatically - or even better, let your site developers do it - use something like CacheRight. If you want to go further and add reverse proxy caching, particularly for generated content, use a product like XCache - which also throws in compression.

It might involve more time and expense to take full advantage of caching, but when you watch your logs shrink because they don’t contain tons of pointless 304 responses, and your bandwidth consumption drop like a stone, even while your total page views increase over the same period, you’ll start to understand why this particular tip was so important. Cache friendly sites are quite rare, but there is plenty of information available online about the enormous benefits to be had by doing it right: Check out Brian Davidson’s page, this nifty tutorial from Mark Nottingham, and what AOL has to say on the subject.

Tip 3: Tune your server
Tuning IIS is no small topic - whole books and courses are dedicated to it. But some good basic help is available online, such as this piece from IIS guru Brett Hill, or this Knowledge Base article from Microsoft itself. However, if you don’t feel like getting your hands dirty - or can’t afford the time and expense of turning yourself into an expert - take a look at XTune, from the makers of XCache. It’s performance tuning wizards step you through the process of tuning your IIS environment, making expert recommendations along the way..

Tip 2: Secure your server with simple fixes
Sure people are going to attack sites, but you don’t have to be a sitting duck if you’re willing to make even a small effort. First off, don’t advertise the fact that you are running IIS by showing your HTTP server header. Remove or replace it using something like ServerMask - probably the best twenty-five bucks you’ll ever spend. You can go farther than this by removing unnecessary file extensions to further camouflage your server environment, and scanning request URLs for signs of exploits. There are number of commercial products that do user input scanning, and Microsoft offers a free tool called URLScan which does the job. URLScan runs in conjunction with IISLockDown, a standard security package which should probably be installed on every IIS server on the planet. These are simple fixes that could pay off big, so do them now.

Tip 1: Patch, patch, patch!
Okay, we in the IIS world do have to patch our systems and make hotfixes. However, as a former Solaris admin I had to do the same thing there, so I am not sure why this is a big surprise. You really need to keep up with the patches, Microsoft is of course the definitive source, but if you can also use the highly-regarded www.cert.org. Simply search on “IIS”.

Understanding Code-Behind with VS 2005 Web Application Projects

Understanding Code-Behind with VS 2005 Web Application Projects

The below tutorial helps explain the code-behind model and project structure of pages built within VS 2005 Web Application Projects. Please make sure that you have already completed Tutorial 1: Building Your First Web Application Project before reviewing this one.

Some Background on the VS 2003 Code-behind Model for ASP.NET Applications

ASP.NET Pages in a VS 2003 web project have two files associated with them -- one is a .aspx file that contains the html and declarative server control markup, and the other is a .cs "code-behind" file that contains the UI logic for the page:

Control markup declarations are defined within the .aspx file itself. For example:

And corresponding protected field declarations are added in the .cs code-behind class that match the name and type of controls defined within the .aspx file. For example:

ASP.NET does the work of wiring up a reference from this code-behind field declaration to point to the declared control in the .aspx file at runtime. Developers can then program directly against this control within their code-behind file.

VS 2003 automatically adds/updates these protected control field declarations at the top of the code-behind file (these are updated everytime a developer switches into WYSIWYG design-view):

VS 2003 also then maintains a hidden region block inside the code-behind of tool-generated code to register event-handlers and keep them in sync with the design-surface:

There are two common complaints/problems with this:

1) VS 2003 is adding/deleting code in the same file where the developer is authoring their own code -- and accidental conflicts/mistakes do end up happening (for example: some code that the developer writes can sometimes get modified or deleted by VS). The tool-generated hidden block above is also a little "messy" for some people's tastes.

2) The control-declarations are only updated when a developer using VS 2003 activates the WYSIWYG page designer. If a developer is only using the source-editor to customize the page, they will not get control updates, and will instead have to add these control declarations manually (which is a pain).

VS 2005 Code-behind Model for ASP.NET Applications

VS 2005 uses a code-behind model conceptually the same as VS 2003. Specifically, each .aspx page continues to inherit from a code-behind class that contains protected control field references for each control in the .aspx page:

What is different between VS 2003 and VS 2005 is that Visual Studio no longer injects its tool-specific wire-up code in the developer's code-behind file. Instead, it takes advantage of a new language feature in C# and VB called "partial types" (or partial classes) to split the code-behind implementation across two files. One of these partial class files is the developer-owned code-behind file that contains developer-written event-handlers and code for the page. The other partial class file is then a tool-generated/maintained file that contains the protected control field declarations and the other design-time code that Visual Studio requires. The benefit of splitting them out into two separate files at design-time is that it ensures that the code that VS creates and maintains never interferes (or deletes) code that a developer writes. At compile-time, these files are compiled together and generate a single code-behind class.

With the VS 2005 Web Application project model, the design-time partial class is generated and persisted on disk by VS 2005. This new design-time partial-class file has the filename naming pattern: PageName.aspx.designer.cs. If you expand any new page created within your VS 2005 Web Application project, you can see this file listed under the associated Page.aspx file along with the developer-owned code-behind file:

If you open up the code-behind file of the page (Default.aspx.cs), you'll then see the code-behind logic of the page -- which contains all of the code and event handlers that a developer writes (and no tool-generated "code-spit" content -- which means it stays very clean):

If you open the Default.aspx.designer.cs file, you'll then see the design-time code of the page -- which contains the field declarations for controls within the .aspx page:

Because the MyWebProject._Default class is marked as "partial" in both of the above two files, the compiler will merge them into a single generated class at compile-time. This means that any variable, method or field generated in the default.aspx.designer.cs file can be used from the default.aspx.cs code-behind file (just as if it was declared in the code-behind file itself). For example, within the Page_Load event handler we could easily add the below code that uses the "Label1" and "Calendar1" control:

This will compile clean and run just fine -- because the "Label1" and "Calendar1" field references have been defined within the default.aspx.designer.cs file.

When you do a build inside a VS 2005 Web Application project, all pages, user-controls, master pages (and their associated code-behind files+design-time generated files), along with all other standalone classes within the project are compiled into a single assembly. This is the same behavior as with VS 2003.

Click here to go to the next tutorial.

code for checking whether a session exists or not

The session will always exist probably because when your session expires and you access a page again it just makes up a new session for you.

One thing you can do is the first time you access the app just set a session variable like:

Session["LoggedIn"] = "1";

Then check to see if it exists. If it doesn't then you know the session has been expired and recreated so you can boot them back.

if (Session["LoggedIn"] == null)
{
Response.Redirect(
"YourSignInPage.aspx");
}
else
{
// Do whatever you were going to do.
}

How to avoid Duplicate records in SQL select query

DISTINCT command in SQL collects the unique or distinct records from a field of a table. In the student table we are interested to know how many class records are there and the DISTINCT sql command should return class once only. So if class five is there ten times then it should return once and if class six one record is there then class six should return once.

So using DISTINCT sql command we can avoid duplicate records in SELECT query
by using DISTINCT sql command we can select unique records from DataBase
There is another related command sql group by which groups the data and brings the unique names. This group by command is usually used along with count, average, minimum, maximum commands. Here we will discuss sql distinct command only


DISTINCT command will return records once only.

SELECT DISTINCT class FROM student
This is our table and we will apply DISTINCT command to this table.

id name class mark
1 John Deo Four 75
2 Max Ruin Three 85
3 Arnold Three 55
4 Krish Star Four 60
5 John Mike Four 60
6 Alex John Four 55
Here again the DISTINCT command in SQL
SELECT DISTINCT class FROM `student`
The output is displayed here
class
Four
Three
As you can see only two rows are returned and they are the distinct class in the table

How to create a stored procedure on SQL Server and how to use them in asp.net applications C#

Introduction

Stored procedures (sprocs) are generally an ordered series of Transact-SQL statements bundled into a single logical unit. They allow for variables and parameters, as well as selection and looping constructs. A key point is that sprocs are stored in the database rather than in a separate file.

Advantages over simply sending individual statements to the server include:

  1. Referred to using short names rather than a long string of text; therefore, less network traffiic is required to run the code within the sproc.
  2. Pre-optimized and precompiled, so they save an incremental amount of time with each sproc call/execution.
  3. Encapsulate a process for added security or to simply hide the complexity of the database.
  4. Can be called from other sprocs, making them reusable and reducing code size.

Parameterization

A stored procedure gives us some procedural capability, and also gives us a performance boost by using mainly two types of parameters:

  • Input parameters
  • Output parameters

From outside the sproc, parameters can be passed in either by position or reference.

Declaring Parameters

  1. The name
  2. The datatype
  3. The default value
  4. The direction

The syntax is :

@parameter_name [AS] datatype [= default|NULL] [VARYING] [OUTPUT|OUT]

Let's now create a stored procedure named "Submitrecord".

First open Microsoft SQL Server -> Enterprise Manager, then navigate to the database in which you want to create the stored procedure and select New Stored Procedure.



See the below Stored Procedure Properties for what to enter, then click OK.



Now create an application named Store Procedure in .net to use the above sprocs.

Stored Procedure.aspx page code

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<
html xmlns="http://www.w3.org/1999/xhtml" >
<
head runat="server">
<
title>Store Procedure</title>
</
head>
<
body>
<
form id="form1" runat="server">
<
div>
<
asp:Label ID="Label1" runat="server" Text="ID"></asp:Label>
<
asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><br /><br />
<
asp:Label ID="Label2" runat="server" Text="Password"></asp:Label>
<
asp:TextBox ID="TextBox2" runat="server"></asp:TextBox><br /><br />
<
asp:Label ID="Label3" runat="server" Text="Confirm Password"></asp:Label>
<
asp:TextBox ID="TextBox3" runat="server"></asp:TextBox><br /><br />
<
asp:Label ID="Label4" runat="server" Text="Email ID"></asp:Label>
<
asp:TextBox ID="TextBox4" runat="server"></asp:TextBox><br /><br /><br />
<
asp:Button ID="Button1" runat="server" Text="Submit Record" OnClick="Button1_Click" />
</
div>
</
form>
</
body>
</
html>

Stored Procedure.aspx.cs page code

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;
public partial class _Default : System.Web.UI.Page
{
DataSet ds = new DataSet();
SqlConnection con;
//Here we declare the parameter which we have to use in our application
SqlCommand cmd = new SqlCommand();
SqlParameter sp1 = new SqlParameter();
SqlParameter sp2 = new SqlParameter();
SqlParameter sp3 = new SqlParameter();
SqlParameter sp4 = new SqlParameter();

protected void Page_Load(object sender, EventArgs e)
{
}

protected void Button1_Click(object sender, EventArgs e)

{
con =
new SqlConnection("server=(local); database= gaurav;uid=sa;pwd=");
cmd.Parameters.Add(
"@ID", SqlDbType.VarChar).Value = TextBox1.Text;
cmd.Parameters.Add(
"@Password", SqlDbType.VarChar).Value = TextBox2.Text;
cmd.Parameters.Add(
"@ConfirmPassword", SqlDbType.VarChar).Value = TextBox3.Text;
cmd.Parameters.Add(
"@EmailID", SqlDbType.VarChar).Value = TextBox4.Text;
cmd =
new SqlCommand("submitrecord", con);
cmd.CommandType =
CommandType.StoredProcedure;
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}

When we run the application, the window will look like this:



After clicking the submit button the data is appended to the database as seen below in the SQL Server table record:

Source: http://www.c-sharpcorner.com/UploadFile/gtomar/storedprocedure12052007003126AM/storedprocedure.aspx

Adding Columns (BoundColumn and HyperLinkColumn) to DataGrid Programmatically in C#

Sometimes we need to create column in DataGrid dynamically. By using the following code you can create BoundColumn and HyperLinkColumn to DataGrid Programmatically using C#.

Snippet 1: Adding BoundColumn and HyperLinkColumn to DataGrid

public void AddboundandHyperLinkColumn()
{
// First add a simple bound column
BoundColumn nameColumn =
new BoundColumn();
nameColumn.DataField =
"ProductName";
nameColumn.DataFormatString =
"{0}";
nameColumn.HeaderText =
"Product";

// Now add the HyperLink column
HyperLinkColumn linkColumn =
new HyperLinkColumn();
linkColumn.DataTextField =
"ProductName";
linkColumn.DataTextFormatString =
"{0} Details";
linkColumn.DataNavigateUrlField =
"ProductID";
linkColumn.DataNavigateUrlFormatString =
"/MyApp/ProductDetails.aspx={0}";
linkColumn.HeaderText =
"Details";

// Add the link in a BoundColumn
// where the text can be the same for all rows
BoundColumn blinkColumn =
new BoundColumn();
blinkColumn.DataField =
"ProductID";
blinkColumn.DataFormatString =
"<a href='/MyApp/ProductDetails.aspx={0}'>Details</a>";
blinkColumn.HeaderText =
"Details";

DataGrid1.Columns.Add(nameColumn);
DataGrid1.Columns.Add(linkColumn);
DataGrid1.Columns.Add(blinkColumn);
DataGrid1.AutoGenerateColumns =
false;

DataTable dt = GetNorthwindProductTable();
DataGrid1.DataSource = dt;
DataGrid1.DataBind();
}

Note that I added three columns. The first was a simple text column (BoundColumn) with the product name. For the second column I added a link to a product details page using the HyperLinkColumn. For the third column I showed an alternate way of adding a link column if the link text can be the same for all rows, such as "Details". Just added the HTML link tag as text to the BoundColumn. It will be rendered as an HTML link when you view the page.

Saturday, December 29, 2007

Getting the identity of the most recently added record

The built-in functions @@Identity() and Scope_Identity() are designed to retrieve the most recently added record's autoincrement identity value from Access and Sql Server respectively. Here are some usage examples.
Access and @@Identity()

The Jet 4.0 provider supports @@Identity(), which means that developers no longer need to use Select Max(ID) or some other contrived method of obtaining new ID values. The key to @@Identity is that it returns the value of an autoincrement column that is generated on the same connection.

This last bit is important, because it means that the Connection object used for the Insert query must be re-used without closing it and opening it up again. Access doesn't support batch statements, so each must be run separately. It is also therefore possible, though not necessary, to create a new Command object to run the Select @@Identity query. The following code shows this in action where the Connection object is opened, then the first query is executed against cmd, followed by changing the CommandText property of cmd to "Select @@Identity" and running that.

[c#]
string query = "Insert Into Categories (CategoryName) Values (?)";
string query2 = "Select @@Identity";
int ID;
string connect = "Provider=Microsoft.Jet.OLEDB.4.0;" +
"Data Source=|DataDirectory|Northwind.mdb";
using (OleDbConnection conn = new OleDbConnection(connect))
{
using (OleDbCommand cmd = new OleDbCommand(query, conn))
{
cmd.Parameters.AddWithValue("", Category.Text);
conn.Open();
cmd.ExecuteNonQuery();
cmd.CommandText = query2;
ID = (int)cmd.ExecuteScalar();
}
}

[VB]
Dim query As String = "Insert Into Categories (CategoryName) Values (?)"
Dim query2 As String = "Select @@Identity"
Dim ID As Integer
Dim connect As String = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=|DataDirectory|Northwind.mdb"
Using conn As New OleDbConnection(connect)
Using cmd As New OleDbCommand(query, conn)
cmd.Parameters.AddWithValue("", Category.Text)
conn.Open()
cmd.ExecuteNonQuery()
cmd.CommandText = query2
ID = cmd.ExecuteScalar()
End Using
End Using

A quick word about the absence of conn.Close() in all these examples: in all the snippets, the Connection object is instantiated within a 'using' block. At the end of the 'using' block, Dispose() is automatically called on objects created in the beginning of the block. If you do not employ 'using' blocks, make sure you explicitly call conn.Close() as soon as you are done with the connection.
Sql Server and Scope_Identity()

While Sql Server also supports @@Identity(), the recommended method for obtaining identity values on this platform is Scope_Identity(), which retrieves the last identity value created in the current scope. 'Scope' is a single module, which can be a stored procedure, batch, function or trigger. This can be used in a number of ways. Sql Server supports batch statements which would have you append Select Scope_Identity() to the end of the Insert statement, optionally separating the two statements with a semicolon, and just using ExecuteScalar() against the batch command to return the single value:

[C#]
string query = "Insert Into Categories (CategoryName) Values (@CategoryName);" +
"Select Scope_Identity()";
int ID;
string connect = @"Server=.\SQLExpress;AttachDbFilename=|DataDirectory|Northwind.mdf;" +
"Database=Northwind;Trusted_Connection=Yes;";
using (SqlConnection conn = new SqlConnection(connect))
{
using (SqlCommand cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@CategoryName", Category.Text);
conn.Open();
ID = (int)cmd.ExecuteScalar();
}
}

[VB]
Dim query As String = "Insert Into Categories (CategoryName) Values (@CategoryName);" & _
"Select Scope_Identity()"
Dim ID As Integer
Dim connect As String = "Server=.\SQLExpress;AttachDbFilename=|DataDirectory|Northwind.mdf;" & _
"Database=Northwind;Trusted_Connection=Yes;"
Using conn As New SqlConnection(connect)
Using cmd As New SqlCommand(query, conn)
cmd.Parameters.AddWithValue("@CategoryName", Category.Text)
conn.Open()
ID = cmd.ExecuteScalar()
End Using
End Using

Alternatively, you may prefer to use an output parameter from a stored procedure, and ExecuteNonQuery().

[C#]
string query = "AddCategory";
int ID;
string connect = @"Server=.\SQLExpress;Database=Northwind;Trusted_Connection=Yes;";
using (SqlConnection conn = new SqlConnection(connect))
{
using (SqlCommand cmd = new SqlCommand(query, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@Category", Category.Text);
cmd.Parameters.Add("@CategoryID", SqlDbType.Int, 0, "CategoryID");
cmd.Parameters["@CategoryID"].Direction = ParameterDirection.Output;
conn.Open();
cmd.ExecuteNonQuery();
ID = (int)cmd.Parameters["@CategoryID"].Value;
}
}

[VB]
Dim query As String = "AddCategory"
Dim ID As Integer
Dim connect As String = "Server=.\SQLExpress;Database=Northwind;Trusted_Connection=Yes;"
Using conn As New SqlConnection(connect)
Using cmd As New SqlCommand(query, conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("@Category", Category.Text)
cmd.Parameters.Add("@CategoryID", SqlDbType.Int, 0, "CategoryID")
cmd.Parameters("@CategoryID").Direction = ParameterDirection.Output
conn.Open()
cmd.ExecuteNonQuery()
ID = cmd.Parameters("@CategoryID").Value
End Using
End Using

And the procedure...

CREATE PROCEDURE AddCategory
-- Add the parameters for the stored procedure here
@Category nvarchar(15),
@CategoryID int OUTPUT
AS
BEGIN
SET NOCOUNT ON;

-- Insert statements for procedure here
Insert Into Categories (CategoryName) Values (@Category)
Set @CategoryID = Scope_Identity()
END

Finally, you can create a stored procedure that contains no output parameter, but ends with 'Select Scope_Identity()'. This version requires ExecuteScalar(), and requires less ADO.NET code and a shorter Stored Procedure.

[C#]
string query = "AddCategory";
int ID;
string connect = @"Server=.\SQLExpress;Database=Northwind;Trusted_Connection=Yes;";
using (SqlConnection conn = new SqlConnection(connect))
{
using (SqlCommand cmd = new SqlCommand(query, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@Category", Category.Text);
conn.Open();
ID = (int)cmd.ExecuteScalar();
}
}

[VB]
Dim query As String = "AddCategory"
Dim ID As Integer
Dim connect As String = "Server=.\SQLExpress;Database=Northwind;Trusted_Connection=Yes;"
Using conn As New SqlConnection(connect)
Using cmd As New SqlCommand(query, conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("@Category", Category.Text)
conn.Open()
ID = cmd.ExecuteScalar()
End Using
End Using

The (slightly) smaller procedure:

CREATE PROCEDURE AddCategory
-- Add the parameters for the stored procedure here
@Category nvarchar(15)
AS
BEGIN
SET NOCOUNT ON;

-- Insert statements for procedure here
Insert Into Categories (CategoryName) Values (@Category)
Select Scope_Identity()
END

So which method should you use with SQL Server? ExecuteNonQuery and an output parameter? Or no parameter and ExecuteScalar? The answer to that is whichever way you prefer. There is no real performance difference between the methods. My personal preference is for a stored procedure and ExecuteScalar. It requries the least amount of code :)
DataSource controls
Sql Server

And finally, for those that like to use the SqlDataSource, here's how to use the Insert() method to retrieve Scope_Identity() from SQL Server. I am grateful to Motley (Robert McKee), an MVP who regularly hangs out at the forums at asp.net for providing this solution.

The SqlDataSource control internally performs an ExecuteNonQuery() when it is called on to perform an Insert, so by just appending SELECT SCOPE_IDENTITY() to the InsertCommand and trying to retrieve the return value will not work. This is because ExecuteNonQuery() returns either the number of rows affected by an Update, Insert or Delete operation, or -1 for all other operations, which will overwrite any resultset produced from the appended Select statement. The way to work around this is to use a parameter value. This can be achieved declaratively using the following steps:

1. First, add the following to the InsertCommand: "SET @NewId = Scope_Identity()", making sure it is separated from the first part of the command by a semicolon
2. Select the SqlDataSource control in Design view
3. Hit F4 to bring up its properties
4. Click "InsertQuery"
5. Click the elipses (...)
6. In the Command and Parameter Editor, click "Add Parameter"
7. Give the new parameter a name (such as NewId) and click "Show advanced properties"
8. Change the Type to Int32 and the Direction to Output
9. Click OK to seal the deal.

Now if you switch to Source view, you will see that an additional parameter has been added to the InsertParameters collection:

<asp:Parameter Direction="Output" Name="NewId" Type="Int32" />

To retrieve the value, we need to use the SqlDataSource.Inserted event. Back in Design view, bring up the properties of the SqlDataSource again, if they are not still showing, and click the lightning bolt to bring up the events list. Double click the Inserted event, which will create an event handler in code-behind, and associate the SqlDataSource with the event handler. To retrieve the value from the event handler is straightforward:

[C#]
protected void SqlDataSource1_Inserted(object sender, SqlDataSourceStatusEventArgs e)
{
int newid = (int)e.Command.Parameters["@NewId"].Value;
Response.Write(newid.ToString());
}

[VB]
Protected Sub SqlDataSource1_Inserted(ByVal sender As Object,
ByVal e As SqlDataSourceStatusEventArgs)
Handles SqlDataSource1.Inserted
Dim newid As Integer = e.Command.Parameters("@NewId").Value
Response.Write(newid.ToString())
End Sub

Access

The above approach won't work with Access, whether you use a SqlDataSource control or an AccessDataSource control. This is because Access is unable to accept batch commands. An alternative that will work with Access and the DataSource controls is to reuse the control's Connection object against a new Command object to retrieve @@Identity:

[C#]
protected void AccessDataSource1_Inserted(object sender, SqlDataSourceStatusEventArgs e)
{
string query = "SELECT @IDENTITY";
OleDbCommand cmd = OleDbCommand(query, e.Command.Connection);
int newid = (int)cmd.ExecuteScalar();
Response.Write(newid.ToString());
}

[VB]
Protected Sub AccessDataSource1_Inserted(ByVal sender As Object,
ByVal e As SqlDataSourceStatusEventArgs)
Handles AccessDataSource1.Inserted
Dim query As String = "SELECT @@IDENTITY"
Dim cmd As New System.OleDb.Command(query, e.Command.Connection)
Dim newid As Integer = cmd.ExecuteScalar()
Response.Write(newid.ToString())
End Sub

Scope_Identity() for Sql Server will not work with the above, because the change in command object changes the scope from the original command.

Handle Session Timeouts and Forms Authentication Timeouts in ASP.NET 2.0

Problems arise when we attempt to set the forms authentication timeout to a long period of time so that users don’t have to log in every time they return to our site. In this case we tend to run into issues with users being logged in even after their session has expired.

It would be much simpler if we just set the forms authentication timeout to a time that was less than the session timeout. In this case, if the user was inactive for too long, they would be redirected to the log in page. Upon redirection to the login page you could clean up and end their session. However, if we want to allow the user to be automatically logged in on return visits, this isn’t a very good solution. In addition there are other unpleasant scenarios that can occur in this model. Take for instance the following example.

You have a user that logs into your web application and goes into edit mode on one of your webforms. When the user enters edit mode, you store a business class object in a session variable for use while they’re working with that form. The user then gets a phone call and begins a lengthy conversation about selling their house or something. While they are on the phone, the forms authentication ticket expires. The user gets off the phone and enters data into more of the fields on form and then hits save. Boom, the user gets redirected to the login page because their authentication ticket expired. They loose the data they had finished entering and aren’t very happy about it.

The solution I implemented to handle these scenarios more gracefully, is to automatically end the users session, log them out and send them to the login page when their session is about to expire.

This solution is specific to the use of a master page throughout your application. If you’re not using a master page, it would be possible to create a user control that you drop on every page to implement the same functionality. However, why not just use a master page.

In my Default.master.vb I have the following two variables declared at a module level.

Protected mintTimeout As Integer
Protected mstrLoginURL As String

Then in the Page_Init of the Default.master.vb I set the two variables.

Dim intRedirectTime As Integer = 2
mintTimeout = (Session.Timeout - intRedirectTime) * 60000
mstrLoginURL = ResolveUrl("~/endsession.aspx")

The variable intRedirectTime represents how many minutes prior to the session timeout that I want to force the redirect to the login page.

60000 represents 1minute in milliseconds. I’ll be using the window.setTimeout method so I need time in milliseconds. Every time there is a postback in the application the above code is run and sets the timeout variable to 2 minutes less than the session timeout setting. I also set the mstrLoginURL with a resolved URL for my endsession.aspx page.

Then on the Default.master just before the end body tag , I put the following JavaScript section.

In the above javascript section the setTimeout method fires 2 minutes before the session is about to end. The endSession function is called which pops up an alert to notify the user that their session has ended. At this point the session is still active, but will be ended regardless once the Ok button is clicked on the alert dialog. In addition the user will be logged out using the FormsAuthentication.SignOut() method.

The page that the user is actually sent to is a blank page with the following code in the Page_Load. I have named the page endsession.aspx.

If User.Identity.IsAuthenticated = True Then
FormsAuthentication.SignOut()
End If
Session.Abandon()
Response.Redirect("~/Login.aspx")

In the above code I check to see if the user is authenticated before trying to sign them out. I then call the Session.Abandon method and redirect them to the login page.

The solution shows what is possible with a little creativity for handling timeout issues in ASP.NET 2.0 web applications. Every application is different and may require unique solutions for handling issues that arise from the new security model in ASP.NET 2.0. I hope this article inspires ideas for others to solve the session issues they may face.

If you have any thoughts, ideas, or input on this subject, please tell me about it by leaving a comment.

How do I get the IDENTITY / AUTONUMBER value for the row I inserted?

SQL Server

With SQL Server 2000, there are a couple of new functions that are better than @@IDENTITY. Both of these functions are not global to the connection, which is an important weak point of @@IDENTITY. After doing an insert, you can call:

PRINT IDENT_CURRENT('table')

This will give the most recent IDENTITY value for 'table' - regardless of whether you created it or not (this overrides the connection limitation of @@IDENTITY -- which can be useful).

Another thing you can do is:

PRINT SCOPE_IDENTITY()

This will give the IDENTITY value last created within the current stored procedure, trigger, etc.

If you using a version of SQL Server prior to 2000 (or you are in compatibility mode < 80), the best way is to use a single stored procedure that handles both the INSERT and the IDENTITY retrieval using @@IDENTITY.

Here is sample code for the stored procedure:

CREATE PROCEDURE myProc
@param1 INT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO someTable
(
intColumn
)
VALUES
(
@param1
)
SELECT NEWID = SCOPE_IDENTITY()
END

And you would call this from ASP as follows:

<%
fakeValue = 5
set conn = CreateObject("ADODB.Connection")
conn.open "<conn string<"
set rs = conn.execute("EXEC myProc @param1=" & fakeValue)
response.write "New ID was " & rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%<

If you are using SQL Server 7.0, simply change the line in the stored procedure from ...

SELECT NEWID = SCOPE_IDENTITY()

... to ...

SELECT NEWID = @@IDENTITY

The reason SCOPE_IDENTITY() is preferred over @@IDENTITY is that if you perform an INSERT, and that table has an INSERT TRIGGER which then, in turn, inserts into another table with an IDENTITY column, @@IDENTITY is populated with the second table's IDENTITY value. So, if you are stuck using SQL Server 7.0 and need a workaround to retrieving the @@IDENTITY value because you have a trigger that also inserts into another IDENTITY-bound table, you're in luck. You can add this code to the first line of the trigger, but you will have to update all of your application and stored procedure code to deal with this new SELECT:

CREATE TRIGGER triggerInsert_tablename ON tablename FOR INSERT AS
BEGIN
SELECT @@IDENTITY
-- rest of trigger's logic...
END
GO

With that said, there are also potential cases where SCOPE_IDENTITY() can fail, but I think this possibility is more remote than with @@IDENTITY. Observe this repro, provided by David Portas:

CREATE TABLE Table1
(
i INTEGER IDENTITY(1,1) PRIMARY KEY,
x INTEGER NOT NULL UNIQUE
)
GO

CREATE TRIGGER trg_Table1 ON Table1
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO Table1 (x)
SELECT x FROM Inserted
END
GO

INSERT INTO Table1 (x) VALUES (1)
GO

SELECT SCOPE_IDENTITY(), IDENT_CURRENT('Table1')

Result:

------ ------
NULL 1

This is because the actual INSERT happened outside of the scope of the caller, so SCOPE_IDENTITY() was not populated there. I have requested that the documentation for SCOPE_IDENTITY() be updated to reflect the above scenario.

Access

Jet/OLEDB provider now supports @@IDENTITY! See KB #232144 for more info, and see Article #2126 to ensure you are using a Jet/OLEDB connection string.

So with that new information, here is the technique for obtaining this value using Access:

<%
fakeValue = 5
set conn = CreateObject("ADODB.Connection")
conn.open "<conn string<"
sql = "INSERT someTable(IntColumn) values(" & fakeValue & ")" & _
VBCrLf & " SELECT @@IDENTITY"
set rs = conn.execute(sql)
response.write "New ID was " & rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%<

If you are unable to use JET 4.0, you can do a more risky hack like this:

<%
fakeValue = 5
set conn = CreateObject("ADODB.Connection")
conn.open "<conn string<"
conn.execute "INSERT someTable(IntColumn) values(" & fakeValue & ")"
set rs = conn.execute("select MAX(ID) from someTable")
response.write "New ID was " & rs(0)
rs.close: set rs = nothing
conn.close: set conn = nothing
%<

This is more risky because it is remotely possible for two people to "cross" inserts, and receive the wrong autonumber value back. To be frank, if there is a possibility of two or more people simultaneously adding records, you should already be considering SQL Server (see Article #2182). However, if you're stuck with Access and need more security that this won't happen, you can use a Recordset object with an adOpenKeyset cursor (this is one of those rare scenarios where a Recordset object actually makes more sense than a direct T-SQL statement):

<%
fakeValue = 5
set conn = CreateObject("ADODB.Connection")
conn.open "<conn string<"
set rs = CreateObject("ADODB.Recordset")
rs.open "SELECT [intColumn] from someTable where 1=0", conn, 1, 3
rs.AddNew
rs("intColumn") = fakeValue
rs.update
response.write "New ID was " & rs("id")
rs.close: set rs = nothing
conn.close: set conn = nothing
%>


You can also check out KB #221931, which has an officially endorsed code sample for retrieving the AUTOINCREMENT value from an Access database.

Source:http://databases.aspfaq.com/general/how-do-i-get-the-identity/autonumber-value-for-the-row-i-inserted.html

Storing Uploaded Files in a Database or in the File System with ASP.NET 2.0

A common requirement in web sites is handling client files that are uploaded through the browser. Whether these files come from a protected Admin section where content managers can upload files, or from a public section doesn't matter; you need a way to retrieve and store these files in your system somewhere so they are available for viewers.

Allowing a user to upload a file is easy; just drop a FileUpload control on your page and .NET will handle the rest for you. However, it's after the file gets uploaded to the server where things get interesting. One of the things you have to take into account is where you're going to store the uploaded file. Two common places are the hard drive of your server and a database. There has been a lot of debate on the Internet about the best way to store your files. Some say the file system is the only acceptable option, while others really like the database solution.

In this article I'll show you how to store files both ways. I'll discuss the pros and cons of each solution, and show you the code you need to save your uploaded files. As a sample application, I'll build a small web site that allows you to upload files and store them at a location that you determine. You can decide where the files are stored through a simple setting in the web.config file. Depending on this setting, files are either stored directly at the hard drive of your server or in a database. You can even change this setting at run-time without affecting existing files.



Introduction

UPDATE 2006/01/17: I added the VB version of the code to the Downloads section

Right now, the article and the code download are in C# only, but I am working on a Visual Basic version of the application as well. Send me a nice e-mail (1) if you want me to hurry up...

I converted the code to Visual Basic .NET using DotNetTaxi's Code converter (2) and then manually fixed a few remaining issues. I tested the application and everything seems to work fine. If you do find an issue, please let me know (3). The full VB code is available in the Downloads section at the end of this article.

Besides showing how to store files in the database and retrieve them again, this article also presents a little demo application that demonstrates the concepts from this article. Instead of performing all the code directly in the code behind of the ASPX pages, this application uses a number of separate classes that each perform a limited set of tasks. If you're not interested in the design of the application or the pros and cons of storing files in the database, you skip directly to the code that shows you how to save files in the database (4) and retrieve them again (5).

Before we start looking at the application and it's code, it's a good idea to briefly look at the pros and cons of both solution. Since so many people have a strong favor for one of the two solutions, there must be interesting differences between the two. Let's look at storing the files in the file system first.

File System - Advantages

One of the main benefits of storing the file on disk is that it's very easy to do. Just call SaveAs on a FileUpload control and you're pretty much done.

Another advantage is that files on disk are easy to backup; you just copy the files to another location. This also makes it easier to do incremental backups; files that have already been backed up don't need to be copied again.

FileSystem - Disadvantages

Storing your files in the file system has a few disadvantages as well. Probably the most problematic issue is the loosely coupled nature of the files on disk. They have no strong relation with a record in the database. So, when you delete, say, a product from the database, you may end up with an orphaned product image. There is no direct way to do an INNER JOIN between the product table and your images folder to determine what orphaned files you have left. This means that a page developer is responsible for writing code that deletes the file from disk whenever the associated database records gets deleted.

Also, to store uploaded files on disk, your web server needs permissions (6) to write to the file system. This is easy to come by (7) when you run your own server, but may prove to be more problematic in an ISP scenario.

Database - Advantages

Of course some of the advantages of a database are the exact opposite of the disadvantages of saving them as physical files: since they are stored in the database, they're easy to relate to other records. They can be retrieved in JOIN style queries, and even be deleted automatically with cascading delete turned on. You also don't need additional permissions on the server; if you can write to the database, you can store files in it.

But another advantage of storing your files in a database is the fact that all data is contained in a single location. Make a backup of your database, and you have everything you need. That makes it a lot easier to move your data to another server; other than the database, you don't need to copy files, set up permissions and so on.

Database - Disadvantages

At the top of the list of disadvantages of storing your files in a database is probably performance. While I don't have any hard figures to support this, the "word is" that it's slow. How slow may depend on your situation, the type of files you have, the server, and so on.

Another downside is the lack of easy access to the files. When you store them on disk, it's easy to download them to your desktop machine and batch process them; for example, use an imaging program to scale or rotate all your images. When you use a database, you need to "materialize" them to disk first and upload them again afterwards.

A final problem with the database is backups. Whenever you make a full backup of your database, all the files are included, whether they have been changed or not. If you copy your backups to a different machine or network for safety reasons, this could be problematic as you need to move the entire backup file. With a file based solution, you can use diff programs that can determine which files have been changed since the last backup, and only download those.

Realizing that both methods have their pros and their cons, the question is: what to choose? Personally, I favor storing them on disk. The accessibility of the files on disk, combined with the simple coding model makes the disk solution a better one in many situations. However, I have also used the database solution at places where that made more sense. Bottom line is that you need to carefully examine your scenario and determine what works for you.

The solution I am presenting in this article shows you how to do both; with a simple option in the web site's config file, you can switch between a file based and a database solution.

Storing the Files

When you store the files on disk, things are pretty easy. The SaveAs method on the PostedFile does all the work for you, provided you set the right security settings (8). Storing them in a database isn't too hard either, but you need to be aware of a few things. First of all, you need to know what data type to use to store your files. With SQL Server 2000, you can use the image data type. Although its name suggests you can only use it to store image files, this is not true. You can store text files, Word documents, spread sheets or any other file type you have. With SQL Server 2005, you can use the new varbinary(max) data type. The sample application that comes with this article uses a SQL Server 2005 Express database and thus the varbinary(max) data type. If you want to use SQL Server 2000 instead, simply replace each occurrence of varbinary(max)with the image data type, including the FileData column in the Files table.

Additionally, you need to know the code to access the database to store and retrieve the files. You'll see a lot more of that right after I discussed the demo application and its feature set.

The Demo Application - A Simple File Manager

The demo application used in the article is a simple web site that allows you to upload, download and view files. With an optional switch in the web.config file, you can determine whether the files are stored in the database or on disk. The code for the entire application is available in the Download Files (9) section at the end of the article.

The application contains four .cs files in the App_Code folder, a SQL Server 2005 Express database, four .ASPX files with code behind and a web.config file, all of them visible in Figure 1.

The Solution Explorer for the Demo Project
Figure 1 - The Solution Explorer for the Project

In this article, I'll show what each file is used for and what code it contains. I won't discuss each and every line of code in the application, but instead focus on the important concepts. You're encouraged to download (10) the application so you can see the full source, and play around with it.

To keep the discussion focused, the design of the application is extremely simple. It features a page with a standard GridView that displays the files that have been upload:


Figure 2 - A GridView Showing the Uploaded Files

Each uploaded file always has a Download link to allow a user to download the file from the server. For files that can be viewed in the browser, there's also a View link. This works only for files like images, Word documents and text files in the demo application, but it's easy to extend this to other files, like Excel spreadsheets.

If you click the Add New File button, you can upload a new file which is then shown in the GridView in Figure 2.

The Demo Application - Class Design

Instead of doing all the code logic in the page's code behind, the application uses a class based design, where each class has a strong focus on a single task. Let's take a look at the class design for the application:


Figure 3 - The Class Diagram for the Sample Application

The class diagram in Figure 3 contains three classes and one enumeration. I'll discuss all four of them in the following section.

The File class

The File class represents an uploaded file and is used to store and retrieve uploaded files on disk and in a database. The database is used in both scenario's. Even when you decide to save the files on disk, some meta data of the file is still stored in the database. The File class has properties to store the ContentType of a file (like image/jpeg, application/msword etc), the date it was created (DateCreated), its unique ID in the database and the original name of the file when it was uploaded (OriginalName). It also contains a FileUrl which is used when files are stored on disk, and a FileData property which contains the actual file when it's retrieved from the database. The last property of the File class is ContainsFile, which indicates whether the file holds the bytes for the uploaded file in FileData, or that it contains a virtual path to the file on disk in FileUrl.

It also contains two constructors and two methods: one to get a file (from the database or from disk) and one to save the file. This Save method has two public overloads and one private version that performs the actual save operation.

The FileInfo class

The FileInfo class serves as a lightweight summary object for the file. A FileInfo object basically contains the meta data of the file, but without the actual file or virtual path to it. This is useful in scenarios where you want to display a list of files, as in Figure 2. In such a case, it doesn't make much sense to have each item in the GridView contain the actual file, as you aren't doing anything with it. By using a lightweight FileInfo object, you can save some overhead and speed up your application.

The AppConfiguration Class

If you're familiar with my latest book ASP.NET 2.0 Instant Results (11), you should be familiar with this class. It's a static class that's essentially a wrapper around some settings in the web.config. While you can certainly access the web.config file from code directly, I prefer to wrap them in a static class with static properties, so I have easy access to them and get Intelli Sense on them.

The ConnectionString property defines the connection string to the SQL Server database (a SQL Server 2005 Express database in the demo application), while UploadsFolder contains a virtual path to the folder where uploaded files are stored (for example: ~/Uploads)

The final property of this class is DataStoreType which determines the location where to store the files. The DataStoreType enumeration is discussed next.

The DataStoreType Enum

The DataStoreType enumeration contains two members: Database and FileSystem. When the application is configured to use Database, all files are stored in the SQL Server 2005 database, When FileSystem is specified, then the meta data is still stored in the database, while the actual files are saved to disk.

Uploading Files

OK, enough for the theory, let's look at some code.

When you click the Add New File button you see in Figure 2, you're taken to UploadFile.aspx. The markup of this page is extremely simple and only contains an control and a Button. When you select a file and then click the Upload File button, the following code is executed:

protected void btnUpload_Click(object sender, EventArgs e)
{
  if (FileUpload1.HasFile)
{
string contentType = FileUpload1.PostedFile.ContentType;

// Get the bytes from the uploaded file
byte[] fileData = new byte[FileUpload1.PostedFile.InputStream.Length];
FileUpload1.PostedFile.InputStream.Read(fileData, 0, fileData.Length);

// Get the name without folder information from the uploaded file.
string originalName = Path.GetFileName(FileUpload1.PostedFile.FileName);

// Create a new instance of the File class based on the uploaded file.
File myFile = new File(contentType, originalName, fileData);

// Save the file, and tell the Save method what data store to use.
switch (AppConfiguration.DataStoreType)
{
case DataStoreType.Database:
myFile.Save();
break;
case DataStoreType.FileSystem:
myFile.Save(Server.MapPath(Path.Combine(
AppConfiguration.UploadsFolder, myFile.FileUrl)));
break;
}
Response.Redirect("~/");
}
}

(From: UploadFile.aspx.cs)

The code first checks if a file has been uploaded by looking at the HasFile property. If that's the case, its content type is derived from the ContentType property of the PostedFile.

Next, a byte array is created from the uploaded file. First, the array is dimensioned to the length of the uploaded file and then it's filled by calling Read on the InputStream of the PostedFile. At this stage, fileData contains the actual bytes of the file the user has uploaded.

Then the original name is retrieved. By default, PostedFile.FileName contains the full path and name of the file at the client's computer. Since we're only interested in the file's name (and extension) and not in the original path, Path.GetFileName is used to strip off the path.

Then a new File object is constructed with a constructor that accepts the content type, the file name and the bytes for the file. Inside this constructor, the parameters are stored in the class's backing variables, like this:

public File(string contentType, string originalName, byte[] fileData)
{
this.id = Guid.NewGuid();
this.contentType = contentType;
this.fileData = fileData;
this.originalName = originalName;

string extension = Path.GetExtension(originalName);
string fileName = this.Id.ToString() + extension;

this.fileUrl = fileName;
}

(From: File.cs)

Notice how the constructor builds up the unique fileUrl for the file, by adding a Guid and the file's extension together. This way, each file ends up with a unique name so you don't have to worry about files being accidentally overwritten. Since the original name of the file is always stored in the database, it's easy to use that name again when the file is downloaded. You'll see how this works later.

Once the new File instance is ready, it's saved by calling Save(). There are two public overloads of the Save method; a parameterless version that saves the file in the database and an overloaded version that accepts the physical location of where the file must be saved. Notice that the correct overload is chosen based on the DataStoreType:

// Save the file, and tell the Save method what data store to use.
switch (AppConfiguration.DataStoreType)
{
case DataStoreType.Database:
myFile.Save();
break;

case DataStoreType.FileSystem:
myFile.Save(Server.MapPath(Path.Combine(
AppConfiguration.UploadsFolder, myFile.FileUrl)));
break;
}

(From: UploadFile.aspx.cs)

Both of the two public versions of the Save method call another, private overload that accepts a DataStoreType and the physical location of the file. This version of the Save method is discussed next.

Saving the File

The Save method contains a lot of code, but most of it is pretty standard ADO.NET code:

private bool Save(DataStoreType dataStoreType, string filePath)
{
using (SqlConnection mySqlConnection = new
SqlConnection(AppConfiguration.ConnectionString))
{
// Set up the Command object
SqlCommand myCommand = new SqlCommand(
"sprocFilesInsertSingleItem", mySqlConnection);
myCommand.CommandType = CommandType.StoredProcedure;

// Set up the ID parameter
SqlParameter prmId = new SqlParameter("@id",
SqlDbType.UniqueIdentifier);

prmId.Value = id;
myCommand.Parameters.Add(prmId);

// Set up the FileData parameter
SqlParameter prmFileData = new SqlParameter("@fileData ",
SqlDbType.VarBinary);
// If we need to store the file in the database,
// pass in the actual file bytes.
if (dataStoreType == DataStoreType.Database)
{
prmFileData.Value = fileData;
prmFileData.Size = fileData.Length;
}
else
{
prmFileData.Value = DBNull.Value;
}
myCommand.Parameters.Add(prmFileData);


// Set up the FileUrl parameter
SqlParameter prmFileUrl = new SqlParameter("@fileUrl",
SqlDbType.NVarChar, 255);
// If we need to store the file on disk, save the fileUrl.
if (dataStoreType == DataStoreType.FileSystem)
{
prmFileUrl.Value = fileUrl;
}
else
{
prmFileUrl.Value = DBNull.Value;
}
myCommand.Parameters.Add(prmFileUrl);


// Set up the OriginalName parameter
SqlParameter prmOriginalName = new SqlParameter("@originalName",
SqlDbType.NVarChar, 50);
prmOriginalName.Value = originalName;
myCommand.Parameters.Add(prmOriginalName);

// Set up the ContentType parameter
SqlParameter prmContentType = new SqlParameter("@contentType",
SqlDbType.NVarChar, 50);
prmContentType.Value = contentType;
myCommand.Parameters.Add(prmContentType);

// Execute the command, and clean up.
mySqlConnection.Open();
bool result = myCommand.ExecuteNonQuery() > 0;
mySqlConnection.Close();

// Save the file to disk if necessary; shown later

return result;
}
}

(From: File.cs)

This code creates a SqlConnection and a SqlCommand object and then creates a number of parameters. Most of them are pretty straight forward, but the fileUrl and fileData parameters need a bit more explanation (highlighted in the code above). When the DataStoreType passed in equals Database, it means the actual file is stored in the database. In that case, the fileData property is assigned to the value of the prmFileData parameter. The type of that parameter is SqlDbType.VarBinary, which corresponds to the data type of the column in the database. (When you're using SQL Server 2000, you should use SqlDbType.Image instead.) Since the file has already been converted to a byte array in the calling code, all you need to do here is assign the fileData variable to the parameter's Value property.

In the else clause (when the FileSystem option is used), DBNull.Value is passed instead, so null is stored in the database.

The FileUrl parameter takes the opposite approach and only passes its value when DataStoreType is FileSystem.

Finally, the Command object is executed against an open database connection and the result of the ExecuteNonQuery call is stored in a temporary variable.

Notice that when the FileSystem is used, the Save method also saves the file to disk:

// Database update is done; now store the file on disk if we need to.
if (dataStoreType == DataStoreType.FileSystem)
{
const int myBufferSize = 1024;
Stream myInputStream = new MemoryStream(fileData);
Stream myOutputStream = System.IO.File.OpenWrite(filePath);
byte[] buffer = new Byte[myBufferSize];
int numbytes;
while ((numbytes = myInputStream.Read(buffer, 0, myBufferSize)) > 0)
{
myOutputStream.Write(buffer, 0, numbytes);
}
myInputStream.Close();
myOutputStream.Close();
}

(From: File.cs)

To save the file, the byte array is converted back to a Stream again, which is then written to disk using another Stream object. For the file name, this code uses the filePath parameter passed to the Save method.

As an alternative to this solution, you can decide to call the SaveAs method on the FileUpload control in the code behind of UploadFile.aspx. This is easier, as the SaveAs method takes care of it all; all you need to do is pass a file name. However, it also means you're spreading logic across your application. By encapsulating all the code into a single File class, your application becomes easier to maintain and extend.

As another alternative, you could pass the entire FileUpload control to the Save method, and have that method call its SaveAs method. Personally, I don't like that solution too much, as it ties the File class to a WebControl, which makes it harder to reuse it in other type of applications.

The stored procedure that is used in the Save method is really simple; it performs a simple INSERT operation in the File table:

CREATE PROCEDURE sprocFilesInsertSingleItem

@id uniqueidentifier,
@fileUrl nvarchar(255),
@fileData varbinary (max),
@originalName nvarchar(50),
@contentType nvarchar(50)

AS

INSERT INTO Files
(
Id,
FileUrl,
FileData,
OriginalName,
ContentType
)
VALUES
(
@id,
@FileUrl,
@FileData,
@originalName,
@contentType
)

(From: sprocFilesInsertSingleItem)

Again, the varbinary(max) data type is used for the fileData parameter, to line up with the SqlParameter and the column for the file in the database as you saw earlier.

With the Save operation done, the next thing to look at is displaying files.

Displaying the Files

To display files in a web browser, there are two scenario's to consider: displaying lists of files, and displaying or downloading a single file. I'll look at the list first, followed by the single file.

Viewing a List of Files

The page Default.aspx, that displays a list of files that can be downloaded or viewed, contains a simple GridView with a few columns that display the file's unique Id, original name, content type and the date and time it was uploaded. It also contains two columns that allow you to download a file and - in certain circumstances - view the file in-line in the browser. In Figure 2 you can see that only some files have the View link enabled. Some code in the code behind looks at the content type of the file in each row, and enables the View link when the file can be viewed in-line in the browser. That's the case for images (jpg, gif for example) and other files that the browser can usually handle (Word files, Excel spreadsheets). Other files that can't be viewed directly, but need a separate application (Zip and Rar files for example) can only be downloaded. You'll see the code for this a little later.

The GridView gets its data from an ObjectDataSource control that is tied to the FileInfo class with its GetList method. You could opt to implement the GetList method in the File class and remove the entire FileInfo class. However, I have chosen to implement it like this for performance reasons. In most scenarios, when you display a list of files, you don't need the actual files; all you need is some meta data like the name and unique ID. By implementing a simple, and read-only class FileInfo, you get all the data you need without the overhead of retrieving all the files from the database.

Because all the work is done by the GetList method of the FileInfo class, the ObjectDataSource is very simple:

   

(From: Default.aspx)

When this control gets its data, it calls the static GetList method that returns a generics List of FileInfo objects:

public static List GetList()
{
List myList = null;

using (SqlConnection mySqlConnection = new SqlConnection(
AppConfiguration.ConnectionString))
{
SqlCommand myCommand = new SqlCommand("sprocFileInfoSelectList",
mySqlConnection);
myCommand.CommandType = CommandType.StoredProcedure;

mySqlConnection.Open();

using (SqlDataReader myReader = myCommand.ExecuteReader())
{
if (myReader.HasRows)
{
myList = new List();
while (myReader.Read())
{
myList.Add(new FileInfo(myReader));
}
}
}

mySqlConnection.Close();
}
return myList;
}

(From: FileInfo.cs)

Again, most of this code is pretty straight forward. A SqlCommand executes the stored procedure sprocFileInfoSelectList to get the data from the database. Each record retrieved from the database is transformed into a FileInfo object that is added to a generics List. To make the code a bit more readable, the FileInfo class has a constructor that accepts a SqlDataReader. This way, you only need a single line of code in the while loop to create a FileInfo instance and add it to the list. The constructor that accepts the SqlDataReader looks like this:

public FileInfo(SqlDataReader myReader)
{
id = myReader.GetGuid(myReader.GetOrdinal("Id"));
dateCreated = myReader.GetDateTime(myReader.GetOrdinal("DateCreated"));
originalName = myReader.GetString(myReader.GetOrdinal("OriginalName"));
contentType = myReader.GetString(myReader.GetOrdinal("ContentType"));
}

(From: FileInfo.cs)

Each of the four fields of the FileInfo class are filled with a value from the SqlDataReader. Since all fields are required in the database, there's no need to check for null values.

At the end of the GetList method, the list with FileInfo objects is returned to the calling code where it's bound to the GridView. For each FileInfo object that is added to the GridView, it fires its RowDataBound event that is used to determine whether the View link should be enabled or not:

protected void GridView1_RowDataBound(object sender,
GridViewRowEventArgs e)
{
switch (e.Row.RowType)
{
case DataControlRowType.DataRow:
FileInfo myFileInfo = (FileInfo) e.Row.DataItem;
switch (myFileInfo.ContentType.ToLower())
{
case "image/pjpeg":
case "image/gif":
case "application/msword":
case "text/plain":
// Do nothing. When the row contains a viewable type,
// we want the View link to be enabled.
break;
default:
// Find the View link and disable it.
HyperLink myLink = (HyperLink)e.Row.FindControl("lnkView");
myLink.Enabled = false;
break;
}
break;
}
}

(From: Default.aspx.cs)

Whenever a DataRow is added, a FileInfo object is retrieved from the DataItem property of e.Row. The FileInfo's ContentType is then used to determine whether the file is viewable in the browser or not. In the example above, this is only done for .jpg, .gif, .doc and .txt files but you can easily add additional case statements to the switch block.

For all other files, the link is retrieved with the FindControl method of the row and then its Enabled property is set to false.

Downloading or Viewing a Single File

When you click the Download or View link, you're take to DownloadFile.aspx or ViewFile.aspx respectively. The code behind of these files are pretty similar, but there are a few interesting differences worth looking at.

Both pages start by getting a File object from the database by calling File.GetItem. This method is pretty straight forward, and uses the same principle as the FileInfo class by implementing a constructor that accepts a SqlDataReader object to fill the private fields:

public File(SqlDataReader myReader)
{
id = myReader.GetGuid(myReader.GetOrdinal("Id"));
dateCreated = myReader.GetDateTime(myReader.GetOrdinal("DateCreated"));
originalName = myReader.GetString(myReader.GetOrdinal("OriginalName"));
contentType = myReader.GetString(myReader.GetOrdinal("ContentType"));

if (!myReader.IsDBNull(myReader.GetOrdinal("FileData")))
{
fileData = (byte[])myReader[myReader.GetOrdinal("FileData")];
containsFile = true;
}
else
{
fileUrl = myReader.GetString(myReader.GetOrdinal("FileUrl"));
containsFile = false;
}
}

(From: File.cs)

The first four fields are retrieved directly from the reader. The code then checks if the column FileData has a value that isn't null. If that's the case, the value is casted to a byte array and assigned to the fileData field. Notice that containsFile is also set to true to indicate that the File instance contains the actual file bytes. The public ContainsFile property is used in the download and view pages to determine whether the File instance contains the actual file bytes, or that they should search for the files on the server's hard drive.

In the else clause, the fileUrl (with the virtual path to the file on disk, starting from the virtual Uploads folder) is set, and containsFile gets a value of false.

Once the File object is returned, DownloadFile.aspx sets Response.ContentType to application/x-unknown and appends an additional header that contains the original name of the file. Then it uses either the BinaryWrite or the WriteFile method of the Response object, based on the fact whether the File object contains the actual file data:

Response.ContentType = "application/x-unknown";
Response.AppendHeader("Content-Disposition",
"attachment; filename=\"" + myFile.OriginalName + "\"");
if (myFile.ContainsFile)
{
Response.BinaryWrite(myFile.FileData);
}
else
{
Response.WriteFile(Path.Combine(AppConfiguration.UploadsFolder, myFile.FileUrl));
}

(From: DownloadFile.aspx.cs)

In both cases, this forces the browser to display a File Download dialog that allows the user to save the file to disk :

The File Download Dialog that Allows a User to Save the File to Disk
Figure 4 - The File Download Dialog

The ViewFile.aspx page takes a similar approach, but first sets the ContentType that it retrieves from the File instance. It then switches between BinaryWrite and Redirect to send the file to the browser where it's displayed in line:

Response.ContentType = myFile.ContentType;
if (myFile.ContainsFile)
{
Response.BinaryWrite(myFile.FileData);
}
else
{
Response.Redirect(Path.Combine(AppConfiguration.UploadsFolder, myFile.FileUrl));
}

(From: ViewFile.aspx.cs)

With the code to download or view an uploaded file, we've come full circle. You can now upload files to the server and store them on disk or in a database depending on your own preferences or requirements. You can list the files in a GridView and offer your users a way to download or display them.

Summary

Storing your uploaded files in a SQL Server database can be very convenient. It allows you to easily relate the uploaded files to other records in the database. However, there are also some disadvantages that you need to be aware of. One is performance, while another is increased backup-time. Whether you should save your files on disk or in a database depends on your own preferences and requirements.

This article showed you how to upload your files and store them in a database or on disk depending on a simple configuration switch. When you store them in a database, you can use the Image or the varbinary(max)data type. When you store them on disk, you need to ensure that the account used by the web server has sufficient permissions to write to the Uploads folder.

At the end of the article I showed you the code to either download a file or view it directly in the browser. Viewing it in the browser is not possible for every type of file. For example, .zip files cannot be viewed directly so they can only be downloaded. The code in the code behind of Default.aspx determines whether the View link must be enabled based on the file's ContentType property.


Source: http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=414