Маршрутизация ASP.NET может использоваться для создания “чистых” URL для .ashx (IHttpHander) обработчики?

У меня есть некоторые сервисы REST, использующие простой IHttpHandlers. Я хотел бы генерировать более чистые 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() делает (Отражатель не показывает тело метода, он просто показывает "Производительность, очень важную для встраивания этого типа метода через границы изображения NGen").

Я вижу это RouteBase не изолируется и имеет относительно простой интерфейс:

public abstract RouteData GetRouteData(HttpContextBase httpContext);

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

Таким образом, возможно, я могу сделать свой собственный HttpHandlerRoute. Я дам этому выстрел, но если бы кто-либо знает о существующем или встроенном способе отобразить маршруты на IHttpHandlers, который был бы большим.

26
задан Samuel Meacham 29 July 2010 в 05:58
поделиться

3 ответа

Хорошо, я разбирался в этом с тех пор, как изначально задал вопрос, и у меня наконец есть решение, которое делает именно то, что я хочу. Однако необходимо сделать небольшое предварительное объяснение. IHttpHandler - это очень простой интерфейс:

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

Не существует встроенного свойства для доступа к данным маршрута, и данные маршрута также нельзя найти в контексте или запросе. Объект System.Web.UI.Page имеет свойство RouteData , ServiceRoute выполняет всю работу по интерпретации ваших шаблонов UriTemplates и передаче значений правильному методу. внутри, а ASP.NET MVC предоставляет собственный способ доступа к данным маршрута. Даже если у вас есть RouteBase , которая (а) определяет, соответствует ли входящий URL-адрес вашему маршруту и ​​(б) анализирует URL-адрес для извлечения всех индивидуальных значений, которые будут использоваться из вашего IHttpHandler, существует нет простого способа передать эти данные маршрута вашему IHttpHandler. Если вы хотите, чтобы ваш IHttpHandler был, так сказать, «чистым», он берет на себя ответственность за работу с URL-адресом и за то, как извлекать из него любые значения. Реализация RouteBase в этом случае используется только для определения того, следует ли вообще использовать ваш IHttpHandler.

Однако остается одна проблема. Как только RouteBase определяет, что входящий URL-адрес соответствует вашему маршруту, он переходит к IRouteHandler, который создает экземпляры IHttpHandler, которые вы хотите обработать по вашему запросу. Но когда вы находитесь в своем IHttpHandler, значение context.Request.CurrentExecutionFilePath вводит в заблуждение. Это URL-адрес, полученный от клиента, за вычетом строки запроса.Значит, это не путь к вашему файлу .ashx. И любые части вашего маршрута, которые являются постоянными (например, имя метода), будут частью этого значения пути к исполняемому файлу. Это может быть проблемой, если вы используете UriTemplates в своем IHttpHandler, чтобы определить, какой конкретный метод в вашем IHttpHandler должен обрабатывать запрос.

Пример: Если у вас есть обработчик .ashx в /myApp/services/myHelloWorldHandler.ashx И у вас был этот маршрут, сопоставленный с обработчиком: "services / hello / {name}" И вы перешли по этому URL-адресу, пытаясь вызвать метод SayHello (string name) вашего обработчика: http: // localhost / myApp / services / hello / SayHello / Sam

Тогда ваш CurrentExecutionFilePath будет: / myApp / services / hello / Sam. Он включает части URL-адреса маршрута, что является проблемой. Вы хотите, чтобы путь к исполняемому файлу соответствовал URL-адресу вашего маршрута. Приведенные ниже реализации RouteBase и IRouteHandler решают эту проблему.

Прежде чем я вставлю 2 класса, рассмотрим очень простой пример использования. Обратите внимание, что эти реализации RouteBase и IRouteHandler действительно будут работать для IHttpHandler, у которых даже нет файла .ashx, что довольно удобно.

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

Это приведет к тому, что все входящие URL-адреса, соответствующие маршруту «services / headless», будут переданы новому экземпляру HeadlessService IHttpHandler (HeadlessService - только пример в этом случае. Это будет что угодно Реализация IHttpHandler, за которую вы хотели перейти).

Хорошо, вот реализации классов маршрутизации, комментарии и все такое:

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

Я знаю, что этот ответ был довольно длинным, но решить эту проблему было непросто. Основная логика была достаточно простой, уловка заключалась в том, чтобы каким-то образом заставить ваш IHttpHandler знать «базовый URL-адрес», чтобы он мог правильно определить, какие части URL-адреса принадлежат маршруту, а какие части являются фактическими аргументами для вызова службы.

Эти классы будут использоваться в моей будущей библиотеке C # REST, RestCake . Я надеюсь, что мой путь по кроличьей норе маршрутизации поможет любому, кто решит использовать RouteBase, и делать интересные вещи с помощью IHttpHandlers.

26
ответ дан 28 November 2019 в 06:48
поделиться

Да, я тоже это заметил. Возможно, есть встроенный способ ASP.NET для этого, но уловка для меня заключалась в том, чтобы создать новый класс, производный от 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.");
            }
        }
    }
}

Чтобы создать маршрут для .ashx:

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

Для приведенного выше кода может потребоваться для работы с аргументами маршрута, но это отправная точка. Комментарии приветствуются.

4
ответ дан 28 November 2019 в 06:48
поделиться

Решение Джоэла мне нравится больше, так как оно не требует, чтобы вы знали тип обработчика, пока вы пытаетесь настроить маршруты. Я бы проголосовал за него, но, увы, у меня нет необходимой репутации.

На самом деле я нашел решение, которое, по моему мнению, лучше, чем оба упомянутых. Оригинальный исходный код, из которого я взял свой пример, можно найти по ссылке 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)));
13
ответ дан 28 November 2019 в 06:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: