Goal: I want to define REST verb names and link them to the REST routes. Next, I want to map custom rules to a defined action name:
GET / => Get()
GET /1 => Get(int id)
POST / => Post()
PUT / => Put()
DELETE /1 => Delete(int id)
GET /custom => [HttpGet][ActionName("custom")] GetCustom()
I have in OwinStartup.cs:
httpConfiguration.Routes.MapHttpRoute (
name: "ControllerWithId",
routeTemplate: "rest/{controller}/{id}",
constraints: new {
id = #"^\d+$"
},
defaults: new {
action = "Index",
id = RouteParameter.Optional
});
httpConfiguration.Routes.MapHttpRoute (
name: "ControllerWithAction",
routeTemplate: "rest/{controller}/{action}/{id}",
defaults: new {
action = "Index",
id = RouteParameter.Optional
});
I have an ApiController:
public class MyController : ApiController {
public async Task<IEnumerable<object>> Get() {}
public async Task<object> Get(int id) {}
public async Task Post([FromBody] data) {}
public async Task Put(int id, [FromBody] data) {}
public async Task Delete(int id) {}
[HttpGet][ActionName("custom")]
public async Task Custom() {}
}
They all return 404 except for custom. It would suck to have to add an ActionName to each REST method. What is the most concise solution?
ASP.NET MVC doesn't provide an easy method using their named conventions for CRUD/custom endpoints. I went the ugly route and explicitly specified ActionName("") for each CRUD method.
reyan-chougle's solution requires you to hardcode strings in your code which is bad practice. I tried to add magic strings to the attributes but got compiler errors.
Solution:
[HttpGet]
[ActionName (Consts.defaultActionMethdName)]
public IEnumerable<Model> Get () {}
[HttpGet]
[ActionName (Consts.defaultActionMethdName)]
public Model Get (int id) {}
[ActionName (Consts.defaultActionMethdName)]
public IHttpActionResult Post ([FromBody] Model input) {}
[ActionName (Consts.defaultActionMethdName)]
public IHttpActionResult Put (int id, [FromBody] Model input) {}
[ActionName (Consts.defaultActionMethdName)]
public IHttpActionResult Delete (int id) {}
[HttpGet]
[ActionName("custom")]
public async Task Custom() {}
Related
I have couple of get methods in my web API class. The route config is as below;
config.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/test/{action}/{id}",
defaults: new { controller = "item", action="testBring", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
The controller class is as below;
public class ItemController : ApiController
{
// GET: api/Item
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[ActionName("testBring")]
[HttpGet]
public string testBring()
{
return "value";
}
// GET: api/Item/5
public string Get(int id)
{
return "value";
}
// POST: api/Item
[HttpPost]
public void CreateItem([FromBody]string value)
{
}
// PUT: api/Item/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE: api/Item/5
public void Delete(int id)
{
}
}
When I try to call /api/test, testBring method gets called. But when I call /api/Item I get an error that multiple actions were found that match the request ... Why is it so?
/api/test maps fine to the first route template api/test/{action}/{id} because of the default parameters included, which allow it to call a specific action.
However, /api/Item maps to the second route template api/{controller}/{id} and the route table is unable to determine which action you wanted since there are multiple HttpGet actions (ie Get() and testBring() ) that match the requested path.
I Have The Following Get Methods In Web api AgentApiController
public IHttpActionResult Get()
public IHttpActionResult Get(string agentid)
public TennetDetails getTenantDetail(string tenantid)
public IHttpActionResult getAgentByNumber(string mobile)
private Agent GetAgentDetail(PartyQuery query)
public IHttpActionResult GetDigiAgentDetail(int agentid)
public IHttpActionResult GetDigiAgentHistory(int agentid)
When I call AgentApi/Get/7 , public IHttpActionResult Get() is fires .actually i need to fire IHttpActionResult Get(string agentid).
when i delete the public IHttpActionResult Get() method and call public IHttpActionResult Get() then
public IHttpActionResult GetDigiAgentHistory(int agentid) fires
i have changed tyhe route in WebApiConfig as bellow
`
config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = #"\d+" });
config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
config.Routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
config.Routes.MapHttpRoute("DefaultApiWithActionWithId", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
`
but i am still not able to find solution for the problem
In my code there was some problem
I changed the
public IHttpActionResult Get(string agentid)
to
public IHttpActionResult Get(int id)
now the code is working fine.
I think since i mention id(parameter name) in the route,
config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = #"\d+" });
I should use id itself as name of parameter for actions.
According to the documentation, if I have this in my WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I should be able to route to a method in my API controller using a URL like this:
http://localhost:55601/api/Customers/Search
Here is my method:
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult Search([FromBody]CustomerDTO SearchTerm)
{
string Name = SearchTerm.Name;
string Email = SearchTerm.Email;
string PhoneNumber = SearchTerm.Phone;
var customer = db.Customers.Single(c => c.Name == Name && c.EmailAddress == Email && c.PhoneNumber == PhoneNumber);
return Ok(customer.id);
}
I'm sending the search data as a JSON object (using HTTP POST method) in the request body.
However, I get an error saying:
Multiple actions were found that match the request
I only have one method in this controller called Search.
I would have thought this should be pretty straightforward, and work the same way it does with MVC controllers. But I think I'm missing something obvious. Can anyone tell me what it is?
EDIT: As per #KevinLaw's request, adding code for controller showing upblic methods. Also, for further information the following request (HTTP GET) works as expected:
http://localhost:55601/api/Customers?email=[recipient#domain]
public class CustomersController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: api/Customers
public IQueryable<Customer> GetCustomers()
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomerByEmail(string email)
{
//...
}
// PUT: api/Customers/5
[ResponseType(typeof(void))]
public IHttpActionResult PutCustomer(int id, Customer customer)
{
//...
}
// POST: api/Customers
[ResponseType(typeof(Customer))]
public IHttpActionResult PostCustomer(Customer customer)
{
//...
}
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult SearchCustomer([FromBody]CustomerDTO SearchTerm)
{
//...
}
// DELETE: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult DeleteCustomer(int id)
{
//...
}
}
The problem here is that the WebApiController uses the REST API specs.
Which state that in a Web Api Controller there can be Zero - One Http Verb.
What i mean by that is that you can have one GET,PUT,POST,DELETE,PATCH
The reason you don't have any problem with the GET is because you have them correctly overloaded. () (int) (string).
But your Posts is (Customer) (CustomerDTO) They are both complex objects and the Binder cannot identify which is which when binding to the complex object.
For this to work you need to use Route Attributes or explicit route.
Attribute Routing
Explicit Routing pt1
Explicit Routing pt2
I think the links are enough to get you started.
If you still want to see some code on your specific case leave a comment below and i will give you some examples.
Thanks
EDIT: Added Examples
Attribute Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
}
On Controller
[RoutePrefix("api/test")]
public class TestingController : ApiController
{
[HttpGet]
[Route("")]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
[Route("search")]
public IHttpActionResult Post([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}
Explicit Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes config.Routes.MapHttpRoute(
name: "SearchTest",
routeTemplate: "api/test/search",
defaults: new { controller = "Testing", action = "Search" }
);
config.Routes.MapHttpRoute(
name: "TestingController",
routeTemplate: "api/test/{id}",
defaults: new { controller = "Testing", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
On Controller
public class TestingController : ApiController
{
[HttpGet]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
public IHttpActionResult Search([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}
From example on other site:
In WebApiConfig.cs
EntitySetConfiguration<ContactType> contactType = builder.EntitySet<ContactType>("ContactType");
var actionY = contactType.EntityType.Action("ChangePersonStatus");
actionY.Parameter<string>("Level");
actionY.Returns<bool>();
var changePersonStatusAction = contactType.EntityType.Collection.Action("ChangePersonStatus");
changePersonStatusAction.Parameter<string>("Level");
changePersonStatusAction.Returns<bool>();
In ContactTypeController
[HttpPost]
[ODataRoute("Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus(ODataActionParameters parameters)
{
if (ModelState.IsValid)
{
var level = parameters["Level"];
// SAVE THIS TO THE DATABASE OR WHATEVER....
}
return Ok(true);
}
Now the action can be called:
For the Entity:
http://localhost:51902/odata/ContactType(5)/Default.ChangePersonStatus
For the Entity Collection:
http://localhost:51902/odata/ContactType/Default.ChangePersonStatus
I don't understand how we retrieve Id = 5 of ContactType in the action and save something to database by this ID when use ..odata/ContactType(5)/Default.ChangePersonStatus link
For the Entity Collection:
http://localhost:51902/odata/ContactType/Default.ChangePersonStatus
Your method in controller should be as follows:
[HttpPost]
[ODataRoute("ContactType/Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus(ODataActionParameters parameters)
{
...
}
For the Entity:
http://localhost:51902/odata/ContactType(5)/Default.ChangePersonStatus
Your method in controller should be as follows:
[HttpPost]
[ODataRoute("ContactType({key})/Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus(int key, ODataActionParameters parameters)
{
...
}
key will have value 5;
To reference a single entity, you need to include [FromODataUri] before the key:
[HttpPost]
[ODataRoute("ContactType({key})/Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus([FromODataUri] int key, ODataActionParameters parameters)
{
// Code
}
You should now be able to access your id within the method.
Learning a lot about MapRouting and have spent WAY too much time trying to get what I need done, so I'm hoping someone can help out. :)
I'm looking to do the following:
/api/Entities/1 <- Views the details entity with id of 1 (this is a string, not int)
/api/Entities/1/Action <- Calls a particular action on the entity with the id of 1.
/api/Entities <- Views the entities set.
/api/Entities/Action <- Calls the particular action on the entities set.
The problem I'm encountering is the last one. That is currently being intercepted by the first case, as the id is a string.
Any assistance would be greatly appreciated!
A very good reference is here
Without using Attribute based routing the answer is somewhat long winded as the RPC style (Action) and Rest methods do not co-exist nicely if you are matching to the same verb. As you have noticed the GET() and DOSOMETHING() are seen as duplicate method signatures on the same controller. To get around this you could try use two controllers:
So I would recommend using attribute based routing; however, it is possible with normal methods:
Using standard routing...
Set your routes like so
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionDefaultApi",
routeTemplate: "api/entities/{action}/{actionId}",
defaults: new
{
//Add more as needed
action = "(|dosomething|dosomethingelse)",
actionId = RouteParameter.Optional,
controller = "EntitiesRpc"
});
Controller 1:
public class EntitiesController : ApiController
{
public string Get()
{
return "http://server/api/Entities";
}
public string Get(string id)
{
return string.Format("http://server/api/Entities/{0}", id);
}
}
Controller 2:
public class EntitiesRpcController : ApiController
{
[HttpGet]
public string DoSomething()
{
return "http://server/api/Entities/doSomething";
}
[HttpGet]
public string DoSomething(string actionId)
{
return string.Format("http://server/api/Entities/doSomething/{0}", actionId);
}
[HttpGet]
public string DoSomethingElse()
{
return "http://server/api/Entities/doSomethingelse";
}
[HttpGet]
public string DoSomethingElse(string actionId)
{
return string.Format("http://server/api/Entities/doSomethingelse/{0}", actionId);
}
}
Now assumming you used attribute based routing you could go back to one controller and use something like this:
public class EntitiesController : ApiController
{
[Get("Entities")]
public string Get()
{
return "http://server/api/Entities";
}
[Get("Entities/{id}")]
public string Get(string id)
{
return string.Format("http://server/api/Entities/{0}", id);
}
[Get("Entities/doSomething")]
public string DoSomething()
{
return "http://server/api/Entities/doSomething";
}
[Get("Entities/doSomething/{actionId}")]
public string DoSomething(string actionId)
{
return string.Format("http://server/api/Entities/doSomething/{0}", actionId);
}
[Get("Entities/doSomethingelse")]
public string DoSomethingElse()
{
return "http://server/api/Entities/doSomethingelse";
}
[Get("Entities/doSomethingelse/{actionId}")]
public string DoSomethingElse(string actionId)
{
return string.Format("http://server/api/Entities/doSomethingelse/{0}", actionId);
}
}