In my web api controller i have a function with following codes
[HttpPost]
public HttpResponseMessage Post(string schooltypeName)
{
_schoolTypeService.RegisterSchoolType(schooltypeName);
var message = Request.CreateResponse(HttpStatusCode.Created);
return message;
}
When i am calling with fiddler i am getting this error
{"Message":"The requested resource does not support http method 'POST'."}
my fiddling parameters are
Header
User-Agent: Fiddler
Host: myhost:8823
Content-Type: application/json; charset=utf-8
Content-Length: 26
Request body
{"schooltypeName":"Aided"}
Requesting url are
http://myhost:8823/SchoolType
( i configured url ,GET is working with this url)
Whats wrong here ?
Change your action to be like Post([FromBody]string schooltypeName) as by default string type is expected to come Uri.
Updated:
Change your body to just "Aided" as currently you would need a class to make the deserialiation work otherwise (ex:class School { public string SchoolTypeName { get; set; } }
See the using namespace at the top of the controller, if you're using System.Web.Mvc, then this problem might be occurred:
Use this:
using System.Web.Http;
The Problem comes down to this:
if your routes in startup is registered with routes.MapRoute(
you must decorate your post methods with [System.Web.Mvc.HttpPost]
If your routes in startup is registered with Routes.MapHttpRoute(
you must decorate your post methods with [System.Web.Http.HttpPost]
if you use MapRoute() with [System.Web.Http.HttpPost] it wont work
if you use MapHttpRoute() with [System.Web.Mvc.HttpPost] it wont work
For future readers. I found this question..but found (my) answer elsewhere.
I decorated the method with the attribute seen below.
[System.Web.Http.HttpPost]
public MyOutputObject DoSomething([FromBody]MyInputObject args)
{
Console.Writeline(args.ToString());
return new MyOutputObject();
}
My client code (C#, console app) for completeness. Please note it is NOT an good example of async/await since it has .Result in it.
private static Task<MyOutputObject> MyClientCall(string baseAddress, MyInputObject args)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
/* Your class name would be "MyEntityController" most likely */
string serviceUrl = baseAddress + #"api/MyEntity/DoSomething";
HttpResponseMessage response = client.PostAsJsonAsync(serviceUrl, args).Result;
Console.WriteLine(response);
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("ERROR: :( " + response.ReasonPhrase);
return null;
}
Task<MyOutputObject> wrap = response.Content.ReadAsAsync<MyOutputObject>();
return wrap;
}
I found my answer here:
http://blog.dontpaniclabs.com/post/2013/01/23/That-Pesky-Requested-Resource-Does-Not-Support-HTTP-Method-POST-Error-When-Using-MVC-Web-API
Please Check your GET Action method name you are using.
Chances are you might be using the same Route names to GET method and POST method and expecting the result.
Example :
Controller name : StudentController
[Route("api/Student/names")]
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "student1", "student2" };
}
Url for method (GET): http://localhost:59342/api/student/names
[HttpPost]
[Route("api/Student/names")]
public String Post(String name)
{
return "success";
}
For POST method to url : http://localhost:59342/api/student/names
Then you will get the above mentioned error
Solution: Change the POST action method name like below
[HttpPost]
[Route("api/Student/getName")]
public String Post(String name)
{
return "success";
}
Then url which is used to get the response for post method is :
http://localhost:59342/api/student/getName
What helped to me at the end was adding the Route attribute, and just repeating there the same route which as registered globally.
If you add attribute [RoutePrefix("samplePrefix")] for controller class, but not add attribute [Route("sampleRoute")] for specified post method, this issue may occurs.
Related
I am trying to execute a POST from my angular application to a .net Web API instance but the server returns null.
server
[HttpPost]
public string callBcknd([FromBody]string body)
{
try
{
Log.Info(string.Format("{0}", body));
}
catch(Exception ex)
{
return "error";
}
}
}
angular *note that I am using HttpClient instead of Http.. not sure if this is also the problem
callServer(){
var test = { "name": "John" }
let data = JSON.stringify(test);
let headers = new HttpHeaders();
headers.set('Content-Type', 'application/json');
this.appService.http.post('http://localhost:3000/api/WebApI/callBcknd',
test,
{headers: headers})
.subscribe(data => {console.log(data);}}}
config
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {action = "GET", id = RouteParameter.Optional}
);
}
}
With above setting, I don't gen any 404 server error in client side(by checking chrome's console) but it returns null in backend. But as I tried with Postman, it sends values properly with the same url. If I don't include [FromBody] inside of method in backend, I get an 404 server error in client side. Furthermore, messages says "NO HTTP resource was found that matches the request URI". Similar question to this seems solving the problem by having [FromBody] but I still get a null... I also suspected maybe my web config file(not the one in above) should contain some headers, so when I added some headers like content type to be json and etc then I get 500 server error in client side. At this point i am really confused and not sure what to proceed.
UPDATE1
Following server code returns the message but I am still getting the body as null.. no errors have been observed
[HttpPost]
public IHttpActionResult Callbcknd([FromBody] string body)
{
try
{
Log.Info(string.Format("called with data {0}", body));
return Ok(new { Message = "It worked!" });
}
catch(Exception ex)
{
return base.Content(HttpStatusCode.InternalServerError, ex.ToString());
}
}
I see multiple reasons why you would get unexpected errors and null values in your code:
(error) Your .net method callBcknd should not even compile as it can only return something if there is an Exception.
(error) You should send json when sending data to your api controller the message body and the api controller method should accept an complex object and not a primitive type like string/int/bool etc.
(warning) Your angular service should expose functionality and return either observables or promises that the component can then subscribe to. Do not expose the HttpClient directly.
(warning) Your web api should return interface IHttpActionResult instead of the type directly. Then you can use the built in methods like Ok and Content and BadRequest to return status information along with data. See also Action Results in Web API 2
(suggestion) Use Route and RoutePrefix as attributes instead of relying on the route config. This is more flexible and will allow you to also specify parameters to be included in the URL which will make for a more RESTful design. See also Attribute Routing in ASP.NET Web API 2
(suggestion) Add CamelCasePropertyNamesContractResolver to resolve between camel and pascal casing between your front end and backend. See also Serialization using ContractResolver
This is a good example of how make calls to a Web API and how to structure your code.
Note that these code samples only show the relevant parts that were added or modified
WebApiConfig.cs
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// add this to ensure that casing is converted between camel case (front end) and pascal case (c#/backend)
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.MapHttpAttributeRoutes();
}
}
ApiModel.cs
public class ApiModel {
public string Content {get;set;}
}
WebApIController.cs
[RoutePrefix("api/WebApI")]
public class WebApIController : ApiController {
[HttpPost]
[Route("callBcknd")]
public IHttpActionResult CallBcknd([FromBody] ApiModel body)
{
try
{
Log.Info(string.Format("{0}", body.Content));
return Ok(new {Message = "It worked!"});
}
catch(Exception ex)
{
// example of how to return error with content. I would not recommend actually returning the exception details to the client in a production setting
return base.Content(HttpStatusCode.InternalServerError, ex.ToString());
}
}
}
application.service.ts
constructor(private httpClient: HttpClient){}
callServer(data: {content: string}) : Observable<any> {
return this.httpClient.post('http://localhost:3000/api/WebApI/callBcknd', data);
}
application.component.ts
constructor(private myService: MyService){}
onDoSomething(){
this.myService.callServer({content: 'This is what I have sent'})
.subscribe(data => console.log("Succeeded, result = " + data), (err)=> console.error("Failed! " + err));
}
Notice the following:
ApiModel represents the incoming object in the request. The angular call then sends {content: 'This is what I have sent'} which mirrors this type.
IHttpActionResult is the response type for your Web API method
You can return different types along with status information in the method CallBcknd
Route and RoutePrefix were added to give more control over the uri path.
The angular component and service have been split into 2 methods, the service returns an observable and the component calls the service methods and subcribes to the returning observable. When you extend this example you want to replace any with defined expected results using interfaces and the same is true for any incoming parameters you want to send.
A Tipical call to a API from "Angular"
update(data: string): Observable<IResponse> {
console.log(data);
let url = '...';
let headers = new Headers({
'Content-Type': 'application/json; charset=utf-8',
});
let options = new RequestOptions({ headers: headers })
return this._http.post(url, data, options)
.map((res: any) => {
return res.json();
})
.catch(this.handleError);
}
The code in API
[HttpPost]
public string callBcknd([FromBody]string body)
{
try
{
Log.Info(string.Format("{0}", body));
//You must return something
return "Post Realized";
}
catch(Exception ex)
{
return "error";
}
}
//I like call async
[HttpPost]
public async Task<IActionResult>callBcknd([FromBody]string body)
{
try
{
Log.Info(string.Format("{0}", body));
//await "some action"
//You can return OK("success") or an object
return Ok(new { success = true, description = "callback sucesfully" });;
}
catch(Exception ex)
{
//You can return OK("error") or an object
return Ok(new { success = false, description = ex.InnerException });;
}
}
Well, what you're posting would look something like
{"body":{// something here }}
Whereas your controller expects:
"valuehere" (which is a valid json for string).
You need to change the c# code to have a model for your DTO:
public class PostedObject{
public object Data {get;set;}
}
[HttpPost]
public string callBcknd([FromBody]PostedObject body)
{
// do things
}
This is a follow-up on Return HTML from ASP.NET Web API.
I followed the instructions but I get Error 406 in the browser.
My code:
[Produces("text/html")]
[Route("api/[controller]")]
public class AboutController : Controller
{
[HttpGet]
public string Get()
{
return "<html><body>Welcome</body></html>";
}
...
and, simply:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
When I remove the Produces line I get the plain text <html><body>Welcome</body></html> in the browser (no error).
What am I missing? Thanks.
As KTCO pointed out here :
Starting with AspNetCore 2.0, it's recommended to use ContentResult
instead of the Produce attribute
The solution is:
[HttpGet]
public ContentResult Get()
{
return new ContentResult {
ContentType = "text/html",
StatusCode = (int) HttpStatusCode.OK,
Content = "<html><body>Welcome</body></html>"
};
}
There is no need to change AddMvc (and there is no Produce attribute, of course).
I hope this helps someone.
You need to add Content-Type header to text/html in the request which you are sending while using Produces attribute.
If you are sending through browser then it will work good because browser by default sends Content-type header as text/html
I have a controller in my web api. Let's call it TimeController.
I have a GET action and a PUT action. They look like this:
public class TimeController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, DateTime.UtcNow);
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
I also have a route config as follows:
routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });
so I can access it in a restful manner.
Now I also want to version the GET action using a custom Route attribute. I'm using code very similar to what Richard Tasker talks about in this blog post.
(the difference being that I use a regular expression to get the version from the accept header. Everything else is pretty much the same)
So my controller now looks like this:
public class TimeController : ApiController
{
private IService _service;
public TimeController(IService service)
{
_service = service;
}
[HttpGet, RouteVersion("Time", 1)]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow);
}
[HttpGet, RouteVersion("Time", 2)]
public HttpResponseMessage GetV2()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow.AddDays(1));
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
However, now when I try to access the PUT endpoint I'm getting a 404 response from the server. If I step through the code in debug mode, I can see that the RouteVersion attribute is being fired, even though I haven't decorated the action with it.
If I add the attribute to the PUT action with a version of 1, or I add the built in Route attribute like this: Route("Time") then it works.
So my question is: why is the attribute firing even though I haven't decorated the action with it?
Edit: Here is the code for the attribute:
public class RouteVersion : RouteFactoryAttribute
{
private readonly int _allowedVersion;
public RouteVersion(string template, int allowedVersion) : base(template)
{
_allowedVersion = allowedVersion;
}
public override IDictionary<string, object> Constraints
{
get
{
return new HttpRouteValueDictionary
{
{"version", new VersionConstraint(_allowedVersion)}
};
}
}
}
public class VersionConstraint : IHttpRouteConstraint
{
private const int DefaultVersion = 1;
private readonly int _allowedVersion;
public VersionConstraint(int allowedVersion)
{
_allowedVersion = allowedVersion;
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection != HttpRouteDirection.UriResolution)
{
return true;
}
int version = GetVersionFromHeader(request) ?? DefaultVersion;
return (version == _allowedVersion);
}
private int? GetVersionFromHeader(HttpRequestMessage request)
{
System.Net.Http.Headers.HttpHeaderValueCollection<System.Net.Http.Headers.MediaTypeWithQualityHeaderValue> acceptHeader = request.Headers.Accept;
var regularExpression = new Regex(#"application\/vnd\.\.v([0-9]+)",
RegexOptions.IgnoreCase);
foreach (var mime in acceptHeader)
{
Match match = regularExpression.Match(mime.MediaType);
if (match.Success)
{
return Convert.ToInt32(match.Groups[1].Value);
}
}
return null;
}
}
Edit2: I think there is some confusion so I've updated the Put action to match the route config
So my question is: why is the attribute firing even though I haven't decorated the action with it?
It is clear from both the way your question is phrased "when I try to access the PUT endpoint" and the fact that it matches the GET action (and then subsequently runs its constraint) that you have not issued a PUT request to the server. Most browsers are not capable of issuing a PUT request, you need a piece of code or script to do that.
Example
using (var client = new System.Net.WebClient())
{
// The byte array is the data you are posting to the server
client.UploadData(#"http://example.com/time/123", "PUT", new byte[0]);
}
Reference: How to make a HTTP PUT request?
I think its because of your action signature in combination with the default route
In your default route you specify the Id attribute as optional, however in your action you use the parameter days, in this case the framework can't resolve it. you either have to add it as a query string parameter eg:
?days={days}
Or change the signature to accept id as input.
Since it can't resove the action with days in the url it will return a 404
Personally i don't use the default routes and always use Attribute routing to prevent this kinda behavior
So my question is: why is the attribute firing even though I haven't decorated the action with it?
Any controller methods that do not have a route attribute use convention-based routing. That way, you can combine both types of routing in the same project.
Please see this link :
attribute-routing-in-web-api-2
Also as method is not decorated with route attribute, When the Web API framework receives an HTTP request, it tries to match the URI against one of the route templates in the routing table. If no route matches, the client receives a 404 error. That is why you are getting 404
Please see this one as well : Routing in ASP.NET Web API
Im a beginner with Web api and Im trying to setup a simple owin selfhosted service that Im trying out.
I've been searching stackoverflow and other places for a while now, but I cant seem to find anything obviously wrong.
The problem I have is that I get a bad request response everytime I try to call my service.
The controller looks like this:
[RoutePrefix("api/ndtdocument")]
public class NDTDocumentsController : ApiController, INDTDocumentsController
{
[HttpGet]
public IHttpActionResult Get()
{
var document = Program.NDTServerSession.GetNextNDTDocument(DateTime.Today);
if (document == null)
return null;
return Ok(document);
}
[Route("")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
{
try
{
Program.NDTServerSession.AddNDTDocument(ndtDocument);
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
And the client looks like this:
static void Main(string[] args)
{
AddNDTDocument(#"C:\Testing.txt");
}
private static void AddNDTDocument(string centralserverPath)
{
var client = GetServerClient();
NDTDocument document = new NDTDocument();
var response = client.PostAsJsonAsync("ndtdocument", document).Result;
}
static HttpClient GetServerClient()
{
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:9000/api/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
I can see when I debug it that the request uri is infact http://localhost:9000/api/ndtdocument
But the response is allways bad request and I have a breakpoint in the controller and it is never invoked.
Everytime I try to do something with web apis I Always run into some weird (but simple problem).
Any thoughts?
Thanks!
Web API will decide your route based on your method names. Since you have added [RoutePrefix("api/ndtdocument")] on class level this will be the route to your controller. When web api looks for an action it will match on method names, so in your case your actual route would be http://localhost:9000/api/ndtdocument/post.
When trying to decide what http method that a specific action requires web api will check your method names and methods starting with post will be http post, get will be http get etc.
So lets say we would instead call our method PostData, for starters we could remove the [HttpPost] attribute. Our route would now be http://localhost:9000/api/ndtdocument/postdata. Let's now say that we want our path to be just /data. We would then first rename our method to Data, but now web api does not know what http method we want to invoke this method with, thats why we add the [HttpPost] attribute.
Edit after reading your comment
[Route("{id:int}")]
public IHttpActionResult Get(int id)
[Route("")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
Okey, after nearly going seriously insane. I found the problem.
I forgot to reference webapi.webhost and then system.web.
After this Everything worked like a charm.
You must use route tags and call this way http://localhost:9000/api/get or http://localhost:9000/api/post
[RoutePrefix("api/ndtdocument")]
public class NDTDocumentsController : ApiController, INDTDocumentsController
{
[HttpGet]
[Route("get")]
public IHttpActionResult Get()
{
var document = Program.NDTServerSession.GetNextNDTDocument(DateTime.Today);
if (document == null)
return null;
return Ok(document);
}
[HttpPost]
[Route("post")]
public IHttpActionResult Post([FromBody] NDTDocument ndtDocument)
{
try
{
Program.NDTServerSession.AddNDTDocument(ndtDocument);
return Ok();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
}
for more infromation pls check this link
I've just switched from AttributeRouting to WebApi 2.0 AttributeRouting, and have got a controller and action defined like so:
public class InvitesController : ApiController
{
[Route("~/api/invites/{email}")]
[HttpGet]
[ResponseType(typeof(string))]
public IHttpActionResult InviteByEmail(string email)
{
return this.Ok(string.Empty);
}
}
Example query:
GET: http://localhost/api/invites/test#foo.com
The response I receive is a 200, with empty content (due to string.Empty).
This all works fine -- but I want to change the email property to be a query parameter instead . So I update the controller to:
public class InvitesController : ApiController
{
[Route("~/api/invites")]
[HttpGet]
[ResponseType(typeof(string))]
public IHttpActionResult InviteByEmail(string email)
{
return this.Ok(string.Empty);
}
}
But now when querying the endpoint with:
GET: http://localhost/api/invites?email=test#foo.com
The response I receive is a 404:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost/api/invites?email=test#foo.com'.",
"messageDetail": "No route providing a controller name was found to match request URI 'http://localhost/api/invites?email=test#foo.com'"
}
Does anyone know why it doesn't match the route when the parameter is swapped to a query parameter, rather than inline of the url ?
As requested, WebApiConfig is defined like so:
public static void Register(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.Indent = true;
jsonFormatter.SerializerSettings.ContractResolver = new RemoveExternalContractResolver();
config.MapHttpAttributeRoutes();
}
Thanks !
I think you need to include the query parameter (along with its type) in the Route as follows:
[Route("api/invites/{email:string}")]
using it will be
POST: http://localhost/api/invites/test#foo.com
Alternatively if you want to name the query parameter:
[Route("api/invites")]
using it will be (as long as you have an email parameter in your method)
POST: http://localhost/api/invites?email=test#foo.com
As you comment in edhedges answer: the route template cannot start with a '/' or a '~', so you can remove that from the route as above
Issue is a clash of route definitions, which went unnoticed due to being cross-controller (and also some of the routes being 'absolute' (~/)). Below is an example that reproduces the result.
public class ValuesController : ApiController
{
[Route("~/api/values")]
[HttpGet]
public IHttpActionResult First(string email)
{
return this.Ok("first");
}
}
[RoutePrefix("api/values")]
public class ValuesTwoController : ApiController
{
[Route("")]
[HttpGet]
public IHttpActionResult Second(string email)
{
return this.Ok("second");
}
}
Issuing a request:
GET: http://localhost/api/values?email=foo
Will return a 404 with a response of:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost/api/values?email=foo'.",
"messageDetail": "No route providing a controller name was found to match request URI 'http://localhost/api/values?email=foo'"
}
What is misleading is the response message.
I think you need to change your Route declaration to this: [Route("~/api/invites?{email}")]
Here is a relevant link: http://attributerouting.net/#route-constraints