webapi post method with parameters doesn't work - c#

When I call my webAPI controller that contains a post method with NO parameters it goes to the method. However, when I pass parameters (and when I update the api controller with paramters as well) into this see the snippet below the 1st snippet I get the 405 error that it doesn't support POST.
var captchURL = "/api/Captcha";
$.ajax({
url: captchURL,
dataType: 'json',
contentType: 'application/json',
type: 'POST'
})
var jsondata = {solution: "7", answer: "7"};
var captchURL = "/api/Captcha";
$.ajax({
url: captchURL,
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(jsondata)
})
UPDATE - Controller Code:
public class CaptchaController : ApiController
{
private readonly ICaptchaService _service;
public CaptchaController(ICaptchaService service)
{
_service = service;
}
public Captcha Get()
{
return _service.Get();
}
[HttpPost]
public bool Post(string solution, string answer)
{
return _service.Post();
}
}
UPDATE - WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Is it because I don't have the solution and answer params (in my WebApiConfig) that it doesn't recognize them?
What am I doing incorrectly?

This is a slightly different method of setting the route, but I prefer it.
In your controller code add a route prefix and routes for each method that will represent a POST of GET request...
[RoutPrefix("Captcha")]
public class CaptchaController : ApiController
{
[Route("Post/{solution}/{answer}")]
[HttpPost]
public bool Post(string solution, string answer)
{
return _service.Post();
}
}
This should work as long as you are setting the path correctly, using the correctly typed parameters, and returning a correctly typed value. If you use a Model then you do not have to add the parameters into the Route path.
This worked for me when I was setting up my WebAPI. If anyone sees anything wrong with explanation please let me know. I am still learning (and always will be), but just wanted to let you know what I did that worked.

create a model
public class Captcha {
public string solution { get; set; }
public string answer { get; set; }
}
the controller is this
[HttpPost]
public string Post([FromBody] Captcha cap)
{
return cap.solution + " - " + cap.answer;
}

Add another MapHttpRoute which will accept 'solution' and 'answer' as parameters
sample :
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{solution}/{answer}",
defaults: new { solution = RouteParameter.Optional, answer= RouteParameter.Optional }
);

[HttpPost]
public bool Post(Captcha cap)
{
return _service.Post();
}
And
Change data: JSON.stringify(jsondata) to data : jsondata .

The Reason it doesn't work is because there is not route that will accept 2 parameter at your [post] address - api/captcha/.
you have 2 options, either set up attribute routing in controller as
[Route("Post/{solution}/{answer}")]
[HttpPost]
public bool Post(string solution, string answer)
{
return _service.Post();
}
or create a model as
Public class CaptchaModel
{
Public string Solution {get; set;}
Public string Answer {get; set;}
}
And in your action method
[HttpPost]
public bool Post([FromBody] CaptchaModel model)
{
var solution = model.solution;
var answer = model.answer;
.........
return _service.Post();
}

According to www.asp.net,
"when an HTTP method is configured for use on the server, but it has been disabled for a given URI, the server will respond with an HTTP 405 Method Not Allowed error."
Further reference on 405
http://www.asp.net/web-api/overview/testing-and-debugging/troubleshooting-http-405-errors-after-publishing-web-api-applications

You must remove JSON.stringify(data) from your request and then add [FromBody] in front your model. Then it will work.

Related

Simple POST with ASP.NET Framework

Here is my problem, I want to receive the Username and Password of the client with POST request.
The code seems simple but doesn't work
The LoginController.cs :
public class LoginController : ApiController
{
[HttpPost]
[ActionName("Login")]
[Route("api/{controller}")]
public HttpResponseMessage Login([FromBody] LoginJson json)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
The LoginJson.cs form :
public class LoginJson
{
public string Username { get; set; }
public string Password { get; set; }
}
The ajax request with jQuery, i don't want to change the url because i want to use 3 urls /api/Login, /api/Method1 and /api/Method2 for 3 different controllers:
$.ajax({
url: '/api/Login',
type: 'POST',
dataType: "json",
contentType: "application/json, charset=utf-8",
data: JSON.stringify({
Username: username,
Password: password,
}),
...
});
The route for API, in Global.asax.cs :
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new HandleErrorAttribute());
RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
RouteTable.Routes.MapRoute(...)
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}",
defaults: new {action = RouteParameter.Optional}
);
}
I get error 404 (Not Found).
I will change the Global.asax.cs file.
You are only calling the controller with no action and since there is no default action defined you'll get a 404 error.
In the jQuery you can do:
url: '/api/Login/Login'
Or change the routing by either putting this tag:
[Route("api/login")]
Or in the RouteConfig.cs, this should be done before the other routes are set including the generic one.
routes.MapRoute("Login", "Login/{action}",
defaults: new { controller = "Login", action = "Login" });
There is no api/login
You can do like that:
[ActionName("api/login")]
public HttpResponseMessage Login([FromBody] LoginJson json)
{
return Request.CreateResponse(HttpStatusCode.OK);
}

How to pass multiple type of parameters in web api from angularjs?

I am struggling pretty badly in passing parameters to my web api.
There are two case I will list them both.
I want to pass a simple string param like this below
[HttpGet]
public string VerifyName(string name)
{
return name + "hi";
}
For which I am creating a url like this in my angularjs controller.
var name = "hello";
var msg = "";
$http.get('/api/VisitorWeb/VerifyName', name).success(function (data) {
msg = data;
}).error(function (data) {
$scope.error = "An error has occured while adding! " + data;
});
This keeps returning 404. Also
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:43516/api/VisitorWeb/VerifyName'.","MessageDetail":"No action was found on the controller 'VisitorWeb' that matches the request."}
Similarly when I am trying to pass a object it is giving the same result
My angular function
var loginModel = {
UserName: $scope.UserName,
PassWord: $scope.Password
};
var msg = "";
$http.get('/api/VisitorWeb/VerifyLogin', loginModel).success(function (data) {
msg = data;
}).error(function (data) {
$scope.error = "An error has occured while adding! " + data;
});
Web Api Method
[HttpGet]
public string VerifyLogin(UserLoginDomainModel loginModel)
{
//do some business logic
return "abc ";
}
Response is 404.
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
I am thinking there is some problem with the routing that is basic but some how not able to figure out what it is , Please suggest
This action:
[HttpGet]
public string VerifyName(string name)
{
return name + "hi";
}
defined without attribute routing is mapped to the standard convention routing into the following URL:
http://yourhost/apiVisitorWeb/VerifyName?name=myNameValue
Because your route template "api/{controller}/{action}/{id}" expects a parameter named id (not name) to be part of the URI.
If you want to keep using your routing as is either use the above URI or change the name of your parameter into id, so you should be able to use this address:
http://yourhost/apiVisitorWeb/VerifyName/myNameValue
The same thing applies to your complex type parameters: in GET actions any complex parameter is expected to be bound from the URI as a collection of query string parameters. Your second action will bind to the following URI:
http://yourhost/apiVisitorWeb/VerifyLogin?UserName=userNameValue&PassWord=myPassword
But this is a bad practice for many reasons (security on the top). I strongly suggest you to turn this kind of action into a POST action, so you can send your model as the body of the request.
Give this a try:
$http({ method: "GET", url: "/api/VisitorWeb/VerifyName", params: { name: name} }).success()

BaseController input param Object results in Multiple actions were found that match the request

I've got this intresting situation where I have a baseController for webApi and have tracked the issue down to this. If I have a base controller that has an input parameter type Object it will end up with http 500 Multiple actions where found...
And this is in a default empty mvc 4.0 project for vs 2012 using IISExpress
public class BaseSecureApi : ApiController
{
public string Wtf(object ohMyGodThisIsSilly)
{
return null;
}
}
And the controller
public class Default1Controller : BaseSecureApi
{
// GET api/default1
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// POST api/default1
[HttpPost]
public string Post([FromBody]string value)
{
return "Postmyvalue:" + value;
}
}
And a route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Now why is the routing going haywire.
IRL the base class returns a httpResponse based on who is logged on and if the result should be encrypted or not.
From the routing-and-action-selection article on the ASP.NET site:
HTTP Methods. The framework only chooses actions that match the HTTP method of the request, determined as follows:
You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
If none of the above, the method supports POST.
This means that the Wtf method will support POST because it is a public method, with no attributes and it does not start with any of the conventional words.
If you make the Wtf method protected and it will not be called by the Web api, you can still call it from the methods of your derived classes where you need to.

Asp Web Api async action - 404 error

I've got some api controller with this action:
public class ProxyController : ApiController {
public async Task<HttpResponseMessage> PostActionAsync(string confirmKey)
{
return await Task<HttpResponseMessage>.Factory.StartNew( () =>
{
var result = GetSomeResult(confirmKey);
return Request.CreateResponse(HttpStatusCode.Created, result);
});
}
}
And here is my api routing confuguration:
routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
When I try to make any Post/Get Requests to this action, it's returns '404' error. How can I fix it? All other not-async actions in this controller works fine.
UPD. JS query:
$.ajax({
url: Url + '/api/Proxy/PostActionAsync',
type: 'POST',
data: { confirmKey: that.confirmKey },
dataType: 'json',
xhrFields: { withCredentials: true },
success: function (data) {
............
},
error: function (jqXHR, textStatus, errorThrown) {
............
}
});
UPD. Resolved by adding [FromBody] to my parameters in action method just like in J. Steen's answer, now it's look's like
public class ProxyController : ApiController {
public async Task<HttpResponseMessage> PostActionAsync([FromBody]string confirmKey)
{
var someModel = new SomeResultModel(User.Identity.Name);
await Task.Factory.StartNew(() => someModel.PrepareModel(confirmKey));
return Request.CreateResponse(HttpStatusCode.OK, someModel);
}
}
And it works!
The routing configuration for Web API works a little differently than MVC.
Try
routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
Note the missing {action} as that is resolved by Web API at call-time, automagically, depending on the HTTP verb you use for your request.
Consider this article on Web API routing which lists (as an example):
HTTP Method URI Path Action Parameter
GET api/products GetAllProducts (none)
GET api/products/4 GetProductById 4
DELETE api/products/4 DeleteProduct 4
In your case, the async version of an action is also resolved automatically.
POST api/products PostActionAsync (Post data)
Since we now know the controller name, requests would be like:
GET api/proxy
GET api/proxy/4
POST api/proxy (with post data)
Edit:
After additional research (brief, I admit) I have found the issue.
You need to add [FromBody] infront of your in-parameter.
public async Task<HttpResponseMessage> PostActionAsync([FromBody] string confirmKey)
This, combined with sending just the value (no wrapping json) works wonders. Set content type to "application/x-www-form-urlencoded" instead of json, and send your parameter as "=" + that.confirmKey.
Alternative:
If you don't want to fiddle around with content-types and prefixing =-signs, just send it as part of the querystring. Forget the [FromBody] and the rest. Call
/api/Proxy/PostActionAsync?confirmKey=' + that.confirmKey
Additional, exhaustive information in this blog.
Is that change possible?
public async Task<HttpResponseMessage> PostActionAsync()
{
var result = await GetSomeResult();
return Request.CreateResponse(HttpStatusCode.Created, result);
}

Multiple HttpPost method in Web API controller

I am starting to use MVC4 Web API project, I have controller with multiple HttpPost methods. The Controller looks like the following:
Controller
public class VTRoutingController : ApiController
{
[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
Here MyRequestTemplate represents the template class responsible for handling the Json coming through the request.
Error:
When I make a request using Fiddler for http://localhost:52370/api/VTRouting/TSPRoute or http://localhost:52370/api/VTRouting/Route I get an error:
Multiple actions were found that match the request
If I remove one of the above method it works fine.
Global.asax
I have tried modifying the default routing table in global.asax, but I am still getting the error, I think I have problem in defining routes in global.asax. Here is what I am doing in global.asax.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute(
name: "MyTSPRoute",
routeTemplate: "api/VTRouting/TSPRoute",
defaults: new { }
);
routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "api/VTRouting/Route",
defaults: new { action="Route" }
);
}
I am making the request in Fiddler using POST, passing json in RequestBody for MyRequestTemplate.
You can have multiple actions in a single controller.
For that you have to do the following two things.
First decorate actions with ActionName attribute like
[ActionName("route")]
public class VTRoutingController : ApiController
{
[ActionName("route")]
public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[ActionName("tspRoute")]
public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
Second define the following routes in WebApiConfig file.
// Controller Only
// To handle routes like `/api/VTRouting`
config.Routes.MapHttpRoute(
name: "ControllerOnly",
routeTemplate: "api/{controller}"
);
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
// Controllers with Actions
// To handle routes like `/api/VTRouting/route`
config.Routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}"
);
Another solution to your problem would be to use Route which lets you specify the route on the method by annotation:
[RoutePrefix("api/VTRouting")]
public class VTRoutingController : ApiController
{
[HttpPost]
[Route("Route")]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost]
[Route("TSPRoute")]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
use:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
it's not a RESTful approach anymore, but you can now call your actions by name (rather than let the Web API automatically determine one for you based on the verb) like this:
[POST] /api/VTRouting/TSPRoute
[POST] /api/VTRouting/Route
Contrary to popular belief, there is nothing wrong with this approach, and it's not abusing Web API. You can still leverage on all the awesome features of Web API (delegating handlers, content negotiation, mediatypeformatters and so on) - you just ditch the RESTful approach.
A web api endpoint (controller) is a single resource that accepts get/post/put/delete verbs. It is not a normal MVC controller.
Necessarily, at /api/VTRouting there can only be one HttpPost method that accepts the parameters you are sending. The function name does not matter, as long as you are decorating with the [http] stuff. I've never tried, though.
Edit: This does not work. In resolving, it seems to go by the number of parameters, not trying to model-bind to the type.
You can overload the functions to accept different parameters. I am pretty sure you would be OK if you declared it the way you do, but used different (incompatible) parameters to the methods. If the params are the same, you are out of luck as model binding won't know which one you meant.
[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}
[HttpPost]
public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}
This part works
The default template they give when you create a new one makes this pretty explicit, and I would say you should stick with this convention:
public class ValuesController : ApiController
{
// GET is overloaded here. one method takes a param, the other not.
// GET api/values
public IEnumerable<string> Get() { .. return new string[] ... }
// GET api/values/5
public string Get(int id) { return "hi there"; }
// POST api/values (OVERLOADED)
public void Post(string value) { ... }
public void Post(string value, string anotherValue) { ... }
// PUT api/values/5
public void Put(int id, string value) {}
// DELETE api/values/5
public void Delete(int id) {}
}
If you want to make one class that does many things, for ajax use, there is no big reason to not use a standard controller/action pattern. The only real difference is your method signatures aren't as pretty, and you have to wrap things in Json( returnValue) before you return them.
Edit:
Overloading works just fine when using the standard template (edited to include) when using simple types. I've gone and tested the other way too, with 2 custom objects with different signatures. Never could get it to work.
Binding with complex objects doesn't look "deep", so thats a no-go
You could get around this by passing an extra param, on the query string
A better writeup than I can give on available options
This worked for me in this case, see where it gets you. Exception for testing only.
public class NerdyController : ApiController
{
public void Post(string type, Obj o) {
throw new Exception("Type=" + type + ", o.Name=" + o.Name );
}
}
public class Obj {
public string Name { get; set; }
public string Age { get; set; }
}
And called like this form the console:
$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
It is Possible to add Multiple Get and Post methods in the same Web API Controller. Here default Route is Causing the Issue. Web API checks for Matching Route from Top to Bottom and Hence Your Default Route Matches for all Requests. As per default route only one Get and Post Method is possible in one controller. Either place the following code on top or Comment Out/Delete Default Route
config.Routes.MapHttpRoute("API Default",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
When creating another Http Method add [HttpPost("Description")]
[HttpPost("Method1")]
public DataType Method1(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost("Method2")]
public DataType Method2(MyRequestTemplate routingRequestTemplate){}
Put Route Prefix [RoutePrefix("api/Profiles")] at the controller level and put a route at action method [Route("LikeProfile")]. Don't need to change anything in global.asax file
namespace KhandalVipra.Controllers
{
[RoutePrefix("api/Profiles")]
public class ProfilesController : ApiController
{
// POST: api/Profiles/LikeProfile
[Authorize]
[HttpPost]
[Route("LikeProfile")]
[ResponseType(typeof(List<Like>))]
public async Task<IHttpActionResult> LikeProfile()
{
}
}
}
You can use this approach :
public class VTRoutingController : ApiController
{
[HttpPost("Route")]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost("TSPRoute")]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
I think the question has already been answered. I was also looking for something a webApi controller that has same signatured mehtods but different names. I was trying to implement the Calculator as WebApi. Calculator has 4 methods with the same signature but different names.
public class CalculatorController : ApiController
{
[HttpGet]
[ActionName("Add")]
public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Add = {0}", num1 + num2);
}
[HttpGet]
[ActionName("Sub")]
public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Subtract result = {0}", num1 - num2);
}
[HttpGet]
[ActionName("Mul")]
public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Multiplication result = {0}", num1 * num2);
}
[HttpGet]
[ActionName("Div")]
public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Division result = {0}", num1 / num2);
}
}
and in the WebApiConfig file you already have
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
Just set the authentication / authorisation on IIS and you are done!
Hope this helps!
Best and simplest explanation I have seen on this topic -
http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx
Edited -
I got it working with only Route, and did not need RoutePrefix.
For example, in the controller
[HttpPost]
[Route("[action]")]
public IActionResult PostCustomer
([FromBody]CustomerOrder obj)
{
}
and
[HttpPost]
[Route("[action]")]
public IActionResult PostCustomerAndOrder
([FromBody]CustomerOrder obj)
{
}
Then, the function name goes in jquery as either -
options.url = "/api/customer/PostCustomer";
or
options.url = "/api/customer/PostCustomerAndOrder";
I am using .Net6. please find the following code. I have achieve like the following.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace ProjectName.Controllers
{
[Route("[controller]")]
[ApiController]
public class WizardAPIController : ControllerBase
{
[HttpGet("Methord1")]
public async Task<IActionResult> Methord1()
{
return Ok("all good");
}
[HttpGet("Methord2")]
public async Task<IActionResult> Methord2()
{
return Ok("all good");
}
}
}
public class Journal : ApiController
{
public MyResult Get(journal id)
{
return null;
}
}
public class Journal : ApiController
{
public MyResult Get(journal id, publication id)
{
return null;
}
}
I am not sure whether overloading get/post method violates the concept of restfull api,but it workds. If anyone could've enlighten on this matter. What if I have a uri as
uri:/api/journal/journalid
uri:/api/journal/journalid/publicationid
so as you might seen my journal sort of aggregateroot, though i can define another controller for publication solely and pass id number of publication in my url however this gives much more sense. since my publication would not exist without journal itself.

Categories