I have an asp.net mvc controller named product.
public class ProductController : ApiController
{
[HttpGet]
public IHttpActionResult Get()
{
return Ok("product");
}
}
And my route is like this.
config.Routes.MapHttpRoute("DefaultRoute",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
I can access the product Get method like this url: localhost:2541/api/product
And I need some estra get methots.
public class ProductController : ApiController
{
[HttpGet]
public IHttpActionResult Get()
{
return Ok("product");
}
[HttpGet]
public IHttpActionResult Hello()
{
return Ok("Hello from product");
}
}
And I set new route.
config.Routes.MapHttpRoute("ActionsRoute",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
But I can not access to localhost:2541/api/product
Error:
Multiple actions were found that match the request: Get
That is because by including {action} route parameter it is now required to include the name of the action in the request other wise it will not know which action to select. like
localhost:2541/api/product/Get
If the root is still desired then include default when mapping route.
config.Routes.MapHttpRoute("ActionsRoute",
"api/{controller}/{action}/{id}",
new { action = "Get", id = RouteParameter.Optional });
With that done, the calls below will map as followed
GET localhost:2541/api/product --> ProductController.Get
GET localhost:2541/api/product/Get --> ProductController.Get
GET localhost:2541/api/product/Hello --> ProductController.Hello
Related
I am creating REST Services. Here I want to create a Post service. Data is inserted into a database, but in response I got this error
UrlHelper.Link must not return null..
My Code
[HttpPost]
[Route("api/CourseRegistrations")]
[ResponseType(typeof(CourseRegistration))]
public IHttpActionResult PostCourseRegistration(CourseRegistration courseRegistration)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.CourseRegistrations.Add(courseRegistration);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = courseRegistration.course_id }, courseRegistration);
}
I assumed you're using default setting inside WebApiConfig class in App_Start folder like this:
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Thus it is necessary to provide controller name parameter in CreatedAtRoute method, as in example below:
return CreatedAtRoute("DefaultApi", new { controller = "ApiControllerName", id = courseRegistration.course_id }, courseRegistration);
However, since RouteAttribute is present, it indicates that attribute routing is enabled, then you should provide route name for GET route with proper parameter name and then set POST route as shown in example below:
// GET method
[HttpGet]
[Route("api/CourseRegistrations/{id}", Name = "GetCourseRegistrationById")]
[ResponseType(typeof(CourseRegistration))]
public IHttpActionResult GetCourseRegistration(int id)
{
// do something
}
// POST method
[HttpPost]
[Route("api/CourseRegistrations")]
public IHttpActionResult PostCourseRegistration(CourseRegistration courseRegistration)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.CourseRegistrations.Add(courseRegistration);
db.SaveChanges();
// replace 'DefaultApi' with route name for redirect to GET method
return CreatedAtRoute("GetCourseRegistrationById", new { id = courseRegistration.course_id }, courseRegistration);
}
Note:
1) The parameter name contained inside CreatedAtRoute must match with target parameter name used in action method, otherwise the UrlHelper.Link() method will return null.
2) The route name in RouteAttribute should be different from action method name to avoid confusion.
Reference:
Attribute Routing in ASP.NET Web API 2: Route Names
Related issue:
Attribute Routing and CreatedAtRoute
Suppose I have a very simple routing table, like this:
routes.MapHttpRoute("root", "",
new { controller = "Home", action = "Index" });
And within my HomeController, I have two methods called Index:
[HttpGet]
public IHttpActionResult Index()
{
return Content(HttpStatusCode.OK, new { service = "hard-coded string" });
}
[HttpGet]
public IHttpActionResult Index(string a)
{
return Content(HttpStatusCode.OK, new { a });
}
If I run this application, routing occurs as I would expect: if I omit the a parameter from the query string, my request gets routed to the first method, and if I include it, the request is routed to the second method.
However, if I change the type of a to a more complex type, such as string[], then when I make a request on the default route, I get the following error (regardless of whether I specified the query parameter):
{
Message: "An error has occurred."
ExceptionMessage: "Multiple actions were found that match the request: Index on type SurveysApi.v1.Web.Controllers.HomeController Index on type SurveysApi.v1.Web.Controllers.HomeController"
ExceptionType: "System.InvalidOperationException"
StackTrace: " at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext) at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()"
}
The error remains if even if I specify FromUri or ModelBinder attributes on the parameter.
Why does this error occur for complex types, and is there any way to avoid it, short of specifying a simple type in the argument list and performing the necessary conversions in the controller method?
Method overloading doesn't seem to play well with Web API. What I suggest you use instead is attribute routing.
Enable it in WebApiConfig.cs
config.MapHttpAttributeRoutes();
An example Controller would be:
public class HomeController : ApiController
{
[Route("home/id/{Id}")]
[HttpGet]
public string Get(int Id)
{
return "id";
}
[Route("home/string/{str}")]
[HttpGet]
public string Get(string str)
{
return "string";
}
}
Method names are the same, but routes are the key factor. Another way you could do this is using the ActionName attribute:
An example Controller would be:
public class HomeController : ApiController
{
[ActionName("GetById")]
public string Get(int id)
{
return "id";
}
[ActionName("GetByGUID")]
public string Get(string id)
{
return "guid";
}
}
Routes would be something like:
//Matches /api/Home/7
config.Routes.MapHttpRoute(
name: "DefaultDigitApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetById" },
constraints: new { id = #"^\d+$" } // id must be digits
);
//Matches /api/Home/CD73FAD2-E226-4715-B6FA-14EDF0764162
config.Routes.MapHttpRoute(
name: "DefaultGuidApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetByGUID" },
constraints: new { id = #"\b[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\b" } // id must be guid
);
Hope this helps at least a bit.
Refer to the official ASP.NET attribute routing documentation, it seems that Route attribute can be used without RoutePrefix.
But, in my webapi controller, below cases are happened.
1. Not working. (error: No matched http route found)
public class GroupController : ApiController
{
[Route("api/group/{id}/register")]
public IHttpActionResult Post(int id, InputModel model)
{
return Ok();
}
}
2. Working good.
[RoutePrefix("api/group")]
public class GroupController : ApiController
{
[Route("{id}/register")]
public IHttpActionResult Post(int id, InputModel model)
{
return Ok();
}
}
Is it right that Route attribute should be used with RoutePrefix, or what am I missing?
In addition, below code is my WebApi route config in WebApiConfig.Register class.
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}/{subId}",
defaults: new { id = RouteParameter.Optional, subId = RouteParameter.Optional }
);
I am a bit new to WebApi so maybe someone can explain this to me, the default route added for WebApis is:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
So given an ApiController that looks like this:
public class LookupController : ApiController
{
[HttpGet]
public IHttpActionResult GetCountries()
{
// do stuff
return Ok();
}
[HttpGet]
public IHttpActionResult GetStates()
{
// do stuff
return Ok();
}
}
How would it know which action to call? It wouldn't right?
Shouldn't the default Route be more like:
config.Routes.MapHttpRoute(
name: "ApiWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Or should you only have one GET/UPDATE/DELETE etc per ApiController? Which really wouldn't cater for some scenarios...
Web API figures out which method to call based on routing data. You don't need to specify the action because Web API will use the verb(GET, POST,DELETE,etc) of the request.
If you would like to have multiple GET actions in a controller, you can specify a route for each action.
[Route("api/lookup/countries"]
[HttpGet]
public IHttpActionResult GetCountries()
{
// do stuff
return Ok();
}
[Route("api/lookup/states"]
[HttpGet]
public IHttpActionResult GetStates()
{
// do stuff
return Ok();
}
More information is available here http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Strictly speaking your controller should never look like that. Out of the box the idea is that a controller handles requests for a single entity (e.g. Customer) and the operations, which map to HTTP verbs, operate against that entity. So under normal circumstances your controller would look something like the following (created using scaffolding against a simple model class, method bodies omitted for brevity):
public class CustomerController : ApiController
{
public IQueryable<Customer> GetCustomers()
{
}
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
}
[ResponseType(typeof(void))]
public IHttpActionResult PutCustomer(int id, Customer customer)
{
}
[ResponseType(typeof(Customer))]
public IHttpActionResult PostCustomer(Customer customer)
{
}
[ResponseType(typeof(Customer))]
public IHttpActionResult DeleteCustomer(int id)
{
}
}
If you have more business-oriented operations to perform (e.g. BillCustomer), rather than just the basic CRUD operations, I would suggest creating a separate route for those. We did that in one of our applications and it created a nice logical separation. For example:
config.Routes.MapHttpRoute(
name: "RestApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "RpcApi",
routeTemplate: "rpc/{controller}/{action}"
);
Right now, my API controller has 2 methods: one to get ALL events, and one to get ONE event.
namespace HobbsEventsMobile.Controllers
{
public class EventController : ApiController
{
// GET api/event
[HttpGet]
public List<HobbsEventsMobile.Models.Event> Get()
{
return HobbsEventsMobile.Models.Event.GetEventSummary();
}
// GET api/event/5
[HttpGet]
public HobbsEventsMobile.Models.Event Get(int id)
{
return HobbsEventsMobile.Models.Event.GetEventDetails(id);
}
}
}
Newly requested functionality requires me to add a way to call events for the current week. I have a stored proc and a method to call this, but I am not sure how to specify the URL. I would like to add this:
[HttpGet]
public List<HobbsEventsMobile.Models.Event> Get()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
but make it accessible at m.mydomain.com/api/event/thisweek (or something). How do I do that?
You have two different options depending on what version of ASP.NET Web API you're running. If you're on version one you can simply follow the convention based routing and use:
public class EventController : ApiController
{
[HttpGet]
public List<HobbsEventsMobile.Models.Event> ThisWeek()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
}
You will also need to modify your route definitions to support an action name (by default the framework picks the method based upon the HTTP verb):
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("GET") }
);
If you're using version two, you can still use the convention based routing, but you also have the ability to use attribute routing:
public class EventController : ApiController
{
[HttpGet]
[Route("event/thisweek")]
public List<HobbsEventsMobile.Models.Event> ICanNameThisWhateverIWant()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
}