WebApi routing issue - c#

I am using the default routing setup in WebApiConfig (MVC 4) but for some reason I am getting unexpected results.
If I call /api/devices/get/ it hits the Get function but the Id is "get" rather than 1. If I call /api/devices/get/1 I get a 404. I also want to be able to support multiple parameters i.e.
public Device[] Get(int? page, int? pageSize) // for multiple devices
The route
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
And my API:
public class DevicesController : ApiController
{
EClient client = new EClient();
// GET api/devices/5
public Device Get(string id)
{
return client.GetDeviceBySerial(id);
}
}

id in the controller parameter should be integer:
public Device Get(int id)
{
return client.GetDeviceBySerial(id);
}
if you need to pass in string, or other prams, just use quesry string:
public Device Get(int id, string pageSize)
{
return client.GetDeviceBySerial(id);
}
the above can be called as:
/api/devices/1
or
/api/devices/?id=1&pageSize=10
Note: you do not need to specify method name. Web API will judge that on the basis of HTTP Verb used. If its a GET request, it will use the Get method, if its a POST request, then it will use Post method ... and so on.
You can change the above behavior, but I guess you mentioned that you want to keep usign the default Routing ... so I am not covering that.

Related

C# REST API Controller: same route with 2 different actions

When using the following routes:
config.Routes.MapHttpRoute(
name: "new_device",
routeTemplate: "api/v1/devices",
defaults: new { controller = "Devices", action = "new_device" }
);
config.Routes.MapHttpRoute(
name: "devices_list",
routeTemplate: "api/v1/devices",
defaults: new { controller = "Devices", action = "devices_list", httpMethod = new HttpMethodConstraint(HttpMethod.Get) }
);
The controller looks as follows:
public class DevicesController : ApiController
{
[HttpPost]
[ResponseType(typeof(IHttpActionResult))]
[Route("api/v1/devices")]
[ActionName("new_device")]
[ValidateModel]
public IHttpActionResult NewDevice([System.Web.Http.FromBody] Device device )
{
...
}
[HttpGet]
[ResponseType(typeof(IHttpActionResult))]
[Route("api/v1/devices")]
[ActionName("devices_list")]
[ValidateModel]
public List<Device> GetAllDevices()
{
...
}
My expectation would be that the router would find the correct route based on the HttpMethod used since even it's using the same URI it is using a different HttpMethod.
But instead it fails with the following:
"Message": "The requested resource does not support http method 'GET'."
My guess is because it fins a match with the URI and then checks if the method if the same.
Is there a way to achieve using the same URI with different Http Method which is by the way REST guidelines? Am I missing something?
Ok , I check your whole code. I think you are trying to achieve the calls in complicated way.
Following code is for the configuration :
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and follwoing is your controller code :
public class DevicesController : ApiController
{
[HttpPost]
[ResponseType(typeof(IHttpActionResult))]
[ActionName("newDevice")]
public IHttpActionResult NewDevice([System.Web.Http.FromBody] Device device)
{
return null;
}
[HttpGet]
[ResponseType(typeof(IHttpActionResult))]
[ActionName("devices_list")]
public List<Device> GetAllDevices()
{
return null;
}
}
I removed ValidateModel. I think it's your custom attribute or somehow related with built in nuget package.
Anyways, execute the calls with Postman or any HTTP client tool. It should work , as it was working at my end with above mentioned code.
Example Calls:
https://localhost:44370/api/v1/devices/devices_list = > Get.
https://localhost:44370/api/v1/devices/newDevice => Post. Provide body as post call for the object.

Same method signature for two diff methods in web api

it might have duplicate but i didn't find right solution,
My web api,
public class SampleController : ApiController
{
public string Get(int id)
{
return "value";
}
public string hello(int id)
{
return "value";
}
}
my webapiconfig,
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
my problem is
When i call http://localhost:1234/api/Sample/5 it's hitting Get(int id) but how can i call method 2 i.e hello(int id) ?? what needs to be changed and what's the best way to handle these kind of scenarios ??
TLDR:
If you want to reference individual actions in your Web API then change your routing to this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then you can access your action like this: localhost/api/{controller}/{action}/. Look here for further information, especially "Routing by Action Name".
Orig:
You seem to expect the same behaviour as with MVC Controllers. The Standard-Routing for MVC-Controller is this:
routeTemplate: "{controller}/{action}/{id}"
This corresponds to the name of the controller, the method which is to be used and some form of input. ApiControllers Route differently:
routeTemplate: "staticPart/{controller}/{id}"
As you can see there is only a reference to the individual controller and the input, as well as the "staticPart" which normally is something like /api/
The Idea is that you use a RESTful approach, connecting methods with different types of http methods (eg. DELETE, GET, POST, PUSH and PUT)
The Get Method in your example is a special because through the name "Get" you have told the compiler that this method corresponds with HTTP-GET.
So to get to your question: Either you change your Routing to that of MVC-Controller. So that you reference individual actions in your requests or you use different HTTP-Methods. Or you set routes indivdually as shown by MaxB
You can find an official overview on Web API routing here There you'll find examples on all possibilities.
I'm not sure if I got your problem right, but if I did:
You should specify the route for the function not in its name, but in some other way. From my little experience with the topic, that's how I do it:
[HttpGet]
[Route("SystemInfo")] // That's the name of the route you will call
public IHttpActionResult SystemInfo()
{
return Ok();
}
Consider checking this out.
So, considering your question, it would be like so:
[Route("Get")]
public string Get(int id)
{
return "value";
}
[Route("hello")]
public string hello(int id)
{
return "value";
}

Web API routing in C#

I am trying to build a web API which would accept 2 parameters. However, upon calling the API it always hits the method without any parameters.
I followed the instructions in here, and can't figure why it won't work.
The request I send using the 'PostMaster' Chrome extension : http://localhost:51403/api/test/title/bf
For the above request, I expect the first method to be hit, but the second method is being reached.
The method within the controller are :
// Get : api/test/type/slug
public void Get(string type,string slug){
//Doesn't reach here
}
// Get : api/test
public void Get() {
// Reaches here even when the api called is GET api/test/type/slug
}
The webApiConfig hasn't been changed much, except that it accepts 2 parameters :
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id1}/{id2}",
defaults: new { id1 = RouteParameter.Optional, id2 = RouteParameter.Optional }
);
}
My understanding from the documentation is that the webapiconfig doesn't have to be changed.
Here's the error that I get
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:51403/api/test/title/bf'.",
"MessageDetail":"No action was found on the controller 'test' that matches the request."}
In order for the routing engine to route the request to the right Action, it looks for the method whose parameters match the names in the route first.
In other words, this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id1}/{id2}",
defaults: new { id1 = RouteParameter.Optional, id2 = RouteParameter.Optional }
);
matches:
public void Get(string id1, string id2) {}
but not:
public void Get(string type, string slug) {}
If you wanted to, this would have also worked:
http://localhost?type=weeeee&slug=herp-derp
which would have matched
public void Get(string type, string slug)
You must use parameters name named id1 and id2 that in your route config.
Like this:
// Get : api/test/type/slug
public void Get(string id1,string id2){
//Doesn't reach here
}

Calling a Get method in Web API with attribute routing

My WebApi.config looks like this:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
which means it is configured to support both attribute based routing and conventional routing.
And the sample controller created:
[RoutePrefix("v1")]
public class Values1Controller : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[Route("products")]
public string Get(int id)
{
return "value";
}
}
When I invoke http://localhost.domain/api/values1/v1/ the response is value1, value2
But, when I try to invoke http://localhost.domain/api/values1/v1/products/1 (notice the products attribute route on the public string Get(int id) method, the break point is not hit.
In other words, how to call the public string Get(int id) method with the attribute routing Route(products) on top of it?
You use /v1/products?id=10 to hit the action public string Get(int id) or change your route template to be [Route("products/{id}")] to make request like /v1/products/10
Note that attributed controllers/actions cannot be reached by requests which match conventional routes, so that means you cannot do something like api/values/1 to reach public string Get(int id) action

ASP.NET WebApi - Two Controllers With Similar Action Methods

I have a scenario where I have 2 different controllers that each have multiple Get methods. I have the methods decorated with the ActionName attribute, but the Routing isn't working as I think it should.
ContactController
public ContactModel GetContactByID(string id)
{
...
}
[ActionName("username")]
public ContactModel GetContactByUserName(string text)
{
...
}
PaymentController
public PaymentModel Get(Guid id)
{
...
}
[HttpGet, ActionName("sale")]
public PaymentActivityModel Sale(Guid id)
{
...
}
Routes
config.Routes.MapHttpRoute(
"PaymentControllerActionId",
"api/client/{clientId}/{controller}/{action}/{id}",
defaults: null
);
config.Routes.MapHttpRoute(
"ContactControllerActionText",
"api/client/{clientId}/{controller}/{action}/{text}",
defaults: null
);
config.Routes.MapHttpRoute(
"ClientControllerId",
"api/client/{clientId}/{controller}/{id}",
new { id = RouteParameter.Optional }
);
When I navigate to a Payment, it works fine. But when I navigate to a Contact, I receive:
No HTTP resource was found that matches the request URI, .../api/client/.../contact/username/exampleUserName
No action was found on the controller 'Contact' that matches the request.
I was under the impression that the parameter name would be matched up with the action name (text vs. id).
Is the problem that one uses a Guid id and one uses a string id?
This is a client-facing API, and I have a client-friendly ContactID that is a string. Whereas the PaymentID is a Guid.
First I want to point out your routes don't make a lot of sense.
This route says it is for PaymentControllerActionId, however you left the routing open to use any controller instead of specifying it is only for the Payment Controller.
config.Routes.MapHttpRoute(
"PaymentControllerActionId",
"api/client/{clientId}/{controller}/{action}/{id}",
defaults: null
);
To answer your question. You likely need to specify {text} as an optional RouteParameter. Otherwise the framework usually expects that you have defined a custom routing constraint (and you should have a routing constraint on {clientId} IMO).
Also, if one of your actions expects a GUID. Put it as a Guid argument type, and WebAPI will match it correctly. Alternatively, since both functions are expecting either a name or a guid, you could just have only 1 function that takes a string, and in your db do a select statement matching the GUID or the NAME and return the contact. Much less code, easier to read, makes more sense.
If you are using Web API 2, then you could use attribute routing for this purpose.
In the below example, the controllers use a mix of conventional + attribute routing, but you can change this to only go by attribute routing if you need.
Example:
Route Configuration
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"ClientControllerId",
"api/client/{clientId}/{controller}/{id}",
new { id = RouteParameter.Optional }
);
ContactController
[RoutePrefix("api/client/{clientId}/Contact")]
public class ContactController
{
// this uses conventional route
public ContactModel GetContactByID(string id)
{
...
}
[Route("username/{userName}")]
public ContactModel GetContactByUserName(string userName)
{
...
}
}
PaymentController
[RoutePrefix("api/client/{clientId}/Payment")]
public class PaymentController
{
// this uses conventional route
public PaymentModel Get(Guid id)
{
...
}
[HttpGet, Route("sale/{id}")]
public PaymentActivityModel Sale(Guid id)
{
...
}
}

Categories