Todas estas respuestas son muy buenos. Me encanta la simplicidad de la clase GenericHandlerRouteHandler<T>
del Sr. Meacham. Es una buena idea eliminar una referencia innecesaria a una ruta virtual si conoce la clase específica HttpHandler
. Sin embargo, la clase GenericHandlerRoute<T>
no es necesaria. La clase existente Route
que deriva de RouteBase
ya maneja toda la complejidad de la coincidencia de rutas, parámetros, etc., así que solo podemos usarla junto con GenericHandlerRouteHandler<T>
.
A continuación se muestra una versión combinada con un ejemplo de uso de la vida real que incluye parámetros de ruta.
Primero están los controladores de ruta. Hay dos incluidos, aquí - ambos con el mismo nombre de clase, pero uno que es genérico y usa información de tipo para crear una instancia del HttpHandler
específico como en el uso del Sr. Meacham, y uno que usa una ruta virtual y BuildManager
para crear una instancia del HttpHandler
apropiado como en el uso de shellscape. La buena noticia es que .NET permite que ambos vivan juntos, así que podemos usar el que queramos y podemos cambiar entre ellos como lo deseemos.
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));
}
}
Supongamos que creamos un HttpHandler
que transmite documentos a los usuarios de un recurso fuera de nuestra carpeta virtual, tal vez incluso de una base de datos, y que queremos para engañar el navegador del usuario en la creencia de que estamos sirviendo directamente archivo específico en lugar de simplemente proporcionar una descarga (es decir, permitir que los complementos del navegador manejen el archivo en lugar de forzar al usuario a guardar el archivo). El HttpHandler
puede esperar una identificación de documento con la que ubicar el documento que se va a proporcionar, y puede esperar un nombre de archivo para proporcionar al explorador, uno que puede diferir del nombre de archivo utilizado en el servidor.
A continuación se muestra el registro de la ruta utilizada para lograr esto con un DocumentHandler
HttpHandler
:
routes.Add("Document", new Route("document/{documentId}/{*fileName}", new HttpHandlerRouteHandler<DocumentHandler>()));
Solía {*fileName}
en lugar de sólo {fileName}
para permitir que el parámetro fileName
para actuar como un cajón de sastre parámetro opcional.
para crear una URL para un archivo servido por este HttpHandler
, podemos añadir el siguiente método estático de una clase, si este método sería apropiado, como por ejemplo en la clase HttpHandler
, sí:
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;
}
Omití las definiciones de MimeMap
y IsPassThruMimeType
para mantener este ejemplo simple. Pero estos están destinados a determinar si los tipos de archivos específicos deben proporcionar sus nombres de archivo directamente en la URL, o más bien en un encabezado HTTP Content-Disposition
. Algunas extensiones de archivos podrían ser bloqueadas por IIS o URL Scan, o podrían causar la ejecución de código que podría causar problemas para los usuarios, especialmente si el origen del archivo es otro usuario que es malicioso. Puede reemplazar esta lógica con otra lógica de filtrado u omitir dicha lógica por completo si no está expuesto a este tipo de riesgo.
Dado que en este ejemplo particular el nombre del archivo puede omitirse de la URL, entonces, obviamente, debemos recuperar el nombre del archivo de alguna parte. En este ejemplo en particular, el nombre del archivo se puede recuperar realizando una búsqueda utilizando la identificación del documento, e incluir un nombre de archivo en la URL tiene como único objetivo mejorar la experiencia del usuario. Por lo tanto, el DocumentHandler
HttpHandler
puede determinar si se proporcionó un nombre de archivo en la URL, y si no fue así, entonces simplemente puede agregar un encabezado HTTP Content-Disposition
a la respuesta.
permanecer en el tema, la parte importante del bloque de código anterior es el uso de RouteTable.Routes.GetVirtualPath()
y los parámetros de ruta para generar una URL desde el objeto Route
que hemos creado durante el proceso de registro de rutas.
Aquí hay una versión suavizada de la clase DocumentHandler
HttpHandler
(mucho omitido por motivos de claridad). Puede ver que esta clase usa parámetros de ruta para recuperar la identificación del documento y el nombre del archivo cuando puede; de lo contrario, intentará recuperar la identificación del documento desde un parámetro de cadena de consulta (es decir, suponiendo que no se usó el enrutamiento).
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.
}
}
En este ejemplo se utiliza algunas clases personalizadas adicionales, como una clase Utility
para simplificar algunas tareas triviales. Pero espero que puedas superar eso. La única parte realmente importante en esta clase con respecto al tema actual, por supuesto, es la recuperación de los parámetros de ruta desde context.Request.RequestContext.RouteData
. Pero he visto varias publicaciones en otros lugares preguntando cómo transmitir archivos grandes usando HttpHandler
sin masticar la memoria del servidor, por lo que me pareció una buena idea combinar ejemplos.
Esta es una respuesta realmente excelente y detallada. – tallseth