API multiple Get methods and routing - c#

I have a controller with only Get Methods
public class DeviceController : ApiController
{
List<Device> machines = new List<Device>();
public IEnumerable<Device> GetAllMachines()
{
//do something
return machines;
}
[HttpGet]
public IEnumerable<Device> GetMachineByID(int id)
{
//do something
return machines;
}
[HttpGet]
public IEnumerable<Device> GetMachinesByKey(string key)
{
//do something
return machines;
}
}
I would like to be able to access these via URL and get the data back
../api/{contorller}/GetAllMachines
../api/{contorller}/GetMachineByID/1001
../api/{contorller}/GetMachiesByKey/HP (machines exist)
When i run the first two in IE developer mode (f12) i get Json back displaying all machines and machine 1001. However when i run GetMachinesByKey/HP i get 404 error.
Also my WebApiConfig looks like this
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{Action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Anyone enlighten me as to what I am doing wrong?

The routing engine is expecting to bind to a variable named id as defined in the route config:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{Action}/{id}", //<--- here {id} means bind to parameter named 'id'
defaults: new { id = RouteParameter.Optional }
);
In your action GetMachinesByKey(string key) parameter is named key and so the framework isn't connecting those dots for you.
You could pass the parameter in the querystring so using a URL of the form /api/{contorller}/GetMachiesByKey/?key=HP will bind correctly (you may need to change the route config as this doesn't pass an id parameter that the current config will be expecting).
Alternatively I believe you can specify a route for an action using attribute routing. This allows you to decorate your action method with an attribute which tells the framework how the route should be resolved e.g:
[Route("<controller>/GetMachinesByKey/{key}")]
public IEnumerable<Device> GetMachinesByKey(string key)

Use the RoutePrefix and Route attributes.
[RoutePrefix("api/device")]
public class DeviceController : ApiController
{
List<Device> machines = new List<Device>();
[HttpGet]
[Route("Machines")]
public IEnumerable<Device> GetAllMachines()
{
//do something
return machines;
}
[HttpGet]
[Route("Machines/{id:int}")]
public IEnumerable<Device> GetMachineByID(int id)
{
//do something
return machines;
}
[HttpGet]
[Route("Machines/{key}")]
public IEnumerable<Device> GetMachinesByKey(string key)
{
//do something
return machines;
}

Related

ASP.Net MVC 4 API custom method name

I have a problem that I want to call a MVC Api method with a custom name.
I changed the WebApi.config as described here
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id="test" }
);
and wrote a class
public class MissingCardBoxModelController : ApiController
{
// GET api/missingcardboxmodel
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/missingcardboxmodel/5
public string Get(string id)
{
return id;
}
public string GetTrackingNumber(string parcelLabelNumber)
{
string trackingNumber = "some number";
return trackingNumber;
}
// POST api/missingcardboxmodel
public void Post([FromBody]string value)
{
}
// PUT api/missingcardboxmodel/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/missingcardboxmodel/5
public void Delete(int id)
{
}
}
But I can't call the method via http://localhost:58528/api/MissingCardBoxModel/GetTrackingNumber/123456
I Get the message
No action was found on the controller 'MissingCardBoxModel' that
matches the request.
Why can't I call the method ?
If your routes are configured to be these (default in the MVC solution template):
url: "{controller}/{action}/{id}"
You should change parcelLabelNumber to id.
You can read more about routes here.
By Default Web API allows Restful conventions that means it will auto map GET, PUT, POST, DELETE etc action names. if you look inside your WebApiConfig in routes it only allows the route below
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
which means it only allows
.../api/yourcontrollername/a parameter that will map to id
.
you basically have 2 options, one to use attribute routing. or you can add a route to your custom method eg:
config.Routes.MapHttpRoute(
name: "custom",
routeTemplate: "api/{controller}/{action}/{parcelLabelNumber}",
defaults: new { parcelLabelNumber = "" }
);
please also notice the parameter name here "parcelLabelNumber", you have to name your parameter same here as in your action. You should be able to reach this action at - http://localhost:23691/api/MissingCardBoxModel/GetTrackingNumber/1245
Also please have a look at Routing in general

REST webapi URI GET with string instead of id not routing as expected

I have the following example where the request is http://{domain}/api/foo/{username} but I get a 404 status code back. No other Get actions exist on this controller. Shouldn't this work?
public class FooController : ApiController
{
public Foo Get(string username)
{
return _service.Get<Foo>(username);
}
}
By default your route will look something like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
When you visit the url http://{domain}/api/foo/{username} the controller is mapped as foo and the optional id parameter is mapped to {username}. As you don't have a Get action method with a parameter called id a 404 is returned.
To fix this you can either call the API method by changing the URL to be explicit about the parameter name:
http://{domain}/api/foo?username={username}
Or you could change your parameter name in your action method:
public Foo Get(string id)
{
var foo = _service.Get<Foo>(username);
return foo;
}
Or you could change your route to accept a username:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{username}",
defaults: new { username = RouteParameter.Optional }
);

Why do my WebAPI RESTful routes throw 404 errors?

On a side-project I am working on, I am creating a RESTful API using WebAPI 2.2. The thing I'm working on is a means of accessing settings for a game. An example of the kind of routes I am trying to accomplish are as follows:
http://x/api/GameSettings/ <-- Returns all settings
http://x/api/GameSettings/audio <-- Returns the 'audio' category
http://x/api/GameSettings/audio/volume <-- Returns the key 'volume' in category audio
Note: the examples are all Get requests.
I've implemented the following controller...
public class GameSettingsController : ApiController
{
// GET /api/GameSettings
public HttpResponseMessage Get()
{
// Magic
return Request.CreateResponse(HttpStatusCode.OK, model);
}
public HttpResponseMessage Get(string category)
{
// Similar.
}
public HttpResponseMessage Get(string category, string key)
{
// Slightly different, but still similar.
}
}
I bound up the following MVC routes:
// Only necessary for the main view...
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
And, I bound up the following WebAPI routes:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ApiGeneralCommand",
routeTemplate: "api/{controller}",
defaults: new { controller = "GameSettings" }
);
config.Routes.MapHttpRoute(
name: "ApiCategoryCommands",
routeTemplate: "api/{controller}/{category})",
defaults: new { controller = "GameSettings" }
);
config.Routes.MapHttpRoute(
name: "ApiKeyCommands",
routeTemplate: "api/{controller}/{category}/{key}",
defaults: new { controller = "GameSettings", category = "master" },
constraints: new { key = "[a-z0-9.-]" }
);
...And finally, my Global.asax configuration is set up like so:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
...But one small problem.
When I navigate to http://x/api/GameSettings/audio, I get a 404 error. It's as if the category argument in the request isn't being properly associated to the Get(string category) method on my controller. This leads me to believe my routes are wrong or I'm missing something.
As a sanity check, I tested the route using a non-RESTful syntax, http://x/api/GameSettings?category=audio, which hit a breakpoint and yielded a result. This only reaffirms my theory that the WebAPI routing is off.
As an additional sanity check, I tested http://x/api/GameSettings/ and not only hit a breakpoint set in that function, but returned the expected result.
Question: What is my routing missing, that will allow http://x/api/GameSettings/audio to work like http://x/api/GameSettings?category=audio? I haven't worked with a RESTful API in a while, so I'm sure I'm missing something really dumb.
I would try to use the attribute routing. I believe that should work well for your scenario.
[RoutePrefix("api/GameSettings")]
public class GameSettingsController
{
// GET /api/GameSettings
[HttpGet]
public HttpResponseMessage Get()
{
// Magic
return Request.CreateResponse(HttpStatusCode.OK, model);
}
[Route("{category}")]
[HttpGet]
public HttpResponseMessage Get(string category)
{
// Similar.
}
[Route("{category}/{key}")]
[HttpGet]
public HttpResponseMessage Get(string category, string key)
{
// Slightly different, but still similar.
}
}
I would remove the stuff that you have added to the config.
Hope this helps.
Change the order and try.Because ASP.NET realizes that you have three routes. It will check the top-most route first and if your data can be placed in that route it will not check any more routes.
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ApiKeyCommands",
routeTemplate: "api/{controller}/{category}/{key}",
defaults: new { controller = "GameSettings", category = "master" },
constraints: new { key = "[a-z0-9.-]" }
);
config.Routes.MapHttpRoute(
name: "ApiCategoryCommands",
routeTemplate: "api/{controller}/{category})",
defaults: new { controller = "GameSettings" }
);
config.Routes.MapHttpRoute(
name: "ApiGeneralCommand",
routeTemplate: "api/{controller}",
defaults: new { controller = "GameSettings" }
);

Why do WebApi routing config defaults not include the action param?

I am a bit new to WebApi so maybe someone can explain this to me, the default route added for WebApis is:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
So given an ApiController that looks like this:
public class LookupController : ApiController
{
[HttpGet]
public IHttpActionResult GetCountries()
{
// do stuff
return Ok();
}
[HttpGet]
public IHttpActionResult GetStates()
{
// do stuff
return Ok();
}
}
How would it know which action to call? It wouldn't right?
Shouldn't the default Route be more like:
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Or should you only have one GET/UPDATE/DELETE etc per ApiController? Which really wouldn't cater for some scenarios...
Web API figures out which method to call based on routing data. You don't need to specify the action because Web API will use the verb(GET, POST,DELETE,etc) of the request.
If you would like to have multiple GET actions in a controller, you can specify a route for each action.
[Route("api/lookup/countries"]
[HttpGet]
public IHttpActionResult GetCountries()
{
// do stuff
return Ok();
}
[Route("api/lookup/states"]
[HttpGet]
public IHttpActionResult GetStates()
{
// do stuff
return Ok();
}
More information is available here http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Strictly speaking your controller should never look like that. Out of the box the idea is that a controller handles requests for a single entity (e.g. Customer) and the operations, which map to HTTP verbs, operate against that entity. So under normal circumstances your controller would look something like the following (created using scaffolding against a simple model class, method bodies omitted for brevity):
public class CustomerController : ApiController
{
public IQueryable<Customer> GetCustomers()
{
}
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
}
[ResponseType(typeof(void))]
public IHttpActionResult PutCustomer(int id, Customer customer)
{
}
[ResponseType(typeof(Customer))]
public IHttpActionResult PostCustomer(Customer customer)
{
}
[ResponseType(typeof(Customer))]
public IHttpActionResult DeleteCustomer(int id)
{
}
}
If you have more business-oriented operations to perform (e.g. BillCustomer), rather than just the basic CRUD operations, I would suggest creating a separate route for those. We did that in one of our applications and it created a nice logical separation. For example:
config.Routes.MapHttpRoute(
name: "RestApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "RpcApi",
routeTemplate: "rpc/{controller}/{action}"
);

Multiple actions were found: /resource/id vs /resource/id/action

I realize this is a common question, but I can't find one that properly asks the same question with my configuration.
I have a default route that looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional }
);
And my controller looks like this:
public class VariablesController : ApiController
{
public IEnumerable<Variable> Get()
{
// get all
}
public Variable Get( int id )
{
// get individual
}
[HttpGet]
public DistributionReport GetDistribution( int id )
{
// get distribution report
}
}
When I GET /api/variables/1/getDistribution, it works fine.
When I GET /api/variables/1, I get a 500 exception:
Multiple actions were found that match the request:
Models.Variable Get(Int32) on type Controllers.Api.VariablesController
Models.DistributionReport GetDistribution(Int32) on type Controllers.Api.VariablesController
What do I need to do to modify my situation so they both work?

Categories

Resources