I have five actions in my web api controller as follows.
http://localhost:1234/products - to map getallproduct action
http://localhost:1234/products/1 - to map getproductnyid action
http://localhost:1234/products - saveproduct action (post)
http://localhost:1234/products/1/getcolor - getproductcolorbyid action
http://localhost:1234/products/1/getcost - getproductcostbyid action
I need only one custom routing url for this.
I have tried following routing but it appends action name in url(http://localhost:24007/product/GetProductByID/Id) which i don't want.
config.Routes.MapHttpRoute(
name: "ProductRoute",
routeTemplate: "product/{action}/{productId}",
defaults: new { controller = "Product", productId= RouteParameter.Optional }
);
You have to use Attribute Routing if you want this kind of flexibility:
[RoutePrefix("products")]
public class ProductsController : ApiController {
[HttpGet]
[Route("")]
public IHttpActionResult GetAllProduct()
{
//...
}
[HttpGet]
[Route("{productId}")]
public IHttpActionResult GetProductById(int id)
{
//...
}
[HttpPost]
[Route("")]
public IHttpActionResult SaveProduct(Product product)
{
//...
}
[HttpGet]
[Route("{productId}/getcolor")]
public IHttpActionResult GetProductColorById(int id)
{
//...
}
[HttpGet]
[Route("{productId}/getcost")]
public IHttpActionResult GetProductCostById(int id)
{
//...
}
}
And remember to register them in your HttpConfiguration object:
config.MapHttpAttributeRoutes();
As an aside: if you are designing a RESTful API (which it seems to me) I strongly suggest you to avoid using RPC-like actions in your URIs (e.g. never use URI segments like getcolor, getcost), but use names that conforms with REST constraints:
http://localhost:1234/products/1/color
http://localhost:1234/products/1/cost
This can be achieved by changing your RouteAttributes:
[Route("{productId}/color")]
//...
[Route("{productId}/cost")]
Related
I'm new to Web API, I know one of the difference between web api and MVC
is that web API uses HTTP method to choose which method to invoke by convention.
Now I added an action parameter into my route,
I think there's something went wrong to my knowledge,
the route doesn't work.
Here's my controller, main problem is that I just need to map the SignIn and SignUp method.
(Other RestFul methods work fine.)
public class UsersController : ApiController
{
private Model1Container db = new Model1Container();
public IEnumerable<User> GetUsers()
{
return db.UserSet;
}
[HttpPost]
public IHttpActionResult SignIn(string account, string password)
{
...
}
[HttpPost]
public IHttpActionResult SignUp(User user)
{
...
}
public IHttpActionResult GetUser(int id)
{
...
}
public IHttpActionResult PutUser(int id, User user)
{
...
}
public IHttpActionResult DeleteUser(int id)
{
...
}
}
What I've tried is even to add the direct constraint on my top two routes for both methods,
config.Routes.MapHttpRoute(
name: "UsersSignIn",
routeTemplate: "api/Users/SignIn",
defaults: new { controller = "Users", action = "SignIn" }
);
config.Routes.MapHttpRoute(
name: "UsersSignUp",
routeTemplate: "api/Users/SignUp",
defaults: new { controller = "Users", action = "SignUp" }
);
config.Routes.MapHttpRoute(
name: "RestFulUser",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { controller = "Users" }
);
But it turns out [POST] "api/Users/SignUp" and "api/Users"
could map to SignUp method, but "api/Users/SignIn" always not succeed.
And here's about how I try the API in POSTMAN
Please point out where did I go wrong ..
If using attribute routing you need to differentiate between the two routes. Also once using routes it is all or nothing on the controller itself. You can't mix them with convention-based routes.
Review Attribute Routing in ASP.NET Web API 2
[RoutePrefix("api/Users")]
public class UsersController : ApiController {
private Model1Container db = new Model1Container();
[HttpGet]
[Route("")] //Matches GET api/Users
public IEnumerable<User> GetUsers() {
return db.UserSet;
}
[HttpPost]
[Route("SignIn")] //Matches POST api/Users/SignIn
public IHttpActionResult SignIn(string account, string password) {
//...
}
[HttpPost]
[Route("SignUp")] //Matches POST api/Users/SignUp
public IHttpActionResult SignUp([FromBody]User user) {
//...
}
[HttpGet]
[Route("{id:int}")] //Matches GET api/Users/5
public IHttpActionResult GetUser(int id) {
//...
}
[HttpPut]
[Route("{id:int}")] //Matches PUT api/Users/5
public IHttpActionResult PutUser(int id,[FromBody]User user) {
//...
}
[HttpDelete]
[Route("{id:int}")] //Matches DELETE api/Users/5
public IHttpActionResult DeleteUser(int id) {
//...
}
}
I'm trying to add custom route to the controller with [RoutePrefix("front")] that contains three methods:
[HttpGet]
[ResponseType(typeof(SparePartsFrontDTO))]
public async Task<IHttpActionResult> Get()
{
...
}
[HttpGet]
[ResponseType(typeof(IEnumerable<SparePartSearchDTO>))]
public async Task<IHttpActionResult> Get(string filter)
{
...
}
[HttpGet]
[Route("categories")]
[ResponseType(typeof(IEnumerable<DeviceCategoryDTO>))]
public async Task<IHttpActionResult> GetCategories()
{
...
}
But when I'm calling method by the route api/front/categories, instead of the custom route, default get method was called.
Here is WebApi config:
config.MapHttpAttributeRoutes();
config.MessageHandlers.Add(new PreflightRequestsHandler());
config.SuppressDefaultHostAuthentication();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
The route prefix needs to have the desired template plus the other actions need Route attribute as well.
[RoutePrefix("api/front")]
public class MyController : ApiController {
[HttpGet]
[Route("")] // matches GET api/front
[ResponseType(typeof(SparePartsFrontDTO))]
public async Task<IHttpActionResult> Get() {
//...
}
[HttpGet]
[Route("filter/{filter}")] // matches GET api/front/filter/anything-here
[ResponseType(typeof(IEnumerable<SparePartSearchDTO>))]
public async Task<IHttpActionResult> Get(string filter) {
//...
}
[HttpGet]
[Route("categories")] //matches GET api/front/categories
[ResponseType(typeof(IEnumerable<DeviceCategoryDTO>))]
public async Task<IHttpActionResult> GetCategories() {
//...
}
}
Reference Attribute Routing in ASP.NET Web API 2 : Route Prefixes
Could anybody little help me with routing?
In WebApiConfig I have this mapping
config.Routes.MapHttpRoute(DefaultApi",
routeTemplate: "{controller}/{uid}",
defaults: new {uid = RouteParameter.Optional}
);
and two methods in controller
[RoutePrefix("AppartmentCategory")]
public class AppartmentCategoryController : ApiController
{
[HttpGet]
public IHttpActionResult Get(Guid uid){...}
[HttpGet]
[Route("{propertyUid?}")]
public IHttpActionResult GetList(Guid propertyUid){...}
}
When I try to send requests
1. http://.../AppartmentCategory/043F61D1-7194-E611-A98B-9C5C8E0005FA
and
2. http://.../AppartmentCategory/?propertyUid=2fdc968d-0192-e611-a98b-9c5c8e0005fa
In both cases executing method public IHttpActionResult GetList(Guid propertyUid)
What shold I fix for executing public IHttpActionResult Get(Guid uid) method for first request?
You are mixing convention-based routing and attribute routing. config.MapHttpAttributeRoutes() is mapped before convention-based routes (as it should)
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(DefaultApi",
routeTemplate: "{controller}/{uid}",
defaults: new {uid = RouteParameter.Optional}
);
so it will hit the GetLists first as first match always win when matching templates. Technically both actions will have similar routes.
[RoutePrefix("AppartmentCategory")]
public class AppartmentCategoryController : ApiController
{
//GET AppartmentCategory/043F61D1-7194-E611-A98B-9C5C8E0005FA VIA CONVENTION-BASED ROUTING
[HttpGet]
public IHttpActionResult Get(Guid uid){...}
//GET AppartmentCategory/2fdc968d-0192-e611-a98b-9c5c8e0005fa VIA ATTRIBUTE ROUTING
[HttpGet]
[Route("{propertyUid?}")]
public IHttpActionResult GetList(Guid propertyUid){...}
}
You should try to differentiate the two actions a little more and also try not to mix convention-based and attribute routing in the same controller.
[RoutePrefix("AppartmentCategory")]
public class AppartmentCategoryController : ApiController {
//GET AppartmentCategory/043F61D1-7194-E611-A98B-9C5C8E0005FA
[HttpGet]
[Route("{uid:guid}")]
public IHttpActionResult Get(Guid uid){...}
//GET AppartmentCategory?propertyUid=2fdc968d-0192-e611-a98b-9c5c8e0005fa
[HttpGet]
[Route("")]
public IHttpActionResult GetList(Guid propertyUid){...}
}
I'm trying to use RoutePrefix, but ASP.NET is partially ignoring it. Given the following controller (method bodies and class fields removed):
[RoutePrefix("api/users/sharepoint")]
public class SharePointController : ApiController
{
public SharePointController(ISharePointUserRepository repo, IAzureUserRepository otherRepo)
{
}
[HttpGet]
public ExternalUser Get(int id)
{
}
[HttpGet]
public ExternalUser Get(Guid guid)
{
}
[HttpGet]
public IEnumerable<ExternalUser> Get()
{
}
[HttpGet]
public ExternalUser Get(string username)
{
}
[HttpGet]
public IEnumerable<ExternalUser> GetByPersonalEmail(string email)
{
}
[HttpGet]
[Route("GetWithDifferences")]
public IEnumerable<ExternalUser> GetWithDifferences()
{
}
[HttpGet]
[Route("GetUnique")]
public IEnumerable<ExternalUser> GetUnique()
{
}
[HttpPost]
[Route("search")]
public IEnumerable<ExternalUser> Search([FromBody] ExternalUserSearchModel model)
{
}
I get the following API (via host/help):
SharePoint
GET api/users/sharepoint/GetWithDifferences
GET api/users/sharepoint/GetUnique
POST api/users/sharepoint/search
GET api/SharePoint/{id}
GET api/SharePoint?guid={guid}
GET api/SharePoint
GET api/SharePoint?username={username}
GET api/SharePoint?email={email}
Which, when tested, works as advertised.
As you can see the RoutePrefix is ignored when I don't specify a [Route(..)] for the exposed methods. However I want default actions on GET, so I don't want to specify any additional routing for these.
How can I enforce the RoutePrefix, or, alternatively, how can I maintain default GET and POST behavior with [Route(..)]?
RoutePrefixAttribute alone does not define any route, it will just add the chosen prefix to any route defined in the class decorated with such attribute.
You need to make a choice then: use only attribute routing (and define a route for each method) so you may benefit from RoutePrefix, or keeping the code as is, and define another convention routing which complies with your URIs.
Sample using attribute routing:
[HttpGet]
[Route("{id:int}"]
public ExternalUser Get(int id)
{ }
[HttpGet]
[Route(""]
public ExternalUser Get(Guid guid)
{ }
[HttpGet]
[Route("")]
public IEnumerable<ExternalUser> Get()
{ }
[HttpGet]
[Route("")]
public ExternalUser Get(string username)
{ }
And here it is another example for the second approach:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "MyCustomApi",
routeTemplate: "api/users/sharepoint/{id}",
defaults: new { controller = "SharePoint", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
I am having a problem when trying to have two different GET methods, one for retrieving a number of resources while one is for retrieving a specific resource.
Startup.cs
config.Routes.MapHttpRoute("DefaultAPI",
"api/{controller}/{action}",
new { id = RouteParameter.Optional });
Controller.cs
[RoutePrefix("api/Files")]
public class FileController : ApiController
{
// /api/Files/
[Authorize]
[Route("")]
public IHttpActionResult GetAll()
{
}
// /api/Files/Id/
[Authorize]
[Route("Id")]
public async Task<HttpResponseMessage> Get([FromBody] string id)
{
}
// /api/Files/Upload
[Authorize]
[HttpPost]
[Route("Upload")]
public async Task<HttpResponseMessage> Post()
{
}
// /api/Files/Delete
[Authorize]
[Route("Delete")]
public IHttpActionResult Delete([FromBody] string id)
{
}
This is new to me and I know I am also making a mistake with using both IHttpActionResult as well as HttpResponseMessage but I figured I would change that later on after I figure out the routing.
Error:
When Startup.cs has
"api/{controller}/{action}"
, it returns a 404 Not found,
when it is "api/{controller}/{id}", the error is:
Multiple actions were found that match the request: \r\nGetAll
If you are using attribute-based routing, you need to add the following code before you declare any WebAPI routes in Startup.cs:
config.MapHttpAttributeRoutes();