I found this issue by the following steps:
Create a new WebApi project with the installed project template
Go to Controllers/ValuesController.cs, there are two Get methods like this:
public IEnumerable<string> Get()
// this one provides GetAll function
public string Get(int id)
// this one is GetOneById
I don't like this design because I think the two api methods can be combined into one:
public IEnumerable<string> Get(string ids)
when ids is null, it returns all the records, otherwise returns the results by ids(which looks like id1,id2,id3...)
I modified the route as well:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{ids}",
defaults: new { ids = RouteParameter.Optional }
);
Now I think everything is ready. However when I visit /values
in the browser(without specifying the ids parameter), I was told "no action was found", until I add a default value for ids
:
public IEnumerable<string> Get(string ids = null)
From now on everything works as I expected. Still I can't move the defaults
parameter in the route. Which means: the code declares the ids parameter as optional twice.
Looking into the source code of MVC4 located at System.Web.Http.Controllers.ActionSelectorCacheItem.FindActionUsingRouteAndQueryParameters
method, I can see the optional parameters defined in the route are ignored when finding the right action. Personally I think it's really bad, even the workaround is quite easy. Or I misunderstood the relations between routes and action methods? I consider routes are rules used to help a request to find its corresponding action, while "a parameter is optional" is exactly part of a rule.
You may well have an argument this feels like an awkward solution to something relatively straight forward, but I think the root cause is because you are trying to roll both 'GetAll' and 'GetOneById' into the same method.
You can leave the route config out of the problem if you are willing to split out the methods in the controller. IMHO this is not such a bad thing; it complies with the separation of concerns principle. It also does not mean any duplication of code as you can centralise the logic to access the object(s) you want to return.
These controller methods should give you the routes you want without having to adjust the route config:
[HttpGet]
public IEnumerable<string> GetAll()
{
return GetStrings(new int[]{});
}
[HttpGet]
public string GetOneById(int id)
{
return GetStrings(new int[]{id}).FirstOrDefault();
}
private IEnumerable<string> GetStrings(int[] ids)
{
return // fetch strings using int array
}
You've got the option to have the values as a Querystring
public class PaymentsController : ApiController {
[HttpGet]
public Person GetValues(string ids = null)
{
return new Person() { FirstName = "Michael" };
}
}
public class Person
{
public string FirstName { get; set; }
}
}
I was able to access this route with both:
NOTE: without any special routing apart from the default
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With