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);
}
Related
I am having issues in Asp.Net Core with trying to pass data in the Ajax call into the parameter of the API Controller function.
I am using the data field in the Ajax call to pass the value of "id". In the API controller this value should be assigned to the "id" parameter, but never is.
// Ajax call
$.ajax({
url: "/api/apicomment/GetPostComments",
type: "GET",
data: { 'id' : 2 },
dataType: "json",
}).done(function (data) {
// Some function
}).fail(function (handleError) {
// Some function
});
The API controller is a normal scaffolded API Controller where I get specific comments with the id parameter. But every time I make a call I get the value 0.
// API Controller
[HttpGet("{id}")]
[Route("GetPostComments")]
public async Task<ActionResult<IEnumerable<Comment>>> GetSpecificComment(int id)
{
var comment = await _context.Comment.Where(c => c.PostId == id).ToListAsync();
if (comment == null)
{
return NotFound();
}
return comment;
}
Have tried many different things, but I can't quite figure it out.
Would love any feedback that might help!
You can also pass via query string :
Comment out data line :
// Ajax call
$.ajax({
url: "/api/apicomment/GetPostComments",
type: "GET",
data: { 'id' : 2 },
}).done(function (data) {
// Some function
}).fail(function (handleError) {
// Some function
});
use FromQuery to get parameter :
public async Task<ActionResult<IEnumerable<Comment>>> GetSpecificComment([FromQuery]int id)
Few thing to try
First about your API url it should be like this
url: "/api/GetPostComments"
It would be much cleaner
Second your data should be like this
data: { id : 2 }
Finally you cant mix up between these 2
[HttpGet("{id}")]
[Route("GetPostComments")]
It should be like this
[Route("api/[controller]")]
[ApiController]
public class SomeController : ControllerBase {
[HttpGet("GetPostComments/{id}")]
public async Task<ActionResult<IEnumerable<Comment>>> GetSpecificComment(int id)
{
var comment = await _context.Comment.Where(c => c.PostId == id).ToListAsync();
if (comment == null)
{
return NotFound();
}
return comment;
}
}
So your url should be something like this api/your-controller/GetPostComments/1
You can read more here
I have my controller action methods as follows,
public class UserController : BaseController<UserController>
{
[HttpGet]
public IActionResult Assignment(Guid id)
{
return View();
}
[HttpPost]
public IActionResult Assignment(Guid id, [FromBody] List<UserViewModel> assignees)
{
return View();
}
}
The ajax method in Assignment.cshtml page
$("#btn-save").click(function () {
var url = "/User/Assignment/#Model.SelectedUser.Id";
$.ajax({
type: "POST",
url: url,
contentType: "application/json",
data: JSON.stringify({ assignees: assignmentPage.SelectedUsers })
});
});
So this builds a url like;
http://localhost:8800/User/Assignment/f474fd0c-69cf-47eb-7281-08d6536da99f
This is the only route configuration in my Startup.cs.
app.UseMvc(routes =>
{
routes.MapRoute(name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
I never hit to Assignment post action, server returns 400, I've searched and couldn't find yet, how am I supposed to configure my route for an action like this?
The data you are sending to your server is invalid. It's expecting a JSON array, but you're sending it a JSON object with a single property that itself is an array. You receive a 400 status-code due to the fact that JSON.NET cannot parse an object as an array.
For further clarification, this is what you're sending:
{
"assignees": [
{ ... assignee1 ... },
{ ... assignee2 ... },
...
]
}
However, it's the array that's expected, so it should look like this:
[
{ ... assignee1 ... },
{ ... assignee2 ... },
...
]
All you need to do is change your current JSON.stringify line to this:
JSON.stringify(assignmentPage.SelectedUsers)
An alternative option is to create a model class in your ASP.NET Core project and use that instead of a list of strings. Here's what that would look like:
public class AssignmentModel
{
public List<UserViewModel> Assignees { get; set; }
}
The Assignment action would look like this:
public IActionResult Assignment(Guid id, [FromBody] AssignmentModel thisNameDoesNotMatter)
The problem for my case is I am using AutoValidateAntiforgeryTokenAttribute configuration
services.AddMvc(options =>{
// Automatically add antiforgery token valdaiton to all post actions.
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
}
So, ajax post has not any request verification token with itself, lack of this token server returns bad request.
To overcome this, I followed this link, so my final working code as,
Assignment.cshtml
#inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
#functions
{
public string GetAntiXsrfRequestToken() => Xsrf.GetAndStoreTokens(Context).RequestToken;
}
<input type="hidden" id="RequestVerificationToken" name="RequestVerificationToken"
value="#GetAntiXsrfRequestToken()">
<script type="text/javascript">
$("#btn-saveinspectors").click(function () {
var url = "/Audit/Assignment/#Model.SelectedUser.Id";
var assignees = JSON.stringify(assignmentPage.SelectedUsers);
$.ajax({
type: "POST",
url: url,
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN", $('#RequestVerificationToken').val());
},
contentType: "application/json",
data: assignees,
});
});
</script>
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
}
Note: This is my case that there is not any form inside my page where a request verification token is generated automatically. If you have a form in your page where you make an ajax request then you can follow this post
I post data using fetch like this in my client js scripts
fetch('/myarea/mycontroller/myaction', {
method: 'post',
body: JSON.stringify({ name: namevalue, address: addressvalue })
})
.then(function (response) {
if (response.status !== 200) {
console.log('fetch returned not ok' + response.status);
}
response.json().then(function (data) {
console.log('fetch returned ok');
console.log(data);
});
})
.catch(function (err) {
console.log(`error: ${err}`);
});
}, false);
Then on my controller
[HttpPost]
public async Task<IActionResult> MyAction(string name, string address)
{
// Error, name and address is null here, shouldn't be!
// more code here ...
}
My controller action is being called correctly, and I am able to debug inside it, but no data being passed. What could be wrong here? Thanks
The controller action is expecting query parameters (/myaction?name=myname&address=myaddress). That's the default.
You're sending them in the body.
You can change the javascript to send them as query parameters. (see here: https://github.com/github/fetch/issues/256)
Or you can tell the controller action to take the values from the body:
[HttpPost]
public async Task<IActionResult> MyAction([FromBody] Person person)
{
var myName = person.Name;
}
public class Person
{
public string Name {get; set; }
public string Address {get; set; }
}
The [FromBody] attribute kicked in for me only after I defined the header that it is an "application/json" type:
fetch('api/Profile/Update', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ description: "Some text here" })
});
And this is how the controller action looks like:
[HttpPost("[action]")]
public async Task<IActionResult> Update([FromBody] ProfileUpdateModel profileUpdateModel)
{
//Do some stuff here...
return RedirectToAction("/", "HomeController");
}
And the description property now receives the value from the request. Hope this gives final clarity to somebody tackling with the issue.
Or if you had <form>...</form> you could use
fetch('/url', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: $('#editContactModal form').serialize()
// body like "id=1&clientid=3&name=me&phones=123&email=#mail.com"
})
without [FromBody] !!! (just with regular action method in controller)
I have an class that inherits from ApiController, some of its methods are called properly, some others are Not found. I can't find out why. I've been looking for a solution for hours now, still not getting it. Note That I'm new at this, it's my first WebApi in C#.
Routing: (WebApiConfig.cs)
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Configuration et services API Web
// Itinéraires de l'API Web
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Controller:
public class ExchangeController : ApiController
{
public HttpResponseMessage GetMailHeader(int id)
{
Console.WriteLine(id);
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("ok");
return response;
}
public HttpResponseMessage GetTest()
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("working !!");
return response;
}
}
JS:
$.ajax({
type: "GET",
url: "/api/exchange/getTest",
done: function (data) {
console.log(data);
}
});
$.ajax({
type: "GET",
url: "/api/exchange/getMailHeader",
data: "42",
done: function (data) {
console.log(data);
}
});
The getTest method returns a 200 OK while the getMailHeader returns 404 Not Found. What did I miss ?
As I understand it, data adds a query string, not a part of the url itself. You define the id to be part of the url, so the right url is /api/exchange/getmailheader/42.
You can also move id out of the routeTemplate.
Because your method starts with 'Get', and does not have a specific attribute, the framework assumes its an HttpGet (see rule 2 below), which requires the id to be part of the url (based on the default route).
If you want it to be an HttpPost (where you pass a json object in the body like you are doing now), then add a [HttpPost] attribute above your method or remove the 'Get' portion of the action name
Reference
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.
Thanks to everyone for your comments and answers, it has led me to the solution.
I was miss writing my ajax request. I didn't get any printed data on the console from console.log, and as #Ahmedilyas said, the data property was badly written.
The following works :
$.ajax({
type: "GET",
url: "/api/exchange/getTest"
})
.done(function (data) {
console.log(data);
});
$.ajax({
type: "GET",
url: "/api/exchange/getMailHeader",
data: { id: 42 }
})
.done(function (data) {
console.log(data);
});
I have seen similar case to mine answered but I have specific need that always differed from others problem.
I am sending json data from my html page to the MVC Web API. Unfortunately the data I am receiving is ALWAYS null (I have tried a lot of different things here). The thing is I really need to received the data as json (string), because my data is very complex and cannot be simply deserialized by the Web API (I have my own custom deserializer that does it).
Heres the code!
First, the web api controller that will receive the ajax post request
public class ActivityController : ApiController
{
// POST api/activity
public string Post([FromBody]string json)
{
}
}
Then, the ajax request itself
$.ajax("/api/activity", {
contentType: "application/json",
data: { json: ko.mapping.toJSON(fusionedModel) },
type: "POST",
success: function (result) {
...
}
});
As far as the data is concerned, it is rendered well (I've used that same request with MVC (not Web Api) and the server was receiving the string perfectly... now for some reason, in my web api controller, the "json" param is ALWAYS null. As I said before, it is IMPORTANT that I receive the data as a json string.
EDIT : I found that my question is a duplicate of this one : POST JSON with MVC 4 API Controller
But I really don't like the answer... having to create an object just to encapsulate the string is very dirty...
I recommend you avoid using standard body parameter binding as that mechanism assumes that you are trying to deserialize the body into a CLR object. Try this,
public class ActivityController : ApiController
{
// POST api/activity
public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
{
var jsonString = await request.Content.ReadAsStringAsync();
return new HttpResponseMessage();
}
}
If you really want to use parameter binding, you can do this.
public HttpResponseMessage Post(JToken jToken)
{
return new HttpResponseMessage()
{
Content = new StringContent(jToken.ToString())
};
}
Please try to use the [HttpPost] Attribute that can be located on System.Web.Http;
public class ActivityController : ApiController
{
// POST api/activity
[HttpPost]
public string Post([FromBody]string json)
{
}
}
I could be wrong here, but it looks like you haven't included your action in the post URL. Try changing
$.ajax("/api/activity", {
contentType: "application/json",
data: { json: ko.mapping.toJSON(fusionedModel) },
type: "POST",
success: function (result) {
...
}
});
To
$.ajax("/api/activity/POST", {
contentType: "application/json",
data: { json: ko.mapping.toJSON(fusionedModel) },
type: "POST",
success: function (result) {
...
}
});