Все эти ответы очень хороши. Я люблю простоту класса мистера Мичама GenericHandlerRouteHandler<T>
. Это хорошая идея, чтобы исключить ненужную ссылку на виртуальный путь, если вы знаете конкретный класс HttpHandler
. Однако класс GenericHandlerRoute<T>
не нужен. Существующий класс Route
, производный от RouteBase
, уже обрабатывает всю сложность сопоставления маршрутов, параметров и т. Д., Поэтому мы можем просто использовать его вместе с GenericHandlerRouteHandler<T>
.
Ниже приведена комбинированная версия с реальным примером использования, который включает параметры маршрута.
Сначала идут обработчики маршрута. Здесь есть два включенных - оба с одним и тем же именем класса, но одно общее и использующее информацию о типе для создания экземпляра конкретного HttpHandler
, как при использовании г-на Мичама, и другое, которое использует виртуальный путь и BuildManager
для создания экземпляра соответствующего HttpHandler
, как при использовании shellscape. Хорошая новость заключается в том, что .NET позволяет обеим сторонам просто прекрасно жить, поэтому мы можем просто использовать все, что захотим, и переключаться между ними по своему желанию.
using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
public class HttpHandlerRouteHandler<T> : IRouteHandler where T : IHttpHandler, new() {
public HttpHandlerRouteHandler() { }
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return new T();
}
}
public class HttpHandlerRouteHandler : IRouteHandler {
private string _VirtualPath;
public HttpHandlerRouteHandler(string virtualPath) {
this._VirtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return (IHttpHandler) BuildManager.CreateInstanceFromVirtualPath(this._VirtualPath, typeof(IHttpHandler));
}
}
Давайте предположим, что мы создали HttpHandler
, который передает документы пользователям из ресурса, находящегося за пределами нашей виртуальной папки, возможно, даже из базы данных, и что мы хотим обмануть браузер пользователя, заставив его поверить, что мы непосредственно обслуживаем определенный файл вместо того, чтобы просто обеспечить загрузку (т. е. позволить подключаемым модулям браузера обрабатывать файл, а не заставлять пользователя сохранять файл). HttpHandler
может ожидать, что идентификатор документа будет использоваться для поиска документа, и может ожидать, что имя файла будет предоставлено браузеру - имя, которое может отличаться от имени файла, используемого на сервере.
Ниже показана регистрация маршрута, используемого для достижения этого с DocumentHandler
HttpHandler
:
routes.Add("Document", new Route("document/{documentId}/{*fileName}", new HttpHandlerRouteHandler<DocumentHandler>()));
Я использовал {*fileName}
, а не просто {fileName}
, чтобы параметр fileName
действовал как необязательный параметр catch-all.
Чтобы создать URL для файла, обслуживаемого этим HttpHandler
, мы можем добавить следующий статический метод в класс, где такой метод будет уместен, например, в самом классе HttpHandler
:
public static string GetFileUrl(int documentId, string fileName) {
string mimeType = null;
try { mimeType = MimeMap.GetMimeType(Path.GetExtension(fileName)); }
catch { }
RouteValueDictionary documentRouteParameters = new RouteValueDictionary { { "documentId", documentId.ToString(CultureInfo.InvariantCulture) }
, { "fileName", DocumentHandler.IsPassThruMimeType(mimeType) ? fileName : string.Empty } };
return RouteTable.Routes.GetVirtualPath(null, "Document", documentRouteParameters).VirtualPath;
}
Я опустил определения MimeMap
и и IsPassThruMimeType
, чтобы упростить этот пример. Но они предназначены для определения того, должны ли определенные типы файлов предоставлять свои имена файлов непосредственно в URL, или, скорее, в заголовке HTTP Content-Disposition
. Некоторые расширения файлов могут быть заблокированы IIS или сканированием URL-адресов или могут привести к выполнению кода, который может вызвать проблемы у пользователей, особенно если источником файла является другой пользователь, который является вредоносным. Вы можете заменить эту логику другой фильтрующей логикой или полностью ее исключить, если вы не подвержены этому типу риска.
Поскольку в этом конкретном примере имя файла может быть опущено в URL, то, очевидно, мы должны извлечь имя файла откуда-то. В этом конкретном примере имя файла можно получить, выполнив поиск с использованием идентификатора документа, и включение имени файла в URL предназначено исключительно для улучшения взаимодействия с пользователем. Таким образом, DocumentHandler
HttpHandler
может определить, было ли указано имя файла в URL, а если нет, то он может просто добавить HTTP-заголовок Content-Disposition
к ответу.
Оставаясь в теме , важной частью приведенного выше блока кода является использование RouteTable.Routes.GetVirtualPath()
и параметров маршрутизации для генерации URL-адреса из объекта Route
, который мы создали в процессе регистрации маршрута. .
Вот расширенная версия класса DocumentHandler
HttpHandler
(для ясности многое опущено). Вы можете видеть, что этот класс использует параметры маршрута для получения идентификатора документа и имени файла, когда это возможно; в противном случае он будет пытаться получить идентификатор документа из параметра строки запроса (т.е. при условии, что маршрутизация не использовалась).
public void ProcessRequest(HttpContext context) {
try {
context.Response.Clear();
// Get the requested document ID from routing data, if routed. Otherwise, use the query string.
bool isRouted = false;
int? documentId = null;
string fileName = null;
RequestContext requestContext = context.Request.RequestContext;
if (requestContext != null && requestContext.RouteData != null) {
documentId = Utility.ParseInt32(requestContext.RouteData.Values["documentId"] as string);
fileName = Utility.Trim(requestContext.RouteData.Values["fileName"] as string);
isRouted = documentId.HasValue;
}
// Try the query string if no documentId obtained from route parameters.
if (!isRouted) {
documentId = Utility.ParseInt32(context.Request.QueryString["id"]);
fileName = null;
}
if (!documentId.HasValue) { // Bad request
// Response logic for bad request omitted for sake of simplicity
return;
}
DocumentDetails documentInfo = ... // Details of loading this information omitted
if (context.Response.IsClientConnected) {
string fileExtension = string.Empty;
try { fileExtension = Path.GetExtension(fileName ?? documentInfo.FileName); } // Use file name provided in URL, if provided, to get the extension.
catch { }
// Transmit the file to the client.
FileInfo file = new FileInfo(documentInfo.StoragePath);
using (FileStream fileStream = file.OpenRead()) {
// If the file size exceeds the threshold specified in the system settings, then we will send the file to the client in chunks.
bool mustChunk = fileStream.Length > Math.Max(SystemSettings.Default.MaxBufferedDownloadSize * 1024, DocumentHandler.SecondaryBufferSize);
// WARNING! Do not ever set the following property to false!
// Doing so causes each chunk sent by IIS to be of the same size,
// even if a chunk you are writing, such as the final chunk, may
// be shorter than the rest, causing extra bytes to be written to
// the stream.
context.Response.BufferOutput = true;
context.Response.ContentType = MimeMap.GetMimeType(fileExtension);
context.Response.AddHeader("Content-Length", fileStream.Length.ToString(CultureInfo.InvariantCulture));
if ( !isRouted
|| string.IsNullOrWhiteSpace(fileName)
|| string.IsNullOrWhiteSpace(fileExtension)) { // If routed and a file name was provided in the route, then the URL will appear to point directly to a file, and no file name header is needed; otherwise, add the header.
context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", HttpUtility.UrlEncode(documentInfo.FileName)));
}
int bufferSize = DocumentHandler.SecondaryBufferSize;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, bufferSize)) > 0 && context.Response.IsClientConnected) {
context.Response.OutputStream.Write(buffer, 0, bytesRead);
if (mustChunk) {
context.Response.Flush();
}
}
}
}
}
catch (Exception e) {
// Error handling omitted from this example.
}
}
В этом примере используются некоторые дополнительные пользовательские классы, такие как класс Utility
, для упрощения некоторых тривиальных задач.Но, надеюсь, вы сможете пройти через это.Конечно, единственная действительно важная часть этого класса в отношении текущей темы - это получение параметров маршрута из context.Request.RequestContext.RouteData
.Но я видел несколько постов в других местах, в которых спрашивалось, как транслировать большие файлы, используя HttpHandler
, не перегружая память сервера, поэтому было неплохо объединить примеры.