Multiple IQueryable in one controller? - asp.net

I have a controller for my entire database, the code is below:
public class YogaController : DbDataController<Yoga.Models.YOGAEntities>
{
public YogaController()
{
}
public IQueryable<Yoga.Models.Action> GetActions(int BugId)
//GetActions retrieves "actions" table from the db, not Actions in MVC term
{
return DbContext.Actions.Where(x => x.FK_BugsID == BugId);
}
public IQueryable<Yoga.Models.Label> GetRequiredLabels()
{
return DbContext.Labels.Where(x => x.IsRequired == true);
}
public IQueryable<Yoga.Models.Role> GetRoles()
{
return DbContext.Roles;
}
public IQueryable<Role> GetRoles2() //TODO: finish this
{
return DbContext.Roles.Where(x => x.RoleID == 1);
}
public IQueryable<Tag> GetTags(int actionid)
{
return DbContext.Tags.Where(x => x.J_Tags.Any(y => y.FK_ActionID == actionid));
}
}
As you can see I have multiple IQueryable in one controller, each querying a different table. Is it something that's forbidden? Because when I go to localhost/api/Yoga/GetActions or localhost/api/Yoga/GetRequiredLabels I get the error message:
Multiple actions were found that match the request:
System.Linq.IQueryable`1[Yoga.Models.Label] GetRequiredLabels() on type Yoga.Controllers.YogaController
System.Linq.IQueryable`1[Yoga.Models.Role] GetRoles() on type Yoga.Controllers.YogaController
System.Linq.IQueryable`1[Yoga.Models.Role] GetRoles2() on type Yoga.Controllers.YogaController
When I disable all but one IQueryable, the results came out fine.
I have googled for similar issues and checked my routing settings, there are no conflicts in controller path and names.
My Routes (default generated):
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//routes.MapRoute(
// name: "Default",
// url: "{controller}/{action}/{id}",
// defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Any ideas?

MVC4 is probably matching your HTTP Verb (Get) against all methods with names starting with 'Get' and no parameters. Try forcing the action name:
[ActionName("GetRequiredLabels")]
public IQueryable<Yoga.Models.Label> GetRequiredLabels()
...
[ActionName("GetActions")]
public IQueryable<Yoga.Models.Action> GetActions(int BugId)
... // etc
EDIT:
Based on the routes you pasted and your controller, I think your routes should be:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
i.e. it should have the {action} in there. The default MVC4 route would work if you only had a single 'Get' method. Since you have multiple, you'll have to force it to pick the action based on the route.

Related

Web Api doesn't work when multiple params?

I have 2 routes :
routes.MapRoute(
name: "Default1",
url: "{controller}/{action}/{id}/{id2}"
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
My controller looks like :
public class ProductsController : ApiController
{
...
public int GetAll(int id,int id2)
{
return 1;
}
public Product GetProduct(int id)
{
....
return item;
}
}
When I write :http://localhost:9000/api/products/2
it matches the second rule and :
But when I write http://localhost:9000/api/products/2/3 ( which suppose to match the first rule) :
Question
Where is my mistake ?
nb :
Running http://localhost:9000/api/products/2?id2=1 does give the right result - but hey ! I made a route specially for this one !
(already read this answer - didn't help much)
I think your problem is that you have defined the routes above in the RouteConfig rather than the WebApiConfig. Web Api routes should be defined within the WebApiConfig and if you take a look you should find the default route:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
In order to match the url: api/products/2/3 you will need to add a new route to the WebApiConfig before the existing default route:
config.Routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{controller}/{id}/{id2}"
);
It's the way your routes are resolving.
You need to change the order of the routes.
Put Default first and Default1 last since it has more directories to resolve.
You aren't supplying an action in your requests. So to trigger your first route you would need to call:
http://localhost:9000/api/products/getall/2/3

Why isn't my ASP.NET API route being called, is my URL not correct?

I have a new ASP.NET MVC 5 application.
I added an /api folder in my controllers folder and added a MVC2 API controller.
My global.asax has:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);
}
My controller looks like:
public class UsersController : ApiController
{
[HttpGet]
[ActionName("User")]
public IToken GetUser()
{
return new User();
}
}
Now I get a 404 resource cannot be found error when I go to:
http://localhost:53323/api/users/user
http://localhost:53323/api/users/getuser
What could the problem be?
Update
My MVC route config:
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 }
);
}
My API route config:
config.MapHttpAttributeRoutes();
//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 }
);
Seems like you are missing correct registration in your Application_Start().
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration); // This should be above the default route registration.
RouteConfig.RegisterRoutes(RouteTable.Routes);
//GlobalConfiguration.Configure(WebApiConfig.Register); // Remove this line.
}
Try using attribute routing on your controller and action methods.
Example
[RoutePrefix("api/users")]
public class UsersController : ApiController
{
[HttpGet]
[Route("user")]
public IToken GetUser()
{
return new User();
}
}

ASP.Net Web Api routing issue with ActionName

I am working with an ASP.Net Web Api project on Web Developer Express 2010. The routing config is defined in WebApiConfig.cs as:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = RouteParameter.Optional,
id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi4",
routeTemplate: "api/{controller}/{action}",
defaults: new { action = RouteParameter.Optional }
);
}
An API Controller called "GCURObservationController" has an action as:
[HttpGet, ActionName("retrieveCuringMaps")]
public IList<SimpleCuringMapsModel> retrieveCuringMaps()
{
... ...
return jsonCuringMapModels;
}
The project was compiled and run successfully. However, I had to go to
http://localhost:2061/api/GCURObservation/retrieveCuringMaps/0
to get the action triggered (action name followed by any integer), rather than what I expected to be
http://localhost:2061/api/GCURObservation/retrieveCuringMaps
That means an arbitrary integer had to follow the action name to get it right. Otherwise, the error was returned. I don't want this action to be triggered with any param.
{"Message":"The request is invalid."}
How to get the second URL to work? Thanks
Cheers,
Alex
If you are using Web API 2, following is one solution you could use. In the below example, I am using attribute routing and conventional routing together in one controller. Here all the actions except GetCustomerOrders are reached via conventional route "DefaultApi".
In general the idea here is not new, that is...even without Web API 2's attribute routing, you could define routes for each individual action of a controller in the global route table, but attribute routing makes this process easier as you can define routes directly and near to the action.
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
[RoutePrefix("api/customers")]
public class CustomersController : ApiController
{
public IEnumerable<Customer> GetAll()
{
}
public Customer GetSingle(int id)
{
}
public void Post(Customer customer)
{
}
public void Put(int id, Customer updatedCustomer)
{
}
public void Delete(int id)
{
}
[Route("{id}/orders")]
public IEnumerable<Order> GetCustomerOrders(int id)
{
}
}

Web API Routing - api/{controller}/{action}/{id} "dysfunctions" api/{controller}/{id}

I have the default Route in Global.asax:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
I wanted to be able to target a specific function, so I created another route:
RouteTable.Routes.MapHttpRoute(
name: "WithActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
So, in my controller, I have:
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
Calling .../api/records/bycategoryid/5 will give me what I want.
However, calling .../api/records/1 will give me the error
Multiple actions were found that match the request: ...
I understand why that is - the routes just define what URLs are valid, but when it comes to function matching, both Get(int id) and ByCategoryId(int id) match api/{controller}/{id}, which is what confuses the framework.
What do I need to do to get the default API route to work again, and keep the one with {action}? I thought of creating a different controller named RecordByCategoryIdController to match the default API route, for which I would request .../api/recordbycategoryid/5. However, I find that to be a "dirty" (thus unsatisfactory) solution. I've looked for answers on this and no tutorial out there on using a route with {action} even mentions this issue.
The route engine uses the same sequence as you add rules into it. Once it gets the first matched rule, it will stop checking other rules and take this to search for controller and action.
So, you should:
Put your specific rules ahead of your general rules(like default), which means use RouteTable.Routes.MapHttpRoute to map "WithActionApi" first, then "DefaultApi".
Remove the defaults: new { id = System.Web.Http.RouteParameter.Optional } parameter of your "WithActionApi" rule because once id is optional, url like "/api/{part1}/{part2}" will never goes into "DefaultApi".
Add an named action to your "DefaultApi" to tell the route engine which action to enter. Otherwise once you have more than one actions in your controller, the engine won't know which one to use and throws "Multiple actions were found that match the request: ...". Then to make it matches your Get method, use an ActionNameAttribute.
So your route should like this:
// Map this rule first
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
RouteTable.Routes.MapRoute(
"DefaultApi",
"api/{controller}/{id}",
new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);
And your controller:
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
You can solve your problem with help of Attribute routing
Controller
[Route("api/category/{categoryId}")]
public IEnumerable<Order> GetCategoryId(int categoryId) { ... }
URI in jquery
api/category/1
Route Configuration
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
and your default routing is working as default convention-based routing
Controller
public string Get(int id)
{
return "object of id id";
}
URI in Jquery
/api/records/1
Route Configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Review article for more information Attribute routing and onvention-based routing here & this
Try this.
public class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var json = config.Formatters.JsonFormatter;
json.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
config.Formatters.Remove(config.Formatters.XmlFormatter);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional , Action =RouteParameter.Optional }
);
}
}
The possible reason can also be that you have not inherited Controller from ApiController.
Happened with me took a while to understand the same.
To differentiate the routes, try adding a constraint that id must be numeric:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
constraints: new { id = #"\d+" }, // Only matches if "id" is one or more digits.
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);

MVC 4 Web API Areas 404 Error: "The controller for path '/Jobs/Send' was not found or does not implement IController."

I'm having an issue that is driving me nuts.
I have an MVC 4 WebAPI application that has several Areas defined.
My Jobs Area Send controller (SendController.cs) is defined like so:
namespace TargetAPI.Areas.Jobs.Controllers
{
public class SendController : ApiController
{
[HttpPost]
public HttpResponseMessage Index(SendRequest req)
{
try
{
//blah blah
}
catch (Exception ex)
{
//blah blah
}
}
}
}
My Jobs Area Registration (JobsAreaRegistration.cs) is defined like so:
namespace TargetAPI.Areas.Jobs
{
public class JobsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Jobs";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Jobs_long",
"Jobs/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new[] { "TargetAPI.Areas.Jobs.Controllers" }
);
}
}
}
My RouteConfig.cs says:
namespace TargetAPI
{
public class RouteConfig
{
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 },
namespaces: new string[] { "TargetAPI.Controllers" }
);
}
}
}
When I run the route debugger on it I get:
(source: boomerang.com)
But when I try to post to the URL "Jobs/Send" I get:
The controller for path '/Jobs/Send' was not found or does not implement IController.
I've tried so many iterations and combinations my head is spinning. Any ideas?
Thanks!
Turns out the WebAPI does NOT handles Areas! Imagine my surprise. So I found a GREAT post http://blogs.infosupport.com/asp-net-mvc-4-rc-getting-webapi-and-areas-to-play-nicely/. Now I am moving forward.
In addition to not supporting Areas (because MapHTTPRoute doesn't have namespace support), The API controller must use MapHttpRoute, not MapRoute as in this example (after removing area):
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Note the absence of {action}, the Method's are not Actions, put are taken from the HTTP request: Get, Head, etc...
I had the same problem, the solution was simple: I forgot to add files _ViewStart.cshtml and _Layout.cshtml, and can help you

Resources