当前位置:  开发笔记 > 编程语言 > 正文

ASP.NET Routing可用于为.ashx(IHttpHander)处理程序创建"干净"的URL吗?

如何解决《ASP.NETRouting可用于为.ashx(IHttpHander)处理程序创建"干净"的URL吗?》经验,为你挑选了4个好方法。

我有一些使用普通旧IHttpHandler的REST服务.我想生成更干净的URL,以便我在路径中没有.ashx.有没有办法使用ASP.NET路由创建映射到ashx处理程序的路由?我以前见过这些类型的路线:

// 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)));

尝试使用会RouteTable.Routes.MapPageRoute()产生错误(处理程序不会派生出错Page). System.Web.Routing.RouteBase似乎只有2个派生类:ServiceRoute用于服务,DynamicDataRoute用于MVC.我不确定是什么MapPageRoute()(Reflector没有显示方法体,它只显示"在NGen图像边界内嵌这种方法的性能至关重要").

我看到它RouteBase没有密封,并且有一个相对简单的界面:

public abstract RouteData GetRouteData(HttpContextBase httpContext);

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

所以也许我可以制作自己的HttpHandlerRoute.我会给出一个镜头,但如果有人知道现有的或内置的映射路由到IHttpHandlers的方式,这将是伟大的.



1> Samuel Meach..:

好吧,自从我最初提出这个问题以来,我一直在想这个问题,我终于找到了一个可以满足我想要的解决方案.然而,一些前期解释是应该的.IHttpHandler是一个非常基本的接口:

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

没有用于访问路径数据的内置属性,也无法在上下文或请求中找到路径数据.一个System.Web.UI.Page对象有一个RouteData属性,ServiceRoute它完成了解释你的UriTemplates并在内部将值传递给正确方法的所有工作,ASP.NET MVC提供了自己的访问路由数据的方法.即使你有一个RouteBase(a)确定传入的URL是否与您的路由匹配,以及(b)解析该URL以从IHttpHandler中提取要使用的所有单个值,没有简单的方法将该路由数据传递给您的IHttpHandler.如果你想保持你的IHttpHandler"纯粹",可以说,它负责处理url,以及如何从中提取任何值.在这种情况下,RouteBase实现仅用于确定是否应该使用您的IHttpHandler.

然而,一个问题仍然存在.一旦RouteBase确定传入的URL与您的路由匹配,它就会传递给IRouteHandler,它会创建您想要处理请求的IHttpHandler的实例.但是,一旦你进入你的IHttpHandler,其价值context.Request.CurrentExecutionFilePath就会产生误导.这是来自客户端的URL,减去查询字符串.所以它不是你的.ashx文件的路径.并且,路由中任何常量的部分(例如方法的名称)都将是该执行文件路径值的一部分.如果您在IHttpHandler中使用UriTemplates来确定IHttpHandler中的哪个特定方法应该处理请求,则可能会出现问题.

示例:如果/myApp/services/myHelloWorldHandler.ashx上有.ashx处理程序并且您有这条路由映射到处理程序:"services/hello/{name}"并且您导航到此URL,尝试调用该SayHello(string name)方法您的处理程序: http:// localhost/myApp/services/hello/SayHello/Sam

那你的意思CurrentExecutionFilePath是:/ myApp/services/hello/Sam.它包括路由URL的部分,这是一个问题.您希望执行文件路径与您的路由URL匹配.下面的实现RouteBaseIRouteHandler处理这个问题.

在我粘贴2个类之前,这是一个非常简单的用法示例.请注意,RouteBase和IRouteHandler的这些实现实际上适用于甚至没有.ashx文件的IHttpHandler,这非常方便.

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

这将导致所有与"服务/无头"路由匹配的传入URL被切换到HeadlessServiceIHttpHandler 的新实例(在这种情况下,HeadlessService只是一个示例.它将是您希望传递给IHttpHandler的任何实现).

好的,所以这里是路由类实现,注释和所有:

/// 
/// 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.
/// 
/// 
public class GenericHandlerRoute : 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 routeHandler = new GenericHandlerRouteHandler();
        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 : IRouteHandler where T : IHttpHandler, new()
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new T();
    }
}

我知道这个答案已经很长时间了,但要解决这个问题并不容易.核心逻辑很简单,诀窍是以某种方式让你的IHttpHandler知道"基本URL",这样它就可以正确地确定url的哪些部分属于路由,以及哪些部分是服务调用的实际参数.

这些类将在我即将推出的C#REST库RestCake中使用.我希望我在路由兔子洞的路径将帮助其他决定使用RouteBase的人,并使用IHttpHandlers做很酷的事情.



2> shellscape..:

我实际上更喜欢Joel的解决方案,因为在您尝试设置路线时,它不需要您知道处理程序的类型.我赞成它,但唉,我没有要求的声誉.

我实际上找到了一种解决方案,我觉得它比上面提到的要好.我从我的例子中得到的原始源代码可以在这里链接到http://weblogs.asp.net/leftslipper/archive/2009/10/07/introducing-smartyroute-a-smarty-ier-way-to-do- routing-in-asp-net-applications.aspx.

这是代码少,类型不可知和快速.

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;
  }
}

并使用一个粗略的例子

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


虽然您可能不必知道IHttpHandler实现的类型,但您必须知道ashx文件的路径.我的解决方案根本不需要ashx文件,这是一个编译时检查.如果设置错误,您的解决方案将导致运行时错误.引用处理程序的类型就像引用ashx的路径一样容易.更安全.但是,它更容易理解,这很好.简单就是好.我会坚持与我的相同,因为我喜欢不必拥有ashx文件.我只是创建一个实现IHttpHandler的普通类.

3> AlexCode..:
编辑:我刚刚编辑了这段代码,因为我遇到了旧代码的一些问题.如果您使用的是旧版本,请更新.

这个线程有点旧,但我只是在这里重新编写了一些代码来做同样的事情,但是以更优雅的方式,使用扩展方法.

我在ASP.net Webforms上使用它,我喜欢将ashx文件放在一个文件夹上,并且可以使用路由或普通请求来调用它们.

所以我几乎抓住了shellscape的代码并制作了一个扩展方法来完成这个工作.最后我觉得我也应该支持传递IHttpHandler对象而不是它的Url,所以我为此编写并重载了MapHttpHandlerRoute方法.

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

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

  public HttpHandlerRoute() { }

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

 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(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());
   routes.Add(routeName, route);
  }
 }
}

我将它放在所有本地路由对象的相同名称空间中,因此它将自动可用.

所以要使用它你只需要打电话:

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

要么

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

享受,亚历克斯



4> 小智..:

所有这些答案都很好。我喜欢Meacham先生GenericHandlerRouteHandler课堂的简单性。如果您知道特定的HttpHandler类,则消除不必要的虚拟路径引用是一个好主意。该GenericHandlerRoute但是不需要类。Route从中派生的现有类RouteBase已经处理了路由匹配,参数等的所有复杂性,因此我们可以将其与一起使用GenericHandlerRouteHandler

以下是结合了实际使用示例的组合版本,其中包括路由参数。

首先是路由处理程序。其中包括两个-两者都具有相同的类名,但是一个是通用的并且使用类型信息来创建特定实例(HttpHandler如Meacham先生所使用的实例),另一个是使用虚拟路径并BuildManager创建实例。与HttpHandlershellscape的用法相同。好消息是.NET允许两者并存,所以我们可以使用任何我们想要的东西,并且可以根据需要在它们之间切换。

using System.Web;
using System.Web.Compilation;
using System.Web.Routing;

public class HttpHandlerRouteHandler : 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可预期的文件ID,以找到该文件提供,并可以期待文件名提供给浏览器-一个可以从服务器上使用的文件名不同。

以下显示了通过来完成此操作的路线的注册DocumentHandler HttpHandler

routes.Add("Document", new Route("document/{documentId}/{*fileName}", new HttpHandlerRouteHandler()));

我使用{*fileName}而不只是{fileName}允许fileName参数充当可选的包罗万象的参数。

要为this所服务的文件创建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中或在Content-DispositionHTTP标头中提供其文件名。某些文件扩展名可能被IIS或URL扫描阻止,或者可能导致执行代码,可能给用户带来麻烦-尤其是在文件源是另一个恶意用户的情况下。您可以使用其他一些过滤逻辑来代替此逻辑,或者,如果您不承担此类风险,则可以完全省略此类逻辑。

由于在此特定示例中,URL中可能省略了文件名,因此,显然,我们必须从某个位置检索文件名。在此特定示例中,可以通过使用文档ID执行查找来检索文件名,并且在URL中包含文件名仅是为了改善用户体验。因此,DocumentHandler HttpHandler可以确定URL中是否提供了文件名,如果没有,则可以简单地将Content-DispositionHTTP标头添加到响应中。

继续讨论主题,上面代码块的重要部分是使用RouteTable.Routes.GetVirtualPath()和路由参数从Route我们在路由注册过程中创建的对象生成URL 。

这是DocumentHandler HttpHandler该类的精简版本(为清楚起见,省略了很多)。您可以看到此类在可能的情况下使用路由参数来检索文档ID和文件名。否则,它将尝试从查询字符串参数中检索文档ID(即,假设未使用路由)。

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不占用服务器内存的情况下使用流式传输大文件,因此组合示例似乎是个好主意。

推荐阅读
大大炮
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有