How to add custom methods to ASP.NET WebAPI controller? - c#

In ASP.NET MVC WebAPI project by default we have created following controller
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
But is possible to add here any custom methods so they can support get/post as well?
Thank you!

You can use attributes such as the RoutePrefix with the Http type.
[Route("ChangePassword")]
[HttpPost] // There are HttpGet, HttpPost, HttpPut, HttpDelete.
public async Task<IHttpActionResult> ChangePassword(ChangePasswordModel model)
{
}
The http type will map it back to its correct method in combination with the Route name.

I am not sure I follow as you have GET and POST right there in your code, but in any case you have other options:
Option 1
First, you can configure your custom Routes in the App_Start folder in the WebApiConfig.cs file. Here is what I normally use:
// GET /api/{resource}/{action}
config.Routes.MapHttpRoute(
name: "Web API RPC",
routeTemplate: "{controller}/{action}",
defaults: new { },
constraints: new { action = #"[A-Za-z]+", httpMethod = new HttpMethodConstraint("GET") }
);
// GET|PUT|DELETE /api/{resource}/{id}/{code}
config.Routes.MapHttpRoute(
name: "Web API Resource",
routeTemplate: "{controller}/{id}/{code}",
defaults: new { code = RouteParameter.Optional },
constraints: new { id = #"\d+" }
);
// GET /api/{resource}
config.Routes.MapHttpRoute(
name: "Web API Get All",
routeTemplate: "{controller}",
defaults: new { action = "Get" },
constraints: new { httpMethod = new HttpMethodConstraint("GET") }
);
// PUT /api/{resource}
config.Routes.MapHttpRoute(
name: "Web API Update",
routeTemplate: "{controller}",
defaults: new { action = "Put" },
constraints: new { httpMethod = new HttpMethodConstraint("PUT") }
);
// POST /api/{resource}
config.Routes.MapHttpRoute(
name: "Web API Post",
routeTemplate: "{controller}",
defaults: new { action = "Post" },
constraints: new { httpMethod = new HttpMethodConstraint("POST") }
);
// POST /api/{resource}/{action}
config.Routes.MapHttpRoute(
name: "Web API RPC Post",
routeTemplate: "{controller}/{action}",
defaults: new { },
constraints: new { action = #"[A-Za-z]+", httpMethod = new HttpMethodConstraint("POST") }
);
I use a combination of RESTful endpoints as well as RPC endpoints. For some purists, this is grounds for a holy war. For me, I use a combination of the two because it is a powerful combination and I can't find any sane reason not to.
Option 2
As the others have pointed out and as I myself am doing more of these days, use attribute routing:
[HttpGet]
[GET("SomeController/SomeUrlSegment/{someParameter}")]
public int SomeUrlSegment(string someParameter)
{
//do stuff
}
I needed a NuGet package for attribute routing to make this work (just search NuGet for "Attribute Routing"), but I think that MVC 5/WebAPI 2 has it natively.
Hope this helps.

You could use attribute routing:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Some documentation to get you started:
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

First Put this route to webapiconfig.cs
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Now you can add actions to your controllers like this
[HttpPost]
public void Upload()
{
//Do some thing
}
I decorated upload action with httppost attribute that means this action accept just only post requests , if you want to actions to be GET , you can remove attribute or just decorate to your suite

Related

Multiple actions were found that match the request in webapi

I have controller with various actions and one of them is:
[HttpGet]
public IList<string> GetFoo(string id = null)
{ ... }
This action has individual route:
routes.MapHttpRoute(
name: "GetFoos",
routeTemplate: "api/my/foo/{_id}",
defaults: new { controller = "My", action = "GetFoo" }
);
When I add another action:
[HttpGet]
public IList<string> GetBar()
{ ... }
Request to localhost/api/my/foo/ fails:
Multiple actions were found that match the request:
↵System.Collections.Generic.IList`1[System.String] GetFoo(System.String) on type Controllers.MyController
↵System.Collections.Generic.IList`1[System.String] GetBar() on type Controllers.MyController"
Can someone explain please why this happens? I specified action = "GetFoo" for api/my/foo why it matches GetBar?
It could be that you configure route as following, and request without ID - /api/my/foo.
config.Routes.MapHttpRoute(
name: "GetFoos",
routeTemplate: "api/my/foo/{id}",
defaults: new {controller = "My", action = "GetFoo"}
);
// Default
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
If so, first route doesn't match and fall throws to default route, but default route matches with multiple actions.
Note: GetFoos route will work if you request with ID explicitly - /api/my/foo/1
Ideally, if you see yourself using too many custom routes, you might want to consider using Route Attribute available in Web API 2, instead of creating individual route in Route config.
For example,
[RoutePrefix("Api/My")]
public class MyController : ApiController
{
[HttpGet]
[Route("foo/{id:int}")]
public IList<string> GetFoo(int id)
{
return new string[] {"Foo1-" + id, "Foo1-" + id};
}
[HttpGet]
[Route("bar/{id:int}")]
public IList<string> GetBar(int id)
{
return new string[] {"Bar1-" + id, "Bar1-" + id};
}
}

WebAPI C# Routing

I have two end points:
api/v1/user/session (For creating user login with post request )
api/v1/user (For creating user with post request)
How to route this two endpoints in same controller? I also want to specify action for a specific request. More clearly:
all get,post,update, patch operations can be done in api/v1/user/session endpoint
all get,post,update, patch operations can be done in api/v1/user endpoint
Is it possible ?
Example:
config.Routes.MapHttpRoute(
"UserApi",
"api/v1/{controller}/session",
new { controller = "User", action="Session" });
Now, I want all rest requests to work for Session method with [httpPost],[httpGet] etc attributes.
config.Routes.MapHttpRoute("lol", "api/v1/{controller}/session",
new { controller = "User", action="Session" });
//config.Routes.MapHttpRoute(
// name: "LoginApi",
// routeTemplate: "api/v1/{controller}",
// defaults: new { controller = "User"}
//);
config.Routes.MapHttpRoute(
name: "RailStationApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I would suggest you look at attribute routing - this is a lot easier to specify than using the central configuration.
[RoutePrefix("api/v1")]
public class UserController : ApiController {
[HttpPost]
[Route("user/session")]
public void Login(/*...*/) {
// ...
}
[HttpGet]
[Route("user/session")] // Note this has the same route as Login
public SessionResult GetSession(/*...*/) {
// ...
}
[HttpPost]
[Route("user")]
public void CreateUser(/*...*/) {
// ...
}
}
Note that you don't technically need [HttpPost] since it is the default, but I included it for clarity. You can add methods with the other Http verbs in the same way.
I tried with hardcoded solution and it worked. I added following route into the webapi.config file and it worked.
RouteTable.Routes.MapHttpRoute(
name: "SessionApi",
routeTemplate: "api/v1/user/session",
defaults: new { Controller = "Session", id = RouteParameter.Optional }
).RouteHandler = new SessionStateRouteHandler();

Multiple GET and POST methods in Web API controller

I have been struggling with this problems for days. I have a controller that need to have multiple GET and POST methods. Somehow I managed to achieve multiple GET methods in the controller. At that time there was only one POST method. Everything was running fine until I introduced one more POST method. Whenever I use POST method from client side using ExtJS, only the first POST method gets called. Here are the methods in my controller:
[AllowAnonymous]
[ActionName("userlist")]
[HttpGet]
public List<MyApp.Entity.Models.usermaster> Get(bool isActive)
{
//My code
}
[AllowAnonymous]
[ActionName("alluserlist")]
[HttpGet]
public List<MyApp.Entity.Models.usermaster> Get()
{
//My code
}
[AllowAnonymous]
[ActionName("updateuser")]
[HttpPost]
public string UpdateUser(JObject userData)
{
//My code
}
[AllowAnonymous]
[ActionName("adduser")]
[HttpPost]
public string AddUser(JObject newUserData)
{
//My code
}
I also have two route config files. The first one has the following configuration:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Another file has the following configuration:
public static void Register(HttpConfiguration config)
{
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ControllersWithAction",
routeTemplate: "api/{controller}/{action}");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
This is how the ajax call is made to the api:
Ext.Ajax.request({
url: 'localhost/myapp/api/users/adduser',
mode: 'POST',
params: {
userName: 'user',
password: 'password'
},
success: function (resp) {
var respObj = Ext.decode(resp.responseText);
if (respObj == 'added') {
Ext.Msg.alert('Success', 'User added.');
}
else {
Ext.Msg.alert('Error', respObj);
}
},
failure: function (resp) {
Ext.Msg.alert('Error', 'There was an error.');
}
});
Can anyone point out the mistake? Alternatively, any example with multiple GET and POST methods in side one controller with route config would be very helpful.
why don't you use PUT method for UpdateUser ?
[AllowAnonymous]
[HttpPut]
public string UpdateUser(JObject userData)
{
//My code
}
update:
you can use multiple POST but then you should either use ActionNames that works but is not restful or stop using Complex Types as parameters. because Web API ignores Complex Types when it tries to select most appropriate Action . check these :
Multiple actions for the same HttpVerb
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
You could try this
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ApiById",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^[0-9]+$" }
);
config.Routes.MapHttpRoute(
name: "ApiByName",
routeTemplate: "api/{controller}/{action}/{name}",
defaults: null,
constraints: new { name = #"^[a-z]+$" }
);
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = "Get" }
);
}
}
you can use ActionName for your method as well
[AllowAnonymous]
[HttpPost]
[ActionName("userlist")]
public string UpdateUser(JObject userData)
{
//My code
}
[AllowAnonymous]
[HttpPost]
[ActionName("alluserlist")]
public string AddUser(JObject newUserData)
{
//My code
}
Also, you have to use ActionName in your URL for API, when you call your post method.
Make sure your route Config have {action} in WebApiConfig.
for custom binding in web API you can refer following link
https://blogs.msdn.microsoft.com/jmstall/2012/04/16/how-webapi-does-parameter-binding/

Web API route being ignored

I have these two routes defined:
routes.MapRoute(
name: "GetVoucherTypesForPartner",
url: "api/Partner/{partnerId}/VoucherType",
defaults: new { controller = "Partner", action = "GetVoucherTypesForPartner"}
);
routes.MapRoute(
name: "Default",
url: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional}
);
In my PartnerProfile controller, I have 2 methods:
public Partner Get(string id)
{ }
public IEnumerable<string> GetVoucherTypesForPartner(string id)
{ }
If I hit the url ~/api/Partner/1234 then, as expected, the Get method is called.
However, if I hit the url ~/api/Partner/1234/VoucherType then the same Get method is called. I am expecting my GetVoucherTypesForPartner to be called instead.
I'm pretty sure something in my route setup is wrong...
You seem to have mapped standard MVC routes, not Web API routes. There's a big difference. The standard routes are used by controllers deriving from the Controller class, but if you are using the ASP.NET Web API and your controllers are deriving from the ApiController type then you should define HTTP routes.
You should do that in your ~/App_Start/WebApiConfig.cs and not inside your ~/App_Start/RouteConfig.cs.
So go ahead:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "GetVoucherTypesForPartner",
routeTemplate: "api/Partner/{partnerId}/VoucherType",
defaults: new { controller = "Partner", action = "GetVoucherTypesForPartner" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
and then:
public class PartnerController : ApiController
{
public Partner Get(string id)
{
...
}
public IEnumerable<string> GetVoucherTypesForPartner(string partnerId)
{
...
}
}
Things to notice:
We have defined HTTP routes not standard MVC routes
The parameter that the GetVoucherTypesForPartner action takes must be called partnerId instead of id in order to respect your route definition and avoid any confusions

How to define the PUT method in routing only limit to the Put methods in controller without parameter?

Here is the routing configuration in WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "DefaultApiPut",
routeTemplate: "api/{controller}",
defaults: new { httpMethod = new HttpMethodConstraint(HttpMethod.Put) }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { httpMethod = new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Delete) }
);
Here is my controller:
public class MyController : ApiController {
[HttpPut]
public void Put()
{
//blah
}
}
Somehow when the client sents the PUT request with the URL /api/myController/12345, it still maps to the Put method in MyController, I am expecting an error like resource not found.
How to force the Put method only accept the request without a parameter?
Thanks in advance!
This works to constrain http method on routes:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "LocationApiPOST",
routeTemplate: "api/{orgname}/{fleetname}/vehicle/location",
defaults: new { controller = "location" }
constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) }
);
config.Routes.MapHttpRoute(
name: "LocationApiGET",
routeTemplate: "api/{orgname}/{fleetname}/{vehiclename}/location/{start}",
defaults: new { controller = "location", start = RouteParameter.Optional }
constraints: new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) }
);
...
}
You're putting your httpMethod constraint into defaults, but it should be in constraints.
defaults just says what the default values will be if the request doesn't include some or all of them as routing parameters (which in the case of the verb, is meaningless, since every HTTP request always has a verb as part of the protocol). constraints limit the combination of route values that will activate the route, which is what you're actually trying to do.
FYI, for this simple/standard routing, you don't need the [HttpPut] attribute in an API controller either. That's already handled by the HTTP routing which maps the verb to the controller method.

Categories