I'm making an API in .NET Framework.
I have a controller called Autos, the API is meant to allow for the registration and retrieval of car models.
At first, I was able to use a GET to fetch an IEnumerable instance of List from Postman. I was getting the serialized version of a list I made with dummy data.
However, after I removed the dummy list, changed the method return to HttpResponseMessage and switched to a static dictionary I'd be using as a repository, the method stopped being executed upon calling it with Postman. I added logging messages and breakpoints and I can see that they just never execute.
My controller:
public class AutosController : ApiController
{
[HttpGet]
public HttpResponseMessage GetModels(HttpRequestMessage request)
{
//Theres a breakpoint here
System.Diagnostics.Debug.WriteLine(Repository.AutoRepository.Keys);
return request.CreateResponse<IEnumerable<Auto>>(HttpStatusCode.OK, Repository.AutoRepository.Values);
}
[HttpPost]
public HttpResponseMessage SetModel(HttpRequestMessage request, Auto serializedAuto)
{
if (serializedAuto.IsValid())
{
if (Repository.AddToRepository(serializedAuto))
return request.CreateResponse<Auto>(HttpStatusCode.Created, Repository.AutoRepository[serializedAuto.Id]);
else
return request.CreateResponse(HttpStatusCode.BadRequest);
} else
{
return request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
And my API Route
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I can successfully use SetModel and I can confirm that the sent model is also added to the repository. I am also able to return it to postman.
The postman address I'm using for the GET is
localhost:51020/api/v1/GetModels
the way i see it, your action could be
[Route("~/api/v1/GetModels")]
public IEnumerable<Auto> GetModels()
{
var values = Repository.AutoRepository.Values;
if (values==null) throw new HttpException(404, "Some description");
return values;
}
I have an application that is written using C# on the top of ASP.NET Core 2.2. I created a controller that responds to a PATCH request called Update. Here is my controller
[Route("api/[controller]"), ApiController, Produces("application/json")]
public class CategoriesController : ControllerBase
{
[HttpPatch("{propertyId}/{listId}/{isOn}"), Authorize]
public ActionResult<bool> Update(int propertyId, int listId, bool isOn)
{
// Do something
return true;
}
}
also, to return a 401 error when the user isn't authorized instead of a redirect, I added the following code to my Startup class
services.ConfigureApplicationCookie(config =>
{
config.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api", StringComparison.CurrentCultureIgnoreCase))
{
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.FromResult(0);
}
};
});
Now to call this API using jQuery I did the following
$('.update-property').click(function (e) {
e.preventDefault();
var obj = $(this);
$.ajax({
url: '/api/Categories/Update',
type: 'PATCH',
data: {
'propertyId': obj.data('property-id'),
'listId': obj.data('list-id'),
'isOn': obj.data('is-on') === 'True' ? 'false' : 'true'
},
success: function (response) {
console.log(response);
// if response unauthorized, redirect to the login page with the ReturnUrl
// else if response.data is true then change the icon
}
});
});
But this request keeps returning a 404 http error code. I inspected this question in the developer tools and I see that the parameter are being set correctly and the URL is valid.
How can I correctly handle this PATCH request?
[Route("api/[controller]"), ApiController, Produces("application/json")]
public class CategoriesController : ControllerBase
{
[HttpPatch("{propertyId}/{listId}/{isOn}"), Authorize]
public ActionResult<bool> Update(int propertyId, int listId, bool isOn)
The route templates here determine the URL for your Update endpoint. In this case, the resulting URL template would be this:
api/Categories/{propertyId}/{listId}/{isOn}
So for example, a valid URL for this endpoint would be /api/Categories/12/34/true.
If you don’t want to pass the values as parameters (since you already pass them in the body), you have to change the route template. For example, you could just remove the route template on the action method:
[HttpPatch, Authorize]
public ActionResult<bool> Update(int propertyId, int listId, bool isOn)
// …
Then, the URL would be just api/Categories.
Of course, you could also make api/Categories/Update the URL for the endpoint, but with REST, it’s usually not recommended to have method names in the URL. A PATCH request usually means “update this resource” and then you would be pointing to the URL of the resource.
you need to send request with localhost:44313/api/Categories url. Don't put update prefix. You just need to send Patch request to your localhost:44313/api/Categories endpoint.
with Parameters
localhost:44313/api/Categories/{propertyId}/{listId}/{isOn}
I have two similar Web API controllers, one working and one not. Can't find why the broken one is not working. What happens is all the fields of the input argument are null. Here's the controller method signature:
public IEnumerable<Product> GetProducts([FromUri] StateQuery query)
Parameter type:
public class StateQuery
{
public State state;
public string username;
public string password;
}
Routing:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "api/{controller}/{action}"
);
Calling the endpoint:
var uri = 'api/product/';
var query = {
username: $("#user").val(),
password: $("#password").val(),
state: { "name": $("#state").val(), "Counties": [{ "Name": $("#county").val() }] }
};
$.getJSON(uri + 'getProducts', query)
.done(function (data) {
$('#product').text(data);
console.log(data);
})
.fail(function (jqXHR, textStatus, err) {
$('#product').text('Error: ' + err);
});
Here's an example request:
http://localhost:47503/api/product/getProducts?username=auser&password=apassword&state%5Bname%5D=CO&state%5BCounties%5D%5B0%5D%5BName%5D=Boulder
I've tried attribute routing, tried calling from the browser address bar, tried simplifying the input to just username and password, no change. All the fields of query are always null. I also tried changing it to return a IHttpActionResult, which is the main difference between this and the working controller (other than what it does of course) but that had no effect. The method is getting called, so the routing is working, but there is something wrong with the argument binding that I'm just not seeing.
Does anybody see the problem or just a direction to look? I've spent all morning reading tutorials and SO questions and everything I'm seeing indicates that I'm on the right track. Maybe there's some little thing I'm missing because I've been staring at it too long.
Json.NET which ASP.NET Web API uses by default works on properties, not fields. You should convert them to using properties.
public class StateQuery
{
public State state { get; set; }
public string username { get; set; }
public string password { get; set; }
}
I have been scratching my head for a whole day since I cannot figure out what is wrong in my code.
First, I have a working controller as this one:
namespace MyProject.Controllers {
[RoutePrefix("api/Account")]
public class AccountController : ApiController {
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(AccountModel model) {
...
return Ok();
}
}
}
However, when I add a new controller and introduce a new route, problem arises.
The new controller is as follows:
namespace MyProject.Controllers {
[RoutePrefix("api/Admin")]
public class AdminController : ApiController {
[AllowAnonymous]
[Route("Encrypt")]
public IHttpActionResult Encrypt(string clientSecret) {
...
return Ok();
}
}
}
Then I make the request via Postman like this:
Unfortunately, an error is returned:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost/api/admin/encrypt'."
}
I am using the default route mapping configuration defined in WebApiConfig.cs:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Can anyone shed some light on it?
UPDATE
If I send the POST request in this way, I can get the Ok() response successfully.
I have a question now. Why can I only pass the parameter via the URL but not with a form object?
To call with the parameter, include parameter in route attribute and add POST verb as follows:
[Route("Encrypt/{clientSecret}")]
[HttpPost]
public IHttpActionResult Encrypt(string clientSecret) {
...
return Ok();
}
Then call as follows:
http://localhost/api/admin/encrypt/123456
However convention for post methods is without the parameter as follows:
[Route("Encrypt")]
[HttpPost]
public IHttpActionResult Encrypt() {
...
return Ok();
}
}
Then call as follows:
http://localhost/api/admin/encrypt
If you do not want to add the POST verb, you can prefix the methods with the POST verb as follows:
public IHttpActionResult PostEncrypt(string clientSecret)
public IHttpActionResult PostEncrypt()
I suggest the slight change in your route definition:
api/{controller}/{action}/{id}
The reason, imo, is that Web api actions default to the standard accept verbs.
Then you can name your api action methods anyhow, only decorate them with the relevant accept attribute.
I am starting to use MVC4 Web API project, I have controller with multiple HttpPost methods. The Controller looks like the following:
Controller
public class VTRoutingController : ApiController
{
[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
Here MyRequestTemplate represents the template class responsible for handling the Json coming through the request.
Error:
When I make a request using Fiddler for http://localhost:52370/api/VTRouting/TSPRoute or http://localhost:52370/api/VTRouting/Route I get an error:
Multiple actions were found that match the request
If I remove one of the above method it works fine.
Global.asax
I have tried modifying the default routing table in global.asax, but I am still getting the error, I think I have problem in defining routes in global.asax. Here is what I am doing in global.asax.
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapHttpRoute(
name: "MyTSPRoute",
routeTemplate: "api/VTRouting/TSPRoute",
defaults: new { }
);
routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "api/VTRouting/Route",
defaults: new { action="Route" }
);
}
I am making the request in Fiddler using POST, passing json in RequestBody for MyRequestTemplate.
You can have multiple actions in a single controller.
For that you have to do the following two things.
First decorate actions with ActionName attribute like
[ActionName("route")]
public class VTRoutingController : ApiController
{
[ActionName("route")]
public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[ActionName("tspRoute")]
public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
Second define the following routes in WebApiConfig file.
// Controller Only
// To handle routes like `/api/VTRouting`
config.Routes.MapHttpRoute(
name: "ControllerOnly",
routeTemplate: "api/{controller}"
);
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
// Controllers with Actions
// To handle routes like `/api/VTRouting/route`
config.Routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}"
);
Another solution to your problem would be to use Route which lets you specify the route on the method by annotation:
[RoutePrefix("api/VTRouting")]
public class VTRoutingController : ApiController
{
[HttpPost]
[Route("Route")]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost]
[Route("TSPRoute")]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
use:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
it's not a RESTful approach anymore, but you can now call your actions by name (rather than let the Web API automatically determine one for you based on the verb) like this:
[POST] /api/VTRouting/TSPRoute
[POST] /api/VTRouting/Route
Contrary to popular belief, there is nothing wrong with this approach, and it's not abusing Web API. You can still leverage on all the awesome features of Web API (delegating handlers, content negotiation, mediatypeformatters and so on) - you just ditch the RESTful approach.
A web api endpoint (controller) is a single resource that accepts get/post/put/delete verbs. It is not a normal MVC controller.
Necessarily, at /api/VTRouting there can only be one HttpPost method that accepts the parameters you are sending. The function name does not matter, as long as you are decorating with the [http] stuff. I've never tried, though.
Edit: This does not work. In resolving, it seems to go by the number of parameters, not trying to model-bind to the type.
You can overload the functions to accept different parameters. I am pretty sure you would be OK if you declared it the way you do, but used different (incompatible) parameters to the methods. If the params are the same, you are out of luck as model binding won't know which one you meant.
[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}
[HttpPost]
public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}
This part works
The default template they give when you create a new one makes this pretty explicit, and I would say you should stick with this convention:
public class ValuesController : ApiController
{
// GET is overloaded here. one method takes a param, the other not.
// GET api/values
public IEnumerable<string> Get() { .. return new string[] ... }
// GET api/values/5
public string Get(int id) { return "hi there"; }
// POST api/values (OVERLOADED)
public void Post(string value) { ... }
public void Post(string value, string anotherValue) { ... }
// PUT api/values/5
public void Put(int id, string value) {}
// DELETE api/values/5
public void Delete(int id) {}
}
If you want to make one class that does many things, for ajax use, there is no big reason to not use a standard controller/action pattern. The only real difference is your method signatures aren't as pretty, and you have to wrap things in Json( returnValue) before you return them.
Edit:
Overloading works just fine when using the standard template (edited to include) when using simple types. I've gone and tested the other way too, with 2 custom objects with different signatures. Never could get it to work.
Binding with complex objects doesn't look "deep", so thats a no-go
You could get around this by passing an extra param, on the query string
A better writeup than I can give on available options
This worked for me in this case, see where it gets you. Exception for testing only.
public class NerdyController : ApiController
{
public void Post(string type, Obj o) {
throw new Exception("Type=" + type + ", o.Name=" + o.Name );
}
}
public class Obj {
public string Name { get; set; }
public string Age { get; set; }
}
And called like this form the console:
$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
It is Possible to add Multiple Get and Post methods in the same Web API Controller. Here default Route is Causing the Issue. Web API checks for Matching Route from Top to Bottom and Hence Your Default Route Matches for all Requests. As per default route only one Get and Post Method is possible in one controller. Either place the following code on top or Comment Out/Delete Default Route
config.Routes.MapHttpRoute("API Default",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
When creating another Http Method add [HttpPost("Description")]
[HttpPost("Method1")]
public DataType Method1(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost("Method2")]
public DataType Method2(MyRequestTemplate routingRequestTemplate){}
Put Route Prefix [RoutePrefix("api/Profiles")] at the controller level and put a route at action method [Route("LikeProfile")]. Don't need to change anything in global.asax file
namespace KhandalVipra.Controllers
{
[RoutePrefix("api/Profiles")]
public class ProfilesController : ApiController
{
// POST: api/Profiles/LikeProfile
[Authorize]
[HttpPost]
[Route("LikeProfile")]
[ResponseType(typeof(List<Like>))]
public async Task<IHttpActionResult> LikeProfile()
{
}
}
}
You can use this approach :
public class VTRoutingController : ApiController
{
[HttpPost("Route")]
public MyResult Route(MyRequestTemplate routingRequestTemplate)
{
return null;
}
[HttpPost("TSPRoute")]
public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
{
return null;
}
}
I think the question has already been answered. I was also looking for something a webApi controller that has same signatured mehtods but different names. I was trying to implement the Calculator as WebApi. Calculator has 4 methods with the same signature but different names.
public class CalculatorController : ApiController
{
[HttpGet]
[ActionName("Add")]
public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Add = {0}", num1 + num2);
}
[HttpGet]
[ActionName("Sub")]
public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Subtract result = {0}", num1 - num2);
}
[HttpGet]
[ActionName("Mul")]
public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Multiplication result = {0}", num1 * num2);
}
[HttpGet]
[ActionName("Div")]
public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
{
Thread.Sleep(1000 * timeDelay);
return string.Format("Division result = {0}", num1 / num2);
}
}
and in the WebApiConfig file you already have
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
Just set the authentication / authorisation on IIS and you are done!
Hope this helps!
Best and simplest explanation I have seen on this topic -
http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx
Edited -
I got it working with only Route, and did not need RoutePrefix.
For example, in the controller
[HttpPost]
[Route("[action]")]
public IActionResult PostCustomer
([FromBody]CustomerOrder obj)
{
}
and
[HttpPost]
[Route("[action]")]
public IActionResult PostCustomerAndOrder
([FromBody]CustomerOrder obj)
{
}
Then, the function name goes in jquery as either -
options.url = "/api/customer/PostCustomer";
or
options.url = "/api/customer/PostCustomerAndOrder";
I am using .Net6. please find the following code. I have achieve like the following.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace ProjectName.Controllers
{
[Route("[controller]")]
[ApiController]
public class WizardAPIController : ControllerBase
{
[HttpGet("Methord1")]
public async Task<IActionResult> Methord1()
{
return Ok("all good");
}
[HttpGet("Methord2")]
public async Task<IActionResult> Methord2()
{
return Ok("all good");
}
}
}
public class Journal : ApiController
{
public MyResult Get(journal id)
{
return null;
}
}
public class Journal : ApiController
{
public MyResult Get(journal id, publication id)
{
return null;
}
}
I am not sure whether overloading get/post method violates the concept of restfull api,but it workds. If anyone could've enlighten on this matter. What if I have a uri as
uri:/api/journal/journalid
uri:/api/journal/journalid/publicationid
so as you might seen my journal sort of aggregateroot, though i can define another controller for publication solely and pass id number of publication in my url however this gives much more sense. since my publication would not exist without journal itself.