After a lengthy discussion in terms of how our API would need to behave due to limitations of the backend design, I'd like to have the following possibilities:
1. /students/251/employment <--- allow GET, PUT, POST
2. /students/251/employment/jobs <--- allow GET only
3. /students/251/employment/jobs/435 <--- allow all verbs
4. /students/251/employment/internships <--- allow GET only
5. /students/251/employment/internships/664 <--- allow all verbs
These cases are working for GET requests. I'm struggling when I try to do a PUT request for case #1 and #3:
Case #1 Error
No HTTP resource was found that matches the request URI '/students/251/employment/221'.,
No action was found on the controller 'Employment' that matches the name '221'.
Case #3 Error
The requested resource does not support http method 'PUT'.
Here's an abridged version of my controller methods:
public ApiEmploymentGetResult Get(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiJob> Jobs(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiOwnVenture> OwnVenture(long id) {
// code omitted
}
public void Put(long id, MyModel model) {
// breaks before getting here
}
My routing looks like this, but I'm not sure it's quite right, even though the GETs are working.
context.Routes.MapHttpRoute(
name: "V1/EmploymentApi",
routeTemplate: "api/v1/Employment/{action}/{jobId}",
defaults: new { controller = "Employment", jobId = RouteParameter.Optional, action = "Get" }
);
Case #1 seems to be conflicting due to the framework expecting an action rather than the 221. I'd like to be able to get all of these cases working.
You may want to look at Attribute Routing (Web API 1 and Web API 2).
public class StudentsController : ApiController
{
[HttpPut]
[Route("students/{studentId}/employment")]
public void UpdateStudentEmployment(int studentId) { ... }
[HttpPut]
[Route("students/{studentId}/employment/jobs/{jobId}")]
public void UpdateStudentEmploymentJob(int studentId, int jobId) { ... }
}
Related
Hey i am having a big trouble updating data in my client side REST application.
I made a Web API controller.
// PUT: api/Contacts/5
[ResponseType(typeof(void))]
public IHttpActionResult PutContact(Contact contact, int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != contact.ContactId)
{
return BadRequest();
}
_contactService.Update(contact);
return StatusCode(HttpStatusCode.NoContent);
}
And also client side service method:
public async Task<T> PutData<T>(T data, int dataId)
{
HttpResponseMessage resp = await this._client.PutAsJsonAsync(_serviceUrl + "/" + dataId, data);
resp.EnsureSuccessStatusCode();
return await resp.Content.ReadAsAsync<T>();
}
Service URL shows in debug mode that i goes to endpoint:
http://localhost:21855/api/Contacts/8
But it does not even go to breakpoint when i debug my server controller PutContact method.
What i am doint wrong? I need to update the data but i cant, because my client-side application won't even go to servers breakpoint on debug mode!!!
It gives me an error response 405 : Method not allowed
You can't have two different body parameters in the same method.
What you need to do is to set the id parameter to come from the URI and the Contact parameter from the body, like this:
public IHttpActionResult PutContact([FromBody]Contact contact, [FromUri]int id)
{
// method code
}
BTW, I suppose you have a GET method in your controller which looks like this:
public IHttpActionResult GetContact(int id)
{
// method code
return Contact; // pseudo-code
}
The error you getting comes from the fact that the system is not really calling your PUT method but the GET one (the system is ignoring the Contact parameter for the reason I expressed before): calling a GET method with a PUT verb results in a 405 Method Not Allowed exception.
Before I updated from Web API 5.0.0-beta2 to 5.0.0-rc1 I could do something like this:
[RoutePrefix("api/v1/test")]
public class TestController : ApiController
{
[HttpGet]
public TestString Get()
{
return new TestString { str = "HELLO WORLD" };
}
}
so when I went to the URL /api/v1/test it would land on the Get() function.
After updating to Web API 5.0.0-rc1 I get a 404 when going to /api/v1/test
However, this works:
[RoutePrefix("api/v1")]
public class TestController : ApiController
{
[HttpGet("test")]
public TestString Get()
{
return new TestString { str = "HELLO WORLD" };
}
}
Can you explain why this doesn't work any longer?
** EDIT **
[HttpGet("")] works. Then it breaks on that Get() function.
I am not sure but I believe the Http[Get, Post, etc] type attributes have had their routing properties removed. This link hints at it:
http://blogs.microsoft.co.il/blogs/bnaya/archive/2013/08/28/asp-net-web-api-attribute-based-routing.aspx
be aware that most of the Attribute based Routing samples available
today on the web, is using old attribute like [PUT] or [HttpPut] which
is no longer supported on the latest bit (currently available from the
ASP.NET nightly build, http://www.myget.org/F/aspnetwebstacknightly/
), those attributes ware replaced with [Route] attribute.
Please see https://aspnetwebstack.codeplex.com/SourceControl/list/changesets and https://aspnetwebstack.codeplex.com/workitem/1206. Basically, the goal is to separate verb filters from attribute routing.
I have just downloaded AttributeRouting NuGet package for WebAPI and having a problem within my controller.
I thought the way to use it was to have something like:
public class InboxController : ApiController
{
private IInboxService _inboxService;
public InboxController(IInboxService inboxService)
{
_inboxService = inboxService;
}
public IEnumerable<MessageModel> GetAll()
{
return _inboxService.GetAllMessages();
}
[HttpGet("Inbox/Count")]
public int GetInboxCount()
{
return _inboxService.GetMessageCount();
}
}
However I get the following error:
Error 2 'System.Web.Http.HttpGetAttribute' does not contain a constructor that takes 1 arguments
I need to get this up and running fairly quickly. Is there any reason why the HttpGet attribute doesn't have an overloaded constructor?
UPDATE
[GET("Inbox/EnquiryCount")]
public EnquiryCountModel GetEnquiryCounts()
{
var model = new EnquiryCountModel();
model.EnquiryCount = _inboxService.GetCustomerEnquiriesCount();
model.EnquiryResponseCount = _inboxService.GetCustomerEnquiryResponseCount();
return model;
}
In routes:
routes.MapHttpRoute("InboxEnquiryApi", "api/inbox/{action}", new { Controller = "Inbox" }, null, new WebApiAuthenticationHandler(GlobalConfiguration.Configuration));
When I hit the URL at 'api/inbox/EnquiryCount' I get the this error:
**No HTTP resource was found that matches the request URI 'http://localhost:49597/api/inbox/enquirycount'**
Attribute routing is supported in Web api 2
Here is the details: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
This syntax has been changed in newer versions of the webapi. The [HTTPPOST] is now standalone and there is a new attribute for the route aptly name ROUTE which takes the route url
eg.
[Route("GetRes/{month}")]
I am using Visual Studio 2012 RC. I am using the default routes and have the following Web API controller:
public class FooController : ApiController
{
// GET api/foo
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/foo/5
public string Get(int id)
{
return "value";
}
// POST api/foo
public string Post(string abc)
{
Console.WriteLine("value: {0}", abc);
return "foo" + abc;
}
// PUT api/foo/5
public void Put(int id, string value)
{
}
// DELETE api/foo/5
public void Delete(int id)
{
}
}
I wanted to do a simple test of POST in Fiddler, so I have
Request Headers
User-Agent: Fiddler
Content-Type: application/json
Request Body
{"abc": "def"}
When I debug the request, the parameter abc comes in as null, not "def". Is there something wrong with my Fiddler syntax?
(1) By default, simple types are taken from the URI. To read a simple type from the request body, add the [FromBody] attribute to the parameter.
public string Post([FromBody] string abc)
(2) '{"abc": "def"}' defines an object with a property named "abc" - to send a JSON string, the request body should just be "def"
This answer comes from a link on the ASP.Net Web API site sending-html-form-data , which turns out to be Mike's blog post (I didn't realize that at first). The Web API team has made a few decisions with parameter binding that are quite different from normal MVC controllers.
The correct syntax for sending "simple types" is
public HttpResponseMessage PostSimple([FromBody] string value)
{
// code goes here
And in Fiddler, you put
=def //THIS CANNOT HAVE QUOTES AND = IS MANDATORY
OK, so here are the parts that work very differently from MVC.
You must use [FromBody], as Mike says.
You can only have 1 parameter. If you want more than 1 parameter, you have 2 choices: i) use url query parameters, instead of request body or ii) use a complex object (i.e. your own class).
The request body should be a simple =def and cannot use named parameters.
I have the below route mapped in my AreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
if (context != null)
{
context.Routes.MapHttpRoute(
name: "API_Default",
routeTemplate: "Areas/Test/AIO/api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
});
When I look at the Global.asax file, I can see the HttpRoute is being Registered, and is listed in the RouteTable.Routes as a {System.Web.Http.WebHost.Routing.HttpWebRoute}.
Problem is, when I go to the url... https://myRoot/Areas/Test/AIO/api/AioApi/test or https://myRoot/Areas/Test/AIO/api/AioApi, it's giving me a 500 internal server error.
I'm not sure how to view the actual error, when stepping thru the code I cannot see anything after it leaves Application_BeginRequest.
My controller code:
public class AioApiController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Any insight as to why I cannot hit the API controller? I can hit my regular MVC controller in the same context.
Any help is greatly appreciated!
One thing I found out with WebApi is that there are two Route collections: System.Web.Routing.RouteCollection and System.Web.Http.HttpRouteCollection. I believe (but can't remember) that you need to use the latter in order for your ApiController derivations to work properly (luckily the syntax is the same).