My routes:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" }
);
routes.MapHttpRoute(
name: "ApiControllerActionRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
My methods:
// GET api/Set/All
[ActionName("All")]
public IEnumerable<Set> GetSets()
{
var sets = _repository.GetAllSets();
return sets;
}
// GET api/Set/TopLevelSets
[ActionName("TopLevelSets")]
public IEnumerable<Set> GetTopLevelSets()
{
var sets = _repository.GetTopLevelSets();
return sets.ToList();
}
// GET api/Set/Breadcrumbs/1
[ActionName("Breadcrumbs")]
public IEnumerable<Set> GetBreadCrumbs(int id)
{
var sets = _repository.GetBreadcrumbs(id);
return sets.ToList();
}
// GET api/Set/5
public Set GetSet(int id)
{
Set set = _repository.GetSet(id);
if (set == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return set;
}
// PUT api/Set/5
public HttpResponseMessage PutSet(Set set)
{
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
_repository.UpdateSet(set);
return Request.CreateResponse(HttpStatusCode.OK);
}
// POST api/Set
public HttpResponseMessage PostSet(Set set)
{
if (ModelState.IsValid)
{
_repository.AddSet(set);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, set);
return response;
}
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
// DELETE api/Set/5
public HttpResponseMessage DeleteSet(int id)
{
_repository.DeleteSet(id);
return Request.CreateResponse(HttpStatusCode.OK, id);
}
At this point I'm trying to hit the localhost/api/set/1 - getSet method. Everything seems to line up with the routes but it's not working for some reason. What am I missing?
Your default route isn't setting a default action, so when you use the 'Get' Action the routing engine could not decide if you wanted GetTopLevelSets or GetSet. Adding a default will fix this:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GetSet" },
constraints: new { id = #"^\d+$" }
);
Would have it default to your GetSet Action in your controller using: api/set (controller)/1 (id) route.
Are you missing the api portion of your query? localhost/api/set/1
Related
I expect following behaviour: when calling http://localhost:59060/api/games/ - it returns all games, when calling http://localhost:59060/api/games/aaa - it returns all games where DeviceId = aaa.
Currently when I call http://localhost:59060/api/games/aaa or http://localhost:59060/api/games/, error is:
No HTTP resource was found that matches the request URI http://localhost:59060/api/games/
No action was found on the controller 'Games' that matches the request.
Controller
public class GamesController : ApiController
{
private List<Game> _GamesRepository;
public GamesController()
{
_GamesRepository = CreateGamesRepository();
}
// GET api/Games/0xa16
public IEnumerable<Game> Get(string deviceId)
{
if (String.IsNullOrEmpty(deviceId))
{
return _GamesRepository;
}
else
{
return _GamesRepository.Where(x => x.DeviceId == deviceId);
}
}
}
Configuration
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "ControllerDefault",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "ApiDefault2",
url: "api/{controller}/{action}/{deviceId}",
defaults: new { action = "get", deviceId = UrlParameter.Optional }
);
Model
public class Game
{
public int TaskId { get; set; }
public string SalesForceId { get; set; }
public string Name { get; set; }
public string Thumbnail { get; set; }
public string DeviceId { get; set; }
}
In your WebApiConfig class, before the "DefaultApi" route you can register your route as follows:
config.Routes.MapHttpRoute(
name: "GetGamesRoute",
routeTemplate: "api/games/{deviceId}",
new { controller = "games", action = "get", deviceId = RouteParameter.Optional }
);
// This is the default route
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Also, make the deviceId parameter optional:
// GET api/Games/0xa16
public IEnumerable<Game> Get(string deviceId = null)
Hello I make a mvc 5 wep api like, now I wanna make a source like this "/countries/{countryId}/cities" to a list of cities from a country.
This is the method:
[Route("countries/{id}/cities")]
[HttpGet]
public IEnumerable<CityDTO> Cities(int id)
{
//todo
}
The default rout config was like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
HelpPAge:
Then I added a new routing config like this:
config.Routes.MapHttpRoute(
name: "ActionBasedApi",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { controller = "Countries", action = "Cities", id = RouteParameter.Optional }
);
But now my HelpPage apear like this:
And when i call make GET to "/countries/{countryId}" to get just one country, I get this error:
"Multiple actions were found that match the request:.."
Somenoe can helpme to solve this, geting a routing that I Want?
EDIT
This is the entire CountryController class
public class CountriesController : BaseController
{
// GET: api/Countries
public IEnumerable<CountryDTO> Get()
{
using (var respository = new CountryRepository())
{
var countries = respository.GetAll().ToList();
return countries.Map<List<CountryDTO>, List<Erp360.DataAccess.Country>>();
}
}
// GET: api/Countries/5
public CountryDTO Get(int id)
{
using (var respository = new CountryRepository())
{
var countries = respository.GetAll().FirstOrDefault(c => c.CountryId == id);
return countries.Map<CountryDTO, DataAccess.Country>();
}
}
// GET: api/Countries/5
[Route("countries/cities/{id}")]
[HttpGet]
public IEnumerable<CityDTO> Cities(int id)
{
using (var respository = new CityRepository())
{
var cities = respository.GetFiltered(c => c.CountryId == id).ToList();
return cities.Map<List<CityDTO>, List<DataAccess.City>>();
}
}
// POST: api/Countries
public void Post([FromBody]CountryDTO value)
{
using (var repository = new CountryRepository())
{
var country = ManageInput(value);
repository.Add(country);
repository.Save();
}
}
// PUT: api/Countries/5
public void Put(int id, [FromBody]CountryDTO value)
{
using (var repository = new CountryRepository())
{
var country = ManageInput(value);
repository.Modify(country);
repository.Save();
}
}
// DELETE: api/Countries/5
public void Delete(int id)
{
using (var repository = new CountryRepository())
{
var country = repository.Get(id);
repository.Remove(country);
repository.Save();
}
}
private DataAccess.Country ManageInput(CountryDTO value)
{
var country = value.Map<Core.BusinessModel.Country, CountryDTO>();
if (!IsValid(country))
return null;
return country.Map<DataAccess.Country, Core.BusinessModel.Country>();
}
}
please be sure that you have enable the attribute routing, in the routes config, add the following line before the config.Routes.MapHttpRoute(...)
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Regards,
There are a few potential issues here.
First of all, you need to ensure the longer route occurs before the shorter route.
Secondly, you cannot put an optional segment ("id") followed by a required segment. In fact, if you want the route only for the purpose of having 1 additional segment, you should make all of its segments required.
// This route matches any 4 segment route beginning with api/
// 3 segments will not match
config.Routes.MapHttpRoute(
name: "ActionBasedApi",
routeTemplate: "api/{controller}/{id}/{action}"
);
// This route will match any 2 or 3 segment route beginning with api/
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Third, you should remove the attribute route if you have created a general route that serves the same purpose.
I use Web Api for list of tales and I want to do this:
List GetAllTales() = api/tales/
Tale GetTale(int id) = api/tales/1
List GetAllTalesByCategory(string categoryName) = api/tales/kids
Tale GetTalesByCategoryAndId(string categoryName, int id) = api/tales/kids/1
I dont know what can ı do this or what can I change in route config?
My TalesController:ApiController
public IEnumerable<Tale> GetAllTales()
{
return TaleService.FindAllTale();
}
public IEnumerable<Tale> GetAllTalesByCategory(string categoryName){}
public Tale GetTale(int id)
{
Tale item = TaleService.FindByTaleId(id);
if (item == null)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return item;
}
Its my WebApiConfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^[0-9]+$" }
);
config.Routes.MapHttpRoute(
name: "ApiByName",
routeTemplate: "api/{controller}/{name}",
defaults: null,
constraints: new { name = #"^[a-z]+$" }
);
Some example for my aim : http://www.bbc.com/news/ - http://www.bbc.co.uk/news/technology/
use the [HttpPost] and [HttpGet] attributes and specify the action name with [ActionName("Your Action Name")]
// api/tales/getall
[HttpGet]
[ActionName("getall")]
public IEnumerable<Tale> GetAllTales()
{
return TaleService.FindAllTale();
}
like this you can decorate all the action with proper attributes to get it working as expected.
Edit
You can refer this answer for creating multiple routes Routing in Asp.net Mvc 4 and Web Api
config.Routes.MapHttpRoute(
name: "DefaultApiAction",
routeTemplate: "api/{action}/{name}/{controller}/{resourceId}",
defaults: new { resourceId= RouteParameter.Optional }
constraints: new { name = #"^[a-z]+$" }
);
maybe you can try define this route ,and add ActionName like Anto's answer .
I have the following in my controller:
public ActionResult Details(string name)
{
Student student = db.Students.FirstOrDefault(x => x.FirstName == name);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
// GET: /Student/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
RouteConfig.cs:
// /Student/Details/Id
routes.MapRoute(
name: "StudentDetailsId",
url: "{Student}/{Action}/{id}",
defaults: new { controller = "Student", action = "Details", id = UrlParameter.Optional },
constraints: new { id = #"\d+" }
);
// /Student/Details/Name
routes.MapRoute(
name: "StudentDetailsName",
url: "{Student}/{Details}/{name}",
defaults: new { controller = "Student", action = "Details" }
);
// /Student/Name
routes.MapRoute(
name: "StudentName",
url: "{Student}/{name}",
defaults: new { controller = "Student", action = "Details" }
);
So pretty much I would like to have the same action name but fetched with either an id:int or string.
However I get the following:
The current request for action 'Details' on controller type 'StudentController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Details(System.String) on type MySuperAwesomeMVCApp.Controllers.StudentController
System.Web.Mvc.ActionResult Details(System.Nullable`1[System.Int32]) on type MySuperAwesomeMVCApp.Controllers.StudentController
Controller:
public ActionResult DetailsByName(string name)
{
Student student = db.Students.FirstOrDefault(x => x.FirstName == name);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
// GET: /Student/Details/5
public ActionResult DetailsById(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
Routes:
// /Student/Details/Id
routes.MapRoute(
name: "StudentDetailsId",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Student", action = "DetailsById", id = UrlParameter.Optional },
constraints: new { id = #"\d+" }
);
// /Student/Details/Name
routes.MapRoute(
name: "StudentDetailsName",
url: "{controller}/{action}/{name}",
defaults: new { controller = "Student", action = "DetailsByName" }
);
// /Student/Name
routes.MapRoute(
name: "StudentName",
url: "{controller}/{name}",
defaults: new { controller = "Student", action = "DetailsByName" }
);
In MVC, you can only have a maximum of 2 methods with the same name, and when you do, one of them must be GET and the other must be POST.
No, MVC cannot figure out which action method you want to invoke given a set of route data. Though your methods are valid C# overloads, MVC's action method selector is not strongly-typed. I have answered this question before, let me look up the link...
Update
Here is the link: https://stackoverflow.com/a/10668537/304832
Another Update
I only bring this up because other people have thanked me for mentioning it when I answer other routing questions.... Look into AttributeRouting.NET. It is so great that it's the most talked about feature of MVC5, though it started out as an open source side project and there is no reason why you can't use it in MVC4. Just download the NuGet package. With this library, you don't need to use MapRoute. Your action methods would look like this:
// GET: /Students/Details/5
[GET("students/details/{id:int}", ControllerPrecedence = 1)]
public ActionResult DetailsById(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
// GET: /Students/Details/Albert+Einstein
// or GET: /Students/Albert+Einstein
[GET("students/{name}", ActionPrecedence = 1)]
[GET("students/details/{name}")]
public ActionResult DetailsByName(string name)
{
Student student = db.Students.FirstOrDefault(x => x.FirstName == name);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
No MapRoute or cryptic regular expression constraints necessary. You just have to make sure your int method has precedence so that MVC doesn't try to pass numbers into your method that takes a string.
There seems to be a thousand people asking the same question on stack overflow, but there doesn't seem to be a single solution to this problem. I am going to ask it again...
I have an API controller that has the following actions:
// GET api/Exploitation
public HttpResponseMessage Get() {
var items = _exploitationRepository.FindAll();
var mappedItems = Mapper.Map<IEnumerable<Exploitation>, IEnumerable<ExploitationView>>(items);
var response = Request.CreateResponse<IEnumerable<ExploitationView>>(HttpStatusCode.OK, mappedItems);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { }));
return response;
}
// GET api/Exploitation/5
[HttpGet, ActionName("Get")]
public HttpResponseMessage Get(int id) {
var item = _exploitationRepository.FindById(id);
var mappedItem = Mapper.Map<Exploitation, ExploitationView>(item);
var response = Request.CreateResponse<ExploitationView>(HttpStatusCode.OK, mappedItem);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = id }));
return response;
}
// GET api/Exploitation/GetBySongwriterId/5
[HttpGet, ActionName("GetBySongwriterId")]
public HttpResponseMessage GetBySongwriterId(int id) {
var item = _exploitationRepository.Find(e => e.Song.SongWriterSongs.Any(s => s.SongWriterId == id))
.OrderByDescending(e => e.ReleaseDate);
var mappedItem = Mapper.Map<IEnumerable<Exploitation>, IEnumerable<ExploitationView>>(item);
var response = Request.CreateResponse<IEnumerable<ExploitationView>>(HttpStatusCode.OK, mappedItem);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = id }));
return response;
}
// GET api/Exploitation/GetBySongwriterId/5
[HttpGet, ActionName("GetBySongId")]
public HttpResponseMessage GetBySongId(int id) {
var item = _exploitationRepository.Find(e => e.SongId == id)
.OrderByDescending(e => e.ReleaseDate);
var mappedItem = Mapper.Map<IEnumerable<Exploitation>, IEnumerable<ExploitationView>>(item);
var response = Request.CreateResponse<IEnumerable<ExploitationView>>(HttpStatusCode.OK, mappedItem);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = id }));
return response;
}
In my APIConfig I have defined the following routes:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional },
constraints: new { id = #"\d+" }
);
I am finding that I can access the following actions no problem:
/api/exploitation
/api/exploitation/getbysongwriterid/1
/api/exploitation/getbysongid/1
When I try to access /api/exploitation/1 I get this exception
"Multiple actions were found that match the request: System.Net.Http.HttpResponseMessage Get(Int32) on type Songistry.API.ExploitationController System.Net.Http.HttpResponseMessage GetBySongwriterId(Int32)" exception.
Can anyone see what is wrong with my routes? Or wrong with something else?
I have found an elegant solution to the problem.
I modified my ApiRouteConfig to have the following routes:
config.Routes.MapHttpRoute(
name: "DefaultGetApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { id = #"\d+", httpMethod = new HttpMethodConstraint(HttpMethod.Get) }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"\d+" }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional }
);
Now I can access:
/api/exploitation
/api/exploitation/1
/api/exploitation/getbysongid/1
/api/exploitation/getbysongwriterid/1
I did not need to modify my controller actions to work with this new routing config at all.
If you had multiple PUT or POST actions you could the create new routes that looked as follows:
config.Routes.MapHttpRoute(
name: "DefaultGetApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Put" },
constraints: new { id = #"\d+", httpMethod = new HttpMethodConstraint(HttpMethod.Put) }
);
config.Routes.MapHttpRoute(
name: "DefaultGetApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Delete" },
constraints: new { id = #"\d+", httpMethod = new HttpMethodConstraint(HttpMethod.Delete) }
);
I hope that this answer helps everyone as this seems to be a common issue that people are having.
The problem you have is that /api/exploitation/1 falls under:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
All of your GET methods satisfy that routing as well particularly because {id} is optional and their controller is the same.
So you have one HTTP GET request from the client and multiple methods that accept GET requests. It doesn't know which one to go to.
api/{controller}/{action}/{id}
//This works fine because you specified which action explicitly
I hope that answers your question.
Try the following in your route definition. Keep only the following route:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { id = #"\d+" }
);
Make the first Get method private, modify the second one so that id has a default value:
// GET api/Exploitation
private HttpResponseMessage Get() {
// implementation stays the same but now it's private
}
// GET api/Exploitation/5
[HttpGet, ActionName("Get")]
public HttpResponseMessage Get(int id = 0) {
if (id == 0) {
return Get();
}
// continue standard implementation
}
This way (I haven't tested it myself) I expect that:
api/Exploitation/ will map to api/Exploitation/Get(as default action) with id = 0 as default param
api/Exploitation/1 will map to api/Exploitation/Get/1 so it will call Get(1)
api/Exploitation/someOtherAction/345 will call the correct action method
This might work. A tighter route definition could actually be like this:
config.Routes.MapHttpRoute(
name: "ApiWithRequiredId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: null /* make sure we have explicit action and id */,
constraints: new { id = #"\d+" }
);
config.Routes.MapHttpRoute(
name: "ApiWithOptionalId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional, action = "Get" },
constraints: new { action = "Get" /* only allow Get method to work with an optional id */, id = #"\d+" }
);
But something along these lines... give it a try I hope it solves your problem.