Making a simple Web API post request - c#

I'm really struggling with making a basic post request in a url to support a tutorial on web api.
I want to do something like this in browser: http://localhost:59445/api/group/post/?newvalue=test and get the post to register. However I don't seem to be able to form the request correctly. What is the correct way to do this?
The error I receive is:
{"Message":"The request is invalid.","MessageDetail":"The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'twin_groupapi.Controllers.GroupController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."}
my model:
public class Group
{
public Int32 GroupID { get; set; }
public Int32 SchoolID { get; set; }
public string GroupName { get; set; }
}
routing:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
controller:
//[Route("api/Group/Post")]
[HttpPost]
public void Post([FromUri] string NewValue)
{
string newstring = NewValue;
}

Hitting a URL in your browser will only do a GET request.
You can either:
create a simple <form> with its method set to POST and form inputs to enter the values you want to send (like NewValue), OR
write some JavaScript to create an AJAX POST request using your favorite framework, OR
Use a tool like Postman to set up a POST request, invoke it, and examine the results.

The error message is most likely coming from your Get() method.
As #StriplingWarrior said you are making a GET request while the method is marked as [HttpPost]. You can see this if you use developer tools in your browser (F12 in most modern browsers to active them).
Have a look at How do I manually fire HTTP POST requests with Firefox or Chrome?
Note: the c# convention for parameter names is camelCase with first letter being common, not capital, e.g. string newValue.

Related

Implicit Verbs from Method name

If I create a webApi controller, and populate it with methods prefixed with http verbs, the Api is able to correctly imply what verb should be used on that controller.
public class TestController : ApiController
{
public string GetData()
{
return "Called Get Method";
}
public string PostData()
{
return "Called Post Method";
}
public string PutData()
{
return "Called Put Method";
}
}
If I replace the Post with Update, the Post method continues to work implicitly.
public string UpdateData()
{
return "Called Updated Method";
}
Is there a list of the possible prefixes on a method and what verb they map to? Additionally, is it possible to define custom prefixes? For instance, if I wanted to always map a method starting with "Search" to a Post, can I define this?
If you put your Routing like the following :
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Consider the routeTemplate.You'll now be able to call the actions on the controller by name,and no prefix issue should exist. This approach is very helpful if you have multiple actions on your controller with similar HTTP Verbs(multiple GET or POST say).
Implicit Verbs are a function of the built in routing, and cannot appear to be manually extended.
This Asp.Net details the specific rules around the implicit routing.
HTTP Methods. The framework only chooses actions that match the HTTP method of the request, determined as follows:
You can specify the HTTP method with an attribute: AcceptVerbs, HttpDelete, HttpGet, HttpHead, HttpOptions, HttpPatch, HttpPost, or HttpPut.
Otherwise, if the name of the controller method starts with "Get", "Post", "Put", "Delete", "Head", "Options", or "Patch", then by convention the action supports that HTTP method.
If none of the above, the method supports POST.
The reason why the UpdateData method appears to work, is because any method not implicitly determined is automatically a Post

Web API QueryString not passing second parameter to controller/action

I have a Web API application which is expected to return a list of clinical alerts for patients. The request is invoked with the following URL
http://myserver:18030/api/Alerts/search?systemId=182&patientId=T000282L
where the systemId determines the clinical information system for which the patientID value is relevant. The routing is set up as follows in WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Alertsapi",
routeTemplate: "api/Alerts/search",
defaults: new { controller = "Alerts" , action = "search"}
);
and the controller actions is as follows:
[ActionName("search")]
public List<Alert> GetAlerts(string systemId = "", string patientId = "")
{
var alerts = from a in db.Alerts
where a.alertAuthorReference.Equals(systemId)
where a.alertSubjectReference.Equals(patientId)
select a;
return alerts.ToList();
}
I was under the impression that QueryString parameters where automatically mapped to action method parameters, but in this example patientId is always null (or an empty string as I am supplying that as a default). I have tried reading the QueryString in code inside the action method, but it only has one member with key systemId.
Why isn't the second parameter being passed through?
I can work around it by using a QueryString of patientId=182:T000282L and then parsing this composite key, but I want to be able eventually to search on multiple parameters, and so may need to access a third or even fourth value from the query string.
You need to define a route for that like
routes.MapHttpRoute(
name: "GetPagedData",
routeTemplate: "api/{controller}/{action}/{pageSize}/{pageNumber}"
)
your controller will be like
[HttpGet]
("GetPagedResult")]
HttpResponseMessage GetPagedResult(int StartIndex, int PageSize)
{
// you can set default values for these parameters like StartIndex = 0 etc.
}
You can easily get what you need now with Web API 2 and Attribute Routing.
Take a look at this article:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
First you'll need to edit WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
[...]
in your case you can test it works with this inside a controller:
[Route("search")]
[HttpGet]
public string search(string systemId = "", string patientId = "")
{
return patientId;
}
and to call it:
http://myserver:18030/search?systemId=182&patientId=T000282L

Attribute routing - Enum not parsed

I'm trying to make a route, but it's not working in this case:
If I call:
http://mysite.com/api/v1/product/Generic/1A
works fine.
If I call:
http://mysite.com/api/v1/product?=Generic
works too, but when I call:
http://mysite.com/api/v1/product/Generic
I get this error:
{
"Message":"The request is invalid.",
"MessageDetail":"The parameters dictionary contains a null entry for parameter 'type'
of non-nullable type 'GType' for method 'System.Net.Http.HttpResponseMessage
Get(GType)' in 'MySite.ControllersApi.V2.ProductController'. An optional parameter must
be a reference type, a nullable type, or be declared as an optional parameter."
}
The code of my route:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{type}/{id}",
defaults: new { id = RouteParameter.Optional, version = "v1", controller = "Product" }
)
}
}
public enum GType
{
Big,
Small,
Generic,
}
And the Controller:
public HttpResponseMessage Get(GType type)
{
...
}
public HttpResponseMessage Get(GType type, string id)
{
...
}
So, Web API isn't parsing the value in the URL. Did I forget anything?
Well, I still can't see how your first url actually works, but the issue with the second url is that you are trying to pass an invalid bit of data as a parameter.
To keep it simple, I'm just going to pretend for a minute that your route definition is this:
routeTemplate: "api/{controller}/{type}/{id}",
defaults: new { id = RouteParameter.Optional }
If you were to call a GET on this url http://mysite.com/product/Generic , then you will get the same error you are running into. This url will resolve a controller of ProductController, with a parameter called type that will have a value of Generic.
However, your type parameter has an actual Type of GType, which is an enum. Generic is not a valid value for that so an error occurs. It's the same as sending "abcd" as the value for a parameter that is an int.
If you tried to call a get to http://mysite.com/product/Big, it would work, as it would be able to parse that value (Big) and a member of the GType enum.
The problem was that I have two equals routes, but with diferents parameters name.
api/{version}/{controller}/{type}/{id}
and
api/{version}/{controller}/{id}
The second must be the last route declared.
The route you show us doesn't remotely match the URLs you're trying to hit. I have a feeling you're looking at the wrong route. Also, shouldn't "/Product/" be "/{controller}/"?
Oh, and you mis-spelled routeTamplate[sic] in the snippet. I assume that was a transcription error -- I'm pretty sure the compiler would bark on that.

Api controller declaring more than one Get statement

Using the new Api Controller in MVC4, and I've found a problem. If I have the following methods:
public IEnumberable<string> GetAll()
public IEnumberable<string> GetSpecific(int i)
This will work. However, if I want to retrieve some different data of a different type, it defaults to the GetAll method, even though the $.getJSON is set to the GetAllIntegers method:
public IEnumberable<int> GetAllIntergers()
(bad naming conventions)
Is it possible for me to be able to do this?
Can I only have a single GetAll method in the Web API controller?
I think it's easier to visualise what I'm trying to achieve. Here is a snippet of code to show what I'd like to be able to do, in a single ApiController:
public IEnumerable<string> GetClients()
{ // Get data
}
public IEnumerable<string> GetClient(int id)
{ // Get data
}
public IEnumerable<string> GetStaffMember(int id)
{ // Get data
}
public IEnumerable<string> GetStaffMembers()
{ // Get data
}
This is all in the routing. The default Web API route looks like this:
config.Routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
With the default routing template, Web API uses the HTTP method to select the action. In result it will map a GET request with no parameters to first GetAll it can find. To work around this you need to define a route where the action name is included:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
After that you can star making requests with following URL's:
api/yourapicontroller/GetClients
api/yourapicontroller/GetStaffMembers
This way you can have multiple GetAll in Controller.
One more important thing here is that with this style of routing, you must use attributes to specify the allowed HTTP methods (like [HttpGet]).
There is also an option of mixing the default Web API verb based routing with traditional approach, it is very well described here:
Web API: Mixing Traditional & Verb-Based Routing
In case someone else faces this problem. Here's how I solved this. Use the [Route] attribute on your controller to route to a specific url.
[Route("api/getClient")]
public ClientViewModel GetClient(int id)
[Route("api/getAllClients")]
public IEnumerable<ClientViewModel> GetClients()

Custom method names in ASP.NET Web API

I'm converting from the WCF Web API to the new ASP.NET MVC 4 Web API. I have a UsersController, and I want to have a method named Authenticate. I see examples of how to do GetAll, GetOne, Post, and Delete, however what if I want to add extra methods into these services? For instance, my UsersService should have a method called Authenticate where they pass in a username and password, however it doesn't work.
public class UsersController : BaseApiController
{
public string GetAll()
{
return "getall!";
}
public string Get(int id)
{
return "get 1! " + id;
}
public User GetAuthenticate(string userName, string password, string applicationName)
{
LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
userName, password, applicationName));
//check if valid leapfrog login.
var decodedUsername = userName.Replace("%40", "#");
var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);
if (leapFrogUsers.Count > 0)
{
return new User
{
Id = (uint)leapFrogUsers[0].Id,
Guid = leapFrogUsers[0].Guid
};
}
else
throw new HttpResponseException("Invalid login credentials");
}
}
I can browse to myapi/api/users/ and it will call GetAll and I can browse to myapi/api/users/1 and it will call Get, however if I call myapi/api/users/authenticate?username={0}&password={1} then it will call Get (NOT Authenticate) and error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
How can I call custom method names such as Authenticate?
By default the route configuration follows RESTFul conventions meaning that it will accept only the Get, Post, Put and Delete action names (look at the route in global.asax => by default it doesn't allow you to specify any action name => it uses the HTTP verb to dispatch). So when you send a GET request to /api/users/authenticate you are basically calling the Get(int id) action and passing id=authenticate which obviously crashes because your Get action expects an integer.
If you want to have different action names than the standard ones you could modify your route definition in global.asax:
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
Now you can navigate to /api/users/getauthenticate to authenticate the user.
This is the best method I have come up with so far to incorporate extra GET methods while supporting the normal REST methods as well. Add the following routes to your WebApiConfig:
routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = #"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});
I verified this solution with the test class below. I was able to successfully hit each method in my controller below:
public class TestController : ApiController
{
public string Get()
{
return string.Empty;
}
public string Get(int id)
{
return string.Empty;
}
public string GetAll()
{
return string.Empty;
}
public void Post([FromBody]string value)
{
}
public void Put(int id, [FromBody]string value)
{
}
public void Delete(int id)
{
}
}
I verified that it supports the following requests:
GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1
Note That if your extra GET actions do not begin with 'Get' you may want to add an HttpGet attribute to the method.
I am days into the MVC4 world.
For what its worth, I have a SitesAPIController, and I needed a custom method, that could be called like:
http://localhost:9000/api/SitesAPI/Disposition/0
With different values for the last parameter to get record with different dispositions.
What Finally worked for me was:
The method in the SitesAPIController:
// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
Site site = db.Sites.Where(s => s.Disposition == disposition).First();
return site;
}
And this in the WebApiConfig.cs
// this was already there
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// this i added
config.Routes.MapHttpRoute(
name: "Action",
routeTemplate: "api/{controller}/{action}/{disposition}"
);
For as long as I was naming the {disposition} as {id} i was encountering:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}
When I renamed it to {disposition} it started working. So apparently the parameter name is matched with the value in the placeholder.
Feel free to edit this answer to make it more accurate/explanatory.
Web Api by default expects URL in the form of api/{controller}/{id}, to override this default routing. you can set routing with any of below two ways.
First option:
Add below route registration in WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Decorate your action method with HttpGet and parameters as below
[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
string param2, string param3)
{
// your code here
}
for calling above method url will be like below
http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1&param2=value2&param3=value3
Second option
Add route prefix to Controller class and Decorate your action method with HttpGet as below.
In this case no need change any WebApiConfig.cs. It can have default routing.
[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{
[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
string param2, string param3)
{
// your code here
}
}
for calling above method url will be like below
http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1&param2=value2&param3=value3
In case you're using ASP.NET 5 with ASP.NET MVC 6, most of these answers simply won't work because you'll normally let MVC create the appropriate route collection for you (using the default RESTful conventions), meaning that you won't find any Routes.MapRoute() call to edit at will.
The ConfigureServices() method invoked by the Startup.cs file will register MVC with the Dependency Injection framework built into ASP.NET 5: that way, when you call ApplicationBuilder.UseMvc() later in that class, the MVC framework will automatically add these default routes to your app. We can take a look of what happens behind the hood by looking at the UseMvc() method implementation within the framework source code:
public static IApplicationBuilder UseMvc(
[NotNull] this IApplicationBuilder app,
[NotNull] Action<IRouteBuilder> configureRoutes)
{
// Verify if AddMvc was done before calling UseMvc
// We use the MvcMarkerService to make sure if all the services were added.
MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);
var routes = new RouteBuilder
{
DefaultHandler = new MvcRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
// Adding the attribute route comes after running the user-code because
// we want to respect any changes to the DefaultHandler.
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
The good thing about this is that the framework now handles all the hard work, iterating through all the Controller's Actions and setting up their default routes, thus saving you some redundant work.
The bad thing is, there's little or no documentation about how you could add your own routes. Luckily enough, you can easily do that by using either a Convention-Based and/or an Attribute-Based approach (aka Attribute Routing).
Convention-Based
In your Startup.cs class, replace this:
app.UseMvc();
with this:
app.UseMvc(routes =>
{
// Route Sample A
routes.MapRoute(
name: "RouteSampleA",
template: "MyOwnGet",
defaults: new { controller = "Items", action = "Get" }
);
// Route Sample B
routes.MapRoute(
name: "RouteSampleB",
template: "MyOwnPost",
defaults: new { controller = "Items", action = "Post" }
);
});
Attribute-Based
A great thing about MVC6 is that you can also define routes on a per-controller basis by decorating either the Controller class and/or the Action methods with the appropriate RouteAttribute and/or HttpGet / HttpPost template parameters, such as the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
namespace MyNamespace.Controllers
{
[Route("api/[controller]")]
public class ItemsController : Controller
{
// GET: api/items
[HttpGet()]
public IEnumerable<string> Get()
{
return GetLatestItems();
}
// GET: api/items/5
[HttpGet("{num}")]
public IEnumerable<string> Get(int num)
{
return GetLatestItems(5);
}
// GET: api/items/GetLatestItems
[HttpGet("GetLatestItems")]
public IEnumerable<string> GetLatestItems()
{
return GetLatestItems(5);
}
// GET api/items/GetLatestItems/5
[HttpGet("GetLatestItems/{num}")]
public IEnumerable<string> GetLatestItems(int num)
{
return new string[] { "test", "test2" };
}
// POST: /api/items/PostSomething
[HttpPost("PostSomething")]
public IActionResult Post([FromBody]string someData)
{
return Content("OK, got it!");
}
}
}
This controller will handle the following requests:
[GET] api/items
[GET] api/items/5
[GET] api/items/GetLatestItems
[GET] api/items/GetLatestItems/5
[POST] api/items/PostSomething
Also notice that if you use the two approaches togheter, Attribute-based routes (when defined) would override Convention-based ones, and both of them would override the default routes defined by UseMvc().
For more info, you can also read the following post on my blog.
See this article for a longer discussion of named actions. It also shows that you can use the [HttpGet] attribute instead of prefixing the action name with "get".
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Web APi 2 and later versions support a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API. For example, you can easily create URIs that describe hierarchies of resources.
For example:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Will perfect and you don't need any extra code for example in WebApiConfig.cs.
Just you have to be sure web api routing is enabled or not in WebApiConfig.cs , if not you can activate like below:
// Web API routes
config.MapHttpAttributeRoutes();
You don't have to do something more or change something in WebApiConfig.cs. For more details you can have a look this article.
Just modify your WebAPIConfig.cs as bellow
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional });
Then implement your API as bellow
// GET: api/Controller_Name/Show/1
[ActionName("Show")]
[HttpGet]
public EventPlanner Id(int id){}

Categories

Resources