感谢icktoofay的帮助,以下是一个完整的示例,可以节省其他开发者的时间:
磁盘示例
public static void TransmitFile(this HttpResponse response, string filename, string etag)
{
var request = HttpContext.Current.Request;
var fileInfo = new FileInfo(filename);
var responseLength = fileInfo.Exists ? fileInfo.Length : 0;
var buffer = new byte[4096];
var startIndex = 0;
if (request.Headers["If-Match"] == "*" && !fileInfo.Exists ||
request.Headers["If-Match"] != null && request.Headers["If-Match"] != "*" && request.Headers["If-Match"] != etag)
{
response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
response.End();
}
if (!fileInfo.Exists)
{
response.StatusCode = (int)HttpStatusCode.NotFound;
response.End();
}
if (request.Headers["If-None-Match"] == etag)
{
response.StatusCode = (int)HttpStatusCode.NotModified;
response.End();
}
if (request.Headers["Range"] != null && (request.Headers["If-Range"] == null || request.Headers["IF-Range"] == etag))
{
var match = Regex.Match(request.Headers["Range"], @"bytes=(\d*)-(\d*)");
startIndex = Parse<int>(match.Groups[1].Value);
responseLength = (Parse<int?>(match.Groups[2].Value) + 1 ?? fileInfo.Length) - startIndex;
response.StatusCode = (int)HttpStatusCode.PartialContent;
response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + fileInfo.Length;
}
response.Headers["Accept-Ranges"] = "bytes";
response.Headers["Content-Length"] = responseLength.ToString();
response.Cache.SetCacheability(HttpCacheability.Public);
response.Cache.SetETag(etag);
response.TransmitFile(filename, startIndex, responseLength);
}
public void ProcessRequest(HttpContext context)
{
var id = Parse<int>(context.Request.QueryString["id"]);
var version = context.Request.QueryString["v"];
var db = new DataClassesDataContext();
var filePath = db.Documents.Where(d => d.ID == id).Select(d => d.Fullpath).FirstOrDefault();
if (String.IsNullOfEmpty(filePath) || !File.Exists(filePath))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.End();
}
context.Response.AddHeader("content-disposition", "filename=" + Path.GetFileName(filePath));
context.Response.ContentType = GetMimeType(filePath);
context.Response.TransmitFile(filePath, version);
}
数据库示例
public static void TransmitFile(this HttpResponse response, string retrieveBinarySql, object[] retrieveBinarySqlParameters, string connectionString, int contentLength, string etag, bool useFilestream)
{
var request = HttpContext.Current.Request;
var responseLength = contentLength;
var buffer = new byte[4096];
var startIndex = 0;
if (request.Headers["If-Match"] == "*" && contentLength == 0 ||
request.Headers["If-Match"] != null && request.Headers["If-Match"] != "*" && request.Headers["If-Match"] != etag)
{
response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
response.End();
}
if (contentLength == 0)
{
response.StatusCode = (int)HttpStatusCode.NotFound;
response.End();
}
if (request.Headers["If-None-Match"] == etag)
{
response.StatusCode = (int)HttpStatusCode.NotModified;
response.End();
}
if (request.Headers["Range"] != null && (request.Headers["If-Range"] == null || request.Headers["IF-Range"] == etag))
{
var match = Regex.Match(request.Headers["Range"], @"bytes=(\d*)-(\d*)");
startIndex = Parse<int>(match.Groups[1].Value);
responseLength = (Parse<int?>(match.Groups[2].Value) + 1 ?? contentLength) - startIndex;
response.StatusCode = (int)HttpStatusCode.PartialContent;
response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + contentLength;
}
response.Headers["Accept-Ranges"] = "bytes";
response.Headers["Content-Length"] = responseLength.ToString();
response.Cache.SetCacheability(HttpCacheability.Public);
response.Cache.SetETag(etag);
response.BufferOutput = false;
if (!useFilestream)
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var command = new SqlCommand(retrieveBinarySql, connection);
for (var i = 0; retrieveBinarySqlParameters != null && i < retrieveBinarySqlParameters.Length; i++)
{
command.Parameters.AddWithValue("p" + i, retrieveBinarySqlParameters[i]);
command.CommandText = command.CommandText.Replace("{" + i + "}", "@p" + i);
}
var reader = command.ExecuteReader(CommandBehavior.SequentialAccess);
if (!reader.Read())
{
response.StatusCode = (int)HttpStatusCode.NotFound;
response.End();
}
for (var i = startIndex; i < contentLength; i += buffer.Length)
{
var bytesRead = (int)reader.GetBytes(0, i, buffer, 0, buffer.Length);
response.OutputStream.Write(buffer, 0, bytesRead);
}
}
}
else
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
var tran = connection.BeginTransaction(IsolationLevel.ReadCommitted);
var command = new SqlCommand(Regex.Replace(retrieveBinarySql, @"select \w+ ", v => v.Value.TrimEnd() + ".PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() "), connection);
command.Transaction = tran;
for (var i = 0; retrieveBinarySqlParameters != null && i < retrieveBinarySqlParameters.Length; i++)
{
command.Parameters.AddWithValue("p" + i, retrieveBinarySqlParameters[i]);
command.CommandText = command.CommandText.Replace("{" + i + "}", "@p" + i);
}
var reader = command.ExecuteReader();
if (!reader.Read())
{
response.StatusCode = (int)HttpStatusCode.NotFound;
response.End();
}
var path = reader.GetString(0);
var transactionContext = (byte[])reader.GetValue(1);
using (var fileStream = new SqlFileStream(path, transactionContext, FileAccess.Read, FileOptions.SequentialScan, 0))
{
fileStream.Seek(startIndex, SeekOrigin.Begin);
int bytesRead;
do
{
bytesRead = fileStream.Read(buffer, 0, buffer.Length);
response.OutputStream.Write(buffer, 0, bytesRead);
}
while (bytesRead == buffer.Length);
}
tran.Commit();
}
}
}
public void ProcessRequest(HttpContext context)
{
var id = Parse<int>(context.Request.QueryString["id"]);
var db = new DataClassesDataContext();
var doc = db.Documents.Where(d => d.ID == id).Select(d => new { d.Data.Length, d.Filename, d.Version }).FirstOrDefault();
if (doc == null)
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.End();
}
context.Response.AddHeader("content-disposition", "filename=" + doc.Filename);
context.Response.ContentType = GetMimeType(doc.Filename);
context.Response.TransmitFile("select data from documents where id = {0}", new[] { id }, db.Connection.ConnectionString, doc.Length, doc.Version, false);
}
辅助方法
public static T Parse<T>(object value)
{
try { return (T)System.ComponentModel.TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(value.ToString()); }
catch (Exception) { return default(T); }
}
public string GetMimeType(string fileName)
{
return (string)Assembly.Load("System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.GetType("System.Web.MimeMapping")
.GetMethod("GetMimeMapping", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Invoke(null, new object[] { fileName });
}
这展示了一种从磁盘或数据库读取文件部分内容并作为响应输出的方法,而不是将整个文件加载到内存中。如果下载在中途暂停或恢复,这种方法可以节省资源。编辑:添加etag以启用IE9中的可恢复下载,感谢EricLaw在使其在IE9中正确工作方面提供的帮助。