문제

I am working on creating a Web API using ASP.NET Web API. I'm currently stubbing out functionality on my endpoints so that I can then start developing against it.

My WebApiConfig.cs file looks like this:

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

config.Routes.MapHttpRoute(
    name: "ActionAPI",
    routeTemplate: "api/{controller}/{id}/{action}",
    defaults: new { action = RouteParameter.Optional }
);

In my UsersController.cs file I have these methods:

//Maps to /Users endpoint
[HttpGet]
public IEnumerable<User> GetAllUsers()
{
   //Code
}

//Maps to /Users/{id} endpoint
[HttpGet]
public User GetUser(int id)
{
   //Code
}

//Maps to /Users/{id}/Enrollments endpoint
[HttpGet, ActionName("Enrollments")]
public IEnumerable<Enrollment> GetUserEnrollments(int id)
{
   //Code
}

//Maps to /Users/{id}/Enrollments/{id}
[HttpGet, ActionName("Enrollments")]
public IEnumerable<Enrollment> GetUserEnrollment(int userid, int id)
{
   //Code
}

How do I prevent /Users/GetUser from being a valid route?

Using [NonAction] on GetUser(int id) prevents it from working completely.

EDIT: Here is the current output from /Users/GetUser

<Error>
    <Message>An error has occurred.</Message>
    <ExceptionMessage>
        Multiple actions were found that match the request: Byui.ILearnAPI2.Business.Entities.User GetUser(Int32) on type Byui.ILearnAPI2.API.Controllers.UsersController System.Collections.Generic.IEnumerable`1[System.String] GetUserEnrollments(Int32) on type Byui.ILearnAPI2.API.Controllers.UsersController
    </ExceptionMessage>
    <ExceptionType>System.InvalidOperationException</ExceptionType>
    <StackTrace>
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext) at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext) at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    </StackTrace>
</Error>
도움이 되었습니까?

해결책

After reviewing your post again, I think that you are not looking at the error correctly.

Because of the way you have your controller configured, there is an ambiguous route.

Multiple actions were found that match the request:

GetUser(Int32) on type Byui.ILearnAPI2.API.Controllers.UsersController

GetUserEnrollments(Int32) on type Byui.ILearnAPI2.API.Controllers.UsersController

GetUser and GetUserEnrollments share the same route. From what I can tell GetUser is not going to work if you pass it an int or a string or whatever, because it does not know how to resolve properly.

You can add a route config before your default to resolve the issue.

config.Routes.MapHttpRoute(
    name: "GetUsers",
    routeTemplate: "api/users/{id}",
    defaults: new { controller = "Users", action = "GetUser" }
);

다른 팁

Try this.

  1. Comment out GetUserEnrollments(int id)
  2. Call /Users/GetUser - you will get
  The request is invalid.
  The parameters dictionary contains a null entry for parameter 'id' of 
  non-nullable type 'System.Int32' for method 'so14610664.Models.User 
  GetUser(Int32)' in 'so14610664.Controllers.UsersController'. An optional 
  parameter must be a reference type, a nullable type, or be declared as an 
  optional parameter.
  1. Now call /User/abcdefg - you get the same message as above. "The request is invalid..."

See, it is not that GetUser is exposed, it is that it is trying to map anything after /Users/ to id and failing, since GetUser and "abcdefg" are not valid Int32 values; in other words: it thinks you forgot id.


Try the AttributeRouting package. This is a package that sits on top of both MVC routing (the routes in App_Start/RouteConfig) and/or Web API routing (what you are doing - the routes in App_Start/WebApiConfig)

With that package, you replace the route mappings in App_Start with ones like this in your code sample:

//Maps to /Users endpoint
[HttpGet, GET("/Users")]
public IEnumerable<User> GetAllUsers()
{
   //Code
}

//Maps to /Users/{id} endpoint
[HttpGet, GET("/Users/{id)"]
public User GetUser(int id)
{
   //Code
}

//Maps to /Users/{id}/Enrollments endpoint
[HttpGet, GET("/Users/{id}/Enrollments")]
public IEnumerable<Enrollment> GetUserEnrollments(int id)
{
   //Code
}

//Maps to /Users/{userid}/Enrollments/{id}
[HttpGet, GET("/Users/{userid}/Enrollments/{id}")]
public IEnumerable<Enrollment> GetUserEnrollment(int userid, int id)
{
   //Code
}

You can use a IHttpRouteConstraint to verify if {id} is an integer and specify the route constraint in your routes.

A sample can be found here.

Have GetUsers return a 404, that is the Restful way to handle it.

You could do something like this to validate the provided id. If there is no Id found, you could return a custom response message with whatever code you wanted.

[HttpGet]
public User GetUser(int id)
{
    if (id > 0 && validIds.Contains(id))
    {
        //Go do something fun...
    }
    else
        throw new HttpResponseException(
            new HttpResponseMessage(HttpStatusCode.NotFound)
                {
                    ReasonPhrase = String.Format("A valid id is required. {0} is most definitely not a valid id.", id);
                }


  );

}

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top