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:
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:
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.
<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.
You can download the sample project and database
here. I hope you find the article useful.
by: sitg on 2010-04-22 at 22:09:57ID: 13713
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