Не найден ресурс HTTP, соответствующий ошибке URI запроса в веб-API ASP.NET.

Это набросок моего класса TransferController.

Все это код веб-API.

public class TransferController : ApiController
{
  [HttpGet, ActionName("Queue")]
  public IEnumerable<object> GetQueue(Guid sessionId) {...}

  [HttpDelete, ActionName("Delete")]
  public void Delete(Guid sessionId, Guid fileId) {...}

  [HttpGet, ActionName("Cancel")]
  public bool Cancel(Guid sessionId, Guid fileId) {...}

  [HttpGet, ActionName("UploadedBytes")]
  public long GetUploadedByteCount(Guid sessionId, Guid fileId) {...}

  [HttpGet, ActionName("DownloadUrl")]
  public string GetDownloadUrl(string fileId) {...}

  [HttpPost, ActionName("FileChunk")] 
  public void PostFileChunk([FromUri]Guid sessionId, [FromUri]Guid fileId) {...}

  [HttpPost, ActionName("UploadDefinition")]
  public Guid PostUploadItem([FromBody]UploadDefinition uploadDef) {...}

}

Это маршрутизация.

public static void Register(HttpConfiguration config)
{
  config.Routes.MapHttpRoute(
    name: "DefaultApi", 
    routeTemplate: "api/{controller}/{action}"
    );
  config.Routes.MapHttpRoute(
    name: "DefaultApiDefaultMethod", 
    routeTemplate: "api/{controller}"
    );
}

Это вызов.

$.ajax({
  url: "api/Transfer/Queue",
  data: { sessiondId: login.SessionId() }    
})
.done(function (result) {
  history.push(new UploadItem());
  for (var i = 0; i < result.length; i++) {
    var ui = new UploadItem(result[i]);
    history.push(ui);
  }
})
.fail(function (result) {
  app.showMessage(JSON.parse(result.responseText).Message);
});

И вот результат.

No HTTP resource was found that matches the request URI 'http://localhost:54770/api/Transfer/Queue?sessiondId=0e2c47b9-e674-446d-a06c-ce16932f9580'.

Это набросок моего класса UserController.

public class UserController : ApiController 

  [HttpGet, ActionName("Authenticate")]
  public object Authenticate(string email, string password) {...}

  [HttpPost]
  public void Register([FromBody]UserDefinition userDef) {...}

  [HttpGet, ActionName("Pulse")]
  public bool Pulse(Guid sessionId) {...}

}

По непостижимым для меня причинам у меня нет проблем с вызовом чего-либо в UserController. Параметры маршалируются точно так же, и используются те же маршруты.


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

Но тесты, как он их показывает, действительно проверяют только синтаксический анализ URL. Например, этот тест проходит

public void TestMvc4RouteWibble()
{
  var config = new HttpConfiguration();
  config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{controller}/{action}/{id}",
      defaults: new { id = RouteParameter.Optional }
      );


  var route =
      config.Routes.GetRouteData(new HttpRequestMessage()
      {
        RequestUri = new Uri("http://localhost:54770/api/Transfer/Wibble?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580&fileId=0e2c47b9-e674-446d-a06c-ce16932f9581")  //?
      });

  Assert.IsNotNull(route);
  Assert.AreEqual("Transfer", route.Values["controller"]);
  Assert.AreEqual("Wibble", route.Values["action"]);

}

несмотря на бросающееся в глаза отсутствие метода Wibble на контроллере Transfer.

Кроме того, объект маршрута на самом деле не является объектом HttpRoute, это объект HttpRouteData. Но это банально поправимо. Объект HttpRoute доступен как свойство объекта HttpRouteData.

public void TestMvc4RouteWibble()
{
  var config = new HttpConfiguration();
  config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{controller}/{action}/{id}",
      defaults: new { id = RouteParameter.Optional }
      );


  var routeData =
      config.Routes.GetRouteData(new HttpRequestMessage()
      {
        RequestUri = new Uri("http://localhost:54770/api/Transfer/Wibble?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580&fileId=0e2c47b9-e674-446d-a06c-ce16932f9581")  //?
      });

  Assert.IsNotNull(routeData);
  Assert.AreEqual("Transfer", routeData.Values["controller"]);
  Assert.AreEqual("Wibble", routeData.Values["action"]);

}

А у него, в свою очередь, есть свойство Handler. Однако это менее информативно, чем могло бы быть, поскольку нулевой обработчик просто означает (из MSDN)

Если значение null, обработчик по умолчанию отправляет сообщения реализациям IHttpController.

Теперь мой контроллер является производным от ApiController, который, безусловно, реализует метод ExecuteAsync, который является единственной вещью, определяемой интерфейсом IHttpController. Я думаю, что это означает, что я мог бы проверить выполнение этого метода, если бы знал о нем больше.


person Peter Wone    schedule 11.04.2014    source источник
comment
Я бы не стал получать за Cancel   -  person Daniel A. White    schedule 11.04.2014
comment
разве все действия не должны возвращать ActionResult ? я не знаю об Apicontroller, но в контроллере у вас должен быть возвращаемый тип Actionresult или производный тип...   -  person Vishal Sharma    schedule 11.04.2014
comment
Это веб-API. Это должно быть очевидно из того факта, что класс наследуется от ApiController, но я назову это в самом вопросе.   -  person Peter Wone    schedule 11.04.2014
comment
Будет ли кто-нибудь когда-нибудь искать головоломку ASP.NET WEB API Routing? Ваш заголовок должен быть чем-то, что кто-то будет искать. В отличие от форума, заголовки здесь должны быть описанием вашей проблемы.   -  person George Stocker    schedule 11.04.2014


Ответы (2)


Вот тест, демонстрирующий, что маршрутизация работает нормально,

[Fact]
public void TestRoute() 
{
    var config = new HttpConfiguration();
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{action}"
        );


    var route =
        config.Routes.GetRouteData(new HttpRequestMessage()
        {
            RequestUri = new Uri("http://localhost:54770/api/Transfer/Queue?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580")  //?
        });

    Assert.NotNull(route);
    Assert.Equal("Transfer",route.Values["controller"]);
    Assert.Equal("Queue",route.Values["action"]);

}

и вот тест, показывающий, что выбор диспетчеризации/действия также работает,

[Fact]
public void TestDispatch()
{
    var config = new HttpConfiguration();
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{action}"
        );

    var server = new HttpServer(config);

    var client = new HttpClient(server);
    var response =
        client.GetAsync(new Uri("http://localhost:54770/api/Transfer/Queue?sessionId=0e2c47b9-e674-446d-a06c-ce16932f9580")) // 
            .Result;

    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}


public class TransferController : ApiController
{
   [HttpGet]
   [ActionName("Queue")]
   public IEnumerable<object> Queue(Guid sessionId) 
   {
       return null;
   }

}
person Darrel Miller    schedule 11.04.2014
comment
Конечно, я попробую. Но зовите меня Мистер Глупый, почему это работает для API/Пользователя/Пульса? - person Peter Wone; 11.04.2014
comment
Изменил все Guids на строки и проанализировал их внутри метода. Не влияет на проблему, которая (я полагаю) связана с ошибкой маршрутизации. - person Peter Wone; 11.04.2014
comment
@PeterWone Я не знаю, почему раньше у меня появлялась ошибка, но теперь у меня все работает нормально ... Очень озадачен. - person Darrel Miller; 11.04.2014
comment
То, что доставило проблемы и теперь работает нормально, вы имеете в виду параметры Guid? - person Peter Wone; 13.04.2014

Хорошо, тогда... спасибо, что поместили идею модульного теста в мою голову, это очень ускорило процесс.

Вот подноготная:

У вас могут быть одинаковые подписи параметров для разных глаголов (get post put delete).

Вы не можете иметь одинаковые сигнатуры параметров для разных имен действий в одной и той же команде.

Вам нужно изменить только одно имя параметра.

Так что это нормально, потому что они все на разные глаголы

[HttpDelete, ActionName("Delete")]
public void Delete(Guid sessionId, Guid fileId) {...}

[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid fileId) {...}

[HttpPost, ActionName("FileChunk")] 
public void PostFileChunk(Guid sessionId, Guid fileId) {...}

но это не круто, потому что они оба получают

[HttpGet, ActionName("UploadedBytes")]
public long GetUploadedByteCount(Guid sessionId, Guid fileId) {...}

[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid fileId) {...}

и вы можете исправить это так

[HttpGet, ActionName("UploadedBytes")]
public long GetUploadedByteCount(Guid sessionId, Guid uploadBytesFileId) {...}

[HttpGet, ActionName("Cancel")]
public bool Cancel(Guid sessionId, Guid cancelFileId) {...}

Может быть, я жесткий зад, но, насколько я понимаю, это не маршрутизация, пока не будет вызван метод.

person Peter Wone    schedule 14.04.2014