I have attribute routing on all the actions on my controller (though there is a default conventional route defined). I have (I think) tried every combination of using [HttpPost] or not, and using [FromBody] or not.
If I try to use an attribute with only the name of the controller defined...
[HttpPost]
[Route("Relationships")]
private async Task<IHttpActionResult> PostRelationship([FromBody]Relationship relationship)
Request URL:http://localhost:51599/api/Relationships
Request method:POST
... I get a 405.
It appears to be using the attribute route- debugging GetRouteData() shows a subroute, that's resolving to the action "GetController".
subroutes {System.Web.Http.Routing.IHttpRouteData[1]} System.Collections.Generic.IEnumerable<System.Web.Http.Routing.IHttpRouteData> {System.Web.Http.Routing.IHttpRouteData[]}
- [0] {System.Web.Http.Routing.HttpRouteData} System.Web.Http.Routing.IHttpRouteData {System.Web.Http.Routing.HttpRouteData}
- Route {System.Web.Http.Routing.HttpRoute} System.Web.Http.Routing.IHttpRoute {System.Web.Http.Routing.HttpRoute}
+ Constraints Count = 0 System.Collections.Generic.IDictionary<string, object> {System.Web.Http.Routing.HttpRouteValueDictionary}
- DataTokens Count = 2 System.Collections.Generic.IDictionary<string, object> {System.Web.Http.Routing.HttpRouteValueDictionary}
- [0] {[actions, System.Web.Http.Controllers.HttpActionDescriptor[]]} System.Collections.Generic.KeyValuePair<string, object>
Key "actions" string
- Value {System.Web.Http.Controllers.HttpActionDescriptor[1]} object {System.Web.Http.Controllers.HttpActionDescriptor[]}
- [0] {System.Web.Http.Controllers.ReflectedHttpActionDescriptor} System.Web.Http.Controllers.HttpActionDescriptor {System.Web.Http.Controllers.ReflectedHttpActionDescriptor}
+ ActionBinding {System.Web.Http.Controllers.HttpActionBinding} System.Web.Http.Controllers.HttpActionBinding
ActionName "GetRelationships" string
If I try to use a route with the action name...
[HttpPost]
[Route("Relationships/PostRelationship")]
private async Task<IHttpActionResult> PostRelationship([FromBody]Relationship relationship)
http://localhost:51599/api/Relationships/PostRelationship
Request method:POST
... I get a 404.
In this case it appears to be falling through to the default controller- debugging GetRouteData() shows that it thinks I'm supplying "controller" and "id".
routeData {System.Web.Routing.RouteData} System.Web.Routing.RouteData
+ DataTokens {System.Web.Routing.RouteValueDictionary} System.Web.Routing.RouteValueDictionary
+ Route {System.Web.Http.WebHost.Routing.HttpWebRoute} System.Web.Routing.RouteBase {System.Web.Http.WebHost.Routing.HttpWebRoute}
+ RouteHandler {System.Web.Http.WebHost.HttpControllerRouteHandler} System.Web.Routing.IRouteHandler {System.Web.Http.WebHost.HttpControllerRouteHandler}
- Values {System.Web.Routing.RouteValueDictionary} System.Web.Routing.RouteValueDictionary
Count 2 int
- Keys Count = 2 System.Collections.Generic.Dictionary<string, object>.KeyCollection
[0] "controller" string
[1] "id" string
- Values Count = 2 System.Collections.Generic.Dictionary<string, object>.ValueCollection
[0] "Relationships" object {string}
[1] "PostRelationship" object {string}
Here's the whole controller:
[RoutePrefix("api")]
public class RelationshipsController : ApiController
{
private CMDBContext db = new CMDBContext();
[Route("Relationships")]
public IQueryable<Relationship> GetRelationships()
{
return db.Relationships;
}
[Route("Relationships/{id:int}")]
private async Task<Relationship> GetRelationship(int id)
{
Relationship relationship = await db.Relationships.FindAsync(id);
if (relationship == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return relationship;
}
[HttpPost]
[Route("Relationships/PostRelationship")]
private async Task<IHttpActionResult> PostRelationship([FromBody]Relationship relationship)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Relationships.Add(relationship);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = relationship.Id }, relationship);
}
[HttpGet]
[Route("Relationships/RelTypes")]
public Dictionary<string, string> RelTypes() // returns a list of available Relationship types
{
var RelTypesDict = new Dictionary<string, string>();
foreach (var type in Relationship.RelTypes)
{
RelTypesDict.Add(type, Regex.Replace(type, @"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1")); // expands camel case with spaces for display
}
return RelTypesDict;
}
private bool RelationshipExists(int id)
{
return db.Relationships.Count(e => e.Id == id) > 0;
}
}
and the WebApiConfig:
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and the AJAX I'm using to call it:
async function postEdge() { // persists a new edge to data store from form
var ItemLeftId = $('#ItemLeftId').val();
var ItemRightId = $('#ItemRightId').val();
var RelType = $('#ddlRelType').val();
var Description = $('#Description').val();
$.ajax({
method: "POST",
contentType: "application/json; charset=utf-8",
url: '/api/Relationships',
data: {
Id: 0, // TESTING
ItemLeftId: ItemLeftId,
ItemRightId: ItemRightId,
RelType: RelType,
Description: Description,
},