Question

This is a sketch of my TransferController class.

All this is Web API code.

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) {...}

}

This is the routing.

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

This is the invocation.

$.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);
});

And this is the result.

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

This is a sketch of my UserController class.

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) {...}

}

For reasons unfathomable to me, I have no trouble calling anything in the UserController. Parameters are marshalled in exactly the same way, and the same routes are in use.


Darrel Miller below uses unit testing to validate routes. Frankly I'm kicking myself for not thinking of this, and now I've done the same.

But tests as he shows them really test only parsing of the URL. For example, this test passes

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"]);

}

despite the conspicuous absence of a method Wibble on the Transfer controller.

Also the route object is not actually a HttpRoute object, it's a HttpRouteData object. But that's trivially corrected. The HttpRoute object is available as a property of the HttpRouteData object.

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"]);

}

And it in turn has a Handler property. However this is less informative than it might be, since a null handler simply means (from MSDN)

If null, the default handler dispatches messages to implementations of IHttpController.

Now, my controller is derived from ApiController which certainly implements the ExecuteAsync method that is the only thing specified by the IHttpController interface. Which I imagine means I could test execution of that method if I knew more about it.

Was it helpful?

Solution 2

OK then... thanks for putting the unit test idea in my head, it sped things up immensely.

Here's the lowdown:

You can have identical parameter signatures on different verbs (get post put delete).

You cannot have identical parameter signatures on different action names on the same verb.

You only need vary one parameter name.

So this is ok because they're all on different verbs

[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) {...}

but this is not cool because they're both gets

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

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

and you can fix it like this

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

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

Maybe I'm a hard-ass but as far as I'm concerned it isn't routing until the method is called.

OTHER TIPS

Here is a test that demonstrates the routing works ok,

[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"]);

}

and here is a test showing the dispatching/action selection is also working,

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

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top