Простые URL-маршруты в WCF Rest 4.0 без косой черты в конце

У меня есть проект WCF REST 4.0, основанный на шаблоне службы WCF REST 40 (CS). Я хотел бы предоставить простые URL-адреса конечных точек служб без завершающих косых черт. Например:

  1. CarService.cs
  2. TruckService.cs

Я смотрю на приведенные выше URL-адреса как на запросы ресурсов (а не на каталоги), поэтому я не думаю, что здесь уместны завершающие косые черты.

К сожалению, я не могу добиться желаемого поведения, потому что меня всегда перенаправляют на / cars / и / trucks / с косой чертой в конце.

Вот как я определил маршрут и метод обслуживания "cars" - обратите внимание, что я не включил косую черту ни в одно из определений шаблона маршрута или URI:

// Global.asax.cs
RouteTable.Routes.Add(new ServiceRoute("cars", new WebServiceHostFactory(), typeof(CarService)));

// CarService.cs
[WebGet(UriTemplate = "")]
public List<Car> GetCollection()
{
    return DataContext.GetAllCars();
}

Обратите внимание, что MVC не работает таким образом. С помощью метода MapRoute я могу направлять запросы напрямую на http://www.domain.com/about без перенаправления на / about / . Как добиться такого же поведения в WCF REST 4.0?


person Will    schedule 19.10.2010    source источник


Ответы (7)


Основная проблема, с которой вы сталкиваетесь, заключается в том, что текущая версия WCF REST вызывает перенаправление 307 (на «/»), когда у вас есть пустая строка для UriTemplate в вашем атрибуте WebGet. Насколько мне известно, в текущей версии этого не обойти.

Однако есть несколько вариантов решения вашей проблемы, если вам нужно решение, которое 1) позволяет дифференцировать услуги и 2) имеет (относительно) короткие URI.

Первое решение. Вы можете поместить это в свой файл global.asax (на этот пример). Вы можете составить служебный маршрут для каждой службы:

RouteTable.Routes.Add(new ServiceRoute("cars", new WebServiceHostFactory(), typeof(CarService)));
RouteTable.Routes.Add(new ServiceRoute("trucks", new WebServiceHostFactory(), typeof(TruckService)));

На этом этапе вы можете заполнить свой UriTemplate в каждой службе:

[WebGet(UriTemplate = "all")]
CarPool GetAllCars();

[WebGet(UriTemplate = "{carName}")]
Car GetCar(string carName);

Это позволит вам использовать URI:

www.domain.com/cars/all
www.domain.com/cars/123 or www.domain.com/cars/honda

аналогично для грузовиков:

www.domain.com/trucks/all
www.domain.com/trucks/123 or www.domain.com/trucks/ford

Второе решение Используйте хост службы из REST Starter Kit (то есть WebServiceHost2Factory).

RouteTable.Routes.Add(new ServiceRoute("cars", new WebServiceHost2Factory(), typeof(CarService)));

Это не приводит к перенаправлению 307 при использовании URI, которые вы пытаетесь использовать выше, и, таким образом, дает вам именно то, что вам нужно. Хотя я понимаю, что использование этой фабрики хостов служб, а не той, которая поставляется с WCF 4, кажется немного странным.

person Steve Michelotti    schedule 20.10.2010
comment
Спасибо за информацию, Стив. Я уже подумал о том, чтобы использовать все ваше предложение (это достойный обходной путь), но я не хочу отказываться от канонического синтаксиса REST, на который я изначально надеялся. Я изучаю различия между поставляемыми WebServiceHostFactory и WebServiceHost2Factory, а также то, что есть в последнем, который предотвращает перенаправление 307. Если это незначительное изменение, которое я могу оценить, я буду придерживаться этого подхода. - person Will; 20.10.2010
comment
Собственно, я просто попробовал использовать WebServiceHost2Factory и получил тот же результат перенаправления. - person Will; 20.10.2010
comment
К сожалению, мое второе решение - вы правы в том, что WebServiceHost2Factory приводит к тому же поведению. (когда я его тестировал, у меня не было пустого UriTemplate). Но первое решение все равно будет работать. - person Steve Michelotti; 20.10.2010
comment
@SteveMichelotti, но это вызывает проблему, можете ли вы указать на суть этого переопределения существующих маршрутов. я столкнулся с такой же ситуацией, как эта - person Andrew; 11.07.2013

Вам нужен UriTemplate, попробуйте что-то вроде этого:

 [ServiceContract()]
 public interface ICarService
 {

     [OperationContract]
     [WebGet(UriTemplate = "/Car")]
     CarPool GetAllCars();

     [OperationContract]
     [WebGet(UriTemplate = "/Car/{carName}")]
     Car GetCar(string carName);

 }
person Shiraz Bhaiji    schedule 19.10.2010
comment
Да, но мне нужны и CarService, и TruckService, поэтому мне придется добавить два маршрута в Global.asax.cs, верно? И чтобы различать их, URL-адреса будут очень подробными: например, domain.com/carservice/cars и domain.com/truckservice/trucks. Я бы предпочел иметь несколько классов обслуживания и коротких URL-адресов: например, domain.com/cars и domain.com/trucks - person Will; 20.10.2010
comment
Вы правы, что у меня могут быть URL-адреса без косой черты, если я хотел бы перечислить все свои методы обслуживания в одиночном классе обслуживания. Но это могут быть сотни методов для большого API. - person Will; 20.10.2010

Попробуйте поместить это в Global.asax.cs

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        string rawUrl = HttpContext.Current.Request.RawUrl.ToLower();

        if (rawUrl.EndsWith("/cars"))
        {
            HttpContext.Current.RewritePath(rawUrl + "/");  // append trailing slash
        }
    }
person Roman Y    schedule 01.06.2012

Я имел дело с этой конкретной проблемой и наткнулся на этот фрагмент в онлайн-документации MS:

По умолчанию маршрутизация не обрабатывает запросы, которые сопоставляются с существующим физическим файлом на веб-сервере. Например, запрос http://server/application/Products/Beverages/Coffee.aspx не обрабатывается маршрутизацией, если физический файл существует в Products / Beverages / Coffee.aspx. Маршрутизация не обрабатывает запрос, даже если он соответствует определенному шаблону, например {controller} / {action} / {id}.

Я понял, что мой шаблон маршрута соответствует каталогу, в котором размещена моя служба. Похоже, что каталог обрабатывается так же, как физический файл, и шаблоны маршрутов, соответствующие каталогу, также игнорируются. Итак, следуя документации, я установил для свойства RouteExistingFiles значение «true» в коллекции RouteCollection. Моя служба теперь, кажется, правильно маршрутизирует запросы, и я смог сохранить синтаксис REST, который мне очень нравится.

person jwill212    schedule 03.01.2012
comment
+1 У меня была служба, которую я хотел вызвать с косой чертой в конце или без нее. Ваше решение отлично работает. - person sheikhjabootie; 22.05.2014

Старый вопрос, но вот как я решил проблему с помощью службы WCF4 REST (используя RouteTable в Global.asax для добавления ServiceRoutes). IIS7 настроен так, что к моменту вызова службы у меня будет пустой относительный путь, поэтому UriTemplate метода обработки пуст, как в примере Will Car. Я использовал правило перезаписи в файле web.config службы, чтобы добавить «/», если необходимо. Он всегда соответствует пути, а затем проверяет исходный URI ({REQUEST_URI}), чтобы увидеть, содержит ли он путь без завершающего "/".

    <rewrite>
        <rules>
            <!--
            This rule will append a "/" after "/car" if
            the client makes a request without a trailing "/".
            ASP however must have a trailing "/" to find
            the right handler.
            -->
            <rule name="FixCarPath" stopProcessing="true">
                <match url=".*" />
                <conditions>
                    <add input="{REQUEST_URI}" pattern="/car\?" />
                </conditions>
                <action type="Rewrite" url="{PATH_INFO}/" />
            </rule>
        </rules>
    </rewrite>
person Community    schedule 02.05.2012

Немного более многоразового использования:

public class Global : NinjectHttpApplication
{

    protected override void OnApplicationStarted()
    {
        base.OnApplicationStarted();
        RegisterRoutes();
    }

    private void RegisterRoutes()
    {
        RouteTable.Routes.Add(new ServiceRoute("login", new NinjectWebServiceHostFactory(), typeof(LoginService)));
        RouteTable.Routes.Add(new ServiceRoute("incidents", new NinjectWebServiceHostFactory(), typeof(IncidentService)));
        SetRoutePrefixes();
    }
    //This is a workaround for WCF forcing you to end with "/" if you dont have a urlTemplate and redirecting if you dont have
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        string rawUrl = HttpContext.Current.Request.RawUrl.ToLower();


        if (_routePrefixes.Any(rawUrl.EndsWith))
        {
            HttpContext.Current.RewritePath(rawUrl + "/");  // append trailing slash
        }
    }


    private static List<string> _routePrefixes; 
    private static void SetRoutePrefixes()
    {
        _routePrefixes = new List<string>();
        foreach (var route in RouteTable.Routes)
        {
            var r = route as Route;
            var routePrefix = r.Url.Split('/').First();
            _routePrefixes.Add(routePrefix);
        }
    }
person Jacques de Vos    schedule 18.06.2013

Попробуйте изменить свой код в Global.asax с ...

Routes.Add(new ServiceRoute("cars", new WebServiceHostFactory(), typeof(CarService))); RouteTable.Routes.Add(new ServiceRoute("trucks", new WebServiceHostFactory(), typeof(TruckService)));

...to...

WebServiceHostFactory factory = new WebServiceHostFactory();

Routes.Add(new ServiceRoute("cars", factory, typeof(CarService))); RouteTable.Routes.Add(new ServiceRoute("trucks", factory, typeof(TruckService)));

person DanC    schedule 24.11.2010