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.
Related
my project contains several WebApi controllers and each of them provides usually three actions: get(guid), post(data) and delete(guid),
A default route is described in the WebApiconfig for this requirement. (name: ControllerAndId)
Now I have to implement a controller which has to handle different post actions. For this requirement I tried to map another route with ActionNames. (name: ControllerAndActionAndId)
Since I have mapped the ControllerAndActionAndId route it is not possible to call the delete route of the "normal" controller (example: Contactscontroller). All routes are working except the delete routes.
StatusCode: 404, ReasonPhrase: 'Not Found'
There is an example of an usually ApiController:
public class ContactsController : ApiController
{
public IEnumerable<Contact> Get()
{
return GetContacts();
}
public HttpResponseMessage Post(Contact contact)
{
SaveContact(contact);
return Request.CreateResponse<Guid>(_code, contact.Id);
}
public void Delete(Guid id)
{
DeleteContact(id);
}
}
Controller with ActionName-Route:
public class AttachmentsController : ApiController
{
[HttpGet]
public Attachment Get(Guid attachmentId)
{
return GetAttachment(attachmentId);
}
[HttpPost]
[ActionName("save")]
public HttpResponseMessage Save(AttachmentSaveData saveData)
{
SaveAttachment(saveData);
}
[HttpPost]
[ActionName("remove")]
public HttpResponseMessage Remove(AttachmentDeleteData deleteData)
{
DeleteAttachment(deleteData);
}
}
WebApiConfig:
// Web API routes
config.MapHttpAttributeRoutes();
// Controller with ID
// To handle routes like `/api/VTRouting/route/1`
config.Routes.MapHttpRoute(
name: "ControllerAndActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional,
action = RouteParameter.Optional
}
);
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
ClientAction delete function:
private void Delete(string uri, int id)
{
using (HttpClient _client = new HttpClient())
{
_client.BaseAddress = BaseAddress;
string _url = string.Format("{0}/{1}", uri, id);
var _response = _client.DeleteAsync(_url).Result;
if (!_response.IsSuccessStatusCode)
{
throw new Exception();
}
}
}
I currently have no further idea how to solve this problem.
If you use Web API, you need add HTTP verb to action.
For example, your code must be as below:
public class ContactsController : ApiController
{
[HttpGet]
public IEnumerable<Contact> Get()
{
return GetContacts();
}
[HttpPost]
public HttpResponseMessage Post(Contact contact)
{
SaveContact(contact);
return Request.CreateResponse<Guid>(_code, contact.Id);
}
[HttpDelete]
public void Delete(Guid id)
{
DeleteContact(id);
}
}
Pay attention to Delete action.
If you use HttpDelete verb on action, you must send delete request from your client httpClient.DeleteAsync(...).
If you use HttpPost verb on action, you must send post request from your client httpClient.PostAsync(...).
AttachmentsController is similar to ContactsController.
I was focused to much on actions and routes of the controller.
But the solution was easy to find at client side:
private void Delete<T>(string uri, T value)
{
using (HttpClient _client = new HttpClient())
{
_client.BaseAddress = BaseAddress;
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string _url = string.Format("{0}/{1}", uri, value);
var _response = _client.DeleteAsync(_url).Result;
}
}
This solution requires only one route in WebApiConfig:
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
Soo easy.. thanks a lot !
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 just started working with ApiController. I'm trying to do an HTTP GET sending an ID, but it is not working.
My ApiController:
[Route("api/Test")]
public class TestController : ApiController
{
private myEntity db = new myEntity();
[HttpGet]
public HttpResponseMessage GetAll()
{
// Get a list of customers
IEnumerable<Customer> customers = db.Customers.ToList();
// Write the list of customers to the response body
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, customers);
return response;
}
[HttpGet]
public HttpResponseMessage GetById(int id)
{
// Get Customer by id
Customer customer = db.Customers.Where(x => x.Id == id).FirstOrDefault();
HttpResponseMessage response;
if (customer == null)
{
response = Request.CreateResponse(HttpStatusCode.NotFound);
return response;
} else
{
response = Request.CreateResponse(HttpStatusCode.OK, customer);
}
return response;
}
When I run it in the browser, the GetAll method work perfectly. However, when I try GetById:
http://localhost:53198/api/Test/1
It returns:
No HTTP resource was found that matches the request URI http://localhost:53198/api/Test/1
Does anyone know what I am doing wrong?
If using attribute routing you would need to make a few changes to make sure that action routes are distinct to avoid any route conflicts.
[RoutePrefix("api/Test")]
public class TestController : ApiController {
private myEntity db = new myEntity();
//GET api/Test
[HttpGet]
[Route("")]
public IHttpActionResult GetAll() {
// Get a list of customers
var customers = db.Customers.ToList();
// Write the list of customers to the response body
return OK(customers);
}
//GET api/Test/1
[HttpGet]
[Route("{id:int}")]
public IHttpActionResult GetById(int id) {
// Get Customer by id
Customer customer = db.Customers.Where(x => x.Id == id).FirstOrDefault();
if (customer == null) {
return NotFound();
}
return Ok(customer);
}
}
This assumes that attribute routing is enabled
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 }
);
}
}
Reference Attribute Routing in ASP.NET Web API 2
You can do either
http://localhost:53198/api/Test/GetById/1 (as DavidG mentioned)
or
http://localhost:53198/api/Test/1
and changing your code to
[HttpGet]
[Route("{id:int}")]
public HttpResponseMessage GetById(int id)
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 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.