2010-07-29 12 views
21

Tengo algunos servicios REST que usan el antiguo IHttpHandler s. Me gustaría generar URL más limpias, para que no tenga .ashx en la ruta. ¿Hay alguna manera de utilizar el enrutamiento ASP.NET para crear rutas que se correlacionen con manejadores de ashx? He visto este tipo de rutas previamente:¿Se puede utilizar el enrutamiento ASP.NET para crear URL "limpias" para los manejadores .ashx (IHttpHander)?

// Route to an aspx page 
RouteTable.Routes.MapPageRoute("route-name", 
    "some/path/{arg}", 
    "~/Pages/SomePage.aspx"); 

// Route for a WCF service 
RouteTable.Routes.Add(new ServiceRoute("Services/SomeService", 
    new WebServiceHostFactory(), 
    typeof(SomeService))); 

Tratar de utilizar RouteTable.Routes.MapPageRoute() genera un error (que el controlador no se deriva de Page). System.Web.Routing.RouteBase solo parece tener 2 clases derivadas: ServiceRoute para servicios, y DynamicDataRoute para MVC. No estoy seguro de qué es lo que MapPageRoute() hace (Reflector no muestra el cuerpo del método, solo muestra "Rendimiento crítico para alinear este tipo de método a través de los límites de la imagen NGen").

veo que RouteBase no está sellado, y tiene una interfaz relativamente simple:

public abstract RouteData GetRouteData(HttpContextBase httpContext); 

public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, 
    RouteValueDictionary values); 

Así que tal vez pueda hacer mi propia HttpHandlerRoute. Voy a dar una oportunidad, pero si alguien sabe de una forma existente o incorporada de mapeo de rutas a IHttpHandlers, sería genial.

Respuesta

24

Ok, he estado averiguando esto desde que originalmente hice la pregunta, y finalmente tengo una solución que hace justo lo que quiero. Sin embargo, hay que dar un poco de explicación inicial. IHttpHandler es una interfaz muy básica:

bool IsReusable { get; } 
void ProcessRequest(HttpContext context) 

No hay construido en la propiedad para acceder a los datos de la ruta, y los datos de la ruta tampoco se puede encontrar en el contexto o la solicitud. Un objeto System.Web.UI.Page tiene una propiedad RouteData, ServiceRoute s hace todo el trabajo de interpretación de sus UriTemplates y pasa los valores al método correcto internamente, y ASP.NET MVC proporciona su propia forma de acceder a los datos de la ruta. Incluso si tuviera un RouteBase que (a) determinó si la URL entrante era compatible con su ruta y (b) analizó la url para extraer todos los valores individuales que se utilizarán desde su IHttpHandler, no hay una manera fácil de pasar eso enrutar datos a su IHttpHandler. Si desea mantener su IHttpHandler "puro", por así decirlo, se responsabiliza de tratar con la url y de extraer cualquier valor de la misma. La implementación de RouteBase en este caso solo se usa para determinar si tu IHttpHandler se debe usar en absoluto.

Sin embargo, queda un problema. Una vez que RouteBase determina que la URL entrante coincide con su ruta, se transfiere a un IRouteHandler, que crea las instancias de IHttpHandler que desea manejar su solicitud. Pero, una vez que está en su IHttpHandler, el valor de context.Request.CurrentExecutionFilePath es engañoso. Es la url que vino del cliente, menos la cadena de consulta. Entonces no es la ruta a su archivo .ashx. Y, cualquier parte de su ruta que sea constante (como el nombre del método) será parte de ese valor de ruta de archivo de ejecución. Esto puede ser un problema si usa UriTemplates dentro de su IHttpHandler para determinar qué método específico dentro de su IHttpHandler debe entregar la solicitud.

Ejemplo: Si tenía un manejador .ashx en /myApp/services/myHelloWorldHandler.ashx y que tenía esta ruta que asigna al controlador: "Los servicios/hola/{nombre}" Y navegado a esta URL , tratando de llamar al método de su manejador SayHello(string name): http://localhost/myApp/services/hello/SayHello/Sam

Luego, su CurrentExecutionFilePath sería:/Myapp/servicios/hola/Sam. Incluye partes de la url de ruta, lo cual es un problema. Desea que la ruta del archivo de ejecución coincida con su url de ruta. Las siguientes implementaciones de RouteBase y IRouteHandler se ocupan de este problema.

Antes de pegar las 2 clases, he aquí un ejemplo de uso muy simple. Tenga en cuenta que estas implementaciones de RouteBase y IRouteHandler en realidad funcionarán para IHttpHandlers que ni siquiera tienen un archivo .ashx, que es bastante conveniente.

// A "headless" IHttpHandler route (no .ashx file required) 
RouteTable.Routes.Add(new GenericHandlerRoute<HeadlessService>("services/headless")); 

que hará que todas las URL entrantes que coinciden con los "servicios/sin cabeza" ruta a ser traspasado a una nueva instancia de la HeadlessService IHttpHandler (HeadlessService es sólo un ejemplo en este caso. Sería lo aplicación IHttpHandler querías pasar a).

Ok, así que aquí están las implementaciones de clase de enrutamiento, los comentarios y todas:

/// <summary> 
/// For info on subclassing RouteBase, check Pro Asp.NET MVC Framework, page 252. 
/// Google books link: http://books.google.com/books?id=tD3FfFcnJxYC&pg=PA251&lpg=PA251&dq=.net+RouteBase&source=bl&ots=IQhFwmGOVw&sig=0TgcFFgWyFRVpXgfGY1dIUc0VX4&hl=en&ei=z61UTMKwF4aWsgPHs7XbAg&sa=X&oi=book_result&ct=result&resnum=6&ved=0CC4Q6AEwBQ#v=onepage&q=.net%20RouteBase&f=false 
/// 
/// It explains how the asp.net runtime will call GetRouteData() for every route in the route table. 
/// GetRouteData() is used for inbound url matching, and should return null for a negative match (the current requests url doesn't match the route). 
/// If it does match, it returns a RouteData object describing the handler that should be used for that request, along with any data values (stored in RouteData.Values) that 
/// that handler might be interested in. 
/// 
/// The book also explains that GetVirtualPath() (used for outbound url generation) is called for each route in the route table, but that is not my experience, 
/// as mine used to simply throw a NotImplementedException, and that never caused a problem for me. In my case, I don't need to do outbound url generation, 
/// so I don't have to worry about it in any case. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
public class GenericHandlerRoute<T> : RouteBase where T : IHttpHandler, new() 
{ 
    public string RouteUrl { get; set; } 


    public GenericHandlerRoute(string routeUrl) 
    { 
     RouteUrl = routeUrl; 
    } 


    public override RouteData GetRouteData(HttpContextBase httpContext) 
    { 
     // See if the current request matches this route's url 
     string baseUrl = httpContext.Request.CurrentExecutionFilePath; 
     int ix = baseUrl.IndexOf(RouteUrl); 
     if (ix == -1) 
      // Doesn't match this route. Returning null indicates to the asp.net runtime that this route doesn't apply for the current request. 
      return null; 

     baseUrl = baseUrl.Substring(0, ix + RouteUrl.Length); 

     // This is kind of a hack. There's no way to access the route data (or even the route url) from an IHttpHandler (which has a very basic interface). 
     // We need to store the "base" url somewhere, including parts of the route url that are constant, like maybe the name of a method, etc. 
     // For instance, if the route url "myService/myMethod/{myArg}", and the request url were "http://localhost/myApp/myService/myMethod/argValue", 
     // the "current execution path" would include the "myServer/myMethod" as part of the url, which is incorrect (and it will prevent your UriTemplates from matching). 
     // Since at this point in the exectuion, we know the route url, we can calculate the true base url (excluding all parts of the route url). 
     // This means that any IHttpHandlers that use this routing mechanism will have to look for the "__baseUrl" item in the HttpContext.Current.Items bag. 
     // TODO: Another way to solve this would be to create a subclass of IHttpHandler that has a BaseUrl property that can be set, and only let this route handler 
     // work with instances of the subclass. Perhaps I can just have RestHttpHandler have that property. My reticence is that it would be nice to have a generic 
     // route handler that works for any "plain ol" IHttpHandler (even though in this case, you have to use the "global" base url that's stored in HttpContext.Current.Items...) 
     // Oh well. At least this works for now. 
     httpContext.Items["__baseUrl"] = baseUrl; 

     GenericHandlerRouteHandler<T> routeHandler = new GenericHandlerRouteHandler<T>(); 
     RouteData rdata = new RouteData(this, routeHandler); 

     return rdata; 
    } 


    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 
    { 
     // This route entry doesn't generate outbound Urls. 
     return null; 
    } 
} 



public class GenericHandlerRouteHandler<T> : IRouteHandler where T : IHttpHandler, new() 
{ 
    public IHttpHandler GetHttpHandler(RequestContext requestContext) 
    { 
     return new T(); 
    } 
} 

Sé que esta respuesta ha sido bastante largo aliento, pero no era un problema fácil de resolver. La lógica del núcleo era bastante fácil, el truco era hacer que tu IHttpHandler estuviera al tanto de la "url base", de modo que pudiera determinar correctamente qué partes de la URL pertenecen a la ruta y qué partes son argumentos reales para la llamada de servicio.

Estas clases se utilizarán en mi próxima biblioteca C# REST, RestCake. Espero que mi camino por el hoyo de enrutamiento de enrutamiento ayude a cualquier otra persona que decida utilizar RouteBase, y haga cosas geniales con IHttpHandlers.

+1

Esta es una respuesta realmente excelente y detallada. – tallseth

4

Sí, me di cuenta de eso también. Tal vez hay una forma integrada de ASP.NET para hacer esto, pero el truco para mí fue la creación de una nueva clase derivada de IRouteHandler:

using System; 
using System.IO; 
using System.Reflection; 
using System.Text.RegularExpressions; 
using System.Web; 
using System.Web.Routing; 

namespace MyNamespace 
{ 
    class GenericHandlerRouteHandler : IRouteHandler 
    { 
     private string _virtualPath; 
     private Type _handlerType; 
     private static object s_lock = new object(); 

     public GenericHandlerRouteHandler(string virtualPath) 
     { 
      _virtualPath = virtualPath; 
     } 

     #region IRouteHandler Members 

     public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) 
     { 
      ResolveHandler(); 

      IHttpHandler handler = (IHttpHandler)Activator.CreateInstance(_handlerType); 
      return handler; 
     } 

     #endregion 

     private void ResolveHandler() 
     { 
      if (_handlerType != null) 
       return; 

      lock (s_lock) 
      { 
       // determine physical path of ashx 
       string path = _virtualPath.Replace("~/", HttpRuntime.AppDomainAppPath); 

       if (!File.Exists(path)) 
        throw new FileNotFoundException("Generic handler " + _virtualPath + " could not be found."); 

       // parse the class name out of the .ashx file 
       // unescaped reg-ex: (?<=Class=")[a-zA-Z\.]* 
       string className; 
       Regex regex = new Regex("(?<=Class=\")[a-zA-Z\\.]*"); 
       using (var sr = new StreamReader(path)) 
       { 
        string str = sr.ReadToEnd(); 

        Match match = regex.Match(str); 
        if (match == null) 
         throw new InvalidDataException("Could not determine class name for generic handler " + _virtualPath); 

        className = match.Value; 
       } 

       // get the class type from the name 
       Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies(); 
       foreach (Assembly asm in asms) 
       { 
        _handlerType = asm.GetType(className); 
        if (_handlerType != null) 
         break; 
       } 

       if (_handlerType == null) 
        throw new InvalidDataException("Could not find type " + className + " in any loaded assemblies."); 
      } 
     } 
    } 
} 

para crear una ruta para una .ashx:

IRouteHandler routeHandler = new GenericHandlerRouteHandler("~/somehandler.ashx"); 
Route route = new Route("myroute", null, null, null, routeHandler); 
RouteTable.Routes.Add(route); 

El código anterior puede necesitar ser mejorado para trabajar con sus argumentos de ruta, pero es el punto de partida. Comentarios bienvenidos.

11

Me gusta más la solución de Joel, ya que no requiere que conozca el tipo de controlador mientras intenta configurar sus rutas. Yo lo hubiera votado, pero por desgracia, no tengo la reputación requerida.

De hecho, encontré una solución que creo que es mejor que las dos mencionadas. El código fuente original del que derivé mi ejemplo se puede encontrar vinculado aquí http://weblogs.asp.net/leftslipper/archive/2009/10/07/introducing-smartyroute-a-smarty-ier-way-to-do-routing-in-asp-net-applications.aspx.

Esto es menos código, tipo agnóstico y rápido.

public class HttpHandlerRoute : IRouteHandler { 

    private String _VirtualPath = null; 

    public HttpHandlerRoute(String virtualPath) { 
    _VirtualPath = virtualPath; 
    } 

    public IHttpHandler GetHttpHandler(RequestContext requestContext) { 
    IHttpHandler httpHandler = (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(_VirtualPath, typeof(IHttpHandler)); 
    return httpHandler; 
    } 
} 

Y un ejemplo aproximado de uso

String handlerPath = "~/UploadHandler.ashx"; 
RouteTable.Routes.Add(new Route("files/upload", new HttpHandlerRoute(handlerPath))); 
+2

Si bien no es necesario que conozca el tipo de implementación de IHttpHandler, debe conocer la ruta al archivo ashx. Mi solución no requiere ningún archivo ashx, y es una verificación en tiempo de compilación. Su solución causará un error de tiempo de ejecución si la configuración es incorrecta. Es tan fácil referirse al tipo de controlador como a la ruta del ashx. Más seguro también Sin embargo, es mucho más fácil de entender, y eso es bueno. Simple es bueno. Voy a seguir con la mía de todos modos, porque me gusta no tener el archivo ashx. Solo creo una clase simple que implementa IHttpHandler. –

4

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 DocumentHandlerHttpHandler:

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 DocumentHandlerHttpHandler 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 DocumentHandlerHttpHandler (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.

6

EDIT: Acabo de editar este código porque tuve algunos problemas con el anterior. Si está utilizando la versión anterior, actualice.

Este hilo es un poco viejo, pero acabo de volver a escribir parte del código aquí para hacer lo mismo, pero de una manera más elegante, utilizando un método de extensión.

Lo estoy utilizando en Webforms de ASP.net, y me gusta tener los archivos ashx en una carpeta y poder llamarlos ya sea mediante el enrutamiento o una solicitud normal.

Así que casi agarré el código de shellscape e hice un método de extensión que funciona. Al final, sentí que también debería admitir pasar el objeto IHttpHandler en lugar de su Url, así que escribí y sobrecargué el método MapHttpHandlerRoute para eso.

namespace System.Web.Routing 
{ 
public class HttpHandlerRoute<T> : IRouteHandler where T: IHttpHandler 
{ 
    private String _virtualPath = null; 

    public HttpHandlerRoute(String virtualPath) 
    { 
    _virtualPath = virtualPath; 
    } 

    public HttpHandlerRoute() { } 

    public IHttpHandler GetHttpHandler(RequestContext requestContext) 
    { 
    return Activator.CreateInstance<T>(); 
    } 
} 

public class HttpHandlerRoute : IRouteHandler 
{ 
    private String _virtualPath = null; 

    public HttpHandlerRoute(String virtualPath) 
    { 
    _virtualPath = virtualPath; 
    } 

    public IHttpHandler GetHttpHandler(RequestContext requestContext) 
    { 
    if (!string.IsNullOrEmpty(_virtualPath)) 
    { 
    return (IHttpHandler)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(IHttpHandler)); 
    } 
    else 
    { 
    throw new InvalidOperationException("HttpHandlerRoute threw an error because the virtual path to the HttpHandler is null or empty."); 
    } 
    } 
} 

public static class RoutingExtension 
{ 
    public static void MapHttpHandlerRoute(this RouteCollection routes, string routeName, string routeUrl, string physicalFile, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null) 
    { 
    var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute(physicalFile)); 
    routes.Add(routeName, route); 
    } 

    public static void MapHttpHandlerRoute<T>(this RouteCollection routes, string routeName, string routeUrl, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null) where T : IHttpHandler 
    { 
    var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute<T>()); 
    routes.Add(routeName, route); 
    } 
} 
} 

Lo estoy poniendo dentro del mismo espacio de nombres de todos los objetos de enrutamiento nativos por lo que estará disponible automáticamente.

Así que para utilizar este sólo hay que llamar:

// using the handler url 
routes.MapHttpHandlerRoute("DoSomething", "Handlers/DoSomething", "~/DoSomething.ashx"); 

O

// using the type of the handler 
routes.MapHttpHandlerRoute<MyHttpHanler>("DoSomething", "Handlers/DoSomething"); 

Disfrute, Alex

+0

Gracias Alex, estoy usando tus métodos, se ven limpios y funcionan bien para mí. –

+0

Hola amigo, gracias por los comentarios. Acabo de actualizar este código debido a un problema de almacenamiento en caché de la solicitud. Por favor usa esta nueva versión. Si desea realizar un seguimiento, eche un vistazo a mi blog: http://www.instanceofanobject.com ¡Gracias! – AlexCode

+0

Gracias Alex por actualizar, estoy usando el código actualizado ahora. –

Cuestiones relacionadas