So I scraped this controller code directly out of the MS .NET tutorials and it works fine:
public class ProductsController : ApiController
{
Product[] products = new Product[]
{
new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
};
public IEnumerable<Product> GetAllProducts()
{
return products;
}
public IHttpActionResult GetProduct(int id)
{
Console.WriteLine("id = " + id);
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
In the same namespace I have created 2 of my own controllers and both display the same problem - only the first action (GetAll*()) responds to a GET, the second action (GetVehicle()) does not and when set by itself and decorated with [HttpGet] errors out with:
{
"Message": "The requested resource does not support http method 'GET'."
}
This controller seems identical to the product controller above:
public class VehiclesController : ApiController
{
Vehicle[] vehicles = new Vehicle[]
{
new Vehicle { Code = 1, Type = "type1", BumperID = "H0002" },
new Vehicle { Code = 2, Type = "type2", BumperID = "T0016" }
};
public IEnumerable<Vehicle> GetAllVehicles()
{
Console.WriteLine("GetAllVehicles()");
return vehicles;
}
public IHttpActionResult GetVehicle(int code)
{
Console.WriteLine("GetVehicle() code = " + code);
var vehicle = vehicles.FirstOrDefault((v) => v.Code == code);
if (vehicle == null)
{
return NotFound();
}
return Ok(vehicle);
}
}
but only the first action gets called. What am I missing? Same exact issue with a separate SecurityController. Pretty new to .NET.....
Self-hosting route maps added:
public void Configuration(IAppBuilder app)
{
// Configure Web API for self-host.
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
app.UseWebApi(config);
}
With the above code,
http://localhost:8080/api/vehicles
works, but
http://localhost:8080/api/vehicles/1
does not.
Anyone getting here via search Jeffrey Rennie's answer below is correct - the id in {id} is literal.
Try renaming GetVehicle()'s argument from code to id. It's related to Anthony Liriano's answer. The default routing pattern expects a argument named id.
You're missing the Route and Verb Attribute. This requires that you're using the System.Web.Http library. You can find more information on Attribute Routing in ASP.NET Web API 2
[HttpGet, Route("api/your/route/here")]
public IHttpActionResult GetVehicle(int code)
{
Console.WriteLine("GetVehicle() code = " + code);
var vehicle = vehicles.FirstOrDefault((v) => v.Code == code);
if (vehicle == null)
{
return NotFound();
}
return Ok(vehicle);
}
Or decorate your api endpoint as such like
[HttpGet("{code}/GetVehicleById")]
public IHttpActionResult GetVehicle(int code)
{
Console.WriteLine("GetVehicle() code = " + code);
var vehicle = vehicles.FirstOrDefault((v) => v.Code == code);
if (vehicle == null)
{
return NotFound();
}
return Ok(vehicle);
}
Your api endpoint call would be then
api/vehicles/123/GetVehicleById
Related
I am trying to create an API and trying to access it via chrome, expecting it to return the list of Items
public class ProductController : ApiController
{
Product product = new Product();
List<Product> productList = new List<Product>();
[HttpGet]
public HttpResponseMessage GetTheProduct(int id)
{
this.productList.Add(new Product {Id = 111,Name= "sandeep" });
return Request.CreateResponse(HttpStatusCode.OK, this.productList.FirstOrDefault(p => p.Id == 111));
}
}
I have not added route so wanna run it using default route but when i am running it, am getting
No HTTP resource was found that matches the request
URI 'http://localhost:65098/api/GetTheProduct()'.
No type was found that matches the controller named
'GetTheProduct()'.
Suggest me what all things are required to make it work.
If using default routes then configuration may look like this
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
This would mean that routing is using convention-based routing with the following route template "api/{controller}/{id}"
Your controller in its current state is not following the convention. This results in requests not being matched in the route tables which result in the Not Found issues being experienced.
Refactor the controller to follow the convention
public class ProductsController : ApiController {
List<Product> productList = new List<Product>();
public ProductsController() {
this.productList.Add(new Product { Id = 111, Name = "sandeep 1" });
this.productList.Add(new Product { Id = 112, Name = "sandeep 2" });
this.productList.Add(new Product { Id = 113, Name = "sandeep 3" });
}
//Matched GET api/products
[HttpGet]
public IHttpActionResult Get() {
return Ok(productList);
}
//Matched GET api/products/111
[HttpGet]
public IHttpActionResult Get(int id) {
var product = productList.FirstOrDefault(p => p.Id == id));
if(product == null)
return NotFound();
return Ok(product);
}
}
Finally based on the route template configured then the controller expects a request that looks like
http://localhost:65098/api/products/111.
To get a single product that matches the provided id if it exists.
Reference Routing in ASP.NET Web API
I want to create some hyperlinks for my DTO`s returned from a REST Api.
The variable url is always null and I do not know why?!
Why is the url not created, what am I still missing?
The name of the route is GetStatusFeedback that's correct and I also use Action and Controller as route parameters + the leadId param!
public class ValuesController : ApiController
{
[Route("")]
[HttpGet]
public IHttpActionResult Get()
{
var leadsFromDataBase = new List<Lead> { new Lead { Id = 1 }, new Lead { Id = 2 } };
var leadDtos = new List<LeadDto>();
foreach (var lead in leadsFromDataBase)
{
var leadDto = new LeadDto();
string url = Url.Link("GetStatusFeedback", new { LeadId = lead.Id, Action = "Accept", Controller = "values"});
leadDto.AcceptLink = new Link { Url = url, Verb = "Get" };
leadDtos.Add(leadDto);
}
return Ok(leadDtos);
}
[Route("leads/{id:int}/statusfeedback", Name = "GetStatusFeedback")]
[HttpPost]
public void Accept(int leadId)
{
}
route parameters need to match the expected parameter in the route template
string url = Url.Link("GetStatusFeedback", new { id = lead.Id });
should match based on the "leads/{id:int}/statusfeedback" route template
also route template placeholders need to match parameter name in the action
[Route("leads/{id:int}/statusfeedback", Name = "GetStatusFeedback")]
[HttpPost]
public void Accept(int id) {
}
Finally you should use proper route names that help describe the route.
It took me a while to get what I needed from the accepted answer (and my mistake differed from the OP), here is a simplified version based on Nkosi's answer and OP's comment:
public class ValuesController : ApiController
{
[HttpGet]
public IHttpActionResult Get()
{
string url = Url.Link("GetStatusFeedback", new { Id = 1, Action = "Accept", Controller = "values"});
/*or*/ url = Url.Link(nameof(Accept), new { Id = 1, Action = "Accept", Controller = "values"});
//not url = Url.Link(nameof(Accept), new { LeadId = 1, Action = "Accept", Controller = "values"});
return Ok(url);
}
// Controller we want to get the URL of:
[HttpPost("leads/{id:int}/statusfeedback", Name = "GetStatusFeedback")]
public void Accept(int id) //param name matches the line above {id} (in OP it mismatched)
{
}
I'm new to Web Api (I'm probably missing something very straightforward here) I have a Web Api project with ProductsController.cs that has a property of type List<Product> and I simply want to call the Api in the browser eg localhost/api/products/1 or /api/products/getproduct/1 to retrieve the product response for the specified Id in the url but I cannot get it to retrieve any data. I get a 'not found' error each time. What am I missing to make it find the data and retrieve the response?
I have tried the following:
public IHttpActionResult Get(int id)
{
var product = products.FirstOrDefault(p => p.Id == id);
if (product == null)
{
return NotFound();
}
else
{
return Ok(product);
}
}
And even the following which still returns not found:
public string Get(int id)
{
return "product test";
}
Make sure the the routing is configured properly
WebApiConfig.cs
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
From there you have two options of routing to the action.
Convention-based.
public class ProductsController : ApiController {
//...constructor code removed for brevity
[HttpGet] // Matches GET api/products
public IHttpActionResult GetAllProducts() {
return Ok(products);
}
[HttpGet] // Matches GET api/products/1
public IHttpActionResult GetProduct(int id) {
var product = products.FirstOrDefault(p => p.Id == id);
if (product == null) {
return NotFound();
}
return Ok(product);
}
}
or Attribute routing
[RoutePrefix("api/products")]
public class ProductsController : ApiController {
//...constructor code removed for brevity
[HttpGet]
[Route("")] // Matches GET api/products
public IHttpActionResult GetAllProducts() {
return Ok(products);
}
[HttpGet]
[Route("{id:int}")] // Matches GET api/products/1
public IHttpActionResult GetProduct(int id) {
var product = products.FirstOrDefault(p => p.Id == id);
if (product == null) {
return NotFound();
}
return Ok(product);
}
}
I am new in web api.
i am sure i am doing something wrong for which my action is not getting called.
this is my action
public IEnumerable<Customer> GetCustomersByCountry(string country)
{
return repository.GetAll().Where(
c => string.Equals(c.Country, country, StringComparison.OrdinalIgnoreCase));
}
when i am calling this action this way http://localhost:38762/api/customer/GetCustomersByCountry/Germany
the error is thrown, and error message is
{"Message":"No HTTP resource was found that matches the request URI
'http://localhost:38762/api/customer/GetCustomersByCountry/Germany'.","MessageDetail":"No
action was found on the controller 'Customer' that matches the
request."}
tell me where i made the mistake ? thanks
Web config routes are
config.Routes.MapHttpRoute(
name: "WithActionApi",
routeTemplate: "api/{controller}/{action}/{customerID}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
EDIT : Full code added
public class CustomerController : ApiController
{
static readonly ICustomerRepository repository = new CustomerRepository();
public IEnumerable<Customer> GetAllCustomers()
{
return repository.GetAll();
}
public Customer GetCustomer(string customerID)
{
Customer customer = repository.Get(customerID);
if (customer == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return customer;
}
//[ActionName("GetCustomersByCountry")]
public IEnumerable<Customer> GetCustomersByCountry(string country)
{
return repository.GetAll().Where(
c => string.Equals(c.Country, country, StringComparison.OrdinalIgnoreCase));
}
public HttpResponseMessage PostCustomer(Customer customer)
{
customer = repository.Add(customer);
var response = Request.CreateResponse<Customer>(HttpStatusCode.Created, customer);
string uri = Url.Link("DefaultApi", new { customerID = customer.CustomerID });
response.Headers.Location = new Uri(uri);
return response;
}
public void PutProduct(string customerID, Customer customer)
{
customer.CustomerID = customerID;
if (!repository.Update(customer))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
public void DeleteProduct(string customerID)
{
Customer customer = repository.Get(customerID);
if (customer == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
repository.Remove(customerID);
}
}
just tell me when controller will have multiple get whose parameter name is different then how could i handle the situation.
thanks
Given CustomerController like
public class CustomerController : ApiController {
[HttpGet]
public IEnumerable<Customer> GetCustomersByCountry(string country) {
return repository.GetAll().Where(
c => string.Equals(c.Country, country, StringComparison.OrdinalIgnoreCase));
}
}
a convention-based route can look like this
config.Routes.MapHttpRoute(
name: "CustomerApi",
routeTemplate: "api/customer/{action}/{countryId}",
default: new { controller = "Customer"}
);
which will map http://localhost:38762/api/customer/GetCustomersByCountry/Germany
The problem with your route is that your parameter name in the route template does not match.
Another option could be to use attribute routing
Attribute Routing in ASP.NET Web API 2
[RoutePrefix("api/customer")]
public class CustomerController : ApiController {
//GET api/customer/country/germany
[HttpGet, Route("country/{country}")]
public IEnumerable<Customer> GetCustomersByCountry(string country) {
return repository.GetAll().Where(
c => string.Equals(c.Country, country, StringComparison.OrdinalIgnoreCase));
}
}
with this configuration
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
Remove "Get" from the action in the url. Just keep CustomersByCountry instead of GetCustomersByCountry. So the url should be http://localhost:38762/api/customer/CustomersByCountry/Germany.
I have a MVC 4 project which consumes services from a web api 1 project .
Here i need to have a service method where i pass an id and a string named action based on the action
the service should go and fetch data from the table. Here i will have different cases based on the actions.
So if my action is person it should go to person table and based on the id passed it should return LIST
IF action is email it should fetch data from the Email table based on the id passed and should return LIST
Is it possible to achieve from single method as my return type will be different in each cases? If so what will be my return type of the method?
public Email GetEmail(int id)
{
Email email = db.Emails.Find(id);
if (email == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return email;
}
public List<Email> GetEmailByPerson(int personid)
{
List<Email> email = db.Emails.Where(n => n.PersonID == personid).ToList();
if (email == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return email;
}
public Person GetPerson(int id)
{
Person person = db.Persons.Find(id);
return person;
}
My get service call always call the same method
Modified as below based on the comments
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 }
);
Code for controller action is:
[ActionName=EmailsByPersonID]
public IEnumerable<Email> GetEmailsByPersonID(int personid)
{
var emails = db.Emails.Where(n => n.Personid == personid).ToList();
if (emails == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return emails.AsEnumerable();
}
I have made these changes in the web api.config file and decorated my method with action name : EmailByPerson and the service call is http://localhost:XXXX/ActionApi/Email/EmailsByPersonID/1
I don't like this approach, but you don't ask us to make opinions about it but a specific question. And the answer to the question is YES it's possible.
You can use HttpResponseMessage for this purpose:
public HttpResponseMessage GetXx(string type, int id)
{
switch(type)
{
case "xx":
Type1 obj1 = <your logic>;
return Request.CreateResponse(HttpStatusCode.OK, obj1);
case "yy":
....
}
with this template -
public class MyController : ApiController
{
public string GetName(string id)
{
return id;
}
public string GetNameById(string id)
{
return id;
}
}
GlobalConfiguration.Configuration.Routes.MapHttpRoute
("default","api/{controller}/{action}/{id}",new { id = RouteParameter.Optional });
then make the calls to api like -
http://localhost:port/api/My/GetName/12
http://localhost:port/api/My/GetNameById/12
works for me atleast. :)
UPDATE
You could also do it like this -
public class CustomActionInvoker : ApiControllerActionSelector
{
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
var routeData = (string)controllerContext.RouteData.Values["optional"];
if (!string.IsNullOrWhiteSpace(routeData))
{
var actionInfo = controllerContext.ControllerDescriptor.ControllerType
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).ToList();
var methodInfo = actionInfo.Where(a => a.Name.Contains(routeData)).FirstOrDefault();
if (methodInfo != null)
{
return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, methodInfo);
}
}
return base.SelectAction(controllerContext);
}
}
In the config -
GlobalConfiguration.Configuration.Routes.MapHttpRoute("default", "api/{controller}/{optional}/{id}", new { id = RouteParameter.Optional });
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpActionSelector), new CustomActionInvoker());
And change the api call to -
http://localhost:port/api/My/Email/12
May be the previous example follows exactly this approach out of the box.
There are several ways to fix this.
1. You can use [RoutePrefix("api/Service")] for your controller and [Route("User")] and [Route("Email")] ,
you should be able too call your web api
api/service/user (GET,POST,PUT,Delete) ,
same thing goes with your Email as well
2. you can create IModel/IResult/SuperClass for your User/Email, and your web api method would be like IEnumerable<IModel> Get(string entityType) or
IModel Get(string entityType,int id)
Hope this will work.