ASP.NET
MVC Uploading and Downloading Files
If you come to ASP.NET MVC from a purely ASP.NET Web Forms
background, one of the first things you are likely to notice is that all those
nice easy Server Controls have disappeared. One of those is the FileUpload, and
its absence seems to cause a few problems. This article looks at how to upload
files to the server in an MVC world, and how to get them back from the server
to the user again.
In Web Forms, when you
drag a FileUpload control on to the designer, something happens when the page
is rendered which you probably don't notice. The resulting html form that wraps
the entire page is decorated with an extra attribute:
enctype="multipart/form-data". The FileUpload itself is rendered as
an html input type=file. Within an MVC View, there are a number of ways to set
this up. The first is with HTML:
<form action="/" method="post" enctype="multipart/form-data">
<input
type="file"
name="FileUpload1"
/><br />
<input
type="submit"
name="Submit"
id="Submit"
value="Upload"
/>
</form>
Notice that the
<form> tag includes the enctype attribute, and method attribute of post. This is needed because the form by default
will be submitted via the HTTP get method. The
following approach, using the Html.BeginForm() extension method renders the
exact same html when the page is requested:
@using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"})){
<input
type="file"
name="FileUpload1"
/><br />
<input
type="submit"
name="Submit"
id="Submit"
value="Upload"
/>
}
Notice the name attribute of the <input type="file"> element. We'll come back to that shortly. In
the meantime, the resulting page should look rather blandly like this:

OK. So we can now
browse to a local file and click the submit button to upload it to the web
server. What is needed next is some way to manage the file on the server. When
using a FileUpload control, you generally see code that checks to see if a file
actually has been uploaded, using the FileUpload.HasFile() method. There
isn't the same convenience when you are working with MVC, as you are much
closer to the raw HTTP. However, a quick extension method can take care
of that:
public static bool HasFile(this HttpPostedFileBase file)
{
return
(file != null
&& file.ContentLength > 0) ? true : false;
}
When you look at
Controller class, you see that it has a Request object as a property, which is
of type HttpRequestBase. This is a wrapper for an HTTP request, and exposes
many properties, including a Files collection (actually a collection of type
HttpFileCollectionBase). Each item within the collection is of type
HttpPostedFileBase. The extension method checks the item to make sure there's
one there, and that it has some content. Essentially, this is identical to the
way that the FileUpload.HasFile() method works.
Putting that into use
within the Controller Action is quite simple:
public class HomeController : Controller
{
public
ActionResult Index()
{
foreach
(string upload in Request.Files)
{
if
(!Request.Files[upload].HasFile()) continue;
string
path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string
filename = Path.GetFileName(Request.Files[upload].FileName);
Request.Files[upload].SaveAs(Path.Combine(path,
filename));
}
return
View();
}
}
Multiple File Uploading
You might already be
ahead of me at this point, and wondering how you might make use of the fact
that Request.Files is a collection. That suggests that it can accommodate more
than one file, and indeed, it can. If you change the original View to this:
@using (Html.BeginForm("", "home", FormMethod.Post, new {enctype="multipart/form-data"})){
<input
type="file"
name="FileUpload1"
/><br />
<input
type="file"
name="FileUpload2"
/><br />
<input
type="file"
name="FileUpload3"
/><br />
<input
type="file"
name="FileUpload4"
/><br />
<input
type="file"
name="FileUpload5"
/><br />
<input
type="submit"
name="Submit"
id="Submit"
value="Upload"
/>
}
you will end up with
this:

The code in the
controller Action already checks all file uploads, so no changes are needed for
it to work with multiple file uploads. Notice that each input has a different name attribute. If you need to reference them individually,
that is what you use. For example, to reference the third one, you would get at
it using Request.Files["FileUpload3"].
Saving to a Database
Before you scream
"Separation of Concerns!" at me, the next piece of code is purely
illustrative. It features ADO.NET within a controller action. As we all know,
this is simply not done. Database access code belongs to your data access layer
somewhere inside the Model. However, the code should give people a starting
point if they want to save uploaded files to a database. First of all, I have
created a database (FileTest) and added a table: FileStore:
CREATE TABLE [dbo].[FileStore](
[ID] [int] IDENTITY(1,1) NOT NULL,
[FileContent] [image] NOT NULL,
[MimeType] [nvarchar](50) NOT NULL,
[FileName] [nvarchar](50) NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
The FileContent field
is an image datatype, and is where the binary data that forms the file will be
stored. The Index Action is changed to the following:
public ActionResult Index()
{
foreach
(string upload in Request.Files)
{
if
(!Request.Files[upload].HasFile()) continue;
string
mimeType = Request.Files[upload].ContentType;
Stream
fileStream = Request.Files[upload].InputStream;
string
fileName = Path.GetFileName(Request.Files[upload].FileName);
int
fileLength =
Request.Files[upload].ContentLength;
byte[] fileData = new byte[fileLength];
fileStream.Read(fileData, 0, fileLength);
const
string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";
using
(var conn = new SqlConnection(connect))
{
var
qry = "INSERT
INTO FileStore (FileContent, MimeType, FileName) VALUES (@FileContent,
@MimeType, @FileName)";
cmd.Parameters.AddWithValue("@MimeType",
mimeType);
cmd.Parameters.AddWithValue("@FileName",
fileName);
conn.Open();
cmd.ExecuteNonQuery();
}
}
return
View();
}
The revised code still
loops through as many uploads as are on the web page, and checks each one to
see if it has file. From there, it extracts 3 pieces of information: the file
name, the mime type (what type of file it is) and the actual binary data that
is streamed as part of the HTTP Request. The binary data is transferred to a
byte array, which is what is stored in the image datatype field in the
database. The mime type and name are important for when the file is returned to
a user. We shall look at that part next.
Serving Files to the
User
How you deliver files
back to users will depend on how you have stored them primarily. If you have
them stored in a database, you will usually stream the file back to the user.
If they are stored on a disk, you can either simply provide a hyperlink to
them, or again, stream them. Whenever you need to stream a file to the browser,
you will use one of the overloads of the File() method (instead of the View()
method that has been used so far in the preceding examples). There are 3
different return types of the File() method: a FilePathResult,
FileContentResult and a FileStreamResult. The first streams a file directly
from disk; the second sends a byte array back to the client, while the third sends
the contents of a Stream object which has been generated and opened.
If you remember, when
saving the uploaded files into a database, we sent a byte array to the
FileContent field. When we need to get that back, it will be as a byte array
again. If you have been keeping up, this means that we can use one of the two
overloads of File() that return a FileContentResult. If you want the name of
the file to be meaningful, you will use the overload that takes 3 arguments -
the byte array, the mime type and the file name:
public FileContentResult GetFile(int id)
{
SqlDataReader
rdr; byte[] fileContent = null;
string
mimeType = "";string fileName = "";
const
string connect = @"Server=.\SQLExpress;Database=FileTest;Trusted_Connection=True;";
using
(var conn = new SqlConnection(connect))
{
var
qry = "SELECT
FileContent, MimeType, FileName FROM FileStore WHERE ID = @ID";
var
cmd = new
SqlCommand(qry,
conn);
cmd.Parameters.AddWithValue("@ID", id);
conn.Open();
rdr = cmd.ExecuteReader();
if
(rdr.HasRows)
{
rdr.Read();
fileContent = (byte[])rdr["FileContent"];
mimeType = rdr["MimeType"].ToString();
fileName = rdr["FileName"].ToString();
}
}
return
File(fileContent, mimeType, fileName);
}
The easiest way to
invoke this method is to provide a hyperlink:
<a href="/GetFile/1">Click
to get file</a>
If the files in the
database are images, instead of a hyperlink, you just point to the controller
action within the src attribute of an <img> element:
<img src="/GetFile/1" alt="My
Image" />
We'll have a look at
how to simply use the FilePathResult now. This is used to stream files directly
from disk:
public FilePathResult GetFileFromDisk()
{
string
path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string
fileName = "test.txt";
return
File(path + fileName, "text/plain",
"test.txt");
}
And this is also
invoked via a simple hyperlink:
<a href="/GetFileFromDisk">Click to get file</a>
The final option -
FileStreamResult can be used to serve files from disk too:
public FileStreamResult StreamFileFromDisk()
{
string
path = AppDomain.CurrentDomain.BaseDirectory + "uploads/";
string
fileName = "test.txt";
return
File(new
FileStream(path
+ fileName, FileMode.Open), "text/plain", fileName);
}
So what's the
difference between FilePathResult and FileStreamResult and which one should you
use? The main difference is that FilePathResult uses HttpResponse.TransmitFile
to write the file to the http output. This method doesn't buffer the file in
memory on the server, so it should be a better option for sending larger files.
It's very much like the difference between using a DataReader or a DataSet. On
the other hand, you might need to check the server you are hosting your site
on, as a bug in TransmitFile may lead to partial delivery of files, or even complete failure.
FileStreamResult is a great way of, for example, returning Chart images
generated in memory by the ASP.NET Chart Controls without having to save them
to disk.
This comment has been removed by the author.
ReplyDeletehi this is good for knowledge i have some information related to this visit Computer and internet
ReplyDeletenice information about this computer and internet
ReplyDelete