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);
});
Related
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
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);
}
I'm brand new to restful APIs after a decade on desktop development. I'm a little confused as to why I am getting a 405 attempting a GET for a controller.
My controller:
public class ApplicantsController : ApiController
{
/// <summary>
/// Gets the details of the applicant and their application
/// </summary>
/// <param name="applicantID">The ID of the applicant to get the most recent application and details for</param>
/// <returns></returns>
public HttpResponseMessage Get(int applicantID)
{
try
{
using (DbQuery query = new DbQuery("SELECT * FROM Applicants AS A WHERE A.ID = #ApplicantID",
new DbParam("#ApplicantID", applicantID)))
{
using (DataTable data = query.ExecuteDataTable())
{
if (data.Rows.Count > 0)
{
Applicant applicant = new Applicant(data.Rows[0]);
return new HttpResponseMessage()
{
Content = new StringContent(applicant.ToJson(), Encoding.UTF8, "text/html")
};
}
}
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
catch (Exception ex)
{
Methods.ProcessException(ex);
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
public HttpResponseMessage Post(Applicant applicant)
{
if (applicant.Save())
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, applicant);
string uri = Url.Link("DefaultApi", new { id = applicant.ID });
response.Headers.Location = new Uri(uri);
return response;
}
return Request.CreateResponse(HttpStatusCode.InternalServerError, "Error saving applicant");
}
}
}
I have the same default routing in my WebApiConfig and confirmed that the way my controller is written it matches a standard Web API 2 controller with read, write, update methods. I've tried using DefaultAction, I've tried decorating methods with [HttpGet] and [AcceptVerbs]. Whenever I try to access the Get through either a browser myself or through ajax, I get a 405 (method not allowed).
Ajax test:
$("#TestGetApplicantButton").click(function (e) {
e.preventDefault();
alert("Getting Applicant...");
$.ajax({
type: "GET",
url: "/api/Applicants/108",
contentType: "application/json; charset-utf-8",
dataType: "json",
success: function (data) {
$("#ResponseDiv").html(JSON.stringify(data));
},
failure: function (errMsg) {
alert(errMsg);
}
});
});
Ajax works perfectly for all of the other controllers, showing the data returned (example: and it even calls the Post method on this controller just fine. Yet I can't get my Get to work. I don't see where I could be going wrong.
My routing:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
I've googled and checked here, but everyone seems to only have issues with POST, PUT, or DELETE, so I haven't found an answer for this. I've also tried removing the POST method in the controller - that gets me a 404 instead (not from my 404, I confirmed the code doesn't execute), which suggests for some reason the routing can't find my get method at all.
You need to add a default value to your applicantID parameter, since your route has the first parameter marked as RouteParameter.Optional.
public HttpResponseMessage Get(int applicantID = 0)
This will ensure that your Get method signature matches your "DefaultApi" route.
Hollo:
I'm sending the following message to mi asp.net web api
var user ={
Username: "user",
Password: "pass"
};
$.ajax({
url: 'http://WebApiDir',
type: 'POST',
contentType: "application/json",
data: JSON.stringify(user),
success: function (data) {
},
});
I Caught the request on a DelegatingHandler.
My question is How to parse the message included on the HttpContent to a NameValueCollection class
I try do the following
var sQuery = await request.Content.ReadAsFormDataAsync().Result;
but this produce a exception becase the Result attr is null.
Thanks for your answers
This is something I do:-
$.ajax
({
url: 'Default.aspx/MyMethod',
type: 'POST',
dataType: "json",
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ ID: ID }),
success: onSuccess,
fail: onFail
});
and then on the C# side:-
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
// MyMethod is used to pass Identifcation number from client side to server side via Ajax.
public static string MyMethod(string ID)
{
........ do whatever needs to be done ........
return string.Format("Thanks for calling me with Ajax, the ID: " + data);
}
If you are indeed using WebAPI, you should allow the framework to do the heavy lifting for you.
First, you need to make sure that your routing is set up correctly and that you are actually calling the route from your javascript. In your case, App_Start/WebApiConfig.cs would look something like:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Then, give your api controller a meaningful name name such as Users and define the method as a post, since you are posting user info, taking User as a parameter
public class User
{
public string Username { get; set; }
public string Password { get; set; }
}
public string Post(User user)
{
// perform actions on user data
return "success";
}
Finally, you need to properly define the route in your javascript call.
var user ={
Username: "user",
Password: "pass"
};
$.ajax({
url: 'http://WebApiDir.com/api/Users',
type: 'POST',
contentType: "application/json",
data: user,
success: function (data){},
});
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);
}