Simple ASP.NET download page

AID: 2655
  • Status: Published

10150 points

  • ByPaulHews
  • TypeResource
  • Posted on2010-03-11 at 14:38:57
Awards
  • Experts Exchange Approved
Download the code and data for this sample application.

There are times when you need to track downloads of a particular resource from your server.  While IIS maintains logs of each HTTP request, it would be painful to use them to track downloads of certain files, because every request for every resource gets logged.  

You can track your downloads easily enough by placing the resources in a folder that is accessible to the ASPNET user, that is not served by IIS.  For this example, I will use my C:\temp folder.  Depending on what folder you use, and in what environment, you may have to check the file permissions for the ASPNET user.  (In some environments, it may be the Network Service user or another user designated by an administrator, depending on the version of IIS and the configuration of ASP.NET.)

The linking page



The goal is to enable links to binary resources through a Download.aspx webform.  Any given link from another page could look like this:

<a href="Download.aspx?file=test.zip">Test file download</a>
                                    
1:

Select allOpen in new window



We will pass the name of the file requested in the Request.QueryString as shown.  If the filename contains spaces or other special characters you must URLEncode the filename, or the browser may not be able to read the link:

<a href="Download.aspx?file=name+with+spaces.zip">Name with spaces download</a>
                                    
1:

Select allOpen in new window



Download.aspx



The Download.aspx page handles requests for downloading the binary references.  Thus it's important that we do not send anything to the client other than the bits of the file that are requested, and a few headers.  To make sure we don't send extra text when the page is requested, we have to remove the HTML and Form1 from the markup.  The markup should only contain a page declaration:

<%@ Page Language="vb" AutoEventWireup="false" 
CodeBehind="Download.aspx.vb" 
Inherits="SampleDownloadScript.Download" %>
                                    
1:
2:
3:

Select allOpen in new window



The actual code behind checks the Request.QueryString for the file parameter.  If it is present, we then check for the existence of the file.

If the file exists, then we need to retrieve the bytes, set a couple of headers, and do a Response.BinaryWrite:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
	Handles Me.Load
	Dim FilePart As String = Request.QueryString("file")

	If Not String.IsNullOrEmpty(FilePart) Then
		'In this case, C:\temp is folder that ASPNET user can access, 
		'but is not served by IIS.
		Dim FileName As String = IO.Path.Combine("C:\temp", FilePart)
		If IO.File.Exists(FileName) Then
			'You can change this according to the file type you 
			'are downloading
			Response.ContentType = "application/zip"
			'This will force the Save As dialog.  Spaces in the filepart 
			'will be okay for IE and Chrome, 
			'but Firefox will truncate at the first space.  So you could 
			'replace spaces with underscores
			'or URLEncode if you wish.  
			Response.AddHeader("Content-disposition", "attachment; filename=" _
							   & FilePart)
			'This will write the bytes to the browser when download is okayed.
			Response.WriteFile(FileName)
		End If
	Else
		Response.Write("<html><body>Nothing to do</body></html>")

	End If

End Sub
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:

Select allOpen in new window



This code offers full control over the download process.  You can restrict the download in anyway you can imagine, or track it with a database.  In this sample, I've decided to demonstrate how to track unique downloads by IP address.

Tracking Database



I designed a simple one table database in the App_Data folder of the solution for this.  Ideally for normalization, you would want one table to list files, and a detail table that shows the downloads for each file, but for the sake of simplicity I created two columns:  IP and Filename, both text types.

 
TableDefinition.jpg
  • 12 KB
  • Table definition
Table definition


The connection string is stored in the web.config in the connectionStrings section.  

<connectionStrings>
	<add name="Downloads" 
		 connectionString="Data Source=.\SQLEXPRESS;
AttachDbFilename=|DataDirectory|\TestDownloads.mdf;
Integrated Security=True;User Instance=True" 
		 providerName="System.Data.SqlClient"/>
</connectionStrings>
                                    
1:
2:
3:
4:
5:
6:
7:

Select allOpen in new window



Again for simplicity, there is a stored procedure for adding a new download record:

ALTER PROCEDURE dbo.AddUniqueDownload
	
	(
	@IP varchar(50),
	@Filename nvarchar(255)
	)
	
AS
	SET NOCOUNT ON 
	--Check if already exists, if not then add it.
	IF NOT EXISTS(SELECT IP FROM UniqueDownloads WHERE IP = @IP AND Filename = @Filename)
	BEGIN
	    INSERT INTO UniqueDownloads (IP, Filename) VALUES (@IP, @Filename)
	END
	RETURN
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:

Select allOpen in new window


      
This will only add a new record for this file, if it hasn't already been downloaded by this IP address.

We also need a way to read the number of downloads for a particular file:

ALTER PROCEDURE dbo.GetUniqueFileDownloads
	
	(
	@filename nvarchar(255),
	@outcount int OUTPUT
	)
	
AS
	SET NOCOUNT ON
	
	SELECT @outcount = COUNT(IP)
	FROM UniqueDownloads 
	WHERE Filename = @Filename
	
	RETURN
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:

Select allOpen in new window


      

Web Application Sample



Here is the revised Download.aspx.vb code-behind that uses the database:

Imports System.Net.Mail
Imports System.Data.SqlClient

Partial Public Class Download
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        Dim FilePart As String = Request.QueryString("file")

        If Not String.IsNullOrEmpty(FilePart) Then
            'In this case, C:\temp is folder that ASPNET user can 
            'access, but is not served by IIS.
            Dim FileName As String = IO.Path.Combine("C:\temp", FilePart)
            If IO.File.Exists(FileName) Then
                'You can change this according to the file type you are downloading
                Response.ContentType = "application/zip"
                'This will force the Save As dialog.  Spaces in the filepart will be 
                'okay for IE and Chrome, but Firefox will truncate at the first space.  
                'So you could replace spaces with underscores or URLEncode 
                'if you wish.  
                Response.AddHeader("Content-disposition", _
                                   "attachment; filename=" & FilePart)
                'This will write the bytes to the browser when download is okayed.
                Response.WriteFile(FileName)
                Debug.WriteLine(Response.Status)

                If Not String.IsNullOrEmpty(Request.UserHostAddress) Then
                    IncrementUniqueDownload(FilePart, Request.UserHostAddress)
                End If
            End If
        Else
            Response.Write("<html><body>Nothing to do</body></html>")

        End If

    End Sub
    
    

    Private Sub IncrementUniqueDownload(ByVal Filename As String, ByVal IP As String)

        Dim ConnString As String = ConfigurationManager. _
            ConnectionStrings("Downloads").ConnectionString
        Using conn As New SqlConnection(ConnString)
            conn.Open()
            Using cmd As New SqlClient.SqlCommand("AddUniqueDownload", conn)
                cmd.CommandType = CommandType.StoredProcedure
                cmd.Parameters.AddWithValue("@filename", Filename)
                cmd.Parameters.AddWithValue("@IP", IP)
                cmd.ExecuteNonQuery()
            End Using
        End Using
    End Sub

End Class
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:

Select allOpen in new window



The IncrementUniqueDownload method takes Filename and IP address, and inputs them into the table.  The connection string is stored in the web.config, and accessed through the WebConfigurationManager.  The UserHostAddress isn't perfect for determining unique requests, because multiple users could be requesting the file from a proxy or gateway, but it will prevent users who download multiple times from being counted more than once.

Now lets look at some code for a referring page.  I put a literal control on the default web form.  

Imports System.Data.SqlClient

Partial Public Class _Default
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles Me.Load
        'Make sure you have some .zip files in your temp directory
        Dim Files() As String = IO.Directory.GetFiles("C:\temp\", "*.zip")
        Dim sb As New System.Text.StringBuilder()

        For Each FilePath As String In Files
            Dim FileName As String = IO.Path.GetFileName(FilePath)
            sb.Append("<a href=""Download.aspx?file=" & _
                      HttpUtility.UrlEncode(FileName) & """>")
            sb.Append(FileName)
            sb.AppendFormat("</a> has been downloaded {0} times.<br />", _
                            GetUniqueDownloads(FileName))
            sb.AppendLine()
        Next

        litFiles.Text = sb.ToString
    End Sub

    Private Function GetUniqueDownloads(ByVal Filename As String) As Integer
        Dim Count As Integer
        Dim ConnString As String = ConfigurationManager. _
             ConnectionStrings("Downloads").ConnectionString()
        Using conn As New SqlConnection(ConnString)
            conn.Open()
            Using cmd As New SqlClient.SqlCommand("GetUniqueFileDownloads", conn)
                cmd.CommandType = CommandType.StoredProcedure
                cmd.Parameters.AddWithValue("@filename", Filename)
                cmd.Parameters.Add("@outcount", SqlDbType.Int).Direction = _
                                        ParameterDirection.Output
                cmd.ExecuteNonQuery()
                Count = CInt(cmd.Parameters("@outcount").Value)
            End Using
        End Using
        Return Count

    End Function
End Class
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:

Select allOpen in new window



If you have some .zip files in your temp folder, you will see them listed along with the number of times they have been downloaded.  
 
DownloadPage.jpg
  • 10 KB
  • Download Page
Download Page


You can download the sample project and database here.  I hope you find the article useful.
    Asked On
    2010-03-11 at 14:38:57ID2655
    Tags

    ASP.NET 2.0

    ,

    track downloads

    Topic

    Programming for ASP.NET

    Views
    5438

    Comments

    Expert Comment

    by: sitg on 2010-04-22 at 22:09:57ID: 13713

    Hi,

    I need to download a .exe from my webspace and track in database that how many number of bytes downloaded by user  in asp.net + sql server.+ C#.

    If there is any solution please provide.

    Regards,

    SITG

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Join Experts Exchange Today

    Gain Access to all our Tech Resources

    Get personalized answers

    Ask unlimited questions

    Access Proven Solutions

    Search 3.2 million solutions

    Read In-Depth How-To Guides

    1000+ articles, demos, & tips

    Watch Step by Step Tutorials

    Learn direct from top tech pros

    And Much More!

    Your complete tech resource

    See Plans and Pricing

    30-day free trial. Register in 60 seconds.

    Loading Advertisement...

    Top ASP.NET Experts

    1. CodeCruiser

      420,756

      Wizard

      2,010 points yesterday

      Profile
      Rank: Genius
    2. kaufmed

      283,163

      Guru

      500 points yesterday

      Profile
      Rank: Genius
    3. BuggyCoder

      269,283

      Guru

      3,600 points yesterday

      Profile
      Rank: Sage
    4. tommyBoy

      171,028

      Guru

      0 points yesterday

      Profile
      Rank: Genius
    5. TheLearnedOne

      165,990

      Guru

      0 points yesterday

      Profile
      Rank: Savant
    6. ddayx10

      139,847

      Master

      0 points yesterday

      Profile
      Rank: Sage
    7. MlandaT

      122,463

      Master

      0 points yesterday

      Profile
      Rank: Genius
    8. techChallenger1

      107,384

      Master

      0 points yesterday

      Profile
      Rank: Guru
    9. navneethegde

      90,197

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    10. Masteraco

      82,909

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    11. Dhaest

      80,693

      Master

      2,000 points yesterday

      Profile
      Rank: Genius
    12. vs00saini

      76,565

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    13. Chinmay_Patel

      73,407

      Master

      0 points yesterday

      Profile
      Rank: Genius
    14. stephanonline

      69,461

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    15. nishantcomp2512

      68,521

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    16. srosebabu

      67,676

      Master

      0 points yesterday

      Profile
      Rank: Guru
    17. markmiddlemist

      65,828

      Master

      0 points yesterday

      Profile
      Rank: Master
    18. jacko72

      65,660

      Master

      0 points yesterday

      Profile
      Rank: Genius
    19. sammySeltzer

      64,318

      Master

      0 points yesterday

      Profile
      Rank: Genius
    20. wdosanjos

      59,639

      Master

      0 points yesterday

      Profile
      Rank: Genius
    21. mroonal

      58,080

      Master

      0 points yesterday

      Profile
      Rank: Sage
    22. mas_oz2003

      56,140

      Master

      0 points yesterday

      Profile
      Rank: Genius
    23. Rouchie

      55,801

      Master

      0 points yesterday

      Profile
      Rank: Genius
    24. Lalit-Chandra

      54,514

      Master

      0 points yesterday

      Profile
      Rank: Master
    25. EaswaranP

      54,203

      Master

      0 points yesterday

      Profile
      Rank: Guru

    Hall Of Fame