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
Related
I have no idea where to start with this. I asked a question previously, and someone suggested I look at attribute routing. I read up on it, and while it helped me to create the below code, I'm still not sure how to limit it like I want to.
public class ReviewController : ApiController
{
private Review db = new Review();
////This GET works. It was auto-generated by Visual Studio.
// GET: api/Review
public IQueryable<Review> GetReview()
{
return db.Review;
}
////This is the GET that I'm trying to write, but don't know what to do
// GET: api
[Route("api/review/site")]
[HttpGet]
public IEnumerable<Review> FindStoreBySite(int SiteID)
{
return db.Review
}
////This GET also works and was generated by VS.
// GET: api/Review/5
[ResponseType(typeof(Review))]
public IHttpActionResult GetReview(int id)
{
Review review = db.Review.Find(id);
if (review == null)
{
return NotFound();
}
return Ok(review);
}
Essentially what I'm aiming to do is to limit what's returned by the API to only the results where the SiteID is equal to whatever value is passed into the URL. I'm not even sure where to get started with this, and googling/searching stack overflow for "what to put in web api return" has been fruitless.
How do I tell the API what I want to have returned based off a parameter besides ReviewID?
Edit: I've updated the code per the suggestions in the answer below, but now I'm getting a new error.
Here's the current code:
private ReviewAPIModel db = new ReviewAPIModel();
// GET: api/Review
[Route("api/Review")]
[HttpGet]
public IQueryable<Review> GetReview()
{
return db.Review;
}
// GET: api
[Route("api/Review/site/{siteid}")]
[HttpGet]
public IEnumerable<Review> FindStoreBySite(int siteid)
{
return db.Review.Where(Review => Review.SiteID == siteid);
}
// GET: api/Review/5
[ResponseType(typeof(Review))]
public IHttpActionResult GetReview(int id)
{
Review review = db.Review.Find(id);
if (review == null)
{
return NotFound();
}
return Ok(review);
}
}
Here's the error that I get:
Multiple actions were found that match the request
When I google it, it takes me to this question: Multiple actions were found that match the request in Web Api
However, I've tried the answers there (I've confirmed that I'm using Web API V2, and my webapiconfig.cs file includes the config.MapHttpAttributeRoutes(); line.
In addition, as you can see in my code above, I've included the appropriate routing. However, I'm still getting an error telling me that it's returning two conflicting API calls.
To pass parameters to a WebApi controller you need to add a [Route()] attribute to that controller and mark the part of the link that's used as the attribute with this {}.
To return reviews that only match the passed in parameter you need to use LINQ to filter the data.
Here is an example:
[Route("api/getFoo/{id}")]
public IHttpActionResult GetFoo(int id)
{
return db.Foo.Where(x => x.Id == id);
}
The {id} part of the string represents the id that will be in the url in your browser: http://localhost:51361/api/getFoo/2. The "2" in the url IS the {id} property that you marked in your [Route("api/getFoo/{id}")] attribute.
I also modified your code:
public class ReviewController : ApiController
{
...
[Route("api/review/site/{siteId}")]
[HttpGet]
public IEnumerable<Review> FindStoreBySite(int SiteID)
{
return db.Review.Where(review => review.Id == SiteID);
}
...
Your request url should look somewhat like this: http://localhost:51361/api/review/site?SiteID=2
This can be difficult to wrap your head around at first but you'll get used to it eventually. It's how arguments are passed to Controller Action parameters.
if you want to get parameters for GET, it's like a simple overload, but if it's done, POST is with [fromBody], because the URL is in the tag [Route ("/abc/123/{id}")]
example
code
[Route ("/abc/123/{idSite}")]
[HttpGet]
public HttpResponseMessage ControllerIdSite(int IdSite){
//CODE . . .
return Request.CreateResponse<int>(HttpStatusCode.OK, IdSite);
}
call
/abc/123/17
return
17
OR
[Route ("/abc/123")]
[HttpGet]
public HttpResponseMessage ControllerIdSite(int IdSite){
//CODE . . .
return Request.CreateResponse<int>(HttpStatusCode.OK, IdSite);
}
call
/abc/123?IdSite=17
return
17
I would like to restrict my Web API endpoints to certain HTTP methods, such as GET and POST. I have searched the internet and I found out that you can add either [HttpGet] or [HttpPost] above your method as follows:
[HttpPost]
public ActionResult Login(string userName, string password) {
// do login stuff
return View();
}
Now I want to test if the example above with [HttpPost] really works so I use postman to send a HTTP request to my Web API. I fill in the URI and set the method to GET. The response I get is as follows:
{
"message": "The requested resource does not support http method 'POST'."
}
I'm able to verify that adding [HttpPost] prevents me from using HTTP GET requests.
What I would like to do now is log the event whenever an user tries to sent GET requests when the application is expecting POST, and vice versa. I could implement something for every single method but this would take a lot of time and it wouldn't be easy to make changes once it's been implemented. So I would like to filter it globally or something.
For example something like:
class MethodRequestFilter : SomeGlobalMethodFilter
{
public override void Filter(SomeRequestContext requestContext)
{
if (usedRequestMethod.isNotValid == true)
{
//implementation for logging
}
}
}
But ofcourse I haven't been able to find this yet within the libraries of .Net. How can I log the event globally whenever a user tries to make a request that isn't a supported method?
Greetings,
Damien.
One way is to using common base controller, to implement you need to add one base controller which would inherited from ApiController
public class BaseController : ApiController
{
public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = await base.ExecuteAsync(controllerContext, cancellationToken);
if (!response.IsSuccessStatusCode) // or if(response.StatusCode == HttpStatusCode.BadRequest)
{
//log here
}
return response;
}
catch(Exception ex)
{
return await InternalServerError(ex).ExecuteAsync(cancellationToken);
}
}
}
Now, let's assume that you're having ValuesController and Login method and it supports only POST, here your all other controllers inherit from BaseController instead ApiController
public class ValuesController : BaseController
{
[HttpPost]
public void Login([FromBody]string value)
{
}
}
So, once you call your login method, it'll call BaseController method first and you will get response there.
Hope this helps!
Thanks to the user div I was able to solve my problem by using a base controller that implements logging. These are the steps that I had to take:
Create a new controller class called BaseController and inherit ApiController:
Override the ExecuteAsync method from ApiController:
Add an implementation for logging in the catch clause
Inherit the new BaseController in every controller class that you would like to have logging functionality.
The code that I used in my implementation:
public class BaseController : ApiController
{
public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
try
{
HttpResponseMessage response = await base.ExecuteAsync(controllerContext, cancellationToken);
return response;
}
catch (HttpResponseException ex)
{
if (ex.Response.StatusCode == HttpStatusCode.MethodNotAllowed)
{
//Logging implementation
}
return Request.CreateErrorResponse(ex.Response.StatusCode, ex.Message);
}
}
}
If there is any way to make my code better, please let me know :)
I'm looking for the correct way to return JSON with a HTTP status code in my .NET Core Web API controller. I use to use it like this:
public IHttpActionResult GetResourceData()
{
return this.Content(HttpStatusCode.OK, new { response = "Hello"});
}
This was in a 4.6 MVC application but now with .NET Core I don't seem to have this IHttpActionResult I have ActionResult and using like this:
public ActionResult IsAuthenticated()
{
return Ok(Json("123"));
}
But the response from the server is weird, as in the image below:
I just want the Web API controller to return JSON with a HTTP status code like I did in Web API 2.
The most basic version responding with a JsonResult is:
// GET: api/authors
[HttpGet]
public JsonResult Get()
{
return Json(_authorRepository.List());
}
However, this isn't going to help with your issue because you can't explicitly deal with your own response code.
The way to get control over the status results, is you need to return a ActionResult which is where you can then take advantage of the StatusCodeResult type.
for example:
// GET: api/authors/search?namelike=foo
[HttpGet("Search")]
public IActionResult Search(string namelike)
{
var result = _authorRepository.GetByNameSubstring(namelike);
if (!result.Any())
{
return NotFound(namelike);
}
return Ok(result);
}
Note both of these above examples came from a great guide available from Microsoft Documentation: Formatting Response Data
Extra Stuff
The issue I come across quite often is that I wanted more granular control over my WebAPI rather than just go with the defaults configuration from the "New Project" template in VS.
Let's make sure you have some of the basics down...
Step 1: Configure your Service
In order to get your ASP.NET Core WebAPI to respond with a JSON Serialized Object along full control of the status code, you should start off by making sure that you have included the AddMvc() service in your ConfigureServices method usually found in Startup.cs.
It's important to note thatAddMvc() will automatically include the Input/Output Formatter for JSON along with responding to other request types.
If your project requires full control and you want to strictly define your services, such as how your WebAPI will behave to various request types including application/json and not respond to other request types (such as a standard browser request), you can define it manually with the following code:
public void ConfigureServices(IServiceCollection services)
{
// Build a customized MVC implementation, without using the default AddMvc(), instead use AddMvcCore().
// https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs
services
.AddMvcCore(options =>
{
options.RequireHttpsPermanent = true; // does not affect api requests
options.RespectBrowserAcceptHeader = true; // false by default
//options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
//remove these two below, but added so you know where to place them...
options.OutputFormatters.Add(new YourCustomOutputFormatter());
options.InputFormatters.Add(new YourCustomInputFormatter());
})
//.AddApiExplorer()
//.AddAuthorization()
.AddFormatterMappings()
//.AddCacheTagHelper()
//.AddDataAnnotations()
//.AddCors()
.AddJsonFormatters(); // JSON, or you can build your own custom one (above)
}
You will notice that I have also included a way for you to add your own custom Input/Output formatters, in the event you may want to respond to another serialization format (protobuf, thrift, etc).
The chunk of code above is mostly a duplicate of the AddMvc() method. However, we are implementing each "default" service on our own by defining each and every service instead of going with the pre-shipped one with the template. I have added the repository link in the code block, or you can check out AddMvc() from the GitHub repository..
Note that there are some guides that will try to solve this by "undoing" the defaults, rather than just not implementing it in the first place... If you factor in that we're now working with Open Source, this is redundant work, bad code and frankly an old habit that will disappear soon.
Step 2: Create a Controller
I'm going to show you a really straight-forward one just to get your question sorted.
public class FooController
{
[HttpPost]
public async Task<IActionResult> Create([FromBody] Object item)
{
if (item == null) return BadRequest();
var newItem = new Object(); // create the object to return
if (newItem != null) return Ok(newItem);
else return NotFound();
}
}
Step 3: Check your Content-Type and Accept
You need to make sure that your Content-Type and Accept headers in your request are set properly. In your case (JSON), you will want to set it up to be application/json.
If you want your WebAPI to respond as JSON as default, regardless of what the request header is specifying you can do that in a couple ways.
Way 1
As shown in the article I recommended earlier (Formatting Response Data) you could force a particular format at the Controller/Action level. I personally don't like this approach... but here it is for completeness:
Forcing a Particular Format If you would like to restrict the response formats for a specific action you can, you can apply the
[Produces] filter. The [Produces] filter specifies the response
formats for a specific action (or controller). Like most Filters, this
can be applied at the action, controller, or global scope.
[Produces("application/json")]
public class AuthorsController
The [Produces] filter will force all actions within the
AuthorsController to return JSON-formatted responses, even if other
formatters were configured for the application and the client provided
an Accept header requesting a different, available format.
Way 2
My preferred method is for the WebAPI to respond to all requests with the format requested. However, in the event that it doesn't accept the requested format, then fall-back to a default (ie. JSON)
First, you'll need to register that in your options (we need to rework the default behavior, as noted earlier)
options.RespectBrowserAcceptHeader = true; // false by default
Finally, by simply re-ordering the list of the formatters that were defined in the services builder, the web host will default to the formatter you position at the top of the list (ie position 0).
More information can be found in this .NET Web Development and Tools Blog entry
You have predefined methods for most common status codes.
Ok(result) returns 200 with response
CreatedAtRoute returns 201 + new resource URL
NotFound returns 404
BadRequest returns 400 etc.
See BaseController.cs and Controller.cs for a list of all methods.
But if you really insist you can use StatusCode to set a custom code, but you really shouldn't as it makes code less readable and you'll have to repeat code to set headers (like for CreatedAtRoute).
public ActionResult IsAuthenticated()
{
return StatusCode(200, "123");
}
With ASP.NET Core 2.0, the ideal way to return object from Web API (which is unified with MVC and uses same base class Controller) is
public IActionResult Get()
{
return new OkObjectResult(new Item { Id = 123, Name = "Hero" });
}
Notice that
It returns with 200 OK status code (it's an Ok type of ObjectResult)
It does content negotiation, i.e. it'll return based on Accept header in request. If Accept: application/xml is sent in request, it'll return as XML. If nothing is sent, JSON is default.
If it needs to send with specific status code, use ObjectResult or StatusCode instead. Both does the same thing, and supports content negotiation.
return new ObjectResult(new Item { Id = 123, Name = "Hero" }) { StatusCode = 200 };
return StatusCode( 200, new Item { Id = 123, Name = "Hero" });
or even more fine grained with ObjectResult:
Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection myContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { System.Net.Mime.MediaTypeNames.Application.Json };
String hardCodedJson = "{\"Id\":\"123\",\"DateOfRegistration\":\"2012-10-21T00:00:00+05:30\",\"Status\":0}";
return new ObjectResult(hardCodedJson) { StatusCode = 200, ContentTypes = myContentTypes };
If you specifically want to return as JSON, there are couple of ways
//GET http://example.com/api/test/asjson
[HttpGet("AsJson")]
public JsonResult GetAsJson()
{
return Json(new Item { Id = 123, Name = "Hero" });
}
//GET http://example.com/api/test/withproduces
[HttpGet("WithProduces")]
[Produces("application/json")]
public Item GetWithProduces()
{
return new Item { Id = 123, Name = "Hero" };
}
Notice that
Both enforces JSON in two different ways.
Both ignores content negotiation.
First method enforces JSON with specific serializer Json(object).
Second method does the same by using Produces() attribute (which is a ResultFilter) with contentType = application/json
Read more about them in the official docs. Learn about filters here.
The simple model class that is used in the samples
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
The easiest way I came up with is :
var result = new Item { Id = 123, Name = "Hero" };
return new JsonResult(result)
{
StatusCode = StatusCodes.Status201Created // Status code here
};
This is my easiest solution:
public IActionResult InfoTag()
{
return Ok(new {name = "Fabio", age = 42, gender = "M"});
}
or
public IActionResult InfoTag()
{
return Json(new {name = "Fabio", age = 42, gender = "M"});
}
Awesome answers I found here and I also tried this return statement see StatusCode(whatever code you wish) and it worked!!!
return Ok(new {
Token = new JwtSecurityTokenHandler().WriteToken(token),
Expiration = token.ValidTo,
username = user.FullName,
StatusCode = StatusCode(200)
});
Instead of using 404/201 status codes using enum
public async Task<IActionResult> Login(string email, string password)
{
if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password))
{
return StatusCode((int)HttpStatusCode.BadRequest, Json("email or password is null"));
}
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return StatusCode((int)HttpStatusCode.BadRequest, Json("Invalid Login and/or password"));
}
var passwordSignInResult = await _signInManager.PasswordSignInAsync(user, password, isPersistent: true, lockoutOnFailure: false);
if (!passwordSignInResult.Succeeded)
{
return StatusCode((int)HttpStatusCode.BadRequest, Json("Invalid Login and/or password"));
}
return StatusCode((int)HttpStatusCode.OK, Json("Sucess !!!"));
}
Controller action return types in ASP.NET Core web API
02/03/2020
6 minutes to read
+2
By Scott Addie Link
Synchronous action
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
if (!_repository.TryGetProduct(id, out var product))
{
return NotFound();
}
return product;
}
Asynchronous action
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
if (product.Description.Contains("XYZ Widget"))
{
return BadRequest();
}
await _repository.AddProductAsync(product);
return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
Please refer below code, You can manage multiple status code with different type JSON
public async Task<HttpResponseMessage> GetAsync()
{
try
{
using (var entities = new DbEntities())
{
var resourceModelList = entities.Resources.Select(r=> new ResourceModel{Build Your Resource Model}).ToList();
if (resourceModelList.Count == 0)
{
return this.Request.CreateResponse<string>(HttpStatusCode.NotFound, "No resources found.");
}
return this.Request.CreateResponse<List<ResourceModel>>(HttpStatusCode.OK, resourceModelList, "application/json");
}
}
catch (Exception ex)
{
return this.Request.CreateResponse<string>(HttpStatusCode.InternalServerError, "Something went wrong.");
}
}
What I do in my Asp Net Core Api applications it is to create a class that extends from ObjectResult and provide many constructors to customize the content and the status code.
Then all my Controller actions use one of the costructors as appropiate.
You can take a look at my implementation at:
https://github.com/melardev/AspNetCoreApiPaginatedCrud
and
https://github.com/melardev/ApiAspCoreEcommerce
here is how the class looks like(go to my repo for full code):
public class StatusCodeAndDtoWrapper : ObjectResult
{
public StatusCodeAndDtoWrapper(AppResponse dto, int statusCode = 200) : base(dto)
{
StatusCode = statusCode;
}
private StatusCodeAndDtoWrapper(AppResponse dto, int statusCode, string message) : base(dto)
{
StatusCode = statusCode;
if (dto.FullMessages == null)
dto.FullMessages = new List<string>(1);
dto.FullMessages.Add(message);
}
private StatusCodeAndDtoWrapper(AppResponse dto, int statusCode, ICollection<string> messages) : base(dto)
{
StatusCode = statusCode;
dto.FullMessages = messages;
}
}
Notice the base(dto) you replace dto by your object and you should be good to go.
I got this to work. My big issue was my json was a string (in my database...and not a specific/known Type).
Ok, I finally got this to work.
////[Route("api/[controller]")]
////[ApiController]
////public class MyController: Microsoft.AspNetCore.Mvc.ControllerBase
////{
//// public IActionResult MyMethod(string myParam) {
string hardCodedJson = "{}";
int hardCodedStatusCode = 200;
Newtonsoft.Json.Linq.JObject job = Newtonsoft.Json.Linq.JObject.Parse(hardCodedJson);
/* "this" comes from your class being a subclass of Microsoft.AspNetCore.Mvc.ControllerBase */
Microsoft.AspNetCore.Mvc.ContentResult contRes = this.Content(job.ToString());
contRes.StatusCode = hardCodedStatusCode;
return contRes;
//// } ////end MyMethod
//// } ////end class
I happen to be on asp.net core 3.1
#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
//C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\3.1.0\ref\netcoreapp3.1\Microsoft.AspNetCore.Mvc.Core.dll
I got the hint from here :: https://www.jianshu.com/p/7b3e92c42b61
The cleanest solution I have found is to set the following in my ConfigureServices method in Startup.cs (In my case I want the TZ info stripped. I always want to see the date time as the user saw it).
services.AddControllers()
.AddNewtonsoftJson(o =>
{
o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
});
The DateTimeZoneHandling options are Utc, Unspecified, Local or RoundtripKind
I would still like to find a way to be able to request this on a per-call bases.
something like
static readonly JsonMediaTypeFormatter _jsonFormatter = new JsonMediaTypeFormatter();
_jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{DateTimeZoneHandling = DateTimeZoneHandling.Unspecified};
return Ok("Hello World", _jsonFormatter );
I am converting from ASP.NET and there I used the following helper method
public static ActionResult<T> Ok<T>(T result, HttpContext context)
{
var responseMessage = context.GetHttpRequestMessage().CreateResponse(HttpStatusCode.OK, result, _jsonFormatter);
return new ResponseMessageResult(responseMessage);
}
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
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.