I have two similar Web API controllers, one working and one not. Can't find why the broken one is not working. What happens is all the fields of the input argument are null. Here's the controller method signature:
public IEnumerable<Product> GetProducts([FromUri] StateQuery query)
Parameter type:
public class StateQuery
{
public State state;
public string username;
public string password;
}
Routing:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{controller}/{action}"
);
Calling the endpoint:
var uri = 'api/product/';
var query = {
username: $("#user").val(),
password: $("#password").val(),
state: { "name": $("#state").val(), "Counties": [{ "Name": $("#county").val() }] }
};
$.getJSON(uri + 'getProducts', query)
.done(function (data) {
$('#product').text(data);
console.log(data);
})
.fail(function (jqXHR, textStatus, err) {
$('#product').text('Error: ' + err);
});
Here's an example request:
http://localhost:47503/api/product/getProducts?username=auser&password=apassword&state%5Bname%5D=CO&state%5BCounties%5D%5B0%5D%5BName%5D=Boulder
I've tried attribute routing, tried calling from the browser address bar, tried simplifying the input to just username and password, no change. All the fields of query are always null. I also tried changing it to return a IHttpActionResult, which is the main difference between this and the working controller (other than what it does of course) but that had no effect. The method is getting called, so the routing is working, but there is something wrong with the argument binding that I'm just not seeing.
Does anybody see the problem or just a direction to look? I've spent all morning reading tutorials and SO questions and everything I'm seeing indicates that I'm on the right track. Maybe there's some little thing I'm missing because I've been staring at it too long.
Json.NET which ASP.NET Web API uses by default works on properties, not fields. You should convert them to using properties.
public class StateQuery
{
public State state { get; set; }
public string username { get; set; }
public string password { get; set; }
}
Related
I have an Angular app that invokes a c#mvc method:
AngularJS:
var data = JSON.stringify({ 'panelists': $scope.arr, 'webId': $scope.webinarId });
//Call the services
$http.post('/Home/CreatePanelists', JSON.stringify(data))
.then(function (response) {
if (response.data)
$scope.msg = 'Post Data Submitted Successfully!';
}, function (response) {
$scope.msg = 'Service not Exists';
});
Contents of data:
{"panelists":[{"name":"ali","email":"ali#email.com"},{"name":"tom","email":"tom#email.com"},{"name":"arthur","email":"arthur#email.com"}],"webId":94395753244}
Added the following Files:
/Models/Home/Panelist.cs
public class Panelist
{
public string name { get; set; }
public string email { get; set; }
}
/Models/Home/Parameters.cs
public class Parameters
{
public IList<Panelist> panelists { get; set; }
public string webId { get; set; }
}
Updated the Controller:
/Controllers/HomeController.cs:
public ActionResult CreatePanelists(Parameters data)
{
System.Diagnostics.Debug.WriteLine(data.webId);
System.Diagnostics.Debug.WriteLine(data.panelists);
return new EmptyResult();
}
By the time when the debugger enters the CreatePanelists method, I added a watch on data, and both, panelists, and webId are null.
I don't understand where the problem resides. When I debug the AngularJS code in Chrome, and get to the step in which the post request is made, I see that the variable data, does have the array of objects with values (as shown above).
Why is the MVC Controller method CreatePanelist is not seeing these values?
If somebody have an idea and would not mind offering it, that would be nice.
Thank you in advance for your help.
Your problem comes from calling JSON.stringify twice. You are already stringifying it when setting the variable data, don't do it a second time in your post.
AngularJS:
$http.defaults.headers.post["Content-Type"]= "application/x-www-form-urlencoded";
$http({
url: 'http://localhost:17438/api/people/PostPerson/',
method: "POST",
data: { name: vm.parent[i].name, dob: '01/15/2001', email: vm.parent[i].email, phone: vm.parent[i].cell, carrierName: vm.parent[i].carrier, personTypeID: 1 }
})
.then(function (response) {
// success
alert('sucess : ' + response);
},
function (response) { // optional
// failed
alert('failure : ' + response);
});
I've also tried this variation:
var data = { name: vm.parent[i].name, dob: '01/15/2001', email: vm.parent[i].email, phone: vm.parent[i].cell, carrierName: vm.parent[i].carrier, personTypeID: 1 };
$http.post('http://localhost:17438/api/people/PostPerson/', data);
Parameters being passed:
{"name":"jv","dob":"01/15/2001","email":"j#live.com","phone":"5551212","carrierName":"Sprint","personTypeID":1}:
webAPI:
[HttpPost]
[HttpOptions]
public string PostPerson(newUserRegistration newReg)
{
var json = JsonConvert.SerializeObject(0);
person myPerson = new person();
myPerson.personName = newReg.name;
myPerson.personEmail = newReg.email;
myPerson.personPhone = newReg.phone;
myPerson.personPhoneCarrier = newReg.carrierName;
myPerson.personDOB = newReg.dob;
myPerson.familyID = newReg.familyID;
myPerson.personTypeID = newReg.personTypeID;
db.people.Add(myPerson);
db.SaveChanges();
return "got here";
}
public class newUserRegistration
{
public string name { get; set; }
public string email { get; set; }
public string phone { get; set; }
public string carrierName { get; set; }
public DateTime dob { get; set; }
public string registrationID { get; set; }
public int familyID { get; set; }
public int personTypeID { get; set; }
}
Parameter population:
I know it is hard to read but you can see the values I'm passing in are NOT being passed into my newUserRegistration object
I've looked at several questions on Stack that seems to reference this type of issue.
Angular POST to Web API doesn't pass data
issue in Angularjs $http.post to webapi
This project is currently using 1.3.15 - I'm not sure if upgrading to 1.5 help?
What am I missing on this?
UPDATE:
There was a comment that is now gone, but I stringified the data as suggested:
var data = JSON.stringify({ name: vm.parent[i].name, dob: '01/15/2001', email: vm.parent[i].email, phone: vm.parent[i].cell, carrierName: vm.parent[i].carrier, personTypeID: 1 });
$http.post('http://localhost:17438/api/people/PostPerson/', data);
I noticed something strange though. It is calling the API method 2 times, the 1st time has null data (as witnessed originally), but it calls it a 2nd time and the data is there!
I'm not sure why it is being called twice though? Is there anything I'm doing incorrectly now that I'm stringifying the data?
Update to double call:
I noticed the following:
You will notice one says OPTIONS and the other says POST. Now the webAPI also has the following tags:
[HttpPost]
[HttpOptions]
If I removed the Options, it fails (can't find 404). Do these tags have something to do with this?
Use JSON.stringify() to wrap your json
var url = 'http://localhost:17438/api/people/PostPerson/';
var data = JSON.stringify({ name: vm.parent[i].name, dob: '01/15/2001', email: vm.parent[i].email, phone: vm.parent[i].cell, carrierName: vm.parent[i].carrier, personTypeID: 1 });
$http.post(url, data);
As you're finding out, this is a web server issue more than an Angular one.
If your angular app is delivered on the same server:port as your service endpoints, try using a relative hyperlink and see if the OPTIONS request disappears. The presence of the domain name in a XmlHttpRequest for the application/json content-type is usually enough to trigger the CORS check.
I will say that I've seen this much more frequently when connecting to IIS, but it's not exclusive. A lot depends on the underlying server config. The OPTIONS request is like the handshake for SSL: the angular request is trying to figure out what the system will allow, then sending the payload once permissions are granted.
MDN - Access Control with CORS
Remove this:
$http.defaults.headers.post["Content-Type"]= "application/x-www-form-urlencoded";
You are telling the api server you are going to send it a form encoded body and you are instead sending it a JSON body and so when the api server goes to parse the body of the request it is null.
NOTE: If you are using Asp.Net Web Api's built in OAuth provider then you will need to post to the /token method using the application/x-www-form-urlencoded data type but for all other calls you can use JSON.
As for the duplicate requests that is caused by your browser making a CORS check. That is perfectly normal if your API hostname is different from you angular hostname.
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.
I'm getting a 404 error with a PUT request when using AngularJS's "$http.put".
Here is my relevant AngularJS code:
/* toggles whether or not the link is disabled */
toggleLinkStatus: function (isActive, linkGUID) {
$http.put('/Explorer/Link/ToggleLinkStatus',
{
isActive: isActive,
linkGUID: linkGUID
}
);
}
And my relevant C# Controller Code:
[HttpPut]
public IHttpActionResult ToggleLinkStatus(Boolean isActive, Guid linkGUID)
{
return Ok();
}
And my WebApiConfig Code
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
/** define routes for Explorer API */
config.Routes.MapHttpRoute(
name: "ExplorerAPI",
routeTemplate: "Explorer/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
What am I missing here? In the Headers for the call I can see that the data is being passed through in the Request Payload:
isActive: true
linkGUID: "b94badb3-2917-4129-9ae3-35d2a635f66d"
If I do a normal query string PUT using POSTMAN it goes through, but not when I use AngularJS.
UPDATE
Here's something interesting, if I use the following angularJS request it goes through:
/* toggles whether or not the link is disabled */
toggleLinkStatus: function (isActive, linkGUID) {
$http.put('/Explorer/Link/ToggleLinkStatus',
{
isActive: isActive,
linkGUID: linkGUID
},
{
params:
{
isActive: isActive,
linkGUID: linkGUID
}
}
);
}
It also works with this:
/* toggles whether or not the link is disabled */
toggleLinkStatus: function (isActive, linkGUID) {
$http.put('/Explorer/Link/ToggleLinkStatus',
{
},
{
params:
{
isActive: isActive,
linkGUID: linkGUID
}
}
);
}
Try changing the controller action to use a class instead of primitive datatypes. MVC Tries to read these from the URI instead the request body.
like this:
[HttpPut]
public IHttpActionResult ToggleLinkStatus(RequestModel model)
{
// model.IsActive ...
return Ok();
}
public class RequestModel {
bool IsActive {get;set;}
Guid LinkGuid {get;set;}
}
If you're successfully using GET (by doing it in the browser address bar) but not successfully using PUT, then there's something wrong with how you're configuring the route / etc. I don't see what it is in the code snippet you have here, but there's definitely something amiss in how you've configured your routes. Perhaps there's an earlier route that matches the GET that's fouling up. It's not Angular's fault. You can test by using the PostMan plugin for Chrome, it'll let you send PUT/POST/DELETE as well as GET and you can see the results.
I'm desperately trying to upload a plain (text/plain) string in Web API and my Controller is simply refusing to do the routing correctly. All I'm getting is a 404 (Not Found) HTTP error (I was so happy all the "Get" methods were working out of the box :-( )
Here are my routes:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "IntegraApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ServComAdminApi",
routeTemplate: "admin/{action}",
defaults: new { controller = "admin", action = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ServComApi",
routeTemplate: "{id}/{action}",
constraints: new { id = #"\d+" },
defaults: new { controller = "servcom", action = "info" }
);
// Get rid of XML responses
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
The pertinent route is the last one that maps to the "servcom" controller. I'm porting a custom written HTTP server that followed that routing pattern ("id/action"). It is working for all "get" methods. I can get a list of "users" for the equipment with id "10" by just using: http://localhost:49410/10/users... But it's not working when I try to upload "string data". This is the pertinent method on my controller:
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public string Vis(long idTerm)
{
return "PUT/POST Vis for: " + idTerm;
}
}
It's stripped down to the bare minimum. I'm not even reading the actual string data. Since I won't be sending form encoded data, just a plain string (this API is currently used under 3G so any byte savings are great as we need to minimize data-plan usage), I didn't use [FromBody] attributes as it won't work at all.
This is the client code used to test it:
using System;
using System.Net;
namespace TestPutPostString
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Sending 'HELLO WORLD!'");
var wc = new WebClient();
var res = wc.UploadString("http://localhost:49410/101/vis", "PUT", "HELLO WORLD!");
Console.WriteLine("Response: " + res);
Console.ReadKey(true);
}
}
}
It fails with: "The remote server returned an error: (404) Not Found."
The above code works perfectly with my hand-written HTTP server, which uses TcpServer and was written from scratch for the specific needs of this API (I'm migrating it to Web.API after 2 years of use since it will be easier to host it on Azure this way). Using this same sample program with hand-written HTTP stack, the body of the message, when hitting the server, does indeed have "HELLO WORLD!". Why isn't it being routed to the ServComController.Vis() method?
Am I sending it wrong? Does WebClient.UploadString() works in other unpredictable ways? Or is my controller method signature wrong? Is the route wrong? What am I missing!?!? :-)
IF you want to avoid the pain of Action Selection you could change your signature to be,
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public HttpResponseMessage Vis(HttpRequestMessage request)
{
var idTerm = request.GetRouteData().Values["idTerm"];
var body = request.Content.ReadAsStringAsync().Result;
return "PUT/POST Vis for: " + idTerm;
}
}
Darrel Miller provided quite a nice solution which is not the actual answer, but I guess will end up with lots of more upvotes than my own answer to my own question. I wasn't aware we could "avoid the pain of Action Selection" by using using a parameter of the type HttpRequestMessage ! This is quite nice and I can see a lot of scenarios where it will be really, really useful.
But that's not the only reason his answer will get a lot more upvotes: this is because my question was silly! I did a very stupid mistake.
This is the route:
config.Routes.MapHttpRoute(
name: "ServComApi",
routeTemplate: "{id}/{action}",
constraints: new { id = #"\d+" },
defaults: new { controller = "servcom", action = "info" }
);
This is the Method I was trying to map the route to:
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public string Vis(long idTerm)
{
return "PUT/POST Vis for: " + idTerm;
}
}
Of course it won't match! In the route I've used "id" as the parameter, and in this method I've used "idTerm" as the parameter, so there was really no action on the ServComController which matched the route!
The solution was simply to change it to:
public class ServcomController : ApiController
{
[HttpPut, HttpPost]
public string Vis(long id) // << "idTerm" to "id"
{
return "PUT/POST Vis for: " + idTerm;
}
}
And since the body is not form encoded, I won't be able to use [FromBody]string data, because it expects form data. The final solution will be to read the body from the request using a good, old StreamReader for that. I'll update the solution with the full code later today.