I try to implement an ASP.NET Web Api Controller that supports the JsonApi standard (http://jsonapi.org/ primarily used by Ember.js)
The URL's may contain a dash. But the corresponding method name in the C# code may not contain a dash.
My javascript try to POST to
http://localhost:50000/jsonapi/activity-exercises
But I am not able to implement an endpoint that can receive that request.
I have tried:
[HttpPost]
public HttpResponseMessage ActivityExercises([FromBody] ActivityExerciseEntry value)
{
// ...
Ideally there should be an attribute to add to the method to specify the mapped action name in the URL. Does such attribute exist?
My route mapping looks like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute("EmberJsonApi", "jsonapi/{action}/{id}", new { controller = "JsonApi", id = RouteParameter.Optional });
routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
}
I figured it out. It took a lot of googling, because this is not very well documented....
As I suspected, there exists an attribute I could use: [ActionName]
So the endpoind definition just looks looks like this:
[HttpPost]
[ActionName("activity-exercises")]
public HttpResponseMessage ActivityExercises([FromBody] ActivityExerciseEntry value)
{
...
Related
Being a noob in MVC web api there is probably something very obvious I'm missing..
However, In my ProjectController I have the following method with attributes (I know this kind of method should be POST but just testing easily...):
[Route("api/projects/archive/{id:int}")]
[HttpGet]
public void Archive(int id)
{
_projectService.Archive(id);
}
However, when I open my URL such as:
http://localhost:49923/api/projects/archive/1
The page simply redirects to the current URL, and the archive method is not called. I also have a breakpoint at the method to verify it's not hit.
My best guess is I also have to update my web api route config which is the default, but I just assumed the route attribute was enough?
Here is my web api route config:
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"));
}
What am I doing wrong here? :-)
EDITS:
Clearification 1 - my ProjectController:
public class ProjectsController : ApiController
{
private ProjectService _projectService;
public ProjectsController()
{
_projectService = new ProjectService();
}
[Route("api/projects/archive/{id:int}")]
[HttpGet]
public void Archive(int id)
{
_projectService.Archive(id);
}
}
Clearification 2 - redirect:
So lets say I stand on the homepage (/). I then go to the URL "http://localhost:49923/api/projects/archive/1", it will just reload page and leave my back at the home page.
The Web API config is configured correctly.
Ensure that the controller and the action are constructed properly
public class ProjectController : ApiController {
//...other code removed for brevity
[HttpGet]
[Route("api/projects/archive/{id:int}")]//Matches GET api/projects/archive/1
public IHttpActionResult Archive(int id) {
_projectService.Archive(id);
return Ok();
}
}
Its bit late to answer but hope you find it useful,
Many times the way how we write the code help us find the solution to the problem,
As Nkosi already answered, that the constructing controller and the action method properly will resolve the issue.
Its always helpful to check the method first rather then looking at the route.config because by default it will be the same unless you provide your custom attribute.
I am trying to build out a new endpoint in API app that already has a lot of other endpoints working just fine. Not sure what I'm doing wrong. I am getting two errors:
Message: No HTTP resource was found that matches the request URI 'http://localhost:62342/api/VoiceMailStatus
and
MessageDetail: No action was found on the controller 'VoiceMailStatus' that matches the request.
Here's the controller:
public class VoiceMailStatusController : ControllerBase
{
[HttpPost]
[Route("api/VoiceMailStatus")]
public string VoiceMailStatus(string var)
{
...
}
}
And here's the route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I'm using PostMan:
There are a LOT of threads here about both of these error messages. I've read many of them, but have yet to find a solution. One of them said to change this:
public string VoiceMailStatus(string var)
to this:
public string VoiceMailStatus(string var = "")
And while that did get the error to go away and I was able to get inside of the method while in debug, var was always just an empty string.
EDIT: GOT IT WORKING
In addition to adding [FromBody] as per Andrii Litvinov's answer, I also had to do one more thing. What had been this:
public string VoiceMailStatus(string var)
{
...
}
Is now this:
public string VoiceMailStatus([FromBody] VMStatus request)
{
...
}
And then VMStatus is just a small little class with a single string property:
public class VMStatus
{
public string var { get; set; }
}
Most likely you need to apply FromBody attribute to your parameter:
public string VoiceMailStatus([FromBody] string var)
If that does not help try to rename parameter to something else, e.g.: var1, because var is reserved work in C# and could cause some binding issues, but I doubt that's the case.
You probably want to add this to your controller since you have api in the route config:
[RoutePrefix("API/VoiceMailStatus")]
and then add this to your action:
[Route("VoiceMailStatus", Name = "VoiceMailStatus")]
This should tie the action to the url localhost/api/voicemailstatus/voicemailstatus
I asked a preliminary and somewhat similar question here.
Now I need to know how to access the values from within a RESTful URL inside a Controller method/action.
I have a Controller named PlatypusController with a route for it set up like this in WebApiConfig:
config.Routes.MapHttpRoute(
name: "ReportsApi",
routeTemplate: "api/{controller}/{unit}/{begindate}/{enddate}",
defaults: new { enddate = RouteParameter.Optional }
);
PlatypusController.cs has this code:
public void Post()
{
int i = 2;
}
The "int i = 2" is nonsense, of course; I just put it there so I could put a breakpoint in the method to verify it was reached. It is when I select "POST" and enter this URL in Postman:
http://localhost:52194/api/Platypus/gramps/201509
(the app is running on port 52194 when I call this)
But to accomplish something worthwhile, I need the "gramps" and the "201509" from the URL. How can I access those? Do I need to add them to the Post method something like this:
public void Post(string unit, string begindate)
{
DoSomething_Anything(unit, begindate);
}
...or is there some other way to get them, such as from a HttpRequest object or something?
I personally prefer to be explicit when defining my routes and that's why I recommend attribute routing instead of convention based routing.
That way, you can explicitly configure routing per controller and action.
Instead of configuring routes this way in the WebApiConfig, just make sure that you have initialized attribute routing by calling this line:
config.MapHttpAttributeRoutes();
in the WebApiConfig file.
Then you can do something like this:
[RoutePrefix("api/platypus")]
public class PlatypusController: ApiController
{
[Route("{unit}/{begindate}")]
[HttpPost]
public void Post(string unit, string begindate)
{
int i = 2;
}
}
To call this method, do a POST request to: /api/platypus/gramps/201509
Try adding in the Post method [FromBody]
public void Post([FromBody] string unit, [FromBody] string begindate)
{
DoSomething_Anything(unit, begindate);
}
Check this for detail examples:
http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
It's as easy as 3.14; this works just dandy:
public void Post(string unit, string begindate)
{
string _unit = unit;
string _begindate = begindate;
}
_unit is "gramps" and _begindate is "201509" using this URL:
http://localhost:52194/api/Platypus/gramps/201509
...so it's just what I need.
I have a web api where I have 2 methods, one without parameter and two with different types of parameter (string and int). When calling the string method it doesnt work...what am I missing here?
public class MyControllerController : ApiController
{
public IHttpActionResult GetInt(int id)
{
return Ok(1);
}
public IHttpActionResult GetString(string test)
{
return Ok("it worked");
}
}
WebApiConfig.cs:
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 }
);
}
My Call:
/api/MyController/MyString //Doesnt work
/api/MyController/1 //work
I get following error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Http.IHttpActionResult GetInt(Int32)' in 'TestAngular.Controllers.MyControllerController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
What am I missing in my request?
Here is my solution:
without changing default route in webapiconfig.cs file
add just a route to your string function:
[Route("Api/MyController/GetString/{test}")]
public IHttpActionResult GetString(string test)
http://localhost:49609/api/MyController/GetString/stringtest
Also this uri's should work:
api/MyController/GetAll
api/MyController/GetString?param=string
api/MyController/GetInt?param=1
I think this is much clearer and should always work.
You use the routing behavior.
See here:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
You have to change your uri's
/api/MyController
/api/MyController/string
/api/MyController/1
You don't have to specify the methods.
You could take look at this tutorial on asp.net for further clarification.
It's been a while since you posted this, but I think I have the answer.
First off, there are two issues. First, as Pinback noted, you can't use the same route for two different endpoints.
However if you just eliminate the int method, you'll still run into the problem.
Remember: the default route looks like this: api/{controller}/{id}
In order to bind the parameter, it has to be called "id", and not "test".
Change the signature to this:
public IHttpActionResult GetString(string id)
and it will work.
(you can also change {id} to {test} in the webapiconfig.cs file).
You can also take string parameter in body
string body;
using (var sr = new StreamReader(Request.Body))
body = sr.ReadToEnd();
Let's have a test model.
public class TestRequestModel
{
public string Text { get; set; }
public int Number { get; set; }
}
I would like this service to be able to accept the following requests:
GET /test?Number=1234&Text=MyText
POST /test with header: Content-Type: application/x-www-form-urlencoded and body: Number=1234&Text=MyText
POST /test with header: Content-Type: application/json and body: {"Text":"Provided!","Number":9876}
The routes are configured the following way:
_config.Routes.MapHttpRoute(
"DefaultPost", "/{controller}/{action}",
new { action = "Post" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
_config.Routes.MapHttpRoute(
"The rest", "/{controller}/{action}",
defaults: new { action = "Get" });
My controller looks like this:
public class TestController : ApiController
{
[HttpGet]
public TestResponseModel Get([FromUri] TestRequestModel model)
{
return Do(model);
}
[HttpPost]
public TestResponseModel Post([FromBody] TestRequestModel model)
{
return Do(model);
}
(...)
This seems like an acceptable amount of boiler plate code, but I still would like to avoid it if possible.
Having the extra route is not ideal too. I have a fear of MVC/WebAPi routes and I believe they are evil.
Is there a way to avoid having two methods and/or the DefaultPost route?
What you are asking for is not typical with ASP.NET Web API. In ASP.NET MVC, it is common to have the same action method handling the initial GET and the subsequent post back (POST). ASP.NET Web API is intended for building HTTP services and GET is used for retrieving a resource without changing anything in the system, while POST is for creating a new resource, as pointed by Matthew.
Anyway, it is not impossible to have one action method in Web API to accomplish this. But you want the same action method to not only handle GET and POST and also do the model binding and formatter binding. Model binding (similar to MVC) binds request URI, query string, etc to parameters while formatter binding (unique to web API) binds the body content to parameter. By default, simple types are bound from URI, query string and complex types from body. So, if you have an action method with parameters of string text, int number, TestRequestModel model, you can have web API bind from URI or body and in this case, you will need to check what is not empty and use that. But, such a solution will look more like a hack, unfortunately. Or if you want the same complex type to be populated from both URI/query string and body, you will need to write your own parameter binder that checks for request parts and populate the parameter accordingly.
Also, you don't need to have two route mappings. The default one like this will do.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);