WebApi routing - many POST methods - asp.net-web-api

I have WebAPI 2 application. How can I specify 2 or more POST methods?
I have the following WebApiConfig:
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and API Controller:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
[Route("Post1")]
[HttpPost]
public IQueryable<string> Post1(string str)
{
return null;
}
[Route("Post2")]
[HttpPost]
public IQueryable<string> Post2(int id)
{
return null;
}
}
It works neither I call:
/api/books/post1
nor
/api/books/post2
why and how to solve it?
UPDATE:
Problem is solved, problem was in simple types as parameters. I get 404 error
Message=No HTTP resource was found that matches the request URI
'http://localhost:37406/api/books/post1'.
with request:
POST http://localhost:37406/api/books/post1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:35979
Content-Type: application/json; charset=utf-8
{
"str" : "Fffff"
}
and code:
[Route("Post1")]
[HttpPost]
public HttpResponseMessage Post1(string str)
{
return Request.CreateResponse();
}
[Route("Post2")]
[HttpPost]
public HttpResponseMessage Post2(int id)
{
return Request.CreateResponse();
}
but it works fine with complex type:
[HttpPost]
[Route("Post1")]
public HttpResponseMessage Post1(Book book)
{
return Request.CreateResponse();
}
[HttpPost]
[Route("Post2")]
public HttpResponseMessage Post2(Book book)
{
return Request.CreateResponse();
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Genre { get; set; }
}
Thank you Nkosi
UPDATE 2:
but it works when parameter is marked with [FromBody]
[Route("Post1")]
[HttpPost]
public HttpResponseMessage Post1([FromBody]string str)
{
return Request.CreateResponse();
}
[Route("Post2")]
[HttpPost]
public HttpResponseMessage Post2([FromBody]int id)
{
return Request.CreateResponse();
}
(for complex types it's unnecessary). Logically, but Route error confused :)

Excerpt taken from Attribute Routing in ASP.NET Web API 2
HTTP Methods
Web API also selects actions based on the HTTP method of the request
(GET, POST, etc). By default, Web API looks for a case-insensitive
match with the start of the controller method name. For example, a
controller method named PutCustomers matches an HTTP PUT request.
You can override this convention by decorating the mathod with any the
following attributes:
[HttpDelete]
[HttpGet]
[HttpHead]
[HttpOptions]
[HttpPatch]
[HttpPost]
[HttpPut]
The following example maps the CreateBook method to HTTP POST requests.
[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }
Example:
public class Book {
public int BookId{get;set;}
public string Title{get;set;}
public string Author{get;set;}
public string Genre{get;set;}
}
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/books
[Route("")]
public IEnumerable<Book> Get() { ... }
// GET api/books/5
[Route("{id:int}")]
public Book Get(int id) { ... }
// POST api/books
[HttpPost]
[Route("")]
public HttpResponseMessage Post1(Book book) { ... }
// POST api/books/alternate
[HttpPost]
[Route("alternate")]
public HttpResponseMessage Post2(Book book) { ... }
}
Sample POST Body for Post1
POST http://localhost:35979/api/books HTTP/1.1
User-Agent: Fiddler
Host: localhost:35979
Content-Type: application/json; charset=utf-8
Content-Length: 80
{
"Title":"Scary Book",
"Author":"John Doe",
"Genre":"Horror"
}
Sample POST Body for Post2
POST http://localhost:35979/api/books/alternate HTTP/1.1
User-Agent: Fiddler
Host: localhost:35979
Content-Type: application/json; charset=utf-8
Content-Length: 85
{
"Title":"Fantastic Book",
"Author":"Jane Doe",
"Genre":"Fantasy"
}

Related

ASP.NET Core WebApi : AmbiguousMatchException: The request matched multiple endpoints

I've this controller code in a standard .NET Core WebApi project:
[ApiController]
[Route("[controller]")]
public class MessageController : ControllerBase
{
[HttpGet]
public string GetByTransactionId([FromRoute(Name = "transactionId")] string transactionId)
{
return "transactionId";
}
[HttpGet]
public string GetByNumber([FromRoute(Name = "number")] string number)
{
return "number";
}
}
And I want to access the api like:
https://localhost:44316/Message?transactionId=abc
https://localhost:44316/Message?number=foobar
When accessing the endpoint-url like: https://localhost:44316/Message?transactionId=abc, I get error like:
AmbiguousMatchException: The request matched multiple endpoints. Matches:
Server.Controllers.MessageController.GetByTransactionId (Server)
Server.Controllers.MessageController.GetByNumber (Server)
Can this be solved by using the correct with [HttpGet] or [Route] attributes?
[ApiController]
[Route("[Message]")]
public class MessageController : ControllerBase
{
[HttpGet]
[Route("transactionId")]
public string GetByTransactionId([FromRoute(Name = "transactionId")] string transactionId)
{
return "transactionId";
}
[HttpGet]
[Route("number")]
public string GetByNumber([FromRoute(Name = "number")] string number)
{
return "number";
}
}
Try with Route-attribute . this will sort out your problem

How do I route to a method in ASP.NET Web API?

According to the documentation, if I have this in my WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I should be able to route to a method in my API controller using a URL like this:
http://localhost:55601/api/Customers/Search
Here is my method:
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult Search([FromBody]CustomerDTO SearchTerm)
{
string Name = SearchTerm.Name;
string Email = SearchTerm.Email;
string PhoneNumber = SearchTerm.Phone;
var customer = db.Customers.Single(c => c.Name == Name && c.EmailAddress == Email && c.PhoneNumber == PhoneNumber);
return Ok(customer.id);
}
I'm sending the search data as a JSON object (using HTTP POST method) in the request body.
However, I get an error saying:
Multiple actions were found that match the request
I only have one method in this controller called Search.
I would have thought this should be pretty straightforward, and work the same way it does with MVC controllers. But I think I'm missing something obvious. Can anyone tell me what it is?
EDIT: As per #KevinLaw's request, adding code for controller showing upblic methods. Also, for further information the following request (HTTP GET) works as expected:
http://localhost:55601/api/Customers?email=[recipient#domain]
public class CustomersController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: api/Customers
public IQueryable<Customer> GetCustomers()
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomerByEmail(string email)
{
//...
}
// PUT: api/Customers/5
[ResponseType(typeof(void))]
public IHttpActionResult PutCustomer(int id, Customer customer)
{
//...
}
// POST: api/Customers
[ResponseType(typeof(Customer))]
public IHttpActionResult PostCustomer(Customer customer)
{
//...
}
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult SearchCustomer([FromBody]CustomerDTO SearchTerm)
{
//...
}
// DELETE: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult DeleteCustomer(int id)
{
//...
}
}
The problem here is that the WebApiController uses the REST API specs.
Which state that in a Web Api Controller there can be Zero - One Http Verb.
What i mean by that is that you can have one GET,PUT,POST,DELETE,PATCH
The reason you don't have any problem with the GET is because you have them correctly overloaded. () (int) (string).
But your Posts is (Customer) (CustomerDTO) They are both complex objects and the Binder cannot identify which is which when binding to the complex object.
For this to work you need to use Route Attributes or explicit route.
Attribute Routing
Explicit Routing pt1
Explicit Routing pt2
I think the links are enough to get you started.
If you still want to see some code on your specific case leave a comment below and i will give you some examples.
Thanks
EDIT: Added Examples
Attribute Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
}
On Controller
[RoutePrefix("api/test")]
public class TestingController : ApiController
{
[HttpGet]
[Route("")]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
[Route("search")]
public IHttpActionResult Post([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}
Explicit Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes config.Routes.MapHttpRoute(
name: "SearchTest",
routeTemplate: "api/test/search",
defaults: new { controller = "Testing", action = "Search" }
);
config.Routes.MapHttpRoute(
name: "TestingController",
routeTemplate: "api/test/{id}",
defaults: new { controller = "Testing", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
On Controller
public class TestingController : ApiController
{
[HttpGet]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
public IHttpActionResult Search([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}

nohttp resource was found that matches the request uri

I'm trying to develop an api that allows both get and post requests at the same address.
public class DataController : ApiController
{
[HttpGet]
public DataResponse Foo()
{
return GetNext();
}
[HttpPost]
public void Foo(long p1, string p2)
{
SaveValue(p1,p2);
}
}
GET works fine. When calling the POST method, I get the following error:
the requested resource does not support http method 'POST'
My WebApiConfig looks like the following:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors();
config.MapHttpAttributeRoutes();
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.Routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{action}");
}
}
Any idea what I need to change to get this working?
You can do this in two ways-
1- Change the way you are calling API i.e - pass values in url like <url>?p1=value&p2=value
2- Change the signature of the Action in Web API to
public void Foo([FromBody] MyContract data)
where MyContract is a class with two properties
public class MyContract
{
public long p1 { get; set; }
public string p2 { get; set; }
}
This is because by default parameter binding is done through URL and you are passing them through body.

Post data through Url to webapi

Hej,
I am unable to post data to the action method through querystring to the action method which is located in the controller class below is my code.
I type a url "http://localhost:53459/api/esb/post/test" to post value and nothing happens
Any help would be appreciated.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{value}",
defaults: new { value = RouteParameter.Optional }
);
}
}
[RoutePrefix("api/esb")]
public class EsbController : ApiController
{
[Route("get")]
[HttpGet]
public string Get()
{
return "Hello there!";
}
[Route("post")]
[HttpPost]
[AcceptVerbs("POST")]
public string Post([FromUri]string value)
{
return string.Format("{0} is posted successfully ", value);
}
[Route("put")]
[HttpPut]
public string Put([FromUri] string value)
{
return string.Format("{0} is updated successfully ", value);
}
[Route("delete")]
[HttpDelete]
public string Delete(string value)
{
return string.Format("{0} is deleted successfully ", value);
}
}
If you are typing the url into a browser you are constructing a GET request so it will never reach your Post action. You can confirm this by adding "GET" to the allowed verbs on the action (note: remove the [HttpPost] attribute).
[Route("post")]
[AcceptVerbs("POST", "GET")]
public string Post([FromUri]string value)
{
return string.Format("{0} is posted successfully ", value);
}
remove parameter binding [FromUri] and update the Route like
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{value}",
defaults: new { value = RouteParameter.Optional }
);
}
}
Controller
[RoutePrefix("api/esb")]
public class EsbController : ApiController
{
[Route("get")]
[HttpGet]
public string Get()
{
return "Hello there!";
}
[Route("post/{value}")]
[HttpPost]
[AcceptVerbs("POST")]
public string Post(string value)
{
return string.Format("{0} is posted successfully ", value);
}
}
This is working for me, try to use Postman in chrome or fiddler.
POST http://localhost:XXXXX/api/esb/post/test HTTP/1.1
For dot.Net Core (I'm using v2.0), use FromRoute
[AllowAnonymous]
[HttpPost]
[Route("validateEmail/{validationCode}")]
public async Task<IActionResult> ValidateEmail(
[FromRoute] Guid validationCode)
{
await authService.ValidateEmailAsync(validationCode);
return Ok();
}
Then post like this:

POST JSON with MVC 4 API Controller

I have this code:
$.ajax({
type: "POST",
url: "/api/slide",
cache: false,
contentType: "application/json; charset=utf-8",
data: '{"Title":"fghfdhgfdgfd"}',
dataType: "json",
An this is my controler:
public class SlideController : ApiController
{
// POST /api/Slide
public void Post(string Title)
{
}
When I run the code and call the /api/Slide, the [Title] has no data and is null.
How do I post JSON to the API controller?
POST http://127.0.0.2:81/api/slide HTTP/1.1
Host: 127.0.0.2:81
Connection: keep-alive
Content-Length: 18
Origin: http://127.0.0.2:81
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1
Content-Type: application/json; charset=UTF-8
Accept: application/json, text/javascript, */*; q=0.01
Referer: http://127.0.0.2:81/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Title=fghfdhgfdgfd
Define a view model:
public class SlideViewModel
{
public string Title { get; set; }
}
then have your controller action take this view model as argument:
public class SlideController : ApiController
{
// POST /api/Slide
public void Post(SlideViewModel model)
{
...
}
}
finally invoke the action:
$.ajax({
type: 'POST',
url: '/api/slide',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ title: "fghfdhgfdgfd" }),
success: function() {
...
}
});
The reason for that is that simple types such as strings are bound from the URI. I also invite you to read the following article about model binding in the Web API.
Ensure the object you are trying to convert to has a default (empty) constructor.
Rule of thumb: If you want to deserialize to an object, you need to make it simple for the objects to be created. These guidelines can help:
All properties that are to be passed around must be public
the object needs to able to be constructed without any parameters.
This JSON string/object for example:
{ Name: "John Doe", Phone: "123-456-7890", Pets: [ "dog", "cat", "snake" ] }
can be converted to an object from the following class:
public class Person {
public string Name { get; set; }
public string Phone { get; set; }
public string[] Pets { get; set; }
}
or this one:
public class Person {
public string Name { get; set; }
public string Phone { get; set; }
public string[] Pets { get; set; }
public Person() {}
public Person(string name, string phone) {
Name = name;
Phone = phone;
}
}
or this one:
public class Person {
public string Name { get; set; }
public string Phone { get; set; }
public string[] Pets { get; set; }
public Person() {}
}
but not this one
public class Person {
public string Name { get; set; }
public string Phone { get; set; }
public string[] Pets { get; set; }
public Person(string name, string phone) {
Name = name;
Phone = phone;
}
}
Now let ASP.NET MVC 4 do the rest
public class PersonController : ApiController
{
// .. other actions
public HttpResponseMessage PostPerson(Person person)
{
if ( null != person)
// CELEBRATE by doing something with your object
else
// BE SAD and throw and exception or pass an error message
}
// .. other actions
}
If your class cannot have a default constructor or if you don't have access to the source code for the class, you can create an adapter class that
has a default constructor
exposes those properties that need to be public
Using the Person class above with no default constructor, an adapter could look like
public class PersonAdapter {
public Person personAdaptee;
public string Name {
get { return personAdaptee.Name; }
set { personAdaptee.Name = value }
}
public string Phone {
get { return personModel.Phone; }
set { personModel.Phone = value; }
}
public string[] Pets {
get { return personAdaptee.Pets; }
set {personAdaptee.Pets = value }
}
public PersonAdapter() {
personAdaptee = new Person("", "", null);
}
}
Now let ASP.NET MVC 4 do the rest
public class PersonController : ApiController
{
// .. other actions
public HttpResponseMessage PostPerson(PersonAdapter person)
{
if ( null != person)
// CELEBRATE by doing something with your object
else
// BE SAD and throw and exception or pass an error message
}
// .. other actions
}
Try this:
$.ajax({
type: "POST",
url: "/api/slide",
data: { Title: "fghfdhgfdgfd" }
});
It is the quotes around the data attribute which are causing this:
i.e >> data: { Title: "fghfdhgfdgfd" }
not >> data: '{ Title: "fghfdhgfdgfd" }'
UPDATE:
Also your controller seems a little strange, although it is hard to tell without seeing your routing, etc.
I would expect to see something more like this:
public class SlideController : ApiController
{
public HttpResponseMessage PostSlide(string Title)
{
// Do your insert slide stuff here....
string uri = Url.Link("DefaultApi", new { id = item.Id });
response.Headers.Location = new Uri(uri);
return response;
}
}
Clearly, you will also need to update the URL in your jQuery too.
Take a look here:
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
ANOTHER UPDATE:
It would be usual to create a CLR object to match your Json and use the MVC model binder to bind directly to that. If you don't want to do that you can bind to an object and deserialize into a Dictionary:
// POST api/values
public void Post(object json)
{
Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json.ToString());
var x = values["Title"];
}
Cast the action parameter to FromBody i.e:
public class SlideController : ApiController
{
// POST /api/Slide
public void Post([FromBody]string Title)
{
}
}
$.ajax({
type: 'POST',
url: '/api/slide',
cache: false,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ title: "fghfdhgfdgfd" }),
success: function() {
...
}
});
Controller is
public class SlideController : ApiController
{
// POST /api/Slide
public void Post(string Title)
{
}
Your url is not valid, url must address the action Post in Slide controller
edit your url to url:"~/ControllerName/ActionName" in this context must be Url:"~/Slide/Post"

Resources